/* dCache - http://www.dcache.org/
*
* Copyright (C) 2014 Deutsches Elektronen-Synchrotron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.dcache.srm;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import eu.emi.security.authn.x509.X509Credential;
import eu.emi.security.authn.x509.impl.PEMCredential;
import eu.emi.security.authn.x509.proxy.ProxyGenerator;
import eu.emi.security.authn.x509.proxy.ProxyRequestOptions;
import org.apache.axis.SimpleTargetedChain;
import org.apache.axis.client.Call;
import org.apache.axis.client.Stub;
import org.apache.axis.configuration.SimpleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.xml.rpc.ServiceException;
import javax.xml.rpc.holders.StringHolder;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.Callable;
import dmg.util.CommandException;
import dmg.util.command.Argument;
import dmg.util.command.Command;
import dmg.util.command.Option;
import org.dcache.delegation.gridsite1.DelegationExceptionType;
import org.dcache.delegation.gridsite1.NewProxyReq;
import org.dcache.delegation.gridsite2.DelegationException;
import org.dcache.delegation.gridsite2.DelegationServiceLocator;
import org.dcache.ssl.CanlContextFactory;
import org.dcache.srm.client.HttpClientSender;
import org.dcache.srm.client.HttpClientTransport;
import org.dcache.util.Args;
import org.dcache.util.cli.ShellApplication;
import static com.google.common.base.Preconditions.checkArgument;
/**
* A client for interacting with a GridSite delegation endpoint that supports
* both scriptable and interactive usage. This shell supports both v1.1.0 and
* v2.0.0 endpoints. Theoretically it also supports v1.0.0
* endpoints as these have the same XML namespace as v1.1.0 and all v1.0.0
* requests are also v1.1.0 requests; however this hasn't been tested.
*/
public class DelegationShell extends ShellApplication
{
private enum GridSiteVersion
{
V1_1_0,
V2_0_0
}
/**
* The policy when given an endpoint: should we assume a GridSite version
* or try to probe which version is supported. The two PROBE options
* differ only if an endpoint supports both versions.
*/
private enum GridSiteVersionPolicy
{
ASSUME_V1,
ASSUME_V2,
PROBE
}
private final DelegationServiceLocator _v2Locator;
private final org.dcache.delegation.gridsite1.DelegationServiceLocator _v1Locator;
private final SimpleProvider _provider = new SimpleProvider();
private String _prompt = "$ ";
private Delegation _client;
private GridSiteVersionPolicy _versionPolicy = GridSiteVersionPolicy.PROBE;
private final PEMCredential _proxy;
private final String _proxyPath;
static {
Call.setTransportForProtocol("http", HttpClientTransport.class);
Call.setTransportForProtocol("https", HttpClientTransport.class);
}
public static void main(String[] arguments) throws Throwable
{
Args args = new Args(arguments);
try (DelegationShell shell = new DelegationShell(args.getOption("x509_user_proxy"))) {
if (args.hasOption("endpoint")) {
shell.setEndpoint(URI.create(args.getOption("endpoint")));
args = args.removeOptions("endpoint");
}
shell.start(args);
} catch (FileNotFoundException | CommandException e) {
System.err.println(e.getMessage());
} catch (RuntimeException e) {
System.err.println("Bug detected: " + e.toString());
e.printStackTrace(System.err);
} catch (Exception e) {
System.err.println(e.toString());
}
}
public DelegationShell(String proxyPath) throws Exception
{
_proxyPath = proxyPath;
_proxy = new PEMCredential(proxyPath, (char[]) null);
HttpClientSender sender = new HttpClientSender();
sender.setSslContextFactory(CanlContextFactory.createDefault());
sender.init();
_provider.deployTransport(HttpClientTransport.DEFAULT_TRANSPORT_NAME, new SimpleTargetedChain(sender));
_v1Locator = new org.dcache.delegation.gridsite1.DelegationServiceLocator(_provider);
_v2Locator = new DelegationServiceLocator(_provider);
}
@Override
protected String getCommandName()
{
return "delegation";
}
@Override
protected String getPrompt()
{
return _prompt;
}
private org.dcache.delegation.gridsite1.Delegation buildV1Client(URL url)
{
try {
org.dcache.delegation.gridsite1.Delegation delegation = _v1Locator.getGridsiteDelegation(url);
((Stub) delegation)._setProperty(HttpClientTransport.TRANSPORT_HTTP_CREDENTIALS, _proxy);
return delegation;
} catch (ServiceException e) {
// Should never happen
throw new RuntimeException("Failed to create v1 client: " +
e.toString(), e);
}
}
private org.dcache.delegation.gridsite2.Delegation buildV2Client(URL url)
{
try {
org.dcache.delegation.gridsite2.Delegation delegation = _v2Locator.getGridsiteDelegation(url);
((Stub) delegation)._setProperty(HttpClientTransport.TRANSPORT_HTTP_CREDENTIALS, _proxy);
return delegation;
} catch (ServiceException e) {
// Should never happen
throw new RuntimeException("Failed to create v1 client: " +
e.toString(), e);
}
}
/**
* Test whether the supplied endpoint supports GridSite v1.1.0. One problem
* is that (broken) implementations will simply close a connection after
* a request if the remote server doesn't like the credential used when
* initialising the SSL connection.
*/
private boolean isEndpointV1(URL url) throws CommandException
{
org.dcache.delegation.gridsite1.DelegationServiceLocator locator =
new org.dcache.delegation.gridsite1.DelegationServiceLocator(_provider);
try {
org.dcache.delegation.gridsite1.Delegation client =
locator.getGridsiteDelegation(url);
((Stub) client)._setProperty(HttpClientTransport.TRANSPORT_HTTP_CREDENTIALS, _proxy);
client.getNewProxyReq();
return true;
} catch (ServiceException e) {
throw new RuntimeException("Problem in isEndpointV1: " +
e.getMessage(), e);
} catch (RemoteException e) {
throwIfProblemWithUrl(e);
throwIfProblemLocalEnvironment(e);
return false;
}
}
private boolean isEndpointV2(URL url) throws CommandException
{
org.dcache.delegation.gridsite2.DelegationServiceLocator locator =
new org.dcache.delegation.gridsite2.DelegationServiceLocator(_provider);
try {
org.dcache.delegation.gridsite2.Delegation client =
locator.getGridsiteDelegation(url);
((Stub) client)._setProperty(HttpClientTransport.TRANSPORT_HTTP_CREDENTIALS, _proxy);
client.getVersion();
return true;
} catch (ServiceException e) {
throw new RuntimeException("Problem in isEndpointV2: " +
e.getMessage(), e);
} catch (RemoteException e) {
throwIfProblemWithUrl(e);
throwIfProblemLocalEnvironment(e);
}
return false;
}
private Delegation buildClient(URL url) throws CommandException
{
switch (_versionPolicy)
{
case ASSUME_V1:
return new Delegation(buildV1Client(url));
case ASSUME_V2:
return new Delegation(buildV2Client(url));
case PROBE:
if (isEndpointV2(url)) {
return new Delegation(buildV2Client(url));
} else if (isEndpointV1(url)) {
return new Delegation(buildV1Client(url));
} else {
throw new CommandException("endpoint seems to be " +
"neither v1.1.0 nor v2.0.0; use \"set endpoint " +
"policy\" to force using a particular version.");
}
}
throw new CommandException("Unknown version policy");
}
private void setEndpoint(URI uri) throws CommandException
{
uri = checkSyntax(uri);
String newPrompt = "[" + uri + "] $ ";
uri = canonicalise(uri);
checkProxyAge();
try {
_client = buildClient(uri.toURL());
_prompt = newPrompt;
} catch (MalformedURLException e) {
throw new CommandException("Bad URI: " + e.toString());
}
}
private void checkProxyAge() throws CommandException
{
if (_proxy.getCertificate().getNotAfter().before(new Date())) {
throw new CommandException(_proxyPath + " has expired.");
}
}
private URI checkSyntax(URI uri) throws IllegalArgumentException
{
String schema = uri.getScheme();
checkArgument(schema != null, "Missing schema: URI should start 'https://', 'srm:', 'fts:' or 'cream:'");
switch (schema.toLowerCase()) {
case "https":
checkArgument(uri.getHost() != null, "URIs must include a host part");
checkArgument(uri.getPath() != null && !uri.getPath().equals(""), "URIs must have a path");
break;
case "srm":
case "fts":
case "cream":
case "dpm":
if (uri.getPath() != null) {
checkArgument(uri.getPath().isEmpty(), "URIs should not specify a path");
checkArgument(uri.getHost() != null, "URIs must include a host part");
checkArgument(uri.getPort() == -1, "URIs should not specify the port");
}
break;
default:
throw new IllegalArgumentException("URI should start 'https://', 'srm:', 'fts:', 'cream:' or 'dpm:'");
}
return uri;
}
private static void throwIfProblemWithUrl(Exception e) throws CommandException
{
Throwable t = Throwables.getRootCause(e);
if (t instanceof UnknownHostException) {
throw new CommandException("Unknown host: " + t.getMessage(), t);
}
if (t instanceof ConnectException) {
throw new CommandException("Failed to connect to endpoint: " + t.getMessage(), t);
}
}
private static void throwIfProblemLocalEnvironment(Exception e) throws CommandException
{
Throwable t = Throwables.getRootCause(e);
if (t instanceof CertificateException || t instanceof SSLPeerUnverifiedException) {
throw new CommandException(t.getMessage(), e);
}
}
private static void throwAsGenericProblem(Exception e) throws CommandException
{
if (e instanceof DelegationException || e instanceof DelegationExceptionType) {
throw new CommandException("Remote server said: " + e.toString(), e);
} else {
Throwable t = Throwables.getRootCause(e);
StringBuilder sb = new StringBuilder();
sb.append("Problem communicating with endpoint: ");
if (!(t instanceof RuntimeException)) {
sb.append(t.getClass().getCanonicalName()).append(": ");
}
sb.append(t.getMessage());
throw new CommandException(sb.toString(), e);
}
}
private static CommandException asCommandException(Exception cause)
{
try {
throwIfProblemWithUrl(cause);
throwIfProblemLocalEnvironment(cause);
throwAsGenericProblem(cause);
} catch (CommandException e) {
return e;
}
return new CommandException("Unexpected message: " + cause.getMessage());
}
private void checkHaveEndpoint() throws CommandException
{
if (_client == null) {
throw new CommandException("No endpoint specified; use the 'endpoint' command");
}
}
private URI canonicalise(URI uri)
{
String portAndPath;
switch (uri.getScheme().toLowerCase()) {
case "https":
int port = uri.getPort();
if (port == 443) {
port = -1;
}
portAndPath = (port == -1 ? "" : (":" + port)) + uri.getPath();
break;
case "srm":
portAndPath = ":8445/srm/delegation";
break;
case "fts":
portAndPath = ":8443/glite-data-transfer-fts/services/gridsite-delegation";
break;
case "cream":
portAndPath = ":8443/ce-cream/services/gridsite-delegation";
break;
case "dpm":
portAndPath = ":443/gridsite-delegation";
break;
default:
throw new RuntimeException("Unexpected URI scheme: " + uri.getScheme());
}
String host = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getHost();
return URI.create("https://" + host + portAndPath);
}
@Command(name = "endpoint policy", hint="manage version discovery policy",
description="This command shows and controls how a new endpoint is handled. " +
"If no argument is specified then the current policy is shown and " +
"the policy is updated if an argument is specified.\n" +
"\n"+
"There are two major versions of the GridSite protocol in use: " +
"v1.1.0 and v2.0.0. These different versions are broadly similar " +
"but incompatible. Both versions are supported, but this " +
"client needs to know which version to use for a given endpoint.\n" +
"\n" +
"Without any argument, the 'endpoint policy' command shows the " +
"current policy: the short name followed by a brief description. " +
"If the short name is 'assume-v1' or 'assume-v2' then the next " +
"'endpoint' command will assume the endpoint is either " +
"GridSite v1.1.0 or v2.0.0 respectively; subsequent commands " +
"will fail if GridSite version is incorrect. If the short name " +
"is 'probe' then the client will try a GridSite v2.0.0 command " +
"and a v1.1.0 command to discover which version the endpoint " +
"supports. If neither command is successful then the 'set " +
"endpoint' command will fail.\n" +
"\n" +
"With an argument, this command will update the policy. The new " +
"policy will only affect subsequent calls to 'endpoint'; the " +
"current endpoint (if any) is not affected\n" +
"\n" +
"NOTE: some implementations of GridSite fail unauthorised " +
"requests in a way indestinguishable from an endpoint that " +
"does not supporting GridSite at all. This can lead to the " +
"'probe' policy failing during the 'endpoint' command.")
public class EndpointPolicy implements Callable<Serializable>
{
@Argument(usage="Updated policy for new connections.",
valueSpec="assume-v1|assume-v2|probe", required=false)
public String policy;
@Override
public Serializable call() throws Exception
{
if (policy == null) {
switch (_versionPolicy) {
case ASSUME_V1:
return "assume-v1 -- connect to endpoint as GridSite v1.1.0";
case ASSUME_V2:
return "assume-v2 -- connect to endpoint as GridSite v2.0.0";
case PROBE:
return "probe -- try GridSite commands to identify version";
}
throw new RuntimeException ("Unknown policy: " + _versionPolicy);
} else {
switch (policy)
{
case "assume-v1":
_versionPolicy = GridSiteVersionPolicy.ASSUME_V1;
return null;
case "assume-v2":
_versionPolicy = GridSiteVersionPolicy.ASSUME_V2;
return null;
case "probe":
_versionPolicy = GridSiteVersionPolicy.PROBE;
return null;
default:
throw new CommandException("Unknown policy '" + policy +
"'. Should be assume-v1, assume-v2 or probe");
}
}
}
}
@Command(name = "get version", hint="query software version",
description="Query the remote server to discover which version " +
"of the software it is running. This command is only " +
"available to GridSite v2.0.0 endpoints.")
public class GetVersionCommand implements Callable<Serializable>
{
@Override
public Serializable call() throws CommandException
{
checkHaveEndpoint();
try {
return _client.getVersion();
} catch (RemoteException e) {
throw asCommandException(e);
}
}
}
@Command(name = "get interface version", hint = "query delegation version",
description = "query the remote server to discover which version " +
"of the GridSite interface it is providing. This command " +
"is only available to GridSite v2.0.0 endpoints.")
public class GetInterfaceVersionCommand implements Callable<Serializable>
{
@Override
public Serializable call() throws CommandException
{
checkHaveEndpoint();
try {
return _client.getInterfaceVersion();
} catch (RemoteException e) {
throw asCommandException(e);
}
}
}
@Command(name = "get service metadata", hint = "query arbitrary service metadata",
description = "Query the remote server to discover the value " +
"corresponding to the supplied key. This command is only " +
"available to GridSite v2.0.0 endpoints.")
public class GetServiceMetadataCommand implements Callable<Serializable>
{
@Argument(usage="Key for the requested information.")
String key;
@Override
public Serializable call() throws CommandException
{
checkHaveEndpoint();
try {
return _client.getServiceMetadata(key);
} catch (RemoteException e) {
throw asCommandException(e);
}
}
}
@Command(name = "delegate", hint="delegate a credential",
description = "Delegate the current credential to the server. " +
"There are two forms of this command: client-supplied-id " +
"and server-supplied-id. In either case, the delegated " +
"credential will have an ID that may be used later to " +
"trigger the credential's destruction.\n" +
"\n" +
"Invoking this command and specifying an ID as the argument " +
"triggers the client-supplied-id form. The server will " +
"store the credential against this ID, allowing it to be " +
"destroyed later on.\n" +
"\n" +
"For a server-supplied ID, invoke the command without " +
"any arguments. The remote server will choose an ID for " +
"the delegated credential and this ID will be reported, " +
"allowing manual triggering of the credential's destruction.\n" +
"\n" +
"The lifetime of the delegated credential may be limited. " +
"By default delegated credentials are limited to 24 hours " +
"but this lifetime may be extended or shortened using the " +
"-lifetime option. Independent of this option, the " +
"delegated credential's lifetime is always limited to that " +
"of the proxy certificate.")
public class DelegateCommand implements Callable<Serializable>
{
@Argument(usage="Id of delegated credential; if omitted the server will choose one.",
required=false)
String id;
@Option(name="lifetime", usage="Desired lifetime of delegated credential in seconds.")
Integer lifetime = 60*60*24;
@Override
public Serializable call() throws Exception
{
checkHaveEndpoint();
String csrData;
String id = this.id;
try {
if (id == null) {
IdAndRequest result = _client.getNewProxyReq();
csrData = result.request;
id = result.id;
} else {
csrData = _client.getProxyReq(id);
}
PKCS10CertificationRequest csr = fromPEM(csrData);
lifetime = limitLifetime(_proxy.getCertificateChain(), lifetime);
X509Certificate certificate = createCertificate(_proxy, csr, lifetime);
String result = toPEM(concat(certificate, _proxy.getCertificateChain()));
_client.putProxy(id, result);
} catch (RemoteException e) {
throw asCommandException(e);
}
if (this.id == null) {
console.println("Delegated credential has id " + id);
}
return null;
}
private int limitLifetime(X509Certificate[] certificates, int lifetime) throws IOException
{
Date expiry = new Date(System.currentTimeMillis() + lifetime*1000);
for (X509Certificate certificate : certificates) {
if (certificate.getNotAfter().before(expiry)) {
expiry = certificate.getNotAfter();
console.println("Generated certificate expire " + expiry);
}
}
return (int)((expiry.getTime() - System.currentTimeMillis()) / 1000);
}
private X509Certificate createCertificate(X509Credential proxy, PKCS10CertificationRequest csr, int lifetime)
throws IOException, GeneralSecurityException
{
ProxyRequestOptions options = new ProxyRequestOptions(proxy.getCertificateChain(), csr);
options.setLifetime(lifetime);
X509Certificate[] chain = ProxyGenerator.generate(options, proxy.getKey());
return chain[0];
}
private Iterable<X509Certificate> concat(X509Certificate certificate,
X509Certificate[] existingChain)
{
return Iterables.concat(Collections.singleton(certificate),
Arrays.asList(existingChain));
}
private String toPEM(Iterable<X509Certificate> certificates) throws IOException
{
StringWriter output = new StringWriter();
PEMWriter writer = new PEMWriter(output);
for (X509Certificate certificate : certificates) {
writer.writeObject(certificate);
}
writer.flush();
return output.toString();
}
private PKCS10CertificationRequest fromPEM(String data) throws IOException
{
PEMParser reader = new PEMParser(new StringReader(data));
return (PKCS10CertificationRequest)reader.readObject();
}
}
@Command(name="destroy", hint="destroy a delegated credential",
description = "This command contacts the endpoint and requests " +
"that the credential delegated against the supplied ID " +
"is destroyed. After being destroyed, the server cannot " +
"undertake any further activity using this credential.")
public class DestroyCommand implements Callable<Serializable>
{
@Argument(usage="Id of delegated credential.")
String id;
@Override
public Serializable call() throws CommandException
{
checkHaveEndpoint();
try {
_client.destroy(id);
} catch (RemoteException e) {
throw asCommandException(e);
}
return null;
}
}
@Command(name = "get termination time", hint="query when delegated credential expiries",
description = "This command will query the remote server to " +
"discover when the delegated credential will expire.")
public class GetTerminationTime implements Callable<Serializable>
{
@Argument
String id;
@Override
public Serializable call() throws CommandException
{
checkHaveEndpoint();
Date termination;
try {
termination = _client.getTerminationTime(id).getTime();
} catch (RemoteException e) {
throw asCommandException(e);
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String relative;
long remaining = (termination.getTime() - System.currentTimeMillis())/1000;
if (remaining <= 0) {
relative = "expired";
} else if (remaining < 120 ) {
relative = remaining + " seconds";
} else if (remaining < 7200 ) {
relative = (remaining / 60) + " minutes";
} else if (remaining < 172800 ) {
relative = (remaining / 3600) + " hours";
} else {
relative = (remaining / 86400) + " days";
}
return df.format(termination) + " (" + relative + ")";
}
}
@Command(name = "endpoint",
hint = "specify which GridSite Delegation endpoint to use",
description = "Specify the endpoint that subsequent commands " +
"will be directed towards. There are two forms for specifying " +
"a URI: a generic URI and a software-specific URI.\n" +
"\n" +
"The generic URI has the form 'https://HOST[:PORT]PATH' " +
"where HOST is the hostname of the server, PORT is the " +
"port number (port 443 is used if not specified) and PATH " +
"is the path to the delegation service. " +
"'https://delegation.example.org:8445/services/delegation' " +
"is an example of a generic URI.\n" +
"\n" +
"There are several software-specific URIs that make it " +
"easier to contact a specific software. All software-" +
"specific URIs may be written 'TYPE://HOST' or 'TYPE:HOST' " +
"where TYPE is the kind of software and HOST is the " +
"hostname of the endpoint. Note that no port number or " +
"path is supplied as the correct values are added " +
"automatically. Currently supported values of TYPE are " +
"'srm' 'dpm' 'cream' and 'fts'.\n" +
"\n" +
"When this command is called, the GridSite version policy " +
"is followed. This may involve assuming the endpoint is " +
"a particular version or probing the endpoint to discover " +
"which version it supports. If the endpoint is probed " +
"and supports neither version then the 'endpoint' command " +
"will fail. The policy is managed with the " +
"'endpoint policy' command.")
public class EndpointCommand implements Callable<Serializable>
{
@Argument(usage="Delegation endpoint.", metaVar="URI")
String uri;
@Override
public Serializable call() throws CommandException, IOException
{
try {
setEndpoint(URI.create(uri));
} catch (IllegalArgumentException | IllegalStateException e) {
throw new CommandException(e.getMessage());
}
return null;
}
}
/**
* An class that represents the delegation endpoint that provides either
* GridSite v1.1.0 or v2.0.0 behaviour. If a request is version specific
* and that version isn't being used then a CommandException is thrown.
*/
private static class Delegation
{
private final org.dcache.delegation.gridsite1.Delegation _v1Client;
private final org.dcache.delegation.gridsite2.Delegation _v2Client;
private Delegation(org.dcache.delegation.gridsite1.Delegation client)
{
_v1Client = client;
_v2Client = null;
}
private Delegation(org.dcache.delegation.gridsite2.Delegation client)
{
_v1Client = null;
_v2Client = client;
}
public GridSiteVersion getGridSiteVersion()
{
return (_v2Client == null) ? GridSiteVersion.V1_1_0 : GridSiteVersion.V2_0_0;
}
public String getVersion() throws CommandException, RemoteException
{
guardVersion(GridSiteVersion.V2_0_0, "only supported for v2.0.0 endpoints");
return _v2Client.getVersion();
}
public String getProxyReq(String id) throws RemoteException
{
if (_v1Client != null) {
return _v1Client.getProxyReq(id);
} else {
return _v2Client.getProxyReq(id);
}
}
public void putProxy(String id, String chain) throws RemoteException
{
if (_v1Client != null) {
_v1Client.putProxy(id, chain);
} else {
_v2Client.putProxy(id, chain);
}
}
public void destroy(String id) throws RemoteException
{
if (_v1Client != null) {
_v1Client.destroy(id);
} else {
_v2Client.destroy(id);
}
}
public IdAndRequest getNewProxyReq() throws RemoteException
{
if (_v1Client != null) {
NewProxyReq result = _v1Client.getNewProxyReq();
return new IdAndRequest(result.getDelegationID(), result.getProxyRequest());
} else {
StringHolder req = new StringHolder();
StringHolder id = new StringHolder();
_v2Client.getNewProxyReq(req, id);
return new IdAndRequest(id.value, req.value);
}
}
public String getInterfaceVersion() throws RemoteException, CommandException
{
guardVersion(GridSiteVersion.V2_0_0, "only supported for v2.0.0 endpoints");
return _v2Client.getInterfaceVersion();
}
public String getServiceMetadata(String key) throws RemoteException,
CommandException
{
guardVersion(GridSiteVersion.V2_0_0, "only supported for v2.0.0 endpoints");
return _v2Client.getServiceMetadata(key);
}
public Calendar getTerminationTime(String id) throws CommandException,
RemoteException
{
guardVersion(GridSiteVersion.V2_0_0, "only supported for v2.0.0 endpoints");
return _v2Client.getTerminationTime(id);
}
private void guardVersion(GridSiteVersion version, String message)
throws CommandException
{
if (version != getGridSiteVersion()) {
throw new CommandException(message);
}
}
}
/**
* Simple class to hold the server-generated delegation ID along with
* the corresponding Certificate Signing Request.
*/
private static class IdAndRequest
{
private final String id;
private final String request;
private IdAndRequest(String delegationId, String certificateSigningRequest)
{
id = delegationId;
request = certificateSigningRequest;
}
}
}