You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by bu...@apache.org on 2020/03/07 10:45:46 UTC

[cxf] 01/03: CXF-8229 OAuth 2.0 Dynamic Client Registration: Client Update Request (#646)

This is an automated email from the ASF dual-hosted git repository.

buhhunyx pushed a commit to branch 3.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 36aa9716829eb4770df73fa02cec3df639f760a2
Author: Alexey Markevich <bu...@gmail.com>
AuthorDate: Fri Mar 6 23:43:31 2020 +0300

    CXF-8229 OAuth 2.0 Dynamic Client Registration: Client Update Request (#646)
    
    * CXF-8229 OAuth 2.0 Dynamic Client Registration: Client Update Request
    
    * add negative test cases
    
    (cherry picked from commit 169be66c11851d723e4a8d93f752e0b3dff1fa60)
---
 .../services/DynamicRegistrationService.java       |  50 +++++---
 .../cxf/rs/security/oauth2/utils/OAuthUtils.java   |   2 +-
 .../security/oidc/OIDCDynamicRegistrationTest.java | 141 +++++++++++++--------
 3 files changed, 120 insertions(+), 73 deletions(-)

diff --git a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
index 12155d9..9b50475 100644
--- a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
+++ b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/services/DynamicRegistrationService.java
@@ -128,8 +128,13 @@ public class DynamicRegistrationService {
     @PUT
     @Path("{clientId}")
     @Consumes("application/json")
-    public Response updateClientRegistration(@PathParam("clientId") String clientId) {
-        return Response.ok().build();
+    @Produces("application/json")
+    public ClientRegistration updateClientRegistration(@PathParam("clientId") String clientId,
+        ClientRegistration request) {
+        Client client = readClient(clientId);
+        fromClientRegistrationToClient(request, client);
+        clientProvider.setClient(client);
+        return fromClientToClientRegistration(client);
     }
 
     @DELETE
@@ -278,16 +283,35 @@ public class DynamicRegistrationService {
         // Client Registration Time
         newClient.setRegisteredAt(System.currentTimeMillis() / 1000L);
 
+        fromClientRegistrationToClient(request, newClient);
+
+        SecurityContext sc = mc.getSecurityContext();
+        if (sc != null && sc.getUserPrincipal() != null && sc.getUserPrincipal().getName() != null) {
+            UserSubject subject = new UserSubject(sc.getUserPrincipal().getName());
+            newClient.setResourceOwnerSubject(subject);
+        }
+
+        newClient.setRegisteredDynamically(true);
+        return newClient;
+    }
+
+    protected void fromClientRegistrationToClient(ClientRegistration request, Client client) {
+        final List<String> grantTypes = client.getAllowedGrantTypes();
+
         // Client Redirect URIs
         List<String> redirectUris = request.getRedirectUris();
         if (redirectUris != null) {
+            String appType = request.getApplicationType();
+            if (appType == null) {
+                appType = DEFAULT_APPLICATION_TYPE;
+            }
             for (String uri : redirectUris) {
                 validateRequestUri(uri, appType, grantTypes);
             }
-            newClient.setRedirectUris(redirectUris);
+            client.setRedirectUris(redirectUris);
         }
 
-        if (newClient.getRedirectUris().isEmpty()
+        if (client.getRedirectUris().isEmpty()
             && (grantTypes.contains(OAuthConstants.AUTHORIZATION_CODE_GRANT)
                 || grantTypes.contains(OAuthConstants.IMPLICIT_GRANT))) {
             // Throw an error as we need a redirect URI for these grants.
@@ -299,39 +323,31 @@ public class DynamicRegistrationService {
         // Client Resource Audience URIs
         List<String> resourceUris = request.getResourceUris();
         if (resourceUris != null) {
-            newClient.setRegisteredAudiences(resourceUris);
+            client.setRegisteredAudiences(resourceUris);
         }
 
         // Client Scopes
         String scope = request.getScope();
         if (!StringUtils.isEmpty(scope)) {
-            newClient.setRegisteredScopes(OAuthUtils.parseScope(scope));
+            client.setRegisteredScopes(OAuthUtils.parseScope(scope));
         }
         // Client Application URI
         String clientUri = request.getClientUri();
         if (clientUri != null) {
-            newClient.setApplicationWebUri(clientUri);
+            client.setApplicationWebUri(clientUri);
         }
         // Client Logo URI
         String clientLogoUri = request.getLogoUri();
         if (clientLogoUri != null) {
-            newClient.setApplicationLogoUri(clientLogoUri);
+            client.setApplicationLogoUri(clientLogoUri);
         }
 
         //TODO: check other properties
         // Add more typed properties like tosUri, policyUri, etc to Client
         // or set them as Client extra properties
-
-        SecurityContext sc = mc.getSecurityContext();
-        if (sc != null && sc.getUserPrincipal() != null && sc.getUserPrincipal().getName() != null) {
-            UserSubject subject = new UserSubject(sc.getUserPrincipal().getName());
-            newClient.setResourceOwnerSubject(subject);
-        }
-
-        newClient.setRegisteredDynamically(true);
-        return newClient;
     }
 
+
     protected boolean isPasswordRequired(List<String> grantTypes, String tokenEndpointAuthMethod) {
         if (grantTypes.contains(OAuthConstants.IMPLICIT_GRANT)) {
             return false;
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 356f599..6a2ca81 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
@@ -443,6 +443,6 @@ public final class OAuthUtils {
     }
 
     public static String convertListOfScopesToString(List<String> registeredScopes) {
-        return String.join(", ", registeredScopes);
+        return String.join(" ", registeredScopes);
     }
 }
diff --git a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCDynamicRegistrationTest.java b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCDynamicRegistrationTest.java
index 74ff6bd..ea5393f 100644
--- a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCDynamicRegistrationTest.java
+++ b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/oidc/OIDCDynamicRegistrationTest.java
@@ -23,6 +23,7 @@ import java.net.URL;
 import java.util.Collections;
 
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
 
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.cxf.jaxrs.provider.json.JsonMapObjectProvider;
@@ -76,16 +77,13 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
     public void testRegisterClientInitialAccessTokenCodeGrant() throws Exception {
         URL busFile = OIDCDynamicRegistrationTest.class.getResource("client.xml");
         String address = "https://localhost:" + DYNREG_SERVER.getPort() + "/services/dynamicWithAt/register";
-        WebClient wc = WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()),
-                         busFile.toString());
+        WebClient wc =
+            WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()), busFile.toString())
+            .accept("application/json").type("application/json")
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
 
-        wc.accept("application/json").type("application/json");
         ClientRegistration reg = newClientRegistrationCodeGrant();
-        ClientRegistrationResponse resp = null;
-        assertEquals(401, wc.post(reg).getStatus());
-
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
-        resp = wc.post(reg, ClientRegistrationResponse.class);
+        ClientRegistrationResponse resp = wc.post(reg, ClientRegistrationResponse.class);
 
         assertNotNull(resp.getClientId());
         assertNotNull(resp.getClientSecret());
@@ -94,12 +92,12 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         String regAccessToken = resp.getRegistrationAccessToken();
         assertNotNull(regAccessToken);
 
-        wc.reset();
         wc.path(resp.getClientId());
         assertEquals(401, wc.get().getStatus());
 
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken));
-        ClientRegistration clientRegResp = wc.get(ClientRegistration.class);
+        ClientRegistration clientRegResp = wc
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken))
+            .get(ClientRegistration.class);
         testCommonRegCodeGrantProperties(clientRegResp);
 
         assertNull(clientRegResp.getTokenEndpointAuthMethod());
@@ -111,16 +109,15 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
     public void testRegisterClientPasswordGrant() throws Exception {
         URL busFile = OIDCDynamicRegistrationTest.class.getResource("client.xml");
         String address = "https://localhost:" + DYNREG_SERVER.getPort() + "/services/dynamicWithAt/register";
-        WebClient wc = WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()),
-                         busFile.toString());
-
-        wc.accept("application/json").type("application/json");
+        WebClient wc =
+            WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()), busFile.toString())
+            .accept("application/json").type("application/json")
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
 
         ClientRegistration reg = new ClientRegistration();
         reg.setClientName("dynamic_client");
         reg.setGrantTypes(Collections.singletonList(OAuthConstants.RESOURCE_OWNER_GRANT));
 
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
         ClientRegistrationResponse resp = wc.post(reg, ClientRegistrationResponse.class);
 
         assertNotNull(resp.getClientId());
