Skip to content

Commit

Permalink
KEYCLOAK-17637 Client Scope Policy for authorization service
Browse files Browse the repository at this point in the history
  • Loading branch information
y-tabata authored and pedroigor committed Apr 26, 2021
1 parent 6d17117 commit 45202bd
Show file tree
Hide file tree
Showing 20 changed files with 1,074 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,12 @@ protected boolean isRetry() {
* @return a {@link AuthorizationResource}
*/
public AuthorizationResource authorization(final String userName, final String password) {
return new AuthorizationResource(configuration, serverConfiguration, this.http, createRefreshableAccessTokenSupplier(userName, password));
return authorization(userName, password, null);
}

public AuthorizationResource authorization(final String userName, final String password, final String scope) {
return new AuthorizationResource(configuration, serverConfiguration, this.http,
createRefreshableAccessTokenSupplier(userName, password, scope));
}

/**
Expand Down Expand Up @@ -276,6 +281,11 @@ private TokenCallable createPatSupplier() {
}

private TokenCallable createRefreshableAccessTokenSupplier(final String userName, final String password) {
return new TokenCallable(userName, password, http, configuration, serverConfiguration);
return createRefreshableAccessTokenSupplier(userName, password, null);
}

private TokenCallable createRefreshableAccessTokenSupplier(final String userName, final String password,
final String scope) {
return new TokenCallable(userName, password, scope, http, configuration, serverConfiguration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,16 @@ public HttpMethod<R> client() {
}

public HttpMethod<R> oauth2ResourceOwnerPassword(String userName, String password) {
return oauth2ResourceOwnerPassword(userName, password, null);
}

public HttpMethod<R> oauth2ResourceOwnerPassword(String userName, String password, String scope) {
client();
this.method.params.put(OAuth2Constants.GRANT_TYPE, Arrays.asList(OAuth2Constants.PASSWORD));
this.method.params.put("username", Arrays.asList(userName));
this.method.params.put("password", Arrays.asList(password));
if (scope != null)
this.method.params.put("scope", Arrays.asList(scope));
return this.method;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,27 @@ public class TokenCallable implements Callable<String> {
private static Logger log = Logger.getLogger(TokenCallable.class);
private final String userName;
private final String password;
private final String scope;
private final Http http;
private final Configuration configuration;
private final ServerConfiguration serverConfiguration;
private AccessTokenResponse tokenResponse;

public TokenCallable(String userName, String password, Http http, Configuration configuration, ServerConfiguration serverConfiguration) {
public TokenCallable(String userName, String password, String scope, Http http, Configuration configuration,
ServerConfiguration serverConfiguration) {
this.userName = userName;
this.password = password;
this.scope = scope;
this.http = http;
this.configuration = configuration;
this.serverConfiguration = serverConfiguration;
}

public TokenCallable(String userName, String password, Http http, Configuration configuration,
ServerConfiguration serverConfiguration) {
this(userName, password, null, http, configuration, serverConfiguration);
}

public TokenCallable(Http http, Configuration configuration, ServerConfiguration serverConfiguration) {
this(null, null, http, configuration, serverConfiguration);
}
Expand Down Expand Up @@ -121,12 +129,12 @@ AccessTokenResponse clientCredentialsGrant() {
* @return an {@link AccessTokenResponse}
*/
AccessTokenResponse resourceOwnerPasswordGrant(String userName, String password) {
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint())
.authentication()
.oauth2ResourceOwnerPassword(userName, password)
.response()
.json(AccessTokenResponse.class)
.execute();
return resourceOwnerPasswordGrant(userName, password, null);
}

AccessTokenResponse resourceOwnerPasswordGrant(String userName, String password, String scope) {
return this.http.<AccessTokenResponse>post(this.serverConfiguration.getTokenEndpoint()).authentication()
.oauth2ResourceOwnerPassword(userName, password, scope).response().json(AccessTokenResponse.class).execute();
}

private AccessTokenResponse refreshToken(String rawRefreshToken) {
Expand All @@ -144,6 +152,8 @@ private AccessTokenResponse refreshToken(String rawRefreshToken) {
private AccessTokenResponse obtainTokens() {
if (userName == null || password == null) {
return clientCredentialsGrant();
} else if (scope != null) {
return resourceOwnerPasswordGrant(userName, password, scope);
} else {
return resourceOwnerPasswordGrant(userName, password);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.keycloak.authorization.policy.provider.clientscope;

import java.util.Set;
import java.util.function.BiFunction;

import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.model.Policy;
import org.keycloak.authorization.policy.evaluation.Evaluation;
import org.keycloak.authorization.policy.provider.PolicyProvider;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.authorization.ClientScopePolicyRepresentation;

/**
* @author <a href="mailto:yoshiyuki.tabata.jy@hitachi.com">Yoshiyuki Tabata</a>
*/
public class ClientScopePolicyProvider implements PolicyProvider {

private final BiFunction<Policy, AuthorizationProvider, ClientScopePolicyRepresentation> representationFunction;

public ClientScopePolicyProvider(
BiFunction<Policy, AuthorizationProvider, ClientScopePolicyRepresentation> representationFunction) {
this.representationFunction = representationFunction;
}

@Override
public void close() {
}

@Override
public void evaluate(Evaluation evaluation) {
Policy policy = evaluation.getPolicy();
Set<ClientScopePolicyRepresentation.ClientScopeDefinition> clientScopeIds = representationFunction
.apply(policy, evaluation.getAuthorizationProvider()).getClientScopes();
AuthorizationProvider authorizationProvider = evaluation.getAuthorizationProvider();
RealmModel realm = authorizationProvider.getKeycloakSession().getContext().getRealm();
Identity identity = evaluation.getContext().getIdentity();

for (ClientScopePolicyRepresentation.ClientScopeDefinition clientScopeDefinition : clientScopeIds) {
ClientScopeModel clientScope = realm.getClientScopeById(clientScopeDefinition.getId());

if (clientScope != null) {
boolean hasClientScope = hasClientScope(identity, clientScope);

if (!hasClientScope && clientScopeDefinition.isRequired()) {
evaluation.deny();
return;
} else if (hasClientScope) {
evaluation.grant();
}
}
}
}

private boolean hasClientScope(Identity identity, ClientScopeModel clientScope) {
String clientScopeName = clientScope.getName();
String[] clientScopes = identity.getAttributes().getValue("scope").asString(0).split(" ");
for (String scope : clientScopes) {
if (clientScopeName.equals(scope))
return true;
}
return false;
}

}
Loading

0 comments on commit 45202bd

Please sign in to comment.