package org.atricore.idbus.capabilities.oauth2.main.token.producers; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.atricore.idbus.capabilities.oauth2.main.*; import org.atricore.idbus.capabilities.oauth2.main.emitter.OAuth2SecurityTokenEmissionContext; import org.atricore.idbus.capabilities.oauth2.main.token.endpoints.TokenEndpoint; import org.atricore.idbus.capabilities.sts.main.SecurityTokenAuthenticationFailure; import org.atricore.idbus.capabilities.sts.main.WSTConstants; import org.atricore.idbus.common.oauth._2_0.protocol.AccessTokenRequestType; import org.atricore.idbus.common.oauth._2_0.protocol.AccessTokenResponseType; import org.atricore.idbus.common.oauth._2_0.protocol.ErrorCodeType; import org.atricore.idbus.common.oauth._2_0.protocol.OAuthAccessTokenType; import org.atricore.idbus.kernel.main.authn.Constants; import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrust; import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptor; import org.atricore.idbus.kernel.main.mediation.Artifact; import org.atricore.idbus.kernel.main.mediation.ArtifactImpl; import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl; import org.atricore.idbus.kernel.main.mediation.MessageQueueManager; import org.atricore.idbus.kernel.main.mediation.camel.AbstractCamelProducer; import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationExchange; import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationMessage; import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel; import org.atricore.idbus.kernel.main.mediation.channel.SPChannel; import org.atricore.idbus.kernel.main.util.UUIDGenerator; import org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.AttributedString; import org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.UsernameTokenType; import org.xmlsoap.schemas.ws._2005._02.trust.RequestSecurityTokenResponseType; import org.xmlsoap.schemas.ws._2005._02.trust.RequestSecurityTokenType; import org.xmlsoap.schemas.ws._2005._02.trust.RequestedSecurityTokenType; import org.xmlsoap.schemas.ws._2005._02.trust.wsdl.SecurityTokenService; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import java.math.BigInteger; /** * @author <a href=mailto:sgonzalez@atricore.org>Sebastian Gonzalez Oyuela</a> */ public class TokenProducer extends AbstractCamelProducer<CamelMediationExchange> { private static final Log logger = LogFactory.getLog(TokenProducer.class); private UUIDGenerator uuidGenerator = new UUIDGenerator(); public TokenProducer(TokenEndpoint endpoint) { super(endpoint); } @Override protected void doProcess(CamelMediationExchange exchange) throws Exception { CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); AccessTokenRequestType atReq = (AccessTokenRequestType) in.getMessage().getContent(); // We are acting as OAUTH 2.0 Authorization server, we consider it an IDP role. // TODO : Use identity planning AccessTokenResponseType atRes = new AccessTokenResponseType(); // --------------------------------------------------------- // Validate Request // --------------------------------------------------------- try { OAuth2Client client = null; // This returns the SP associated with the OAuth request. validateRequest(atReq, atRes); client = resolveOAuth2Client(atReq, atRes); if (client == null) { throw new OAuth2ServerException(ErrorCodeType.UNAUTHORIZED_CLIENT, "Invalid clientId/clientSecret"); } // Authenticate the client, unless an error has occurred. authenticateRequest(client, atReq, atRes); OAuth2SecurityTokenEmissionContext securityTokenEmissionCtx = new OAuth2SecurityTokenEmissionContext(); // Send extra information to STS, using the emission context //securityTokenEmissionCtx.setMember(sp); //securityTokenEmissionCtx.setAuthnState(authnState); securityTokenEmissionCtx.setSessionIndex(uuidGenerator.generateId()); // TODO : Support : EMIT ACCESS TOKEN From AUTHORIZATION TOKEN emitAccessTokenFromClaims(exchange, securityTokenEmissionCtx, atReq.getUsername(), atReq.getPassword()); // Call STS and wait for OAuth AccessToken OAuthAccessTokenType at = securityTokenEmissionCtx.getAccessToken(); // send access token back to requester // build response atRes.setAccessToken(at.getAccessToken()); atRes.setExpiresIn(BigInteger.valueOf(at.getExpiresIn())); atRes.setTokeyType(at.getTokenType()); } catch (OAuth2ServerException e) { // Send oauth error in response atRes.setError(e.getErrorCode()); atRes.setErrorDescription(e.getErrorDescription()); // Dump stack trance if we have a cause: if (e.getCause() != null) logger.error(e.getErrorCode().value() + " ["+e.getErrorDescription()+"]", e); else logger.warn(e.getErrorCode().value() + " ["+e.getErrorDescription()+"]"); } catch (SecurityTokenAuthenticationFailure e) { atRes.setError(ErrorCodeType.ACCESS_DENIED); atRes.setErrorDescription(e.getMessage()); if (logger.isDebugEnabled()) logger.debug(e.getMessage(), e); } catch (Exception e) { // Something went wrong atRes.setError(ErrorCodeType.SERVER_ERROR); atRes.setErrorDescription(e.getMessage()); logger.error(e.getMessage(), e); } // -------------------------------------------- // Emit OAuth Access Token // -------------------------------------------- // send response back EndpointDescriptor ed = null; // TODO : Only works for SOAP messages! CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); out.setMessage(new MediationMessageImpl(uuidGenerator.generateId(), atRes, "AccessTokenResponseType", null, ed, null)); exchange.setOut(out); } /** * This will return an emission context with both, the required SAMLR2 Assertion and the associated Subject. * * @return SamlR2 Security emission context containing SAMLR2 Assertion and Subject. */ protected OAuth2SecurityTokenEmissionContext emitAccessTokenFromClaims(CamelMediationExchange exchange, OAuth2SecurityTokenEmissionContext accessAccessTokenEmissionCtx, String username, String password) throws Exception { MessageQueueManager aqm = getArtifactQueueManager(); // ------------------------------------------------------- // Emit a new security token // ------------------------------------------------------- // TODO : Improve communication mechanism between STS and IDP! // Queue this contenxt and send the artifact as RST context information Artifact emitterCtxArtifact = aqm.pushMessage(accessAccessTokenEmissionCtx); SecurityTokenService sts = ((SPChannel) channel).getSecurityTokenService(); // Send artifact id as RST context information, similar to relay state. RequestSecurityTokenType rst = buildRequestSecurityToken(username, password, emitterCtxArtifact.getContent()); if (logger.isDebugEnabled()) logger.debug("Requesting OAuth 2 Access Token (RST) w/context " + rst.getContext()); // Send request to STS RequestSecurityTokenResponseType rstrt = sts.requestSecurityToken(rst); if (logger.isDebugEnabled()) logger.debug("Received Request Security Token Response (RSTR) w/context " + rstrt.getContext()); // Recover emission context, to retrive Subject information accessAccessTokenEmissionCtx = (OAuth2SecurityTokenEmissionContext) aqm.pullMessage(ArtifactImpl.newInstance(rstrt.getContext())); /// Obtain assertion from STS Response JAXBElement<RequestedSecurityTokenType> token = (JAXBElement<RequestedSecurityTokenType>) rstrt.getAny().get(1); OAuthAccessTokenType accessToken = (OAuthAccessTokenType) token.getValue().getAny(); if (logger.isDebugEnabled()) logger.debug("Generated OAuth Access Token [" + accessToken.getAccessToken() + "]"); accessAccessTokenEmissionCtx.setAccessToken(accessToken); // Return context with Assertion and Subject return accessAccessTokenEmissionCtx; } private RequestSecurityTokenType buildRequestSecurityToken(String username, String password, String context) throws OAuth2Exception { logger.debug("generating RequestSecurityToken..."); org.xmlsoap.schemas.ws._2005._02.trust.ObjectFactory of = new org.xmlsoap.schemas.ws._2005._02.trust.ObjectFactory(); RequestSecurityTokenType rstRequest = new RequestSecurityTokenType(); rstRequest.getAny().add(of.createTokenType(WSTConstants.WST_OAUTH2_TOKEN_TYPE)); rstRequest.getAny().add(of.createRequestType(WSTConstants.WST_ISSUE_REQUEST)); org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.ObjectFactory ofwss = new org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.ObjectFactory(); // Send credentials with authn request: UsernameTokenType usernameToken = new UsernameTokenType (); AttributedString usernameString = new AttributedString(); usernameString.setValue( username ); usernameToken.setUsername( usernameString ); usernameToken.getOtherAttributes().put(new QName(Constants.PASSWORD_NS), password); rstRequest.getAny().add(ofwss.createUsernameToken(usernameToken)); if (context != null) rstRequest.setContext(context); logger.debug("generated RequestSecurityToken [" + rstRequest + "]"); return rstRequest; } protected MessageQueueManager getArtifactQueueManager() { OAuth2IdPMediator a2Mediator = (OAuth2IdPMediator) channel.getIdentityMediator(); return a2Mediator.getArtifactQueueManager(); } protected CircleOfTrust getCot() { if (this.channel instanceof FederationChannel) { return ((FederationChannel) channel).getCircleOfTrust(); } if (logger.isDebugEnabled()) logger.debug("There is no associated circle of trust, channel is not a federation channel"); return null; } protected void validateRequest(AccessTokenRequestType atReq, AccessTokenResponseType atRes) throws InvalidRequestException { if (atReq.getClientId() == null) { throw new InvalidRequestException(ErrorCodeType.INVALID_REQUEST, "Access Token Request MUST include a client id"); } if (atReq.getClientSecret() == null) { throw new InvalidRequestException(ErrorCodeType.INVALID_REQUEST, "Access Token Request MUST include a client secret"); } } protected OAuth2Client resolveOAuth2Client(AccessTokenRequestType atReq, AccessTokenResponseType atRes) { if (atRes.getError() != null) return null; // Now we need the idpChannel configured to talk to us. SPChannel spChannel = (SPChannel) channel; if (spChannel == null) { logger.error("No IDP Channel found for request"); atRes.setError(ErrorCodeType.INVALID_REQUEST); atRes.setErrorDescription("No IDP Channel found for request"); return null; } // TODO : Look for configured client authentication mechanism: authn token, secret, others?! // Take oauth2 client configuration from mediator OAuth2IdPMediator mediator = (OAuth2IdPMediator) spChannel.getIdentityMediator(); // Authenticate client using secret if (mediator.getClients() != null && mediator.getClients().size() > 0) { for (OAuth2Client oAuth2Client : mediator.getClients()) { if (oAuth2Client.getId().equals(atReq.getClientId())) { if (logger.isTraceEnabled()) logger.trace("Found OAuth2 client for " + atReq.getClientId()); return oAuth2Client; } } } else { logger.warn("No OAuth2 clients configured for mediator in channel " + spChannel.getName()); return null; } logger.warn("OAuth2 client not found for " + atReq.getClientId()); return null; } protected boolean authenticateRequest(OAuth2Client client, AccessTokenRequestType atReq, AccessTokenResponseType atRes) throws OAuth2ServerException { // TODO : Improve: support other authn methods (user grant permission), strong authn, add password hashing, etc if (logger.isTraceEnabled()) { logger.trace("Authenticating req "+atReq.getClientId()+" for client " + client); } if (!client.getSecret().equals(atReq.getClientSecret())) { throw new OAuth2ServerException(ErrorCodeType.UNAUTHORIZED_CLIENT, "Invalid clientId/clientSecret"); } return true; } }