SoapSamlTokenProvider.java revision 4afc0d6edb00f1b334970a8a5d4396736c331659
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyrighted [year] [name of copyright owner]".
*
* Copyright 2014-2015 ForgeRock AS.
*/
/**
* The TokenProvider responsible for issuing SAML2 assertions by consuming the TokenGenerationService.
*/
public class SoapSamlTokenProvider implements TokenProvider {
public static class SoapSamlTokenProviderBuilder {
private AMSessionInvalidator amSessionInvalidator;
private String stsInstanceId;
private XMLUtilities xmlUtilities;
public SoapSamlTokenProviderBuilder tokenGenerationServiceConsumer(TokenGenerationServiceConsumer tokenGenerationServiceConsumer) {
return this;
}
public SoapSamlTokenProviderBuilder amSessionInvalidator(AMSessionInvalidator amSessionInvalidator) {
return this;
}
public SoapSamlTokenProviderBuilder threadLocalAMTokenCache(ThreadLocalAMTokenCache threadLocalAMTokenCache) {
return this;
}
this.stsInstanceId = stsInstanceId;
return this;
}
return this;
}
this.xmlUtilities = xmlUtilities;
return this;
}
public SoapSamlTokenProviderBuilder authnContextMapper(XmlTokenAuthnContextMapper authnContextMapper) {
this.authnContextMapper = authnContextMapper;
return this;
}
public SoapSamlTokenProviderBuilder amSessionTokenXmlMarshaller(XmlMarshaller<OpenAMSessionToken> amSessionTokenXmlMarshaller) {
return this;
}
public SoapSamlTokenProviderBuilder soapSTSAccessTokenProvider(SoapSTSAccessTokenProvider soapSTSAccessTokenProvider) {
return this;
}
return this;
}
public SoapSamlTokenProvider build() {
return new SoapSamlTokenProvider(this);
}
}
private final TokenGenerationServiceConsumer tokenGenerationServiceConsumer;
private final AMSessionInvalidator amSessionInvalidator;
private final ThreadLocalAMTokenCache threadLocalAMTokenCache;
private final String stsInstanceId;
private final XMLUtilities xmlUtilities;
private final XmlTokenAuthnContextMapper authnContextMapper;
private final SoapSTSAccessTokenProvider soapSTSAccessTokenProvider;
/*
ctor not injected as this class created by TokenOperationFactoryImpl
*/
}
public static SoapSamlTokenProviderBuilder builder() {
return new SoapSamlTokenProviderBuilder();
}
/**
* @see org.apache.cxf.sts.token.provider.TokenProvider
* Note that I got rid of realm support as defined by the CXF-STS. See
* org.apache.cxf.sts.token.provider.SAMLTokenProvider#canHandleToken for details on the realm support.
*/
}
/**
* @see org.apache.cxf.sts.token.provider.TokenProvider
* Note that I got rid of realm support as defined by the CXF-STS. See
* org.apache.cxf.sts.token.provider.SAMLTokenProvider#canHandleToken for details on the realm support.
*/
return (WSConstants.WSS_SAML2_TOKEN_TYPE.equals(tokenType) || WSConstants.SAML2_NS.equals(tokenType));
}
/**
* @see org.apache.cxf.sts.token.provider.TokenProvider
*/
try {
final SAML2SubjectConfirmation subjectConfirmation = determineSubjectConfirmation(tokenProviderParameters);
}
try {
} catch (TokenCreationException e) {
}
if (assertionDocument == null) {
logger.error("Could not turn assertion string returned from TokenGenerationService into DOM Document. " +
"The assertion string: " + assertion);
"Could not turn assertion string returned from TokenGenerationService into DOM Document.");
}
return tokenProviderResponse;
} finally {
if (amSessionInvalidator != null) {
try {
} catch (Exception e) {
/*
The fact that the interim OpenAM session was not invalidated should not prevent a token from being issued, so
I will not throw a AMSTSRuntimeException
*/
}
}
}
}
/*
The TokenGenerationService needs to the SAML2SubjectConfirmation as a parameter. This method will return the appropriate
SubjectConfirmation value, depending upon the KeyType specified in the RST invocation, and also the OnBehalfOf value.
*/
private SAML2SubjectConfirmation determineSubjectConfirmation(TokenProviderParameters tokenProviderParameters) {
return SAML2SubjectConfirmation.BEARER;
/*
The OnBehalfOf element defined in WS-Trust is used to indicate that the STS is being asked to issue a
token OnBehalfOf another party, which is idiom which matches the STS issuing a SAML2 SV assertion. It appears
that the ActAs element also has the same semantics. See the following links for details:
*/
} else {
}
/*
The TokenGenerationService does not, as of now, support HoK assertions with KeyInfo state in the SubjectConfirmationData
corresponding to symmetric keys.
*/
throw new AMSTSRuntimeException(ResourceException.NOT_SUPPORTED, "Issuing SAML2 assertions with symmetric KeyInfo" +
"in the SubjectConfirmationData of HoK assertions is currently not supported.");
} else {
String message = "Unexpected keyType in SoapSamlTokenProvider#determineSubjectConfirmation: " + keyType;
}
}
/*
Will return the ProofTokenState necessary for HoK assertions.
*/
if (certificate == null) {
String exceptionMessage = "The ReceivedKey instance in the KeyRequirements has a null X509Cert. Thus the " +
"ProofTokenState necessary to consume the TokenGenerationService cannot be created.";
}
try {
} catch (TokenMarshalException e) {
String message = "In SoapSamlTokenProvider#getAssertion, could not marshal X509Cert in ReceivedKey " +
"into ProofTokenState: " + e;
}
}
/*
This method must return the AuthnContextClassRef, a set of values defined in SAML2, included in the assertion generated
by the TokenGenerationService, which specify the authentication performed as part of issuing the assertion. Essentially
this value tells the relying party how the assertion subject was authenticated.
A SAML2 assertion will be generated under two circumstances:
1. as part of token transformation defined in the validate operation
2. as part of an issue operation
For case #1, the type of the validated token must be determined - accessed via the TokenRequirements in the TokenProviderParameters
For case #2, I imagine that it is possible to obtain the validated token from the Security-Policy enforcing interceptors
traversed during the Issue operation invocation.
Firstly, I have to determine what invocation I am dealing with - presumably this is possible by looking at the
tokens in the TokenRequirements.
*/
/*
Should not enter this block, as Cancel operation was never bound in the STSEndpoint. Log an throw an exception
if this occurs, as it is unexpected.
*/
String message = "Unexpected state in SoapSamlTokenProvider: TokenProviderParameters has a non-null cancelTarget " +
"in the TokenRequirments. A cancel operation is not bound, so this state is unexpected!";
} else {
/*
Here we must be dealing with an Issue operation, so I need to obtain the token validated by the SecurityPolicy
bindings protecting the issue operation, or from the ActAs/OnBehalfOf token, if present. The ActAs/OnBehalfOf
token has precedence, as it is the identity of this token for which an assertion will be generated.
*/
} else {
}
}
}
private String getAuthnContextClassRefFromDelegatedContext(TokenProviderParameters tokenProviderParameters) {
} else {
"Error in SoapSamlTokenProvider#getAuthnContextClassRefFromDelegatedContext - neither ActAs nor " +
"OnBehalfOf tokens found!");
}
}
/*
This examination does not have to be exhaustive, as this method is only called in a delegated context, and can only return
token types which we validate. Will return null if no TokenType could be determined.
See org.apache.cxf.sts.request.RequestParser#parseTokenElements for details on how this token is parsed - but it looks
like only an element will be set straight out of the RST payload.
This code will be refactored on an ongoing basis as token set supported by the soap-sts expands.
*/
if (receivedToken.isUsernameToken()) {
} else if (receivedToken.isBinarySecurityToken()) { //TODO: it appears that custom tokens are passed as BSTs - refactor as necessary
} else if (receivedToken.isDOMElement()) {
} else if ((tokenString != null) && tokenString.contains("X509")) { //TODO: clean up - use globally-defined constant
} else {
logger.error("Unexpected state in parseTokenTypeFromDelegatedReceivedToken: dealing with a token element, " +
"but it is neither UNT or X509. The element string: " + tokenString + "; Returning null for the TokenType.");
return null;
}
} else {
logger.error("Unexpected state in parseTokenTypeFromDelegatedReceivedToken: not dealing with a USERNAME or " +
return null;
}
}
/*
This method is called to obtain the Element corresponding to a delegated token (Act-As and OnBehalfOf), which is
then passed to the configured XmlTokenAuthnContextMapper implementation, so the AuthnContextClassRef can be obtained
for the invocation of the TokenGenerationService.
See org.apache.cxf.sts.request.RequestParser#parseTokenRequirements for details on how ActAs/OnBehalfOf token elements
are parsed - an element will be set straight out of the RST payload. It will be either a DOM Element or a
JAXBElement. See the org.apache.cxf.sts.request.ReceivedToken ctor for details.
TODO: if we are supporting ActAs/OnBehalfOf asserted as x509 or even a OpenAM token, then this method will have to
get more sophisticated, if we want to determine unequivocally the type of object returned. For now, I will simply
return the Object encapsulated in the ReceivedToken. Note that the objects
in the ReceivedTokens appear to be either be DOM Elements, or objects in the org.apache.cxf.ws.security.sts.provider.model.secext
package, as these seem to be the objects wrapped in the JAXBElement<?> passed to the ReceivedToken ctor.
*/
return receivedToken.getToken();
}
/*
Approach to obtain the token state from the SecurityPolicy binding traversal yield detailed in question in forums here:
*/
private String getAuthnContextFromSecurityPolicyBindings(TokenProviderParameters tokenProviderParameters) {
tokenProviderParameters.getWebServiceContext().getMessageContext().get(WSHandlerConstants.RECV_RESULTS));
/*
Note that the code referenced in the forum link above
seems to be doing about the same thing (obtaining a token element), but does not do any sanity checking on the
number of handlerResults or engineResults - it just processes and returns the first element encountered. I will
do the same, but log a warning if additional elements encountered.
*/
logger.warn("WSHanderResults obtained from the MessageContext in SoapSamlTokenProvider#getAuthnContextClassRef > 1:"
}
final Element supportingTokenElement = parseSupportingTokenElementFromWSSecurityEngineResult(engineResult);
if (supportingTokenElement != null) {
}
}
}
throw new AMSTSRuntimeException(ResourceException.INTERNAL_ERROR, "No WSHandlerResult instances to " +
"inspect to obtain the input token validated by SecurityPolicy bindings in " +
"SoapSamlTokenProvider#getAuthnContextClassRef");
} else {
throw new AMSTSRuntimeException(ResourceException.INTERNAL_ERROR, "No WSSecurityEngineResult obtained " +
"from the WSHandlerResult as necessary to inspect to obtain the input token validated by " +
"SecurityPolicy bindings in SoapSamlTokenProvider#getAuthnContextClassRef");
}
}
/*
There are multiple WSSecurityEngineResult instances (5) generated for a successful invocation of e.g. a UNT
over the asymmetric binding: two for the client's cert (one as a BinarySecurityToken representation of the client's
cert, and one with more generic information about this cert), one for the server's cert, one for a timestamp, and
one for the UNT ProtectionToken. The non-BinarySecurityToken representations of a x509 cert do not have a token-element
entry, but e.g. the timestamp does. The question is what can be used to identify the WSSecurityEngine result corresponding
to the actual identity-asserting SupportingToken asserted by the caller, rather than the various tokens which
serve to protect this identity token.
Note that this method will return null unless it can obtain an Element corresponding to the the SupportingToken asserting
the client's identity.
*/
private Element parseSupportingTokenElementFromWSSecurityEngineResult(WSSecurityEngineResult engineResult) {
final Element tokenElement =
if (tokenElement != null) {
if ((Boolean)validatedObject) {
/*
The TAG_ACTION is used by the various validators to indicate the action to which this result corresponds.
Exclude Timestamp actions. See classes on org.apache.ws.security.processor class for details.
*/
if (((Integer)engineResult.get(WSSecurityEngineResult.TAG_ACTION) & WSConstants.TS) != WSConstants.TS) {
return tokenElement;
}
}
}
}
return null;
}
/*
TODO: if we are doing OpenAM-based transformations, what would this input token type look like? Probably
TAG_BINARY_SECURITY_TOKEN - but then I have to determine the valueType to determine if it is an OpenAM token,
or some other custom type (OIDC?)
*/
} else {
logger.warn("In SoapSamlTokenProvider#parseTokenTypeFromWSSecurityEngineResult, encountered an unknown token type: " +
return null;
}
}
/*
The set of input tokens we support is currently limited to:
1. UNTs
2. OpenAM tokens - will be represented as a DOM Element
When we support X509 Certificates, a new branch has to be added here. Not sure whether a X509Cert is represented
as a JAXBElement or a DOM Element. See ReceivedToken for details.
*/
if (receivedToken.isUsernameToken()) {
return authnContextMapper.getAuthnContext(TokenType.USERNAME, ((UsernameToken)receivedToken.getToken()).getElement());
} else {
String message = "Unexpected type for a ReceivedToken which reports that it is a UsernameToken: the type: "
+ receivedToken.getToken().getClass().getCanonicalName() + "; The actual token: " + receivedToken.getToken();
}
} else if (receivedToken.isDOMElement()) {
/*
Right now, this can only mean we are dealing with an OpenAMSession token. Attempt to marshal with
the amSessionTokenXmlMarshaller, just to be sure.
*/
try {
} catch (TokenMarshalException e) {
/*
Note it seems that we could enter this branch because the CXF-STS does not distinguish token validators
for status validation, and token validators for the input in a transformation operation. Thus if a
deployed STS instance supports a set of token validation operations which is a superset of the input
set in token transformation operations, then this branch could be entered.
TODO: see of this contingency could be obviated by distinguishing the status validators from the
transformation validators.
*/
String message = "Unexpected state in SoapSamlTokenProivder#getAuthnContextClassRef: the ReceivedToken" +
" in the validateTarget is a DOM Element, but cannot be marshaled to an OpenAM Session token. " +
" This means that the validate operation is being invoked with an unsupported token type. " +
"The token element " + tokenElement;
}
} else {
String message = "Unexpected validateTarget token in SoapSamlTokenProvider#getAuthnContextClassRef - " +
}
}
/*
Throw TokenCreationException as threadLocalAMTokenCache.getAMToken throws a TokenCreationException. Let caller above
map that to an AMSTSRuntimeException.
*/
private String getAssertion(String authnContextClassRef, SAML2SubjectConfirmation subjectConfirmation,
try {
switch (subjectConfirmation) {
case BEARER:
case SENDER_VOUCHES:
return tokenGenerationServiceConsumer.getSAML2SenderVouchesAssertion(threadLocalAMTokenCache.getAMToken(),
case HOLDER_OF_KEY:
return tokenGenerationServiceConsumer.getSAML2HolderOfKeyAssertion(threadLocalAMTokenCache.getAMToken(),
}
"Unexpected SAML2SubjectConfirmation in AMSAMLTokenProvider: " + subjectConfirmation);
} finally {
if (consumptionToken != null) {
}
}
}
try {
return soapSTSAccessTokenProvider.getAccessToken();
} catch (ResourceException e) {
}
}
}