/* * JBoss, Home of Professional Open Source * * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * Licensed 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.picketlink.identity.federation.core.wstrust; import org.picketlink.common.PicketLinkLogger; import org.picketlink.common.PicketLinkLoggerFactory; import org.picketlink.common.constants.WSTrustConstants; import org.picketlink.common.exceptions.fed.WSTrustException; import org.picketlink.common.util.DocumentUtil; import org.picketlink.common.util.StringUtil; import org.picketlink.identity.federation.core.parsers.wst.WSTrustParser; import org.picketlink.identity.federation.core.wstrust.wrappers.RequestSecurityToken; import org.picketlink.identity.federation.core.wstrust.wrappers.RequestSecurityTokenResponse; import org.picketlink.identity.federation.core.wstrust.wrappers.RequestSecurityTokenResponseCollection; import org.picketlink.identity.federation.core.wstrust.writers.WSTrustRequestWriter; import org.picketlink.identity.federation.ws.trust.CancelTargetType; import org.picketlink.identity.federation.ws.trust.RenewTargetType; import org.picketlink.identity.federation.ws.trust.StatusType; import org.picketlink.identity.federation.ws.trust.ValidateTargetType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.namespace.QName; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPPart; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.ws.BindingProvider; import javax.xml.ws.Dispatch; import javax.xml.ws.Service; import javax.xml.ws.Service.Mode; import javax.xml.ws.soap.SOAPBinding; import java.io.InputStream; import java.net.URI; import java.security.Principal; import java.util.Map; /** * WS-Trust Client * * @author Anil.Saldhana@redhat.com * @since Aug 29, 2009 */ public class STSClient implements STSClientConfigKeyProvider { private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger(); private final ThreadLocal<Dispatch<Source>> dispatchLocal = new InheritableThreadLocal<Dispatch<Source>>(); private final String targetNS = "http://org.picketlink.trust/sts/"; private String wsaIssuerAddress; private String wspAppliesTo; private String soapBinding = SOAPBinding.SOAP11HTTP_BINDING; private String serviceName; private String portName; private String endPointAddress; private String userName; /** * Indicates whether the request is a batch request - will be read from the {@link STSClientConfig} */ private boolean isBatch = false; /** * Constructor * * @see {@link #setDispatch(Dispatch)} for the setting of the {@link Dispatch} object */ public STSClient() { } /** * <p> * Constructor that creates the {@link Dispatch} for use. * </p> * <p> * If you need to customize the ws properties, it is suggested to preconstruct a {@link Dispatch} object and use the * default * no-arg constructor followed by a {@linkplain #setDispatch(Dispatch)} call * </p> * * @param config */ public STSClient(STSClientConfig config) { this.serviceName = config.getServiceName(); this.portName = config.getPortName(); this.endPointAddress = config.getEndPointAddress(); this.userName = config.getUsername(); QName service = new QName(targetNS, this.serviceName); QName portName = new QName(targetNS, this.portName); isBatch = config.isBatch(); wsaIssuerAddress = config.getWsaIssuer(); wspAppliesTo = config.getWspAppliesTo(); soapBinding = config.getSoapBinding(); Service jaxwsService = Service.create(service); jaxwsService.addPort(portName, soapBinding, this.endPointAddress); Dispatch<Source> dispatch = jaxwsService.createDispatch(portName, Source.class, Mode.PAYLOAD); Map<String, Object> reqContext = dispatch.getRequestContext(); String username = config.getUsername(); if (username != null) { // add the username and password to the request context. reqContext.put(BindingProvider.USERNAME_PROPERTY, config.getUsername()); reqContext.put(BindingProvider.PASSWORD_PROPERTY, config.getPassword()); } setDispatch(dispatch); } /** * Set the {@link Dispatch} object for use * * @param dispatch */ public void setDispatch(Dispatch<Source> dispatch) { if (dispatch == null) throw logger.nullArgumentError("dispatch"); dispatchLocal.set(dispatch); } /** * Issue a token * * @param tokenType * * @return * * @throws WSTrustException */ public Element issueToken(String tokenType) throws WSTrustException { // create a custom token request message. RequestSecurityToken request = new RequestSecurityToken(); setTokenType(tokenType, request); if (wsaIssuerAddress != null) { request.setIssuer(WSTrustUtil.createIssuer(wsaIssuerAddress)); } if (wspAppliesTo != null) { request.setAppliesTo(WSTrustUtil.createAppliesTo(wspAppliesTo)); } // send the token request to JBoss STS and get the response. return issueToken(request); } /** * Issues a Security Token for the ultimate recipient of the token. * * @param endpointURI - The ultimate recipient of the token. This will be set at the AppliesTo for the * RequestSecurityToken * which is an optional element so it may be null. * * @return Element - The Security Token Element which will be of the TokenType configured for the endpointURI passed * in. * * @throws WSTrustException */ public Element issueTokenForEndpoint(String endpointURI) throws WSTrustException { RequestSecurityToken request = new RequestSecurityToken(); if (wsaIssuerAddress != null) { request.setIssuer(WSTrustUtil.createIssuer(wsaIssuerAddress)); } setAppliesTo(endpointURI, request); return issueToken(request); } /** * Issues a Security Token from the STS. This methods has the option of specifying one or both of * endpointURI/tokenType but * at least one must specified. * * @param endpointURI - The ultimate recipient of the token. This will be set at the AppliesTo for the * RequestSecurityToken * which is an optional element so it may be null. * @param tokenType - The type of security token to be issued. * * @return Element - The Security Token Element issued. * * @throws IllegalArgumentException If neither endpointURI nor tokenType was specified. * @throws WSTrustException */ public Element issueToken(String endpointURI, String tokenType) throws WSTrustException { if (endpointURI == null && tokenType == null) throw logger.nullArgumentError("endpointURI or tokenType"); RequestSecurityToken request = new RequestSecurityToken(); if (wsaIssuerAddress != null) { request.setIssuer(WSTrustUtil.createIssuer(wsaIssuerAddress)); } setAppliesTo(endpointURI, request); setTokenType(tokenType, request); return issueToken(request); } /** * <p> * Issues a security token on behalf of the specified principal. * </p> * * @param endpointURI the ultimate recipient of the token. This will be set at the AppliesTo for the * RequestSecurityToken * which is an optional element so it may be null. * @param tokenType the type of the token to be issued. * @param principal the {@code Principal} to whom the token will be issued. * * @return an {@code Element} representing the issued security token. * * @throws IllegalArgumentException If neither endpointURI nor tokenType was specified. * @throws WSTrustException if an error occurs while issuing the security token. */ public Element issueTokenOnBehalfOf(String endpointURI, String tokenType, Principal principal) throws WSTrustException { if (endpointURI == null && tokenType == null) throw logger.nullArgumentError("endpointURI or tokenType"); RequestSecurityToken request = new RequestSecurityToken(); if (wsaIssuerAddress != null) { request.setIssuer(WSTrustUtil.createIssuer(wsaIssuerAddress)); } setAppliesTo(endpointURI, request); setTokenType(tokenType, request); setOnBehalfOf(principal, request); return issueToken(request); } private RequestSecurityToken setAppliesTo(String endpointURI, RequestSecurityToken rst) { if (StringUtil.isNotNull(wspAppliesTo)) { rst.setAppliesTo(WSTrustUtil.createAppliesTo(wspAppliesTo)); } else if (endpointURI != null) rst.setAppliesTo(WSTrustUtil.createAppliesTo(endpointURI)); return rst; } private RequestSecurityToken setTokenType(String tokenType, RequestSecurityToken rst) { if (tokenType != null) rst.setTokenType(URI.create(tokenType)); return rst; } private RequestSecurityToken setOnBehalfOf(Principal principal, RequestSecurityToken request) { if (principal != null) request.setOnBehalfOf(WSTrustUtil.createOnBehalfOfWithUsername(principal.getName(), "ID")); return request; } /** * Issue a token * * @param request * * @return * * @throws WSTrustException */ public Element issueToken(RequestSecurityToken request) throws WSTrustException { if (request.getRequestType() == null) { if (isBatch) request.setRequestType(URI.create(WSTrustConstants.BATCH_ISSUE_REQUEST)); else request.setRequestType(URI.create(WSTrustConstants.ISSUE_REQUEST)); } if (request.getContext() == null) request.setContext("default-context"); validateDispatch(); DOMSource requestSource = this.createSourceFromRequest(request); Source response = dispatchLocal.get().invoke(requestSource); NodeList nodes; try { Node documentNode = DocumentUtil.getNodeFromSource(response); Document responseDoc = documentNode instanceof Document ? (Document) documentNode : documentNode.getOwnerDocument(); nodes = null; if (responseDoc instanceof SOAPPart) { SOAPPart soapPart = (SOAPPart) responseDoc; SOAPEnvelope env = soapPart.getEnvelope(); SOAPBody body = env.getBody(); Node data = body.getFirstChild(); nodes = ((Element) data).getElementsByTagNameNS(WSTrustConstants.BASE_NAMESPACE, "RequestedSecurityToken"); if (nodes == null || nodes.getLength() == 0) nodes = ((Element) data).getElementsByTagName("RequestedSecurityToken"); } else { nodes = responseDoc.getElementsByTagNameNS(WSTrustConstants.BASE_NAMESPACE, "RequestedSecurityToken"); if (nodes == null || nodes.getLength() == 0) nodes = responseDoc.getElementsByTagName("RequestedSecurityToken"); } } catch (Exception e) { throw new WSTrustException(logger.processingError(e)); } if (nodes == null) throw new WSTrustException(logger.nullValueError("NodeList")); Node rstr = nodes.item(0); if (rstr == null) throw new WSTrustException(logger.nullValueError("RSTR in the payload")); return (Element) rstr.getFirstChild(); } /** * Renew a token * * @param tokenType * @param token * * @return * * @throws WSTrustException */ public Element renewToken(String tokenType, Element token) throws WSTrustException { validateDispatch(); RequestSecurityToken request = new RequestSecurityToken(); request.setContext("context"); request.setTokenType(URI.create(WSTrustConstants.STATUS_TYPE)); request.setRequestType(URI.create(WSTrustConstants.RENEW_REQUEST)); RenewTargetType renewTarget = new RenewTargetType(); renewTarget.add(token); request.setRenewTarget(renewTarget); // send the token request to JBoss STS and get the response. DOMSource requestSource = this.createSourceFromRequest(request); Source response = dispatchLocal.get().invoke(requestSource); NodeList nodes; try { Node documentNode = DocumentUtil.getNodeFromSource(response); Document responseDoc = documentNode instanceof Document ? (Document) documentNode : documentNode.getOwnerDocument(); nodes = null; if (responseDoc instanceof SOAPPart) { SOAPPart soapPart = (SOAPPart) responseDoc; SOAPEnvelope env = soapPart.getEnvelope(); SOAPBody body = env.getBody(); Node data = body.getFirstChild(); nodes = ((Element) data).getElementsByTagNameNS(WSTrustConstants.BASE_NAMESPACE, "RequestedSecurityToken"); if (nodes == null || nodes.getLength() == 0) nodes = ((Element) data).getElementsByTagName("RequestedSecurityToken"); } else { nodes = responseDoc.getElementsByTagNameNS(WSTrustConstants.BASE_NAMESPACE, "RequestedSecurityToken"); if (nodes == null || nodes.getLength() == 0) nodes = responseDoc.getElementsByTagName("RequestedSecurityToken"); } } catch (Exception e) { throw new WSTrustException(logger.processingError(e)); } if (nodes == null) throw new WSTrustException(logger.nullValueError("NodeList")); Node rstr = nodes.item(0); return (Element) rstr.getFirstChild(); } /** * Validate a token * * @param token * * @return * * @throws WSTrustException */ public boolean validateToken(Element token) throws WSTrustException { validateDispatch(); RequestSecurityToken request = new RequestSecurityToken(); request.setContext("context"); request.setTokenType(URI.create(WSTrustConstants.STATUS_TYPE)); request.setRequestType(URI.create(WSTrustConstants.VALIDATE_REQUEST)); ValidateTargetType validateTarget = new ValidateTargetType(); validateTarget.add(token); request.setValidateTarget(validateTarget); DOMSource requestSource = this.createSourceFromRequest(request); Source response = dispatchLocal.get().invoke(requestSource); try { InputStream stream = DocumentUtil.getNodeAsStream(DocumentUtil.getNodeFromSource(response)); RequestSecurityTokenResponseCollection responseCollection = (RequestSecurityTokenResponseCollection) new WSTrustParser() .parse(stream); RequestSecurityTokenResponse tokenResponse = responseCollection.getRequestSecurityTokenResponses().get(0); StatusType status = tokenResponse.getStatus(); if (status != null) { String code = status.getCode(); return WSTrustConstants.STATUS_CODE_VALID.equals(code); } return false; } catch (Exception e) { throw new WSTrustException(logger.parserError(e)); } } /** * <p> * Cancels the specified security token by sending a WS-Trust cancel message to the STS. * </p> * * @param securityToken the security token to be canceled. * * @return {@code true} if the token has been canceled by the STS; {@code false} otherwise. * * @throws WSTrustException if an error occurs while processing the cancel request. */ public boolean cancelToken(Element securityToken) throws WSTrustException { validateDispatch(); // create a WS-Trust cancel request containing the specified token. RequestSecurityToken request = new RequestSecurityToken(); request.setRequestType(URI.create(WSTrustConstants.CANCEL_REQUEST)); CancelTargetType cancelTarget = new CancelTargetType(); cancelTarget.add(securityToken); request.setCancelTarget(cancelTarget); request.setContext("context"); DOMSource requestSource = this.createSourceFromRequest(request); Source response = dispatchLocal.get().invoke(requestSource); // get the WS-Trust response and check for presence of the RequestTokenCanceled element. try { InputStream stream = DocumentUtil.getNodeAsStream(DocumentUtil.getNodeFromSource(response)); RequestSecurityTokenResponseCollection responseCollection = (RequestSecurityTokenResponseCollection) new WSTrustParser() .parse(stream); RequestSecurityTokenResponse tokenResponse = responseCollection.getRequestSecurityTokenResponses().get(0); if (tokenResponse.getRequestedTokenCancelled() != null) return true; return false; } catch (Exception e) { throw new WSTrustException(logger.parserError(e)); } } /** * Get the dispatch object * * @return */ public Dispatch<Source> getDispatch() { return dispatchLocal.get(); } private DOMSource createSourceFromRequest(RequestSecurityToken request) throws WSTrustException { try { DOMResult result = new DOMResult(DocumentUtil.createDocument()); WSTrustRequestWriter writer = new WSTrustRequestWriter(result); writer.write(request); return new DOMSource(result.getNode()); } catch (Exception e) { throw new WSTrustException(logger.processingError(e)); } } /** * Validate that we have a {@code Dispatch} to work with */ private void validateDispatch() { if (getDispatch() == null) throw logger.injectedValueMissing("Dispatch"); } public String getSoapBinding() { return soapBinding; } public void setSoapBinding(String soapBinding) { this.soapBinding = soapBinding; } @Override public String getSTSClientConfigKey() { return STSClientConfig.computeSTSClientConfigKey(STSClientConfig.SUBSTITUTE_MODULE, serviceName, portName, endPointAddress, userName); } }