Skip to content
This repository was archived by the owner on Jul 3, 2020. It is now read-only.

Adds password grant configuration #107

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE PUBLIC.CLIENT ADD COLUMN ALLOWEDPASSWORDGRANT BIT(1) DEFAULT 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INSERT INTO resourceowner (id, username, password)
VALUES (99999,'emma.blunt', 'cafebabe');
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Client for password grant
*/
INSERT INTO client (id, contactEmail, contactName, description, clientName, thumbNailUrl, resourceserver_id,
clientId, secret, allowedPasswordGrant)
VALUES
(99991, '[email protected]', 'john.password.grant', 'it test password grant',
'it test password grant',
'thumbnailurl', 99997,
'it-test-password-grant', 'some-secret-client-password', 1);
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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'])
Expand All @@ -257,4 +262,4 @@ var clientFormController = (function() {

hide: view.hide
}
})();
})();
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -71,6 +71,3 @@ var popoverBundle = (function () {
}
}
})();



Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ <h1>{{formTitle}}</h1>
</div>
</div>

<div class="alert hide" id="password_grant_warning">
<button class="close" data-dismiss="alert">×</button>
<span>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.</span>
</div>

<div class="control-group">
<label class="control-label">Allow password grant</label>
<div class="controls">
<input type="checkbox" name="allowedPasswordGrant" value="true" {{#if allowedPasswordGrant}}checked="checked"{{/if}}>
<i class="icon-question-sign icon-blue" rel="popover" name="client-allowedPasswordGrant"></i>
</div>
</div>

<div class="control-group">
<label class="control-label">Use refresh tokens</label>
<div class="controls">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccessToken> accessTokens;
Expand Down Expand Up @@ -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)
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE PUBLIC.CLIENT ADD COLUMN ALLOWEDPASSWORDGRANT BIT(1) DEFAULT 0;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
Client for password grant
*/
INSERT INTO client (id, contactEmail, contactName, description, clientName, thumbNailUrl, resourceserver_id,
clientId, secret, allowedPasswordGrant)
VALUES
(99991, '[email protected]', 'john.password.grant', 'it test password grant',
'it test password grant',
'thumbnailurl', 99997,
'it-test-password-grant', 'some-secret-client-password', 1);