/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cxf.sts.operation;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBElement;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.sts.QNameConstants;
import org.apache.cxf.sts.event.STSIssueFailureEvent;
import org.apache.cxf.sts.event.STSIssueSuccessEvent;
import org.apache.cxf.sts.request.KeyRequirements;
import org.apache.cxf.sts.request.ReceivedToken;
import org.apache.cxf.sts.request.ReceivedToken.STATE;
import org.apache.cxf.sts.request.RequestRequirements;
import org.apache.cxf.sts.request.TokenRequirements;
import org.apache.cxf.sts.service.EncryptionProperties;
import org.apache.cxf.sts.token.provider.TokenProvider;
import org.apache.cxf.sts.token.provider.TokenProviderParameters;
import org.apache.cxf.sts.token.provider.TokenProviderResponse;
import org.apache.cxf.sts.token.provider.TokenReference;
import org.apache.cxf.sts.token.validator.TokenValidatorResponse;
import org.apache.cxf.ws.security.sts.provider.STSException;
import org.apache.cxf.ws.security.sts.provider.model.BinarySecretType;
import org.apache.cxf.ws.security.sts.provider.model.EntropyType;
import org.apache.cxf.ws.security.sts.provider.model.LifetimeType;
import org.apache.cxf.ws.security.sts.provider.model.RequestSecurityTokenCollectionType;
import org.apache.cxf.ws.security.sts.provider.model.RequestSecurityTokenResponseCollectionType;
import org.apache.cxf.ws.security.sts.provider.model.RequestSecurityTokenResponseType;
import org.apache.cxf.ws.security.sts.provider.model.RequestSecurityTokenType;
import org.apache.cxf.ws.security.sts.provider.model.RequestedProofTokenType;
import org.apache.cxf.ws.security.sts.provider.model.RequestedReferenceType;
import org.apache.cxf.ws.security.sts.provider.model.RequestedSecurityTokenType;
import org.apache.cxf.ws.security.sts.provider.operation.IssueOperation;
import org.apache.cxf.ws.security.sts.provider.operation.IssueSingleOperation;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.principal.SAMLTokenPrincipal;
import org.apache.wss4j.common.principal.SAMLTokenPrincipalImpl;
import org.apache.wss4j.common.saml.SamlAssertionWrapper;
import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
import org.apache.wss4j.stax.securityToken.SamlSecurityToken;
import org.apache.xml.security.exceptions.XMLSecurityException;
/**
* An implementation of the IssueOperation interface.
*/
public class TokenIssueOperation extends AbstractOperation implements IssueOperation, IssueSingleOperation {
static final Logger LOG = LogUtils.getL7dLogger(TokenIssueOperation.class);
public RequestSecurityTokenResponseCollectionType issue(
RequestSecurityTokenType request,
Principal principal,
Map<String, Object> messageContext
) {
RequestSecurityTokenResponseType response = issueSingle(request, principal, messageContext);
RequestSecurityTokenResponseCollectionType responseCollection =
QNameConstants.WS_TRUST_FACTORY.createRequestSecurityTokenResponseCollectionType();
responseCollection.getRequestSecurityTokenResponse().add(response);
return responseCollection;
}
public RequestSecurityTokenResponseCollectionType issue(
RequestSecurityTokenCollectionType requestCollection,
Principal principal,
Map<String, Object> messageContext
) {
RequestSecurityTokenResponseCollectionType responseCollection =
QNameConstants.WS_TRUST_FACTORY.createRequestSecurityTokenResponseCollectionType();
for (RequestSecurityTokenType request : requestCollection.getRequestSecurityToken()) {
RequestSecurityTokenResponseType response = issueSingle(request, principal, messageContext);
responseCollection.getRequestSecurityTokenResponse().add(response);
}
return responseCollection;
}
public RequestSecurityTokenResponseType issueSingle(
RequestSecurityTokenType request,
Principal principal,
Map<String, Object> messageContext
) {
long start = System.currentTimeMillis();
TokenProviderParameters providerParameters = new TokenProviderParameters();
try {
RequestRequirements requestRequirements = parseRequest(request, messageContext);
providerParameters = createTokenProviderParameters(requestRequirements, principal, messageContext);
providerParameters.setClaimsManager(claimsManager);
String realm = providerParameters.getRealm();
TokenRequirements tokenRequirements = requestRequirements.getTokenRequirements();
String tokenType = tokenRequirements.getTokenType();
if (stsProperties.getSamlRealmCodec() != null) {
SamlAssertionWrapper assertion = fetchSAMLAssertionFromWSSecuritySAMLToken(messageContext);
if (assertion != null) {
String wssecRealm = stsProperties.getSamlRealmCodec().getRealmFromToken(assertion);
SAMLTokenPrincipal samlPrincipal = new SAMLTokenPrincipalImpl(assertion);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("SAML token realm of user '" + samlPrincipal.getName() + "' is " + wssecRealm);
}
ReceivedToken wssecToken = new ReceivedToken(assertion.getElement());
wssecToken.setState(STATE.VALID);
TokenValidatorResponse tokenResponse = new TokenValidatorResponse();
tokenResponse.setPrincipal(samlPrincipal);
tokenResponse.setToken(wssecToken);
tokenResponse.setTokenRealm(wssecRealm);
tokenResponse.setAdditionalProperties(new HashMap<String, Object>());
processValidToken(providerParameters, wssecToken, tokenResponse);
providerParameters.setPrincipal(wssecToken.getPrincipal());
}
}
// Validate OnBehalfOf token if present
if (providerParameters.getTokenRequirements().getOnBehalfOf() != null) {
ReceivedToken validateTarget = providerParameters.getTokenRequirements().getOnBehalfOf();
handleDelegationToken(validateTarget, providerParameters, principal, messageContext,
realm, requestRequirements);
}
// See whether ActAs is allowed or not
if (providerParameters.getTokenRequirements().getActAs() != null) {
ReceivedToken validateTarget = providerParameters.getTokenRequirements().getActAs();
handleDelegationToken(validateTarget, providerParameters, principal, messageContext,
realm, requestRequirements);
}
// create token
TokenProviderResponse tokenResponse = null;
for (TokenProvider tokenProvider : tokenProviders) {
boolean canHandle = false;
if (realm == null) {
canHandle = tokenProvider.canHandleToken(tokenType);
} else {
canHandle = tokenProvider.canHandleToken(tokenType, realm);
}
if (canHandle) {
try {
tokenResponse = tokenProvider.createToken(providerParameters);
} catch (STSException ex) {
LOG.log(Level.WARNING, "", ex);
throw ex;
} catch (RuntimeException ex) {
LOG.log(Level.WARNING, "", ex);
throw new STSException("Error in providing a token", ex, STSException.REQUEST_FAILED);
}
break;
}
}
if (tokenResponse == null || tokenResponse.getToken() == null) {
LOG.log(Level.WARNING, "No token provider found for requested token type: " + tokenType);
throw new STSException(
"No token provider found for requested token type: " + tokenType,
STSException.REQUEST_FAILED
);
}
// prepare response
try {
KeyRequirements keyRequirements = requestRequirements.getKeyRequirements();
EncryptionProperties encryptionProperties = providerParameters.getEncryptionProperties();
RequestSecurityTokenResponseType response =
createResponse(
encryptionProperties, tokenResponse, tokenRequirements, keyRequirements
);
STSIssueSuccessEvent event = new STSIssueSuccessEvent(providerParameters,
System.currentTimeMillis() - start);
publishEvent(event);
return response;
} catch (Throwable ex) {
LOG.log(Level.WARNING, "", ex);
throw new STSException("Error in creating the response", ex, STSException.REQUEST_FAILED);
}
} catch (RuntimeException ex) {
LOG.log(Level.SEVERE, "Cannot issue token: " + ex.getMessage(), ex);
STSIssueFailureEvent event = new STSIssueFailureEvent(providerParameters,
System.currentTimeMillis() - start, ex);
publishEvent(event);
throw ex;
}
}
private void handleDelegationToken(
ReceivedToken validateTarget,
TokenProviderParameters providerParameters,
Principal principal,
Map<String, Object> messageContext,
String realm,
RequestRequirements requestRequirements
) {
TokenValidatorResponse tokenResponse = validateReceivedToken(
principal, messageContext, realm, requestRequirements.getTokenRequirements(), validateTarget);
if (tokenResponse == null) {
LOG.fine("No Token Validator has been found that can handle this token");
} else if (validateTarget.getState().equals(STATE.INVALID)) {
throw new STSException("Incoming token is invalid", STSException.REQUEST_FAILED);
} else if (validateTarget.getState().equals(STATE.VALID)) {
processValidToken(providerParameters, validateTarget, tokenResponse);
} else {
//[TODO] Add plugin for validation out-of-band
// Example:
// If the requestor is in the possession of a certificate (mutual ssl handshake)
// the STS trusts the token sent in OnBehalfOf element
}
Principal tokenPrincipal = null;
Set<Principal> tokenRoles = null;
if (tokenResponse != null) {
Map<String, Object> additionalProperties = tokenResponse.getAdditionalProperties();
if (additionalProperties != null) {
providerParameters.setAdditionalProperties(additionalProperties);
}
tokenPrincipal = tokenResponse.getPrincipal();
tokenRoles = tokenResponse.getRoles();
}
// See whether OnBehalfOf/ActAs is allowed or not
performDelegationHandling(requestRequirements, principal, messageContext,
validateTarget, tokenPrincipal, tokenRoles);
}
protected RequestSecurityTokenResponseType createResponse(
EncryptionProperties encryptionProperties,
TokenProviderResponse tokenResponse,
TokenRequirements tokenRequirements,
KeyRequirements keyRequirements
) throws WSSecurityException {
RequestSecurityTokenResponseType response =
QNameConstants.WS_TRUST_FACTORY.createRequestSecurityTokenResponseType();
String context = tokenRequirements.getContext();
if (context != null) {
response.setContext(context);
}
// TokenType
JAXBElement<String> jaxbTokenType =
QNameConstants.WS_TRUST_FACTORY.createTokenType(tokenRequirements.getTokenType());
response.getAny().add(jaxbTokenType);
// RequestedSecurityToken
RequestedSecurityTokenType requestedTokenType =
QNameConstants.WS_TRUST_FACTORY.createRequestedSecurityTokenType();
JAXBElement<RequestedSecurityTokenType> requestedToken =
QNameConstants.WS_TRUST_FACTORY.createRequestedSecurityToken(requestedTokenType);
tokenWrapper.wrapToken(tokenResponse.getToken(), requestedTokenType);
response.getAny().add(requestedToken);
if (returnReferences) {
// RequestedAttachedReference
TokenReference attachedReference = tokenResponse.getAttachedReference();
RequestedReferenceType requestedAttachedReferenceType = null;
if (attachedReference != null) {
requestedAttachedReferenceType = createRequestedReference(attachedReference, true);
} else {
requestedAttachedReferenceType =
createRequestedReference(
tokenResponse.getTokenId(), tokenRequirements.getTokenType(), true
);
}
JAXBElement<RequestedReferenceType> requestedAttachedReference =
QNameConstants.WS_TRUST_FACTORY.createRequestedAttachedReference(
requestedAttachedReferenceType
);
response.getAny().add(requestedAttachedReference);
// RequestedUnattachedReference
TokenReference unAttachedReference = tokenResponse.getUnAttachedReference();
RequestedReferenceType requestedUnattachedReferenceType = null;
if (unAttachedReference != null) {
requestedUnattachedReferenceType = createRequestedReference(unAttachedReference, false);
} else {
requestedUnattachedReferenceType =
createRequestedReference(
tokenResponse.getTokenId(), tokenRequirements.getTokenType(), false
);
}
JAXBElement<RequestedReferenceType> requestedUnattachedReference =
QNameConstants.WS_TRUST_FACTORY.createRequestedUnattachedReference(
requestedUnattachedReferenceType
);
response.getAny().add(requestedUnattachedReference);
}
// AppliesTo
response.getAny().add(tokenRequirements.getAppliesTo());
// RequestedProofToken
if (tokenResponse.isComputedKey() && keyRequirements.getComputedKeyAlgorithm() != null) {
JAXBElement<String> computedKey =
QNameConstants.WS_TRUST_FACTORY.createComputedKey(keyRequirements.getComputedKeyAlgorithm());
RequestedProofTokenType requestedProofTokenType =
QNameConstants.WS_TRUST_FACTORY.createRequestedProofTokenType();
requestedProofTokenType.setAny(computedKey);
JAXBElement<RequestedProofTokenType> requestedProofToken =
QNameConstants.WS_TRUST_FACTORY.createRequestedProofToken(requestedProofTokenType);
response.getAny().add(requestedProofToken);
} else if (tokenResponse.getEntropy() != null) {
Object token =
constructSecretToken(tokenResponse.getEntropy(), encryptionProperties, keyRequirements);
RequestedProofTokenType requestedProofTokenType =
QNameConstants.WS_TRUST_FACTORY.createRequestedProofTokenType();
requestedProofTokenType.setAny(token);
JAXBElement<RequestedProofTokenType> requestedProofToken =
QNameConstants.WS_TRUST_FACTORY.createRequestedProofToken(requestedProofTokenType);
response.getAny().add(requestedProofToken);
}
// Entropy
if (tokenResponse.isComputedKey() && tokenResponse.getEntropy() != null) {
Object token =
constructSecretToken(tokenResponse.getEntropy(), encryptionProperties, keyRequirements);
EntropyType entropyType = QNameConstants.WS_TRUST_FACTORY.createEntropyType();
entropyType.getAny().add(token);
JAXBElement<EntropyType> entropyElement =
QNameConstants.WS_TRUST_FACTORY.createEntropy(entropyType);
response.getAny().add(entropyElement);
}
// Lifetime
LifetimeType lifetime =
createLifetime(tokenResponse.getCreated(), tokenResponse.getExpires());
JAXBElement<LifetimeType> lifetimeType = QNameConstants.WS_TRUST_FACTORY.createLifetime(lifetime);
response.getAny().add(lifetimeType);
// KeySize
long keySize = tokenResponse.getKeySize();
if (keySize <= 0) {
keySize = keyRequirements.getKeySize();
}
if (keyRequirements.getKeySize() > 0) {
JAXBElement<Long> keySizeType =
QNameConstants.WS_TRUST_FACTORY.createKeySize(keySize);
response.getAny().add(keySizeType);
}
return response;
}
/**
* Method to fetch SAML assertion from the WS-Security header
*/
private static SamlAssertionWrapper fetchSAMLAssertionFromWSSecuritySAMLToken(
Map<String, Object> messageContext
) {
final List<WSHandlerResult> handlerResults =
CastUtils.cast((List<?>) messageContext.get(WSHandlerConstants.RECV_RESULTS));
// Try DOM results first
if (handlerResults != null && !handlerResults.isEmpty()) {
WSHandlerResult handlerResult = handlerResults.get(0);
List<WSSecurityEngineResult> engineResults = handlerResult.getResults();
for (WSSecurityEngineResult engineResult : engineResults) {
Object token = engineResult.get(WSSecurityEngineResult.TAG_SAML_ASSERTION);
if (token instanceof SamlAssertionWrapper) {
return (SamlAssertionWrapper)token;
}
}
}
// Now try steaming results
try {
org.apache.xml.security.stax.securityToken.SecurityToken securityToken =
findInboundSecurityToken(WSSecurityEventConstants.SAML_TOKEN, messageContext);
if (securityToken instanceof SamlSecurityToken
&& ((SamlSecurityToken)securityToken).getSamlAssertionWrapper() != null) {
return ((SamlSecurityToken)securityToken).getSamlAssertionWrapper();
}
} catch (XMLSecurityException e) {
LOG.log(Level.FINE, e.getMessage(), e);
return null;
}
return null;
}
/**
* Construct a token containing the secret to return to the client. The secret is returned in a
* BinarySecretType JAXBElement.
*/
private Object constructSecretToken(
byte[] secret,
EncryptionProperties encryptionProperties,
KeyRequirements keyRequirements
) throws WSSecurityException {
/*if (encryptIssuedToken) {
return encryptSecret(secret, encryptionProperties, keyRequirements);
} else {
*/
BinarySecretType binarySecretType = QNameConstants.WS_TRUST_FACTORY.createBinarySecretType();
String nonce = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Nonce";
binarySecretType.setType(nonce);
binarySecretType.setValue(secret);
return QNameConstants.WS_TRUST_FACTORY.createBinarySecret(binarySecretType);
}
}