@@ -130,11 +127,10 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         String regAccessToken = resp.getRegistrationAccessToken();
         assertNotNull(regAccessToken);
 
-        wc.reset();
-        wc.path(resp.getClientId());
+        ClientRegistration clientRegResp = wc.path(resp.getClientId())
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken))
+            .get(ClientRegistration.class);
 
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken));
-        ClientRegistration clientRegResp = wc.get(ClientRegistration.class);
         assertEquals("web", clientRegResp.getApplicationType());
         assertEquals("dynamic_client", clientRegResp.getClientName());
         assertEquals(Collections.singletonList(OAuthConstants.RESOURCE_OWNER_GRANT),
@@ -150,16 +146,15 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
     public void testRegisterClientPasswordGrantPublic() throws Exception {
         URL busFile = OIDCDynamicRegistrationTest.class.getResource("client.xml");
         String address = "https://localhost:" + DYNREG_SERVER.getPort() + "/services/dynamicWithAt/register";
-        WebClient wc = WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()),
-                         busFile.toString());
-
-        wc.accept("application/json").type("application/json");
+        WebClient wc =
+            WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()), busFile.toString())
+            .accept("application/json").type("application/json")
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
 
         ClientRegistration reg = new ClientRegistration();
         reg.setClientName("dynamic_client");
         reg.setGrantTypes(Collections.singletonList(OAuthConstants.RESOURCE_OWNER_GRANT));
         reg.setTokenEndpointAuthMethod(OAuthConstants.TOKEN_ENDPOINT_AUTH_NONE);
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
         ClientRegistrationResponse resp = wc.post(reg, ClientRegistrationResponse.class);
 
         assertNotNull(resp.getClientId());
