Return-Path: X-Original-To: apmail-cxf-commits-archive@www.apache.org Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 02E7C18DA4 for ; Thu, 5 Nov 2015 17:24:17 +0000 (UTC) Received: (qmail 88269 invoked by uid 500); 5 Nov 2015 17:24:16 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 88208 invoked by uid 500); 5 Nov 2015 17:24:16 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 88199 invoked by uid 99); 5 Nov 2015 17:24:16 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Nov 2015 17:24:16 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 9529EE3938; Thu, 5 Nov 2015 17:24:16 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sergeyb@apache.org To: commits@cxf.apache.org Message-Id: <1b926a12cbb84fdd96b159050ea96563@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: cxf git commit: Some modifications to support implicit OIDC flows Date: Thu, 5 Nov 2015 17:24:16 +0000 (UTC) Repository: cxf Updated Branches: refs/heads/master 6d7d8eb39 -> 295091064 Some modifications to support implicit OIDC flows Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/29509106 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/29509106 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/29509106 Branch: refs/heads/master Commit: 295091064b8459e6e05a38b974a76d56c0bb9f3a Parents: 6d7d8eb Author: Sergey Beryozkin Authored: Thu Nov 5 17:21:32 2015 +0000 Committer: Sergey Beryozkin Committed: Thu Nov 5 17:23:59 2015 +0000 ---------------------------------------------------------------------- .../apache/cxf/jaxrs/impl/ResponseImplTest.java | 4 +- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 7 +++ .../oauth2/provider/DefaultSubjectCreator.java | 34 ++++++++++ .../services/AbstractImplicitGrantService.java | 66 ++++++++++++++------ .../oauth2/services/ImplicitGrantService.java | 5 ++ .../services/RedirectionBasedGrantService.java | 20 +++--- .../rs/security/oauth2/utils/OAuthUtils.java | 9 ++- .../security/oidc/idp/OidcImplicitService.java | 62 ++++++++++++++++++ 8 files changed, 175 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseImplTest.java ---------------------------------------------------------------------- diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseImplTest.java index b750508..d409072 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseImplTest.java @@ -367,7 +367,7 @@ public class ResponseImplTest extends Assert { assertFalse(ri.hasLink("prev")); assertNull(ri.getLink("prev")); - meta.add(HttpHeaders.LINK, ";rel=next"); + meta.add(HttpHeaders.LINK, ";rel=next"); meta.add(HttpHeaders.LINK, ";rel=prev"); assertTrue(ri.hasLink("next")); @@ -381,7 +381,7 @@ public class ResponseImplTest extends Assert { assertTrue(links.contains(next)); assertTrue(links.contains(prev)); - assertEquals("http://next", next.getUri().toString()); + assertEquals("http://localhost:8080/next;a=b", next.getUri().toString()); assertEquals("next", next.getRel()); assertEquals("http://prev", prev.getUri().toString()); assertEquals("prev", prev.getRel()); http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java ---------------------------------------------------------------------- diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index a9ec843..2bf7829 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -1547,6 +1547,13 @@ public class UriBuilderImplTest extends Assert { } @Test + public void testFromUriWithMatrix() { + String expected = "http://localhost:8080/name;a=b"; + URI uri = UriBuilder.fromUri("http://localhost:8080/name;a=b").build(); + assertEquals(expected, uri.toString()); + } + + @Test public void testPathParamSpaceBuildEncoded() { String expected = "http://localhost:8080/name/%20"; URI uri = UriBuilder.fromUri("http://localhost:8080").path("name/%20").buildFromEncoded(); http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/DefaultSubjectCreator.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/DefaultSubjectCreator.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/DefaultSubjectCreator.java new file mode 100644 index 0000000..ae870fb --- /dev/null +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/DefaultSubjectCreator.java @@ -0,0 +1,34 @@ +/** + * 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 org.apache.cxf.jaxrs.ext.MessageContext; +import org.apache.cxf.rs.security.oauth2.common.UserSubject; +import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils; +import org.apache.cxf.security.SecurityContext; + +public class DefaultSubjectCreator implements SubjectCreator { + + @Override + public UserSubject createUserSubject(MessageContext mc) throws OAuthServiceException { + return OAuthUtils.createSubject(mc, + (SecurityContext)mc.get(SecurityContext.class.getName())); + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractImplicitGrantService.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractImplicitGrantService.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractImplicitGrantService.java index 63fcfa2..d78feaf 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractImplicitGrantService.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/AbstractImplicitGrantService.java @@ -23,6 +23,7 @@ import java.net.URI; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import javax.ws.rs.core.Response; @@ -48,6 +49,10 @@ public abstract class AbstractImplicitGrantService extends RedirectionBasedGrant String supportedGrantType) { super(supportedResponseType, supportedGrantType); } + protected AbstractImplicitGrantService(Set supportedResponseTypes, + String supportedGrantType) { + super(supportedResponseTypes, supportedGrantType); + } protected Response createGrant(OAuthRedirectionState state, Client client, @@ -55,43 +60,63 @@ public abstract class AbstractImplicitGrantService extends RedirectionBasedGrant List approvedScope, UserSubject userSubject, ServerAccessToken preAuthorizedToken) { + + boolean tokenCanBeReturned = preAuthorizedToken != null; ServerAccessToken token = null; if (preAuthorizedToken == null) { - AccessTokenRegistration reg = new AccessTokenRegistration(); - reg.setClient(client); - reg.setGrantType(super.getSupportedGrantType()); - reg.setSubject(userSubject); - reg.setRequestedScope(requestedScope); - if (approvedScope != null && approvedScope.isEmpty()) { - // no down-scoping done by a user, all of the requested scopes have been authorized - reg.setApprovedScope(requestedScope); - } else { - reg.setApprovedScope(approvedScope); + tokenCanBeReturned = canAccessTokenBeReturned(requestedScope, approvedScope); + if (tokenCanBeReturned) { + AccessTokenRegistration reg = new AccessTokenRegistration(); + reg.setClient(client); + reg.setGrantType(super.getSupportedGrantType()); + reg.setSubject(userSubject); + reg.setRequestedScope(requestedScope); + if (approvedScope != null && approvedScope.isEmpty()) { + // no down-scoping done by a user, all of the requested scopes have been authorized + reg.setApprovedScope(requestedScope); + } else { + reg.setApprovedScope(approvedScope); + } + reg.setAudience(state.getAudience()); + token = getDataProvider().createAccessToken(reg); } - reg.setAudience(state.getAudience()); - token = getDataProvider().createAccessToken(reg); } else { token = preAuthorizedToken; } - ClientAccessToken clientToken = OAuthUtils.toClientAccessToken(token, isWriteOptionalParameters()); + ClientAccessToken clientToken = null; + if (token != null) { + clientToken = OAuthUtils.toClientAccessToken(token, isWriteOptionalParameters()); + } else { + // this is not ideal - it is only done to have OIDC Implicit to have an id_token added + // via AccessTokenResponseFilter. Note if id_token is needed (with or without access token) + // then the service needs to be injected with SubjectCreator, example, DefaultSubjectCreator + // extension which will have a chance to attach id_token to Subject properties which are checked + // by id_token AccessTokenResponseFilter. If at is also needed then OAuthDataProvider may deal + // with attaching id_token itself in which case no SubjectCreator injection is necessary + clientToken = new ClientAccessToken(); + } processClientAccessToken(clientToken, token); // return the token by appending it as a fragment parameter to the redirect URI StringBuilder sb = getUriWithFragment(state.getRedirectUri()); + if (tokenCanBeReturned) { + sb.append(OAuthConstants.ACCESS_TOKEN).append("=").append(clientToken.getTokenKey()); + sb.append("&"); + sb.append(OAuthConstants.ACCESS_TOKEN_TYPE).append("=").append(clientToken.getTokenType()); + } - sb.append(OAuthConstants.ACCESS_TOKEN).append("=").append(clientToken.getTokenKey()); if (state.getState() != null) { sb.append("&"); sb.append(OAuthConstants.STATE).append("=").append(state.getState()); } - sb.append("&") - .append(OAuthConstants.ACCESS_TOKEN_TYPE).append("=").append(clientToken.getTokenType()); if (isWriteOptionalParameters()) { - sb.append("&").append(OAuthConstants.ACCESS_TOKEN_EXPIRES_IN) - .append("=").append(clientToken.getExpiresIn()); + if (tokenCanBeReturned) { + sb.append("&").append(OAuthConstants.ACCESS_TOKEN_EXPIRES_IN) + .append("=").append(clientToken.getExpiresIn()); + } if (!StringUtils.isEmpty(clientToken.getApprovedScope())) { sb.append("&").append(OAuthConstants.SCOPE).append("=") .append(HttpUtils.queryEncode(clientToken.getApprovedScope())); @@ -100,7 +125,7 @@ public abstract class AbstractImplicitGrantService extends RedirectionBasedGrant sb.append("&").append(entry.getKey()).append("=").append(HttpUtils.queryEncode(entry.getValue())); } } - if (token.getRefreshToken() != null) { + if (tokenCanBeReturned && token.getRefreshToken() != null) { processRefreshToken(sb, token.getRefreshToken()); } if (reportClientId) { @@ -109,6 +134,9 @@ public abstract class AbstractImplicitGrantService extends RedirectionBasedGrant return Response.seeOther(URI.create(sb.toString())).build(); } + protected boolean canAccessTokenBeReturned(List requestedScope, List approvedScope) { + return true; + } protected void processRefreshToken(StringBuilder sb, String refreshToken) { LOG.warning("Implicit grant tokens MUST not have refresh tokens, refresh token will not be reported"); } http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/ImplicitGrantService.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/ImplicitGrantService.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/ImplicitGrantService.java index a73e118..d2dcdbf 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/ImplicitGrantService.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/ImplicitGrantService.java @@ -19,6 +19,8 @@ package org.apache.cxf.rs.security.oauth2.services; +import java.util.Set; + import javax.ws.rs.Path; import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants; @@ -39,6 +41,9 @@ public class ImplicitGrantService extends AbstractImplicitGrantService { public ImplicitGrantService() { super(OAuthConstants.TOKEN_RESPONSE_TYPE, OAuthConstants.IMPLICIT_GRANT); } + public ImplicitGrantService(Set responseTypes) { + super(responseTypes, OAuthConstants.IMPLICIT_GRANT); + } } http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/RedirectionBasedGrantService.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/RedirectionBasedGrantService.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/RedirectionBasedGrantService.java index c174429..51ea97e 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/RedirectionBasedGrantService.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/RedirectionBasedGrantService.java @@ -19,9 +19,11 @@ package org.apache.cxf.rs.security.oauth2.services; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -53,7 +55,7 @@ import org.apache.cxf.security.SecurityContext; * The Base Redirection-Based Grant Service */ public abstract class RedirectionBasedGrantService extends AbstractOAuthService { - private String supportedResponseType; + private Set supportedResponseTypes; private String supportedGrantType; private boolean partialMatchScopeValidation; private boolean useRegisteredRedirectUriIfPossible = true; @@ -65,7 +67,11 @@ public abstract class RedirectionBasedGrantService extends AbstractOAuthService protected RedirectionBasedGrantService(String supportedResponseType, String supportedGrantType) { - this.supportedResponseType = supportedResponseType; + this(Collections.singleton(supportedResponseType), supportedGrantType); + } + protected RedirectionBasedGrantService(Set supportedResponseTypes, + String supportedGrantType) { + this.supportedResponseTypes = supportedResponseTypes; this.supportedGrantType = supportedGrantType; } @@ -131,7 +137,7 @@ public abstract class RedirectionBasedGrantService extends AbstractOAuthService // Check response_type String responseType = params.getFirst(OAuthConstants.RESPONSE_TYPE); - if (responseType == null || !responseType.equals(supportedResponseType)) { + if (responseType == null || !supportedResponseTypes.contains(responseType)) { return createErrorResponse(params, redirectUri, OAuthConstants.UNSUPPORTED_RESPONSE_TYPE); } // Get the requested scopes @@ -324,13 +330,7 @@ public abstract class RedirectionBasedGrantService extends AbstractOAuthService return subject; } } - - subject = getMessageContext().getContent(UserSubject.class); - if (subject != null) { - return subject; - } else { - return OAuthUtils.createSubject(securityContext); - } + return OAuthUtils.createSubject(getMessageContext(), securityContext); } protected Response createErrorResponse(MultivaluedMap params, http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java index c96de44..ad190df 100644 --- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java +++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/utils/OAuthUtils.java @@ -91,7 +91,14 @@ public final class OAuthUtils { } return sessionToken; } - + public static UserSubject createSubject(MessageContext mc, SecurityContext sc) { + UserSubject subject = mc.getContent(UserSubject.class); + if (subject != null) { + return subject; + } else { + return OAuthUtils.createSubject(sc); + } + } public static UserSubject createSubject(SecurityContext securityContext) { List roleNames = Collections.emptyList(); if (securityContext instanceof LoginSecurityContext) { http://git-wip-us.apache.org/repos/asf/cxf/blob/29509106/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/OidcImplicitService.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/OidcImplicitService.java b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/OidcImplicitService.java new file mode 100644 index 0000000..c6638e3 --- /dev/null +++ b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/idp/OidcImplicitService.java @@ -0,0 +1,62 @@ +/** + * 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.Arrays; +import java.util.HashSet; +import java.util.List; + +import javax.ws.rs.Path; + +import org.apache.cxf.rs.security.oauth2.common.Client; +import org.apache.cxf.rs.security.oauth2.common.OAuthPermission; +import org.apache.cxf.rs.security.oauth2.services.ImplicitGrantService; + +@Path("/login") +public class OidcImplicitService extends ImplicitGrantService { + private static final String OPEN_ID_CONNECT_SCOPE = "openid"; + private static final String ID_TOKEN_RESPONSE_TYPE = "id_token"; + private static final String ID_TOKEN_AND_AT_RESPONSE_TYPE = "id_token token"; + private boolean skipAuthorizationWithOidcScope; + + public OidcImplicitService() { + super(new HashSet(Arrays.asList(ID_TOKEN_RESPONSE_TYPE, + ID_TOKEN_AND_AT_RESPONSE_TYPE))); + } + + @Override + protected boolean canAccessTokenBeReturned(List requestedScope, List approvedScope) { + return requestedScope.contains(ID_TOKEN_AND_AT_RESPONSE_TYPE); + } + + @Override + protected boolean canAuthorizationBeSkipped(Client client, + List requestedScope, + List permissions) { + // No need to challenge the authenticated user with the authorization form + // if all the client application redirecting a user needs is to get this user authenticated + // with OIDC IDP + return requestedScope.size() == 1 && permissions.size() == 1 && skipAuthorizationWithOidcScope + && OPEN_ID_CONNECT_SCOPE.equals(requestedScope.get(0)); + } + public void setSkipAuthorizationWithOidcScope(boolean skipAuthorizationWithOidcScope) { + this.skipAuthorizationWithOidcScope = skipAuthorizationWithOidcScope; + } + +}