/** * 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.rs.security.oauth2.provider; import javax.ws.rs.core.MultivaluedMap; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.cxf.rs.security.jose.jwe.JweDecryptionProvider; import org.apache.cxf.rs.security.jose.jwe.JweEncryptionProvider; import org.apache.cxf.rs.security.jose.jwe.JweUtils; import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider; import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier; import org.apache.cxf.rs.security.jose.jws.JwsUtils; import org.apache.cxf.rs.security.oauth2.common.OAuthRedirectionState; import org.apache.cxf.rs.security.oauth2.common.UserSubject; import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils; import org.apache.cxf.rs.security.oauth2.utils.crypto.ModelEncryptionSupport; public class JoseSessionTokenProvider implements SessionAuthenticityTokenProvider { private JwsSignatureProvider jwsProvider; private JwsSignatureVerifier jwsVerifier; private JweEncryptionProvider jweEncryptor; private JweDecryptionProvider jweDecryptor; private boolean jwsRequired; private boolean jweRequired; private int maxDefaultSessionInterval; @Override public String createSessionToken(MessageContext mc, MultivaluedMap<String, String> params, UserSubject subject, OAuthRedirectionState secData) { String stateString = convertStateToString(secData); String sessionToken = protectStateString(stateString); return OAuthUtils.setSessionToken(mc, sessionToken, maxDefaultSessionInterval); } @Override public String getSessionToken(MessageContext mc, MultivaluedMap<String, String> params, UserSubject subject) { return OAuthUtils.getSessionToken(mc); } @Override public String removeSessionToken(MessageContext mc, MultivaluedMap<String, String> params, UserSubject subject) { return getSessionToken(mc, params, subject); } @Override public OAuthRedirectionState getSessionState(MessageContext messageContext, String sessionToken, UserSubject subject) { String stateString = decryptStateString(sessionToken); return convertStateStringToState(stateString); } public void setJwsProvider(JwsSignatureProvider jwsProvider) { this.jwsProvider = jwsProvider; } public void setJwsVerifier(JwsSignatureVerifier jwsVerifier) { this.jwsVerifier = jwsVerifier; } public void setJweEncryptor(JweEncryptionProvider jweEncryptor) { this.jweEncryptor = jweEncryptor; } public void setJweDecryptor(JweDecryptionProvider jweDecryptor) { this.jweDecryptor = jweDecryptor; } protected JwsSignatureProvider getInitializedSigProvider() { if (jwsProvider != null) { return jwsProvider; } return JwsUtils.loadSignatureProvider(jwsRequired); } protected JweEncryptionProvider getInitializedEncryptionProvider() { if (jweEncryptor != null) { return jweEncryptor; } return JweUtils.loadEncryptionProvider(jweRequired); } public void setJwsRequired(boolean jwsRequired) { this.jwsRequired = jwsRequired; } public void setJweRequired(boolean jweRequired) { this.jweRequired = jweRequired; } protected JweDecryptionProvider getInitializedDecryptionProvider() { if (jweDecryptor != null) { return jweDecryptor; } return JweUtils.loadDecryptionProvider(jweRequired); } protected JwsSignatureVerifier getInitializedSigVerifier() { if (jwsVerifier != null) { return jwsVerifier; } return JwsUtils.loadSignatureVerifier(jwsRequired); } private String decryptStateString(String sessionToken) { JweDecryptionProvider jwe = getInitializedDecryptionProvider(); String stateString = jwe.decrypt(sessionToken).getContentText(); JwsSignatureVerifier jws = getInitializedSigVerifier(); if (jws != null) { stateString = JwsUtils.verify(jws, stateString).getDecodedJwsPayload(); } return stateString; } private String protectStateString(String stateString) { JwsSignatureProvider jws = getInitializedSigProvider(); JweEncryptionProvider jwe = getInitializedEncryptionProvider(); if (jws == null && jwe == null) { throw new OAuthServiceException("Session token can not be created"); } if (jws != null) { stateString = JwsUtils.sign(jws, stateString, null); } if (jwe != null) { stateString = jwe.encrypt(StringUtils.toBytesUTF8(stateString), null); } return stateString; } private OAuthRedirectionState convertStateStringToState(String stateString) { String[] parts = ModelEncryptionSupport.getParts(stateString); OAuthRedirectionState state = new OAuthRedirectionState(); if (!StringUtils.isEmpty(parts[0])) { state.setClientId(parts[0]); } if (!StringUtils.isEmpty(parts[1])) { state.setAudience(parts[1]); } if (!StringUtils.isEmpty(parts[2])) { state.setClientCodeChallenge(parts[2]); } if (!StringUtils.isEmpty(parts[3])) { state.setState(parts[3]); } if (!StringUtils.isEmpty(parts[4])) { state.setProposedScope(parts[4]); } if (!StringUtils.isEmpty(parts[5])) { state.setRedirectUri(parts[5]); } if (!StringUtils.isEmpty(parts[6])) { state.setNonce(parts[6]); } if (!StringUtils.isEmpty(parts[7])) { state.setResponseType(parts[7]); } if (!StringUtils.isEmpty(parts[8])) { state.setExtraProperties(ModelEncryptionSupport.parseSimpleMap(parts[8])); } return state; } protected String convertStateToString(OAuthRedirectionState secData) { //TODO: make it simpler, convert it to JwtClaims -> JSON StringBuilder state = new StringBuilder(); // 0: client id state.append(ModelEncryptionSupport.tokenizeString(secData.getClientId())); state.append(ModelEncryptionSupport.SEP); // 1: client audience state.append(ModelEncryptionSupport.tokenizeString(secData.getAudience())); state.append(ModelEncryptionSupport.SEP); // 2: client code verifier state.append(ModelEncryptionSupport.tokenizeString(secData.getClientCodeChallenge())); state.append(ModelEncryptionSupport.SEP); // 3: state state.append(ModelEncryptionSupport.tokenizeString(secData.getState())); state.append(ModelEncryptionSupport.SEP); // 4: scope state.append(ModelEncryptionSupport.tokenizeString(secData.getProposedScope())); state.append(ModelEncryptionSupport.SEP); // 5: redirect uri state.append(ModelEncryptionSupport.tokenizeString(secData.getRedirectUri())); state.append(ModelEncryptionSupport.SEP); // 6: nonce state.append(ModelEncryptionSupport.tokenizeString(secData.getNonce())); state.append(ModelEncryptionSupport.SEP); // 7: response_type state.append(ModelEncryptionSupport.tokenizeString(secData.getResponseType())); state.append(ModelEncryptionSupport.SEP); // 8: extra props state.append(secData.getExtraProperties().toString()); return state.toString(); } public void setMaxDefaultSessionInterval(int maxDefaultSessionInterval) { this.maxDefaultSessionInterval = maxDefaultSessionInterval; } }