/** * 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.oidc.idp; import java.util.LinkedList; import java.util.List; import java.util.Properties; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Message; import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; import org.apache.cxf.rs.security.jose.jwk.JsonWebKey; import org.apache.cxf.rs.security.jose.jws.JwsUtils; import org.apache.cxf.rs.security.jose.jwt.JwtToken; import org.apache.cxf.rs.security.oauth2.common.Client; import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.provider.AccessTokenResponseFilter; import org.apache.cxf.rs.security.oauth2.provider.OAuthServerJoseJwtProducer; import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils; import org.apache.cxf.rs.security.oidc.common.IdToken; import org.apache.cxf.rs.security.oidc.utils.OidcUtils; public class IdTokenResponseFilter extends OAuthServerJoseJwtProducer implements AccessTokenResponseFilter { private IdTokenProvider idTokenProvider; private WebClient keyServiceClient; @Override public void process(ClientAccessToken ct, ServerAccessToken st) { if (st.getResponseType() != null && OidcUtils.CODE_AT_RESPONSE_TYPE.equals(st.getResponseType()) && OAuthConstants.IMPLICIT_GRANT.equals(st.getGrantType())) { // token post-processing as part of the current hybrid (implicit) flow // so no id_token is returned now - however when the code gets exchanged later on // this filter will add id_token to the returned access token return; } // Only add an IdToken if the client has the "openid" scope if (ct.getApprovedScope() == null || !ct.getApprovedScope().contains(OidcUtils.OPENID_SCOPE)) { return; } String idToken = getProcessedIdToken(st); if (idToken != null) { ct.getParameters().put(OidcUtils.ID_TOKEN, idToken); } } private String getProcessedIdToken(ServerAccessToken st) { if (idTokenProvider != null) { IdToken idToken = idTokenProvider.getIdToken(st.getClient().getClientId(), st.getSubject(), OAuthUtils.convertPermissionsToScopeList(st.getScopes())); setAtHashAndNonce(idToken, st); return processJwt(new JwtToken(idToken), st.getClient()); } else if (st.getSubject().getProperties().containsKey(OidcUtils.ID_TOKEN)) { return st.getSubject().getProperties().get(OidcUtils.ID_TOKEN); } else if (st.getSubject() instanceof OidcUserSubject) { OidcUserSubject sub = (OidcUserSubject)st.getSubject(); if (sub.getIdToken() != null) { IdToken idToken = new IdToken(sub.getIdToken()); idToken.setAudience(st.getClient().getClientId()); idToken.setAuthorizedParty(st.getClient().getClientId()); // if this token was refreshed then the cloned IDToken might need to have its // issuedAt and expiry time properties adjusted if it proves to be necessary setAtHashAndNonce(idToken, st); return processJwt(new JwtToken(idToken), st.getClient()); } } return null; } private void setAtHashAndNonce(IdToken idToken, ServerAccessToken st) { String rType = st.getResponseType(); boolean atHashRequired = idToken.getAccessTokenHash() == null && (rType == null || !rType.equals(OidcUtils.ID_TOKEN_RESPONSE_TYPE)); boolean cHashRequired = idToken.getAuthorizationCodeHash() == null && rType != null && (rType.equals(OidcUtils.CODE_ID_TOKEN_AT_RESPONSE_TYPE) || rType.equals(OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE)); Message m = JAXRSUtils.getCurrentMessage(); if (atHashRequired || cHashRequired) { Properties props = JwsUtils.loadSignatureOutProperties(false); SignatureAlgorithm sigAlgo = null; if (super.isSignWithClientSecret()) { sigAlgo = OAuthUtils.getClientSecretSignatureAlgorithm(props); } else { sigAlgo = JwsUtils.getSignatureAlgorithm(props, SignatureAlgorithm.RS256); } if (sigAlgo != SignatureAlgorithm.NONE) { if (atHashRequired) { String atHash = OidcUtils.calculateAccessTokenHash(st.getTokenKey(), sigAlgo); idToken.setAccessTokenHash(atHash); } if (cHashRequired) { // c_hash can be returned from either Authorization or Token endpoints String code; if (st.getGrantCode() != null) { // This is a token endpoint, the code has been exchanged for a token code = st.getGrantCode(); } else { // Authorization endpoint: hybrid flow, implicit part code = (String)m.getExchange().get(OAuthConstants.AUTHORIZATION_CODE_VALUE); } if (code != null) { idToken.setAuthorizationCodeHash(OidcUtils.calculateAuthorizationCodeHash(code, sigAlgo)); } } } } if (m != null && m.getExchange().containsKey(OAuthConstants.NONCE)) { idToken.setNonce((String)m.getExchange().get(OAuthConstants.NONCE)); } else if (st.getNonce() != null) { idToken.setNonce(st.getNonce()); } } public void setIdTokenProvider(IdTokenProvider idTokenProvider) { this.idTokenProvider = idTokenProvider; } @Override public String processJwt(JwtToken jwt, Client client) { if (keyServiceClient != null) { List<String> opers = new LinkedList<String>(); if (super.isJwsRequired()) { opers.add(JsonWebKey.KEY_OPER_SIGN); } if (super.isJweRequired()) { opers.add(JsonWebKey.KEY_OPER_ENCRYPT); } // the form request can be supported too keyServiceClient.resetQuery(); keyServiceClient.query(JsonWebKey.KEY_OPERATIONS, opers); //TODO: OIDC core talks about various security algorithm preferences // that may be set during the client registrations, they can be passed along too return keyServiceClient.post(jwt, String.class); } else { return super.processJwt(jwt, client); } } public void setKeyServiceClient(WebClient keyServiceClient) { this.keyServiceClient = keyServiceClient; } }