From 364d0f0c73b4ffdfff482dd287d0f31d067e3fba Mon Sep 17 00:00:00 2001 From: Thiago Zanivan Felisberto Date: Mon, 25 Sep 2017 13:43:00 -0300 Subject: [PATCH] merge --- .../migration/hsqldb/V2_1__add_resowner.sql | 2 ++ .../hsqldb/V3_1__allowed_password_grant.sql | 1 + .../hsqldb_content/V2_2__insert_resowner.sql | 2 ++ .../V3_2__insert_password_grant_client.sql | 10 +++++++++ .../src/main/webapp/client/js/clientForm.js | 7 +++++- .../main/webapp/client/js/popoverBundle.js | 5 +---- .../client/templates/tplEditClient.html | 13 +++++++++++ .../surfnet/oaaas/auth/OAuth2Validator.java | 6 +++-- .../oaaas/auth/OAuth2ValidatorImpl.java | 12 ++++++++++ .../java/org/surfnet/oaaas/model/Client.java | 20 +++++++++++++++++ .../resourceserver/ClientResource.java | 4 ++-- .../oaaas/auth/OAuth2ValidatorImplTest.java | 22 +++++++++++++++++++ .../hsqldb/V3_1__allowed_password_grant.sql | 1 + .../V3_2__insert_password_grant_client.sql | 10 +++++++++ 14 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V2_1__add_resowner.sql create mode 100644 apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql create mode 100644 apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V2_2__insert_resowner.sql create mode 100644 apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V3_2__insert_password_grant_client.sql create mode 100644 apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql create mode 100644 apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_2__insert_password_grant_client.sql diff --git a/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V2_1__add_resowner.sql b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V2_1__add_resowner.sql new file mode 100644 index 00000000..a62a6132 --- /dev/null +++ b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V2_1__add_resowner.sql @@ -0,0 +1,2 @@ +CREATE MEMORY TABLE PUBLIC.RESOURCEOWNER(ID BIGINT NOT NULL PRIMARY KEY,CREATIONDATE TIMESTAMP,MODIFICATIONDATE TIMESTAMP,USERNAME VARCHAR(255),PASSWORD VARCHAR(255),CONSTRAINT U_ROWN_USERNAME UNIQUE(USERNAME)); +CREATE INDEX I_RSCOWN_USERNAME ON PUBLIC.RESOURCEOWNER(USERNAME); diff --git a/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql new file mode 100644 index 00000000..2ec37687 --- /dev/null +++ b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql @@ -0,0 +1 @@ +ALTER TABLE PUBLIC.CLIENT ADD COLUMN ALLOWEDPASSWORDGRANT BIT(1) DEFAULT 0; diff --git a/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V2_2__insert_resowner.sql b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V2_2__insert_resowner.sql new file mode 100644 index 00000000..73fc8400 --- /dev/null +++ b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V2_2__insert_resowner.sql @@ -0,0 +1,2 @@ +INSERT INTO resourceowner (id, username, password) +VALUES (99999,'emma.blunt', 'cafebabe'); diff --git a/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V3_2__insert_password_grant_client.sql b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V3_2__insert_password_grant_client.sql new file mode 100644 index 00000000..61bff810 --- /dev/null +++ b/apis-authorization-server-war/src/main/resources/db/migration/hsqldb_content/V3_2__insert_password_grant_client.sql @@ -0,0 +1,10 @@ +/* +Client for password grant +*/ +INSERT INTO client (id, contactEmail, contactName, description, clientName, thumbNailUrl, resourceserver_id, +clientId, secret, allowedPasswordGrant) +VALUES + (99991, 'it-test-password@example.com', 'john.password.grant', 'it test password grant', + 'it test password grant', + 'thumbnailurl', 99997, + 'it-test-password-grant', 'some-secret-client-password', 1); diff --git a/apis-authorization-server-war/src/main/webapp/client/js/clientForm.js b/apis-authorization-server-war/src/main/webapp/client/js/clientForm.js index e6b8814c..681f7875 100644 --- a/apis-authorization-server-war/src/main/webapp/client/js/clientForm.js +++ b/apis-authorization-server-war/src/main/webapp/client/js/clientForm.js @@ -85,6 +85,10 @@ var clientFormView = (function() { $("#client_credentials_warning").fadeToggle($(this).is(':checked')); }); + $("input[name='allowedPasswordGrant']").change(function(){ + $("#password_grant_warning").fadeToggle($(this).is(':checked')); + }); + if (mode == "add") { // Trigger the onchange beforehand for new clients, to populate the scopes list for the first time. clientFormController.onChangeResourceServer($("select#clientResourceServer option:selected").val()); @@ -236,6 +240,7 @@ var clientFormController = (function() { useRefreshTokens: formAsObject['useRefreshTokens'], allowedImplicitGrant: formAsObject['allowedImplicitGrant'], allowedClientCredentials: formAsObject['allowedClientCredentials'], + allowedPasswordGrant: formAsObject['allowedPasswordGrant'], expireDuration: formAsObject['expireDuration'], attributes: attributes, redirectUris: cleanFormArray(formAsObject['redirectUris']) @@ -257,4 +262,4 @@ var clientFormController = (function() { hide: view.hide } -})(); \ No newline at end of file +})(); diff --git a/apis-authorization-server-war/src/main/webapp/client/js/popoverBundle.js b/apis-authorization-server-war/src/main/webapp/client/js/popoverBundle.js index 8ba0a6f2..d17d30ee 100644 --- a/apis-authorization-server-war/src/main/webapp/client/js/popoverBundle.js +++ b/apis-authorization-server-war/src/main/webapp/client/js/popoverBundle.js @@ -37,10 +37,10 @@ var popoverBundle = (function () { "client-expireDuration": ["Token expiration time", "The time (in seconds) an access token will be valid after being issued by the authorization server. Leave 0 for infinite validity"], "client-allowedImplicitGrant": ["Client allowed implicit grant", "If a Client is allowed implicit grant - e.g. is a JavaScript client - then it can leverage the implicit grant flow where no secret is used."], "client-allowedClientCredentials": ["Client allowed client credentials", "If a Client is allowed the credit credentials grant - e.g. is a highly trusted client - it will authenticate only with the key/secret and not with user authentication."], + "client-allowedPasswordGrant": ["Client allowed password grant", "If a Client is allowed password grant - e.g. is a trusted mobile app - then it can leverage the password grant flow where only username and password are used to authenticate the user."], "client-useRefreshTokens": ["Client uses refresh tokens", "The client is issued (typically short-lived) a refresh token which is included when issuing an access token. Note that unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers"], "client-redirectUri": ["Client app redirect uri's", "A client app has to provide a redirect uri at runtime when obtaining an access token. The provided redirect uri at runtime is checked against the configured redirect uri here. Although this is not a required field, we strongly advice to configure the redirect uri to prevent possible client frauds to tamper with the authorization server"], "client-attributes": ["Client app attributes", "A client may have additional attributes (key -value pairs) to configure extra info for the client app. The additional data can be used to add extra (OAuth) validation checks on the authorization server prior to granting a client app an access token and/ or enrichen the user consent form"] - } return { @@ -71,6 +71,3 @@ var popoverBundle = (function () { } } })(); - - - diff --git a/apis-authorization-server-war/src/main/webapp/client/templates/tplEditClient.html b/apis-authorization-server-war/src/main/webapp/client/templates/tplEditClient.html index 23dd1fad..6a3f236e 100644 --- a/apis-authorization-server-war/src/main/webapp/client/templates/tplEditClient.html +++ b/apis-authorization-server-war/src/main/webapp/client/templates/tplEditClient.html @@ -119,6 +119,19 @@

{{formTitle}}

+
+ + A password grant is usually for highly trusted mobile or desktop apps. These clients should should not have a secret, since it can't be protected. +
+ +
+ +
+ + +
+
+
diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2Validator.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2Validator.java index ccd8abd2..e282d223 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2Validator.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2Validator.java @@ -95,9 +95,11 @@ enum ValidationResponse { SCOPE_NOT_VALID("invalid_scope", "The requested scope is invalid, unknown, malformed, " + "or exceeds the scope granted by the resource owner."), - IMPLICIT_GRANT_NOT_PERMITTED("unsupported_response_type", "The client has no permisssion for implicit grant"), + IMPLICIT_GRANT_NOT_PERMITTED("unsupported_response_type", "The client has no permission for implicit grant"), - CLIENT_CREDENTIALS_NOT_PERMITTED("unauthorized_client", "The client has no permisssion for client credentials"), + PASSWORD_GRANT_NOT_PERMITTED("unsupported_response_type", "The client has no permission for password grant"), + + CLIENT_CREDENTIALS_NOT_PERMITTED("unauthorized_client", "The client has no permission for client credentials"), REDIRECT_URI_FRAGMENT_COMPONENT("invalid_request", "The redirect_uri endpoint must not include a fragment component"), diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2ValidatorImpl.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2ValidatorImpl.java index 5e39feb6..587d1097 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2ValidatorImpl.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/auth/OAuth2ValidatorImpl.java @@ -245,6 +245,18 @@ protected void validateAccessTokenRequest(AccessTokenRequest accessTokenRequest) accessTokenRequest.setClient(null); throw new ValidationResponseException(CLIENT_CREDENTIALS_NOT_PERMITTED); } + } else if (accessTokenRequest.getGrantType().equals(GRANT_TYPE_PASSWORD)) { + // We must have a client + Client client = accessTokenRequest.getClient(); + if (client == null) { + throw new ValidationResponseException(INVALID_GRANT_PASSWORD); + } + + // And the client must be allowed to perform this grant type + if (!client.isAllowedPasswordGrant()) { + accessTokenRequest.setClient(null); + throw new ValidationResponseException(PASSWORD_GRANT_NOT_PERMITTED); + } } } diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/model/Client.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/model/Client.java index a9511376..67ef8802 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/model/Client.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/model/Client.java @@ -119,6 +119,9 @@ public class Client extends AbstractEntity { @Column private boolean allowedClientCredentials; + @Column + private boolean allowedPasswordGrant; + // Listed here so Cascade will work. @OneToMany(mappedBy ="client", cascade = CascadeType.ALL) private List accessTokens; @@ -353,6 +356,18 @@ public void setAllowedClientCredentials(boolean allowedClientCredentials) { this.allowedClientCredentials = allowedClientCredentials; } + public boolean isAllowedPasswordGrant() { + return allowedPasswordGrant; + } + + public void setAllowedPasswordGrant(boolean allowedPasswordGrant) { + this.allowedPasswordGrant = allowedPasswordGrant; + } + + public boolean shouldGenerateASecret() { + return !(isAllowedImplicitGrant() || isAllowedPasswordGrant()); + } + /* * (non-Javadoc) @@ -373,6 +388,11 @@ public boolean validate(ConstraintValidatorContext context) { isValid = false; } + if (isAllowedClientCredentials() && isAllowedPasswordGrant()) { + violation(context, "A Client can not be issued the client credentials grant AND the password grant as client credentials requires a secret."); + isValid = false; + } + if (scopes != null && !resourceServer.getScopes().containsAll(scopes)) { String message = "Client should only contain scopes that its resource server defines. " + "Client scopes: " + scopes + ". Resource server scopes: " + resourceServer.getScopes(); diff --git a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/ClientResource.java b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/ClientResource.java index 2dd93ea9..02df150d 100644 --- a/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/ClientResource.java +++ b/apis-authorization-server/src/main/java/org/surfnet/oaaas/resource/resourceserver/ClientResource.java @@ -99,7 +99,7 @@ public Response put(@Context HttpServletRequest request, client.setResourceServer(resourceServer); client.setClientId(generateClientId(client)); - client.setSecret(client.isAllowedImplicitGrant() ? null : generateSecret()); + client.setSecret(client.shouldGenerateASecret() ? generateSecret() : null); Client clientSaved; @@ -171,7 +171,7 @@ public Response post(@Valid Client newOne, @PathParam("clientId") Long id, // Copy over read-only fields newOne.setResourceServer(resourceServer); newOne.setClientId(clientFromStore.getClientId()); - newOne.setSecret(newOne.isAllowedImplicitGrant() ? null : clientFromStore.getSecret()); + newOne.setSecret(newOne.shouldGenerateASecret() ? clientFromStore.getSecret() : null); Client savedInstance; try { diff --git a/apis-authorization-server/src/test/java/org/surfnet/oaaas/auth/OAuth2ValidatorImplTest.java b/apis-authorization-server/src/test/java/org/surfnet/oaaas/auth/OAuth2ValidatorImplTest.java index 3e3d97ad..499070ca 100644 --- a/apis-authorization-server/src/test/java/org/surfnet/oaaas/auth/OAuth2ValidatorImplTest.java +++ b/apis-authorization-server/src/test/java/org/surfnet/oaaas/auth/OAuth2ValidatorImplTest.java @@ -208,4 +208,26 @@ private AuthorizationRequest getAuthorizationRequest(Client client) { return request; } + @Test + public void testPasswordTokenRequest() { + AccessTokenRequest noClientAccessTokenRequest = new AccessTokenRequest(); + noClientAccessTokenRequest.setGrantType(OAuth2Validator.GRANT_TYPE_PASSWORD); + ValidationResponse noClientResponse = validator.validate(noClientAccessTokenRequest, BasicAuthCredentials.createCredentialsFromHeader(null)); + assertEquals(ValidationResponse.INVALID_GRANT_PASSWORD, noClientResponse); + + AccessTokenRequest unallowedAccessTokenRequest = new AccessTokenRequest(); + unallowedAccessTokenRequest.setGrantType(OAuth2Validator.GRANT_TYPE_PASSWORD); + unallowedAccessTokenRequest.setClientId(client.getClientId()); + ValidationResponse unallowedResponse = validator.validate(unallowedAccessTokenRequest, BasicAuthCredentials.createCredentialsFromHeader(null)); + assertEquals(ValidationResponse.INVALID_GRANT_PASSWORD, unallowedResponse); + + client.setAllowedPasswordGrant(true); + AccessTokenRequest validAccessTokenRequest = new AccessTokenRequest(); + validAccessTokenRequest.setGrantType(OAuth2Validator.GRANT_TYPE_PASSWORD); + validAccessTokenRequest.setClientId(client.getClientId()); + validAccessTokenRequest.setUsername("username"); + validAccessTokenRequest.setPassword("password"); + ValidationResponse validResponse = validator.validate(validAccessTokenRequest, BasicAuthCredentials.createCredentialsFromHeader(null)); + assertEquals(ValidationResponse.VALID, validResponse); + } } diff --git a/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql b/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql new file mode 100644 index 00000000..2ec37687 --- /dev/null +++ b/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_1__allowed_password_grant.sql @@ -0,0 +1 @@ +ALTER TABLE PUBLIC.CLIENT ADD COLUMN ALLOWEDPASSWORDGRANT BIT(1) DEFAULT 0; diff --git a/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_2__insert_password_grant_client.sql b/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_2__insert_password_grant_client.sql new file mode 100644 index 00000000..61bff810 --- /dev/null +++ b/apis-authorization-server/src/test/resources/db/migration/hsqldb/V3_2__insert_password_grant_client.sql @@ -0,0 +1,10 @@ +/* +Client for password grant +*/ +INSERT INTO client (id, contactEmail, contactName, description, clientName, thumbNailUrl, resourceserver_id, +clientId, secret, allowedPasswordGrant) +VALUES + (99991, 'it-test-password@example.com', 'john.password.grant', 'it test password grant', + 'it test password grant', + 'thumbnailurl', 99997, + 'it-test-password-grant', 'some-secret-client-password', 1);