@@ -168,11 +163,10 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         String regAccessToken = resp.getRegistrationAccessToken();
         assertNotNull(regAccessToken);
 
-        wc.reset();
-        wc.path(resp.getClientId());
+        ClientRegistration clientRegResp = wc.path(resp.getClientId())
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken))
+            .get(ClientRegistration.class);
 
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken));
-        ClientRegistration clientRegResp = wc.get(ClientRegistration.class);
         assertEquals("native", clientRegResp.getApplicationType());
         assertEquals("dynamic_client", clientRegResp.getClientName());
         assertEquals(Collections.singletonList(OAuthConstants.RESOURCE_OWNER_GRANT),
@@ -184,37 +178,21 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         assertEquals(200, wc.delete().getStatus());
     }
 
-    private void testCommonRegCodeGrantProperties(ClientRegistration clientRegResp) {
-        assertNotNull(clientRegResp);
-        assertEquals("web", clientRegResp.getApplicationType());
-        assertEquals("dynamic_client", clientRegResp.getClientName());
-        assertEquals("openid", clientRegResp.getScope());
-        assertEquals(Collections.singletonList(OAuthConstants.AUTHORIZATION_CODE_GRANT),
-                     clientRegResp.getGrantTypes());
-        assertEquals(Collections.singletonList("https://a/b/c"),
-                     clientRegResp.getRedirectUris());
-        assertEquals(Collections.singletonList("https://rp/logout"),
-                     clientRegResp.getListStringProperty("post_logout_redirect_uris"));
-    }
-
     @org.junit.Test
     public void testRegisterClientInitialAccessTokenCodeGrantTls() throws Exception {
         URL busFile = OIDCDynamicRegistrationTest.class.getResource("client.xml");
         String address = "https://localhost:" + DYNREG_SERVER.getPort() + "/services/dynamicWithAt/register";
-        WebClient wc = WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()),
-                         busFile.toString());
+        WebClient wc =
+            WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()), busFile.toString())
+            .accept("application/json").type("application/json")
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
 
-        wc.accept("application/json").type("application/json");
         ClientRegistration reg = newClientRegistrationCodeGrant();
         reg.setTokenEndpointAuthMethod(OAuthConstants.TOKEN_ENDPOINT_AUTH_TLS);
         reg.setProperty(OAuthConstants.TLS_CLIENT_AUTH_SUBJECT_DN,
                         "CN=whateverhost.com,OU=Morpit,O=ApacheTest,L=Syracuse,C=US");
 
-        ClientRegistrationResponse resp = null;
-        assertEquals(401, wc.post(reg).getStatus());
-
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
-        resp = wc.post(reg, ClientRegistrationResponse.class);
+        ClientRegistrationResponse resp = wc.post(reg, ClientRegistrationResponse.class);
 
         assertNotNull(resp.getClientId());
         assertNull(resp.getClientSecret());
@@ -223,12 +201,10 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         String regAccessToken = resp.getRegistrationAccessToken();
         assertNotNull(regAccessToken);
 
