[CXF-7519] The cxf-rt-transports-http-jetty should not rely on the old blueprint...
[cxf.git] / rt / rs / security / sso / oidc / src / main / java / org / apache / cxf / rs / security / oidc / rp / OidcClientCodeRequestFilter.java
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.cxf.rs.security.oidc.rp;
20
21 import java.util.Arrays;
22 import java.util.List;
23
24 import javax.ws.rs.container.ContainerRequestContext;
25 import javax.ws.rs.core.MultivaluedMap;
26 import javax.ws.rs.core.SecurityContext;
27 import javax.ws.rs.core.UriBuilder;
28 import javax.ws.rs.core.UriInfo;
29
30 import org.apache.cxf.common.util.StringUtils;
31 import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
32 import org.apache.cxf.jaxrs.utils.ExceptionUtils;
33 import org.apache.cxf.rs.security.oauth2.client.ClientCodeRequestFilter;
34 import org.apache.cxf.rs.security.oauth2.client.ClientTokenContext;
35 import org.apache.cxf.rs.security.oauth2.common.ClientAccessToken;
36 import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
37 import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
38 import org.apache.cxf.rs.security.oidc.common.ClaimsRequest;
39 import org.apache.cxf.rs.security.oidc.common.IdToken;
40
41 public class OidcClientCodeRequestFilter extends ClientCodeRequestFilter {
42
43 private static final String ACR_PARAMETER = "acr_values";
44 private static final String LOGIN_HINT_PARAMETER = "login_hint";
45 private static final String MAX_AGE_PARAMETER = "max_age";
46 private static final String PROMPT_PARAMETER = "prompt";
47 private static final List<String> PROMPTS = Arrays.asList("none", "consent", "login", "select_account");
48 private IdTokenReader idTokenReader;
49 private UserInfoClient userInfoClient;
50 private List<String> authenticationContextRef;
51 private String promptLogin;
52 private Long maxAgeOffset;
53 private String claims;
54 private String claimsLocales;
55 private String roleClaim;
56
57 public OidcClientCodeRequestFilter() {
58 super();
59 setScopes("openid");
60 }
61
62 public void setAuthenticationContextRef(String acr) {
63 this.authenticationContextRef = Arrays.asList(StringUtils.split(acr, " "));
64 }
65
66 @Override
67 protected ClientTokenContext createTokenContext(ContainerRequestContext rc,
68 ClientAccessToken at,
69 MultivaluedMap<String, String> requestParams,
70 MultivaluedMap<String, String> state) {
71 if (rc.getSecurityContext() instanceof OidcSecurityContext) {
72 return ((OidcSecurityContext)rc.getSecurityContext()).getOidcContext();
73 }
74 OidcClientTokenContextImpl ctx = new OidcClientTokenContextImpl();
75 if (at != null) {
76 if (idTokenReader == null) {
77 throw new OAuthServiceException(OAuthConstants.SERVER_ERROR);
78 }
79 IdToken idToken = idTokenReader.getIdToken(at,
80 requestParams.getFirst(OAuthConstants.AUTHORIZATION_CODE_VALUE),
81 getConsumer());
82 // Validate the properties set up at the redirection time.
83 validateIdToken(idToken, state);
84
85 ctx.setIdToken(idToken);
86 if (userInfoClient != null) {
87 ctx.setUserInfo(userInfoClient.getUserInfo(at,
88 ctx.getIdToken(),
89 getConsumer()));
90 }
91 OidcSecurityContext oidcSecCtx = new OidcSecurityContext(ctx);
92 oidcSecCtx.setRoleClaim(roleClaim);
93 rc.setSecurityContext(oidcSecCtx);
94 }
95
96 return ctx;
97 }
98
99 @Override
100 protected MultivaluedMap<String, String> toCodeRequestState(ContainerRequestContext rc, UriInfo ui) {
101 MultivaluedMap<String, String> state = super.toCodeRequestState(rc, ui);
102 if (maxAgeOffset != null) {
103 state.putSingle(MAX_AGE_PARAMETER, Long.toString(System.currentTimeMillis() + maxAgeOffset));
104 }
105 return state;
106 }
107
108 private void validateIdToken(IdToken idToken, MultivaluedMap<String, String> state) {
109
110 String nonce = state.getFirst(IdToken.NONCE_CLAIM);
111 String tokenNonce = idToken.getNonce();
112 if (nonce != null && (tokenNonce == null || !nonce.equals(tokenNonce))) {
113 throw new OAuthServiceException(OAuthConstants.INVALID_REQUEST);
114 }
115 if (maxAgeOffset != null) {
116 Long authTime = Long.parseLong(state.getFirst(MAX_AGE_PARAMETER));
117 Long tokenAuthTime = idToken.getAuthenticationTime();
118 if (tokenAuthTime > authTime) {
119 throw new OAuthServiceException(OAuthConstants.INVALID_REQUEST);
120 }
121 }
122
123 String acr = idToken.getAuthenticationContextRef();
124 // Skip the check if the acr is not set given it is a voluntary claim
125 if (acr != null && authenticationContextRef != null && !authenticationContextRef.contains(acr)) {
126 throw new OAuthServiceException(OAuthConstants.INVALID_REQUEST);
127 }
128
129 }
130 public void setIdTokenReader(IdTokenReader idTokenReader) {
131 this.idTokenReader = idTokenReader;
132 }
133
134 public void setUserInfoClient(UserInfoClient userInfoClient) {
135 this.userInfoClient = userInfoClient;
136 }
137
138 @Override
139 protected void checkSecurityContextStart(ContainerRequestContext rc) {
140 SecurityContext sc = rc.getSecurityContext();
141 if (!(sc instanceof OidcSecurityContext) && sc.getUserPrincipal() != null) {
142 throw ExceptionUtils.toNotAuthorizedException(null, null);
143 }
144 }
145
146 @Override
147 protected void setAdditionalCodeRequestParams(UriBuilder ub,
148 MultivaluedMap<String, String> redirectState,
149 MultivaluedMap<String, String> codeRequestState) {
150 if (redirectState != null) {
151 if (redirectState.getFirst(IdToken.NONCE_CLAIM) != null) {
152 ub.queryParam(IdToken.NONCE_CLAIM, redirectState.getFirst(IdToken.NONCE_CLAIM));
153 }
154 if (redirectState.getFirst(MAX_AGE_PARAMETER) != null) {
155 ub.queryParam(MAX_AGE_PARAMETER, redirectState.getFirst(MAX_AGE_PARAMETER));
156 }
157 }
158 if (codeRequestState != null && codeRequestState.getFirst(LOGIN_HINT_PARAMETER) != null) {
159 ub.queryParam(LOGIN_HINT_PARAMETER, codeRequestState.getFirst(LOGIN_HINT_PARAMETER));
160 }
161 if (claims != null) {
162 ub.queryParam("claims", claims);
163 }
164 if (claimsLocales != null) {
165 ub.queryParam("claims_locales", claimsLocales);
166 }
167 if (authenticationContextRef != null) {
168 ub.queryParam(ACR_PARAMETER, authenticationContextRef);
169 }
170 if (promptLogin != null) {
171 ub.queryParam(PROMPT_PARAMETER, promptLogin);
172 }
173
174 }
175
176 public void setPromptLogin(String promptLogin) {
177 if (PROMPTS.contains(promptLogin)) {
178 this.promptLogin = promptLogin;
179 } else {
180 throw new IllegalArgumentException("Illegal prompt value");
181 }
182 }
183
184 public void setMaxAgeOffset(Long maxAgeOffset) {
185 this.maxAgeOffset = maxAgeOffset;
186 }
187
188 public void setClaimsRequest(ClaimsRequest claimsRequest) {
189 setClaims(new JsonMapObjectReaderWriter().toJson(claimsRequest));
190 }
191
192 public void setClaims(String claims) {
193 this.claims = claims;
194 }
195
196 public void setClaimsLocales(String claimsLocales) {
197 this.claimsLocales = claimsLocales;
198 }
199
200 public void setRoleClaim(String roleClaim) {
201 this.roleClaim = roleClaim;
202 }
203 }