-        wc.reset();
-        wc.path(resp.getClientId());
-        assertEquals(401, wc.get().getStatus());
+        ClientRegistration clientRegResp = wc.path(resp.getClientId())
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken))
+            .get(ClientRegistration.class);
 
-        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken));
-        ClientRegistration clientRegResp = wc.get(ClientRegistration.class);
         testCommonRegCodeGrantProperties(clientRegResp);
         assertEquals(OAuthConstants.TOKEN_ENDPOINT_AUTH_TLS, clientRegResp.getTokenEndpointAuthMethod());
         assertEquals("CN=whateverhost.com,OU=Morpit,O=ApacheTest,L=Syracuse,C=US",
@@ -237,12 +213,52 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         assertEquals(200, wc.delete().getStatus());
     }
 
+    @org.junit.Test
+    public void testUpdateClient() throws Exception {
+        URL busFile = OIDCDynamicRegistrationTest.class.getResource("client.xml");
+        String address = "https://localhost:" + DYNREG_SERVER.getPort() + "/services/dynamicWithAt/register";
+        WebClient wc =
+            WebClient.create(address, Collections.singletonList(new JsonMapObjectProvider()), busFile.toString())
+            .accept("application/json").type("application/json")
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, ACCESS_TOKEN));
+
+        final ClientRegistration reg = newClientRegistrationCodeGrant();
+        final ClientRegistrationResponse clientRegistrationResponse = wc
+            .post(reg, ClientRegistrationResponse.class);
+
+        final String regAccessToken = clientRegistrationResponse.getRegistrationAccessToken();
+        assertNotNull(regAccessToken);
+
+        reg.setScope(OidcUtils.getEmailScope());
+        final ClientRegistration updatedClientRegistration = wc.path(clientRegistrationResponse.getClientId())
+            .authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken))
+            .put(reg, ClientRegistration.class);
+
+        assertEquals(OidcUtils.getEmailScope(), updatedClientRegistration.getScope());
+        // https://tools.ietf.org/html/rfc7592#section-2.2
+        assertNull(updatedClientRegistration.getProperty("registration_access_token"));
+        assertNull(updatedClientRegistration.getProperty("registration_client_uri"));
+        assertNull(updatedClientRegistration.getProperty("client_secret_expires_at"));
+        assertNull(updatedClientRegistration.getProperty("client_id_issued_at"));
+
+        wc.authorization(null);
+
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(),
+            wc.put(reg).getStatus());
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(),
+            wc.delete().getStatus());
+
+        wc.authorization(new ClientAccessToken(OAuthConstants.BEARER_AUTHORIZATION_SCHEME, regAccessToken));
+        assertEquals(200, wc.delete().getStatus());
+    }
+
     private static ClientRegistration newClientRegistrationCodeGrant() {
         final ClientRegistration reg = new ClientRegistration();
         reg.setApplicationType("web");
         reg.setScope(OidcUtils.getOpenIdScope());
         reg.setClientName("dynamic_client");
         reg.setGrantTypes(Collections.singletonList(OAuthConstants.AUTHORIZATION_CODE_GRANT));
+//        reg.setResponseTypes(Collections.singletonList(OAuthConstants.CODE_RESPONSE_TYPE));
         reg.setRedirectUris(Collections.singletonList("https://a/b/c"));
 
         reg.setProperty("post_logout_redirect_uris",
@@ -250,4 +266,19 @@ public class OIDCDynamicRegistrationTest extends AbstractBusClientServerTestBase
         return reg;
     }
 
+    private static void testCommonRegCodeGrantProperties(ClientRegistration clientRegResp) {
+        assertNotNull(clientRegResp);
+        assertEquals("web", clientRegResp.getApplicationType());
+        assertEquals("openid", clientRegResp.getScope());
+        assertEquals("dynamic_client", clientRegResp.getClientName());
+        assertEquals(Collections.singletonList(OAuthConstants.AUTHORIZATION_CODE_GRANT),
+                     clientRegResp.getGrantTypes());
+//        assertEquals(Collections.singletonList(OAuthConstants.CODE_RESPONSE_TYPE),
+//                     clientRegResp.getResponseTypes());
+        assertEquals(Collections.singletonList("https://a/b/c"),
+                     clientRegResp.getRedirectUris());
+        assertEquals(Collections.singletonList("https://rp/logout"),
+                     clientRegResp.getListStringProperty("post_logout_redirect_uris"));
+    }
+
 }