KEYCLOAK-14189 Client Policy : Basics

This commit is contained in:
Takashi Norimatsu 2020-02-27 08:57:22 +09:00 committed by Stian Thorgersen
parent 6d5495141d
commit e0fbfa722e
62 changed files with 3657 additions and 9 deletions

View file

@ -53,7 +53,8 @@ public class Profile {
SCRIPTS(Type.PREVIEW),
TOKEN_EXCHANGE(Type.PREVIEW),
UPLOAD_SCRIPTS(DEPRECATED),
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW);
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW),
CLIENT_POLICIES(Type.PREVIEW);
private Type typeProject;
private Type typeProduct;

View file

@ -21,8 +21,8 @@ public class ProfileTest {
@Test
public void checkDefaultsKeycloak() {
Assert.assertEquals("community", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION);
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.CLIENT_POLICIES);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.CLIENT_POLICIES);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
@ -37,8 +37,8 @@ public class ProfileTest {
Profile.init();
Assert.assertEquals("product", Profile.getName());
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN);
assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN, Profile.Feature.CLIENT_POLICIES);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ACCOUNT2, Profile.Feature.ACCOUNT_API, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN, Profile.Feature.CLIENT_POLICIES);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());

View file

@ -0,0 +1,48 @@
/*
* Copyright 2020 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.services.clientpolicy;
import java.util.List;
import org.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
/**
* Provides Client Policy which accommodates several Conditions and Executors.
*/
public interface ClientPolicyProvider extends Provider {
/**
* returns the list of conditions which this provider accommodates.
*
* @return list of conditions
*/
List<ClientPolicyConditionProvider> getConditions();
/**
* returns the list of executors which this provider accommodates.
*
* @return list of executors
*/
List<ClientPolicyExecutorProvider> getExecutors();
String getName();
String getProviderId();
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.component.ComponentFactory;
public interface ClientPolicyProviderFactory extends ComponentFactory<ClientPolicyProvider, ClientPolicyProvider> {
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class ClientPolicySpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "client-policy";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientPolicyProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientPolicyProviderFactory.class;
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2020 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.services.clientpolicy;
public enum ClientPolicyVote {
YES,
NO,
ABSTAIN
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2020 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.services.clientpolicy.condition;
import org.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyVote;
/**
* This condition determines to which client a {@link ClientPolicyProvider} is adopted.
* The condition can be evaluated on the events defined in {@link ClientPolicyEvent}.
* It is sufficient for the implementer of this condition to implement methods in which they are interested
* and {@link isEvaluatedOnEvent} method.
*/
public interface ClientPolicyConditionProvider extends Provider {
@Override
default void close() {
}
/**
* returns ABSTAIN if this condition is not evaluated due to its nature.
* returns YES if the client satisfies this condition on the event defined in {@link ClientPolicyEvent}.
* If not, returns NO.
*
* @param context - the context of the event.
* @return returns ABSTAIN if this condition is not evaluated due to its nature.
* @throws {@link ClientPolicyException} - thrown if the condition is not evaluated in its nature on the event specified by context.
*/
default ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPolicyException {
return ClientPolicyVote.ABSTAIN;
}
String getName();
String getProviderId();
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2020 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.services.clientpolicy.condition;
import org.keycloak.component.ComponentFactory;
public interface ClientPolicyConditionProviderFactory extends ComponentFactory<ClientPolicyConditionProvider, ClientPolicyConditionProvider> {
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2020 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.services.clientpolicy.condition;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class ClientPolicyConditionSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "client-policy-condition";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientPolicyConditionProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientPolicyConditionProviderFactory.class;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2020 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.services.clientpolicy.executor;
import org.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
/**
* This executor specifies what action is executed on the client to which {@link ClientPolicyProvider} is adopted.
* The executor can be executed on the events defined in {@link ClientPolicyEvent}.
* It is sufficient for the implementer of this executor to implement methods in which they are interested
* and {@link isEvaluatedOnEvent} method.
*/
public interface ClientPolicyExecutorProvider extends Provider {
@Override
default void close() {
}
/**
* execute actions against the client on the event defined in {@link ClientPolicyEvent}.
*
* @param context - the context of the event.
* @throws {@link ClientPolicyException} - if something wrong happens when execution actions.
*/
default void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
}
String getName();
String getProviderId();
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2020 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.services.clientpolicy.executor;
import org.keycloak.component.ComponentFactory;
public interface ClientPolicyExecutorProviderFactory extends ComponentFactory<ClientPolicyExecutorProvider, ClientPolicyExecutorProvider> {
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2020 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.services.clientpolicy.executor;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
public class ClientPolicyExecutorSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "client-policy-executor";
}
@Override
public Class<? extends Provider> getProviderClass() {
return ClientPolicyExecutorProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ClientPolicyExecutorProviderFactory.class;
}
}

View file

@ -80,3 +80,6 @@ org.keycloak.crypto.CekManagementSpi
org.keycloak.crypto.ContentEncryptionSpi
org.keycloak.validation.ClientValidationSPI
org.keycloak.headers.SecurityHeadersSpi
org.keycloak.services.clientpolicy.ClientPolicySpi
org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi
org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi

View file

@ -20,6 +20,7 @@ package org.keycloak.models;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
import org.keycloak.vault.VaultTranscriber;
@ -201,4 +202,10 @@ public interface KeycloakSession {
* Vault transcriber
*/
VaultTranscriber vault();
/**
* Client Policy Manager
*/
ClientPolicyManager clientPolicy();
}

View file

@ -0,0 +1,34 @@
/*
* Copyright 2020 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.services.clientpolicy;
/**
* Provides Client Policy Context.
* The implementation of this interface for handling an event defined in {@link ClientPolicyEvent}
* needs to provide methods depending on this event.
*/
public interface ClientPolicyContext {
/**
* returns {@link ClientPolicyEvent} of client policy related events.
*
* @return {@link ClientPolicyEvent}
*/
ClientPolicyEvent getEvent();
}

View file

@ -0,0 +1,32 @@
/*
* Copyright 2020 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.services.clientpolicy;
public enum ClientPolicyEvent {
REGISTER,
UPDATE,
AUTHORIZATION_REQUEST,
TOKEN_REQUEST,
TOKEN_REFRESH,
TOKEN_REVOKE,
TOKEN_INTROSPECT,
USERINFO_REQUEST,
LOGOUT_REQUEST
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2020 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.services.clientpolicy;
public class ClientPolicyException extends Exception {
private String error;
private String errorDetail;
public ClientPolicyException(String error, String errorDetail) {
super(error);
setError(error);
setErrorDetail(errorDetail);
}
public ClientPolicyException(String error, String errorDetail, Throwable throwable) {
super(throwable);
setError(error);
setErrorDetail(errorDetail);
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getErrorDetail() {
return errorDetail;
}
public void setErrorDetail(String errorDetail) {
this.errorDetail = errorDetail;
}
/**
* If {@link ClientPolicyException} is used to notify the event so that it needs not to have stack trace.
* @return always null
*/
@Override
public Throwable fillInStackTrace() {
return null;
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2020 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.services.clientpolicy;
/**
* Provides a method for handling an event defined in {@link ClientPolicyEvent}.
*/
public interface ClientPolicyManager {
/**
* execute a method for handling an event defined in {@link ClientPolicyEvent}.
*
* @param context - the context of the event.
* @throws {@link ClientPolicyException}
*/
void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException;
}

View file

@ -44,6 +44,10 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.AuthorizationRequestContext;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
@ -153,6 +157,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
return errorResponse;
}
try {
session.clientPolicy().triggerOnEvent(new AuthorizationRequestContext(parsedResponseType, request, redirectUri));
} catch (ClientPolicyException cpe) {
return redirectErrorToClient(parsedResponseMode, cpe.getError(), cpe.getErrorDetail());
}
authenticationSession = createAuthenticationSession(client, request.getState());
updateAuthenticationSession();

View file

@ -42,6 +42,9 @@ import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.LogoutRequestContext;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.messages.Messages;
@ -193,6 +196,12 @@ public class LogoutEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
}
try {
session.clientPolicy().triggerOnEvent(new LogoutRequestContext(form));
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
RefreshToken token = null;
try {
// KEYCLOAK-6771 Certificate Bound Token

View file

@ -83,6 +83,10 @@ import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.TokenRefreshContext;
import org.keycloak.services.clientpolicy.TokenRequestContext;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
@ -402,6 +406,13 @@ public class TokenEndpoint {
checkParamsForPkceNotEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername);
}
try {
session.clientPolicy().triggerOnEvent(new TokenRequestContext(formParams, parseResult));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
updateClientSession(clientSession);
updateUserSessionFromClientAuth(userSession);
@ -521,6 +532,13 @@ public class TokenEndpoint {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
}
try {
session.clientPolicy().triggerOnEvent(new TokenRefreshContext(formParams));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
AccessTokenResponse res;
try {
// KEYCLOAK-6771 Certificate Bound Token

View file

@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc.endpoints;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
@ -28,7 +29,11 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.AccessTokenIntrospectionProviderFactory;
import org.keycloak.protocol.oidc.TokenIntrospectionProvider;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.TokenIntrospectContext;
import javax.ws.rs.POST;
import javax.ws.rs.core.Context;
@ -94,6 +99,12 @@ public class TokenIntrospectionEndpoint {
throw throwErrorResponseException(Errors.INVALID_REQUEST, "Unsupported token type [" + tokenTypeHint + "].", Status.BAD_REQUEST);
}
try {
session.clientPolicy().triggerOnEvent(new TokenIntrospectContext(formParams));
} catch (ClientPolicyException cpe) {
throw throwErrorResponseException(Errors.INVALID_REQUEST, cpe.getErrorDetail(), Status.BAD_REQUEST);
}
try {
Response response = provider.introspect(token);

View file

@ -44,6 +44,9 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.CorsErrorResponseException;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientpolicy.TokenRevokeContext;
import org.keycloak.services.managers.UserSessionCrossDCManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.Cors;
@ -93,6 +96,13 @@ public class TokenRevocationEndpoint {
formParams = request.getDecodedFormParameters();
try {
session.clientPolicy().triggerOnEvent(new TokenRevokeContext(formParams));
} catch (ClientPolicyException cpe) {
event.error(cpe.getError());
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
checkToken();
checkIssuedFor();

View file

@ -45,6 +45,8 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.UserInfoRequestContext;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.UserSessionCrossDCManager;
@ -60,6 +62,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -128,6 +131,13 @@ public class UserInfoEndpoint {
}
private Response issueUserInfo(String tokenString) {
try {
session.clientPolicy().triggerOnEvent(new UserInfoRequestContext(tokenString));
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(Errors.INVALID_REQUEST, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
EventBuilder event = new EventBuilder(realm, session, clientConnection)
.event(EventType.USER_INFO_REQUEST)
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);

View file

@ -37,6 +37,8 @@ import org.keycloak.models.cache.CacheRealmProvider;
import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.ClientStorageManager;
import org.keycloak.storage.UserStorageManager;
@ -75,6 +77,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private ThemeManager themeManager;
private TokenManager tokenManager;
private VaultTranscriber vaultTranscriber;
private ClientPolicyManager clientPolicyManager;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
@ -321,6 +324,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
return this.vaultTranscriber;
}
@Override
public ClientPolicyManager clientPolicy() {
if (clientPolicyManager == null) {
clientPolicyManager = new DefaultClientPolicyManager(this);
}
return clientPolicyManager;
}
public void close() {
Consumer<? super Provider> safeClose = p -> {
try {
@ -332,4 +343,5 @@ public class DefaultKeycloakSession implements KeycloakSession {
providers.values().forEach(safeClose);
closable.forEach(safeClose);
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.resources.admin.AdminAuth;
public class AdminClientRegisterContext implements ClientUpdateContext {
private final ClientRepresentation clientRepresentation;
private final AdminAuth adminAuth;
public AdminClientRegisterContext(ClientRepresentation clientRepresentation,
AdminAuth adminAuth) {
this.clientRepresentation = clientRepresentation;
this.adminAuth = adminAuth;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.REGISTER;
}
@Override
public ClientRepresentation getProposedClientRepresentation() {
return clientRepresentation;
}
@Override
public ClientModel getAuthenticatedClient() {
return adminAuth.getClient();
}
@Override
public UserModel getAuthenticatedUser() {
return adminAuth.getUser();
}
@Override
public JsonWebToken getToken() {
return adminAuth.getToken();
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.resources.admin.AdminAuth;
public class AdminClientUpdateContext implements ClientUpdateContext {
private final ClientRepresentation clientRepresentation;
private final AdminAuth adminAuth;
private final ClientModel client;
public AdminClientUpdateContext(ClientRepresentation clientRepresentation,
AdminAuth adminAuth, ClientModel client) {
this.clientRepresentation = clientRepresentation;
this.adminAuth = adminAuth;
this.client = client;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.UPDATE;
}
@Override
public ClientRepresentation getProposedClientRepresentation() {
return clientRepresentation;
}
@Override
public ClientModel getClientToBeUpdated() {
return client;
}
@Override
public ClientModel getAuthenticatedClient() {
return adminAuth.getClient();
}
@Override
public UserModel getAuthenticatedUser() {
return adminAuth.getUser();
}
@Override
public JsonWebToken getToken() {
return adminAuth.getToken();
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class AuthorizationRequestContext implements ClientPolicyContext {
private final OIDCResponseType parsedResponseType;
private final AuthorizationEndpointRequest request;
private final String redirectUri;
public AuthorizationRequestContext(OIDCResponseType parsedResponseType,
AuthorizationEndpointRequest request,
String redirectUri) {
this.parsedResponseType = parsedResponseType;
this.request = request;
this.redirectUri = redirectUri;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.AUTHORIZATION_REQUEST;
}
public OIDCResponseType getparsedResponseType() {
return parsedResponseType;
}
public AuthorizationEndpointRequest getAuthorizationEndpointRequest() {
return request;
}
public String getRedirectUri() {
return redirectUri;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.jboss.logging.Logger;
public class ClientPolicyLogger {
public static void log(Logger logger, String content) {
if(!logger.isTraceEnabled()) return;
String buf = new StringBuffer()
.append("#").append(getMethodName())
.append(", ").append(content)
.toString();
logger.trace(buf);
}
public static void logv(Logger logger, String format, Object...params) {
if(!logger.isTraceEnabled()) return;
String buf = new StringBuffer()
.append("#").append(getMethodName())
.append(", ").append(format)
.toString();
logger.tracev(buf, params);
}
private static String getClassName() {
return Thread.currentThread().getStackTrace()[2].getClassName();
}
private static String getMethodName() {
return Thread.currentThread().getStackTrace()[3].getMethodName();
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
/**
* Represents the context in the client registration/update by Dynamic Client Registration or Admin REST API.
*/
public interface ClientUpdateContext extends ClientPolicyContext {
/**
* returns {@link ClientRepresentation} for creating or updating the current client.
*
* @return {@link ClientRepresentation}
*/
ClientRepresentation getProposedClientRepresentation();
/**
* returns {@link ClientModel} of the current client to be updated.
*
* @return {@link ClientModel}
*/
default ClientModel getClientToBeUpdated() {
return null;
}
/**
* returns {@link UserModel} of the authenticated user.
*
* @return {@link UserModel}
*/
UserModel getAuthenticatedUser();
/**
* returns {@link UserModel} of the authenticated client.
*
* @return {@link UserModel}
*/
ClientModel getAuthenticatedClient();
/**
* returns {@link JsonWebToken} of the token accompanied with registration/update client
*
* @return {@link JsonWebToken}
*/
JsonWebToken getToken();
}

View file

@ -0,0 +1,156 @@
/*
* Copyright 2020 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.services.clientpolicy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyManager;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class DefaultClientPolicyManager implements ClientPolicyManager {
private static final Logger logger = Logger.getLogger(DefaultClientPolicyManager.class);
private final KeycloakSession session;
private final Map<String, List<ClientPolicyProvider>> providersMap = new HashMap<>();
public DefaultClientPolicyManager(KeycloakSession session) {
this.session = session;
}
@Override
public void triggerOnEvent(ClientPolicyContext context) throws ClientPolicyException {
if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_POLICIES)) return;
ClientPolicyLogger.logv(logger, "Client Policy Operation : event = {0}", context.getEvent());
doPolicyOperation(
(ClientPolicyConditionProvider condition) -> condition.applyPolicy(context),
(ClientPolicyExecutorProvider executor) -> executor.executeOnEvent(context)
);
}
private void doPolicyOperation(ClientConditionOperation condition, ClientExecutorOperation executor) throws ClientPolicyException {
RealmModel realm = session.getContext().getRealm();
for (ClientPolicyProvider policy : getProviders(realm)) {
ClientPolicyLogger.logv(logger, "Policy Operation : name = {0}, provider id = {1}", policy.getName(), policy.getProviderId());
if (!isSatisfied(policy, condition)) continue;
execute(policy, executor);
}
}
private List<ClientPolicyProvider> getProviders(RealmModel realm) {
List<ClientPolicyProvider> providers = providersMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<ComponentModel> policyModels = realm.getComponents(realm.getId(), ClientPolicyProvider.class.getName());
for (ComponentModel policyModel : policyModels) {
try {
ClientPolicyProvider policy = session.getProvider(ClientPolicyProvider.class, policyModel);
ClientPolicyLogger.logv(logger, "Loaded Policy Name = {0}", policyModel.getName());
session.enlistForClose(policy);
providers.add(policy);
} catch (Throwable t) {
logger.errorv(t, "Failed to load provider {0}", policyModel.getId());
}
}
providersMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached policies.");
}
return providers;
}
private boolean isSatisfied(
ClientPolicyProvider policy,
ClientConditionOperation op) throws ClientPolicyException {
List<ClientPolicyConditionProvider> conditions = policy.getConditions();
if (conditions == null || conditions.isEmpty()) {
ClientPolicyLogger.log(logger, "NEGATIVE :: This policy is not applied. No condition exists.");
return false;
}
boolean ret = false;
for (ClientPolicyConditionProvider condition : conditions) {
try {
ClientPolicyVote vote = op.run(condition);
if (vote == ClientPolicyVote.ABSTAIN) {
ClientPolicyLogger.logv(logger, "SKIP : This condition is not evaluated due to its nature. name = {0}, provider id = {1}", condition.getName(), condition.getProviderId());
continue;
} else if (vote == ClientPolicyVote.NO) {
ClientPolicyLogger.logv(logger, "NEGATIVE :: This policy is not applied. condition not satisfied. name = {0}, provider id = {1}, ", condition.getName(), condition.getProviderId());
return false;
}
ret = true;
} catch (ClientPolicyException cpe) {
ClientPolicyLogger.logv(logger, "CONDITION EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", condition.getName(), condition.getProviderId(), cpe.getError(), cpe.getErrorDetail());
throw cpe;
}
}
if (ret == true) {
ClientPolicyLogger.log(logger, "POSITIVE :: This policy is applied.");
} else {
ClientPolicyLogger.log(logger, "NEGATIVE :: This policy is not applied. No condition is evaluated.");
}
return ret;
}
private void execute(
ClientPolicyProvider policy,
ClientExecutorOperation op) throws ClientPolicyException {
List<ClientPolicyExecutorProvider> executors = policy.getExecutors();
if (executors == null || executors.isEmpty()) {
ClientPolicyLogger.log(logger, "NEGATIVE :: This executor is not executed. No executor executable.");
return;
}
for (ClientPolicyExecutorProvider executor : executors) {
try {
op.run(executor);
} catch(ClientPolicyException cpe) {
ClientPolicyLogger.logv(logger, "EXECUTOR EXCEPTION : name = {0}, provider id = {1}, error = {2}, error_detail = {3}", executor.getName(), executor.getProviderId(), cpe.getError(), cpe.getErrorDetail());
throw cpe;
}
}
}
private interface ClientConditionOperation {
ClientPolicyVote run(ClientPolicyConditionProvider condition) throws ClientPolicyException;
}
private interface ClientExecutorOperation {
void run(ClientPolicyExecutorProvider executor) throws ClientPolicyException;
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright 2020 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.services.clientpolicy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class DefaultClientPolicyProvider implements ClientPolicyProvider {
private static final Logger logger = Logger.getLogger(DefaultClientPolicyProvider.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
private final Map<String, List<ClientPolicyConditionProvider>> conditionsMap = new HashMap<>();
private final Map<String, List<ClientPolicyExecutorProvider>> executorsMap = new HashMap<>();
public DefaultClientPolicyProvider(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public void close() {
}
@Override
public List<ClientPolicyConditionProvider> getConditions() {
return getConditions(session.getContext().getRealm());
}
@Override
public List<ClientPolicyExecutorProvider> getExecutors() {
return getExecutors(session.getContext().getRealm());
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
private List<String> getConditionIds() {
return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.CONDITION_IDS);
}
private List<String> getExecutorIds() {
return componentModel.getConfig().getList(DefaultClientPolicyProviderFactory.EXECUTOR_IDS);
}
private List<ClientPolicyConditionProvider> getConditions(RealmModel realm) {
List<ClientPolicyConditionProvider> providers = conditionsMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<String> conditionIds = getConditionIds();
if (conditionIds == null || conditionIds.isEmpty()) return null;
for(String conditionId : conditionIds) {
ComponentModel cm = session.getContext().getRealm().getComponent(conditionId);
try {
ClientPolicyConditionProvider provider = session.getProvider(ClientPolicyConditionProvider.class, cm);
providers.add(provider);
session.enlistForClose(provider);
ClientPolicyLogger.logv(logger, "Loaded Condition id = {0}, name = {1}, provider id = {2}", conditionId, cm.getName(), cm.getProviderId());
} catch (Throwable t) {
logger.errorv(t, "Failed to load condition {0}", cm.getId());
}
}
conditionsMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached conditions.");
}
return providers;
}
private List<ClientPolicyExecutorProvider> getExecutors(RealmModel realm) {
List<ClientPolicyExecutorProvider> providers = executorsMap.get(realm.getId());
if (providers == null) {
providers = new LinkedList<>();
List<String> executorIds = getExecutorIds();
if (executorIds == null || executorIds.isEmpty()) return null;
for(String executorId : executorIds) {
ComponentModel cm = session.getContext().getRealm().getComponent(executorId);
try {
ClientPolicyExecutorProvider provider = session.getProvider(ClientPolicyExecutorProvider.class, cm);
providers.add(provider);
session.enlistForClose(provider);
ClientPolicyLogger.logv(logger, "Loaded Executor id = {0}, name = {1}, provider id = {2}", executorId, cm.getName(), cm.getProviderId());
} catch (Throwable t) {
logger.errorv(t, "Failed to load executor {0}", cm.getId());
}
}
executorsMap.put(realm.getId(), providers);
} else {
ClientPolicyLogger.log(logger, "Use cached executors.");
}
return providers;
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2020 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.services.clientpolicy;
import java.util.Arrays;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.ClientPolicyProviderFactory;
public class DefaultClientPolicyProviderFactory implements ClientPolicyProviderFactory {
public static final String PROVIDER_ID = "client-policy-provider";
public static final String CONDITION_IDS = "client-policy-condition-ids";
public static final String EXECUTOR_IDS = "client-policy-executor-ids";
private static final ProviderConfigProperty CONDITION_IDS_PROPERTY = new ProviderConfigProperty(CONDITION_IDS, null, null, ProviderConfigProperty.LIST_TYPE, null);
private static final ProviderConfigProperty EXECUTOR_IDS_PROPERTY = new ProviderConfigProperty(EXECUTOR_IDS, null, null, ProviderConfigProperty.LIST_TYPE, null);
@Override
public ClientPolicyProvider create(KeycloakSession session, ComponentModel model) {
return new DefaultClientPolicyProvider(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Arrays.asList(CONDITION_IDS_PROPERTY, EXECUTOR_IDS_PROPERTY);
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientregistration.ClientRegistrationContext;
public class DynamicClientRegisterContext implements ClientUpdateContext {
private final ClientRegistrationContext context;
private JsonWebToken token;
private UserModel user;
private ClientModel client;
public DynamicClientRegisterContext(ClientRegistrationContext context,
JsonWebToken token, RealmModel realm) {
this.context = context;
this.token = token;
if (token != null) {
if (token.getSubject() != null) {
this.user = context.getSession().users().getUserById(token.getSubject(), realm);
}
if (token.getIssuedFor() != null) {
this.client = realm.getClientByClientId(token.getIssuedFor());
}
}
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.REGISTER;
}
@Override
public ClientRepresentation getProposedClientRepresentation() {
return context.getClient();
}
@Override
public ClientModel getAuthenticatedClient() {
return client;
}
@Override
public UserModel getAuthenticatedUser() {
return user;
}
@Override
public JsonWebToken getToken() {
return token;
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
import org.keycloak.services.clientregistration.ClientRegistrationContext;
public class DynamicClientUpdateContext implements ClientUpdateContext {
private final ClientRegistrationContext context;
private final ClientModel clientToBeUpdated;
private JsonWebToken token;
private UserModel user;
private ClientModel client;
public DynamicClientUpdateContext(ClientRegistrationContext context,
ClientModel client, JsonWebToken token, RealmModel realm) {
this.context = context;
this.clientToBeUpdated = client;
this.token = token;
if (token != null) {
if (token.getSubject() != null) {
this.user = context.getSession().users().getUserById(token.getSubject(), realm);
}
if (token.getIssuedFor() != null) {
this.client = realm.getClientByClientId(token.getIssuedFor());
}
}
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.UPDATE;
}
@Override
public ClientRepresentation getProposedClientRepresentation() {
return context.getClient();
}
@Override
public ClientModel getClientToBeUpdated() {
return clientToBeUpdated;
}
@Override
public ClientModel getAuthenticatedClient() {
return client;
}
@Override
public UserModel getAuthenticatedUser() {
return user;
}
@Override
public JsonWebToken getToken() {
return token;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2020 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.services.clientpolicy;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class LogoutRequestContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params;
public LogoutRequestContext(MultivaluedMap<String, String> params) {
this.params = params;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.LOGOUT_REQUEST;
}
public MultivaluedMap<String, String> getParams() {
return params;
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 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.services.clientpolicy;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class TokenIntrospectContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params;
public TokenIntrospectContext(MultivaluedMap<String, String> params) {
this.params = params;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.TOKEN_INTROSPECT;
}
public MultivaluedMap<String, String> getParams() {
return params;
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 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.services.clientpolicy;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class TokenRefreshContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params;
public TokenRefreshContext(MultivaluedMap<String, String> params) {
this.params = params;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.TOKEN_REFRESH;
}
public MultivaluedMap<String, String> getParams() {
return params;
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2020 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.services.clientpolicy;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class TokenRequestContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params;
private final OAuth2CodeParser.ParseResult parseResult;
public TokenRequestContext(MultivaluedMap<String, String> params,
OAuth2CodeParser.ParseResult parseResult) {
this.params = params;
this.parseResult = parseResult;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.TOKEN_REQUEST;
}
public MultivaluedMap<String, String> getParams() {
return params;
}
public OAuth2CodeParser.ParseResult getParseResult() {
return parseResult;
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 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.services.clientpolicy;
import javax.ws.rs.core.MultivaluedMap;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class TokenRevokeContext implements ClientPolicyContext {
private final MultivaluedMap<String, String> params;
public TokenRevokeContext(MultivaluedMap<String, String> params) {
this.params = params;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.TOKEN_REVOKE;
}
public MultivaluedMap<String, String> getParams() {
return params;
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2020 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.services.clientpolicy;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyEvent;
public class UserInfoRequestContext implements ClientPolicyContext {
private final String tokenString;
public UserInfoRequestContext(String tokenString) {
this.tokenString = tokenString;
}
@Override
public ClientPolicyEvent getEvent() {
return ClientPolicyEvent.USERINFO_REQUEST;
}
public String getTokenString() {
return tokenString;
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright 2020 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.services.clientpolicy.executor;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientUpdateContext;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public abstract class AbstractAugumentingClientRegistrationPolicyExecutor implements ClientPolicyExecutorProvider {
protected static final Logger logger = Logger.getLogger(AbstractAugumentingClientRegistrationPolicyExecutor.class);
protected static final String IS_AUGMENT = "is-augment";
protected final KeycloakSession session;
protected final ComponentModel componentModel;
public AbstractAugumentingClientRegistrationPolicyExecutor(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case REGISTER:
case UPDATE:
ClientUpdateContext clientUpdateContext = (ClientUpdateContext)context;
augment(clientUpdateContext.getProposedClientRepresentation());
validate(clientUpdateContext.getProposedClientRepresentation());
break;
default:
return;
}
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
/**
* overrides the client settings specified by the argument.
*
* @param rep - the client settings
*/
protected abstract void augment(ClientRepresentation rep);
/**
* validate the client settings specified by the argument to check
* whether they follows what the executor expects.
*
* @param rep - the client settings
*/
protected abstract void validate(ClientRepresentation rep) throws ClientPolicyException;
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2020 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.services.clientpolicy.executor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProviderFactory;
public abstract class AbstractAugumentingClientRegistrationPolicyExecutorFactory implements ClientPolicyExecutorProviderFactory {
protected static final String IS_AUGMENT = "is-augment";
private static final ProviderConfigProperty IS_AUGMENT_PROPERTY = new ProviderConfigProperty(
IS_AUGMENT, null, null, ProviderConfigProperty.BOOLEAN_TYPE, false);
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return new ArrayList<>(Arrays.asList(IS_AUGMENT_PROPERTY));
}
}

View file

@ -36,6 +36,9 @@ import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DynamicClientRegisterContext;
import org.keycloak.services.clientpolicy.DynamicClientUpdateContext;
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyException;
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
@ -149,8 +152,9 @@ public class ClientRegistrationAuth {
}
try {
session.clientPolicy().triggerOnEvent(new DynamicClientRegisterContext(context, jwt, realm));
ClientRegistrationPolicyManager.triggerBeforeRegister(context, registrationAuth);
} catch (ClientRegistrationPolicyException crpe) {
} catch (ClientRegistrationPolicyException | ClientPolicyException crpe) {
throw forbidden(crpe.getMessage());
}
@ -210,8 +214,9 @@ public class ClientRegistrationAuth {
RegistrationAuth regAuth = requireUpdateAuth(client);
try {
session.clientPolicy().triggerOnEvent(new DynamicClientUpdateContext(context, client, jwt, realm));
ClientRegistrationPolicyManager.triggerBeforeUpdate(context, regAuth, client);
} catch (ClientRegistrationPolicyException crpe) {
} catch (ClientRegistrationPolicyException | ClientPolicyException crpe) {
throw forbidden(crpe.getMessage());
}

View file

@ -46,7 +46,6 @@ public class ClientRegistrationPolicyManager {
});
}
public static void triggerAfterRegister(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) {
try {
triggerPolicies(context.getSession(), context.getProvider(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {

View file

@ -51,6 +51,10 @@ import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.clientpolicy.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.AdminClientUpdateContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
import org.keycloak.services.managers.ClientManager;
@ -144,6 +148,12 @@ public class ClientResource {
);
}
try {
session.clientPolicy().triggerOnEvent(new AdminClientUpdateContext(rep, auth.adminAuth(), client));
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
try {
updateClientFromRep(rep, client, session);

View file

@ -34,6 +34,9 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentati
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.clientpolicy.AdminClientRegisterContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.DefaultClientPolicyManager;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
@ -186,6 +189,12 @@ public class ClientsResource {
);
}
try {
session.clientPolicy().triggerOnEvent(new AdminClientRegisterContext(rep, auth.adminAuth()));
} catch (ClientPolicyException cpe) {
throw new ErrorResponseException(cpe.getError(), cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
}
try {
ClientModel clientModel = ClientManager.createClient(session, realm, rep, true);

View file

@ -0,0 +1 @@
org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory

View file

@ -0,0 +1,113 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.ClientUpdateContext;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils;
import org.keycloak.util.TokenUtil;
public class TestAuthnMethodsCondition implements ClientPolicyConditionProvider {
private static final Logger logger = Logger.getLogger(TestAuthnMethodsCondition.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
public TestAuthnMethodsCondition(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case REGISTER:
case UPDATE:
if (isAuthMethodMatched((ClientUpdateContext)context)) return ClientPolicyVote.YES;
return ClientPolicyVote.NO;
default:
return ClientPolicyVote.ABSTAIN;
}
}
private boolean isAuthMethodMatched(String authMethod) {
if (authMethod == null) return false;
ClientPolicyLogger.log(logger, "auth method = " + authMethod);
componentModel.getConfig().get(TestAuthnMethodsConditionFactory.AUTH_METHOD).stream().forEach(i -> ClientPolicyLogger.log(logger, "auth method expected = " + i));
boolean isMatched = componentModel.getConfig().get(TestAuthnMethodsConditionFactory.AUTH_METHOD).stream().anyMatch(i -> i.equals(authMethod));
if (isMatched) {
ClientPolicyLogger.log(logger, "auth method matched.");
} else {
ClientPolicyLogger.log(logger, "auth method unmatched.");
}
return isMatched;
}
private boolean isAuthMethodMatched(ClientUpdateContext context) {
String authMethod = null;
if (context.getToken() == null) {
authMethod = TestAuthnMethodsConditionFactory.BY_ANONYMOUS;
} else if (isInitialAccessToken(context.getToken())) {
authMethod = TestAuthnMethodsConditionFactory.BY_INITIAL_ACCESS_TOKEN;
} else if (isRegistrationAccessToken(context.getToken())) {
authMethod = TestAuthnMethodsConditionFactory.BY_REGISTRATION_ACCESS_TOKEN;
} else if (isBearerToken(context.getToken())) {
if (context.getAuthenticatedUser() != null || context.getAuthenticatedClient() != null) {
authMethod = TestAuthnMethodsConditionFactory.BY_AUTHENTICATED_USER;
} else {
authMethod = TestAuthnMethodsConditionFactory.BY_ANONYMOUS;
}
}
return isAuthMethodMatched(authMethod);
}
private boolean isInitialAccessToken(JsonWebToken jwt) {
return jwt != null && ClientRegistrationTokenUtils.TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType());
}
private boolean isRegistrationAccessToken(JsonWebToken jwt) {
return jwt != null && ClientRegistrationTokenUtils.TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType());
}
private boolean isBearerToken(JsonWebToken jwt) {
return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType());
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory;
public class TestAuthnMethodsConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "test-authnmethods-condition";
public static final String AUTH_METHOD = "auth-method";
public static final String BY_AUTHENTICATED_USER = "ByAuthenticatedUser";
public static final String BY_ANONYMOUS = "ByAnonymous";
public static final String BY_INITIAL_ACCESS_TOKEN = "ByInitialAccessToken";
public static final String BY_REGISTRATION_ACCESS_TOKEN = "ByRegistrationAccessToken";
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty(AUTH_METHOD, null, null, ProviderConfigProperty.MULTIVALUED_LIST_TYPE, BY_AUTHENTICATED_USER);
List<String> updateProfileValues = Arrays.asList(BY_AUTHENTICATED_USER, BY_ANONYMOUS, BY_INITIAL_ACCESS_TOKEN, BY_REGISTRATION_ACCESS_TOKEN);
property.setOptions(updateProfileValues);
configProperties.add(property);
}
@Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) {
return new TestAuthnMethodsCondition(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyLogger;
import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
public class TestClientRolesCondition implements ClientPolicyConditionProvider {
private static final Logger logger = Logger.getLogger(TestClientRolesCondition.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
public TestClientRolesCondition(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPolicyException {
switch (context.getEvent()) {
case AUTHORIZATION_REQUEST:
case TOKEN_REQUEST:
case TOKEN_REFRESH:
case TOKEN_REVOKE:
case TOKEN_INTROSPECT:
case USERINFO_REQUEST:
case LOGOUT_REQUEST:
if (isRolesMatched(session.getContext().getClient())) return ClientPolicyVote.YES;
return ClientPolicyVote.NO;
default:
return ClientPolicyVote.ABSTAIN;
}
}
private boolean isRolesMatched(ClientModel client) {
if (client == null) return false;
List<String> rolesForMatching = getRolesForMatching();
if (rolesForMatching == null) return false;
client.getRoles().stream().forEach(i -> ClientPolicyLogger.log(logger, "client role = " + i.getName()));
rolesForMatching.stream().forEach(i -> ClientPolicyLogger.log(logger, "roles expected = " + i));
boolean isMatched = rolesForMatching.stream().anyMatch(i->client.getRoles().stream().anyMatch(j->j.getName().equals(i)));
if (isMatched) {
ClientPolicyLogger.log(logger, "role matched.");
} else {
ClientPolicyLogger.log(logger, "role unmatched.");
}
return isMatched;
}
private List<String> getRolesForMatching() {
return componentModel.getConfig().get(TestClientRolesConditionFactory.ROLES);
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import java.util.ArrayList;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory;
public class TestClientRolesConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "test-clientroles-condition";
public static final String ROLES = "roles";
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty(ROLES, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, "view-profile");
configProperties.add(property);
}
@Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) {
return new TestClientRolesCondition(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyVote;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
public class TestRaiseExeptionCondition implements ClientPolicyConditionProvider {
private static final Logger logger = Logger.getLogger(TestRaiseExeptionCondition.class);
private final KeycloakSession session;
private final ComponentModel componentModel;
public TestRaiseExeptionCondition(KeycloakSession session, ComponentModel componentModel) {
this.session = session;
this.componentModel = componentModel;
}
@Override
public String getName() {
return componentModel.getName();
}
@Override
public String getProviderId() {
return componentModel.getProviderId();
}
@Override
public ClientPolicyVote applyPolicy(ClientPolicyContext context) throws ClientPolicyException {
throw new ClientPolicyException(OAuthErrorException.SERVER_ERROR, "intentional exception for test");
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.condition;
import java.util.Collections;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProviderFactory;
public class TestRaiseExeptionConditionFactory implements ClientPolicyConditionProviderFactory {
public static final String PROVIDER_ID = "test-raise-exception-condition";
@Override
public ClientPolicyConditionProvider create(KeycloakSession session, ComponentModel model) {
return new TestRaiseExeptionCondition(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return Collections.emptyList();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.executor;
import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.OAuthErrorException;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutor;
public class TestClientAuthenticationExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor {
private static final Logger logger = Logger.getLogger(TestClientAuthenticationExecutor.class);
public TestClientAuthenticationExecutor(KeycloakSession session, ComponentModel componentModel) {
super(session, componentModel);
}
protected void augment(ClientRepresentation rep) {
if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT)))
rep.setClientAuthenticatorType(enforcedClientAuthenticatorType());
}
protected void validate(ClientRepresentation rep) throws ClientPolicyException {
verifyClientAuthenticationMethod(rep.getClientAuthenticatorType());
}
private String enforcedClientAuthenticatorType() {
return componentModel.getConfig().getFirst(TestClientAuthenticationExecutorFactory.CLIENT_AUTHNS_AUGMENT);
}
private void verifyClientAuthenticationMethod(String clientAuthenticatorType) throws ClientPolicyException {
List<String> acceptableClientAuthn = componentModel.getConfig().getList(TestClientAuthenticationExecutorFactory.CLIENT_AUTHNS);
if (acceptableClientAuthn != null && acceptableClientAuthn.stream().anyMatch(i->i.equals(clientAuthenticatorType))) return;
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: token_endpoint_auth_method");
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.executor;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutorFactory;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class TestClientAuthenticationExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory {
public static final String PROVIDER_ID = "test-client-authn-executor";
public static final String CLIENT_AUTHNS = "client-authns";
public static final String CLIENT_AUTHNS_AUGMENT = "client-authns-augment";
private static final ProviderConfigProperty CLIENTAUTHNS_PROPERTY = new ProviderConfigProperty(
CLIENT_AUTHNS, null, null, ProviderConfigProperty.MULTIVALUED_STRING_TYPE, null);
private static final ProviderConfigProperty CLIENTAUTHNS_AUGMENT = new ProviderConfigProperty(
CLIENT_AUTHNS_AUGMENT, null, null, ProviderConfigProperty.STRING_TYPE, JWTClientAuthenticator.PROVIDER_ID);
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) {
return new TestClientAuthenticationExecutor(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
List<ProviderConfigProperty> l = super.getConfigProperties();
l.add(CLIENTAUTHNS_PROPERTY);
l.add(CLIENTAUTHNS_AUGMENT);
return l;
}
}

View file

@ -0,0 +1,191 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.executor;
import java.security.MessageDigest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.core.MultivaluedMap;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.Base64Url;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.Errors;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.protocol.oidc.utils.OAuth2Code;
import org.keycloak.protocol.oidc.utils.OAuth2CodeParser;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientpolicy.AuthorizationRequestContext;
import org.keycloak.services.clientpolicy.ClientPolicyContext;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.TokenRequestContext;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutor;
public class TestPKCEEnforceExecutor extends AbstractAugumentingClientRegistrationPolicyExecutor {
private static final Logger logger = Logger.getLogger(TestPKCEEnforceExecutor.class);
private static final Pattern VALID_CODE_CHALLENGE_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$");
private static final Pattern VALID_CODE_VERIFIER_PATTERN = Pattern.compile("^[0-9a-zA-Z\\-\\.~_]+$");
public TestPKCEEnforceExecutor(KeycloakSession session, ComponentModel componentModel) {
super(session, componentModel);
}
protected void augment(ClientRepresentation rep) {
if (Boolean.valueOf(componentModel.getConfig().getFirst(AbstractAugumentingClientRegistrationPolicyExecutor.IS_AUGMENT)))
OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).setPkceCodeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256);
}
protected void validate(ClientRepresentation rep) throws ClientPolicyException {
String pkceMethod = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep).getPkceCodeChallengeMethod();
if (pkceMethod != null && pkceMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) return;
throw new ClientPolicyException(OAuthErrorException.INVALID_CLIENT_METADATA, "Invalid client metadata: code_challenge_method");
}
@Override
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
super.executeOnEvent(context);
switch (context.getEvent()) {
case AUTHORIZATION_REQUEST:
AuthorizationRequestContext authorizationRequestContext = (AuthorizationRequestContext)context;
executeOnAuthorizationRequest(authorizationRequestContext.getparsedResponseType(),
authorizationRequestContext.getAuthorizationEndpointRequest(),
authorizationRequestContext.getRedirectUri());
return;
case TOKEN_REQUEST:
TokenRequestContext tokenRequestContext = (TokenRequestContext)context;
executeOnTokenRequest(tokenRequestContext.getParams(), tokenRequestContext.getParseResult());
return;
default:
return;
}
}
private void executeOnAuthorizationRequest(
OIDCResponseType parsedResponseType,
AuthorizationEndpointRequest request,
String redirectUri) throws ClientPolicyException {
ClientModel client = session.getContext().getClient();
String codeChallenge = request.getCodeChallenge();
String codeChallengeMethod = request.getCodeChallengeMethod();
String pkceCodeChallengeMethod = OIDCAdvancedConfigWrapper.fromClientModel(client).getPkceCodeChallengeMethod();
// check whether code challenge method is specified
if (codeChallengeMethod == null) {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: code_challenge_method");
}
// check whether specified code challenge method is configured one in advance
if (!codeChallengeMethod.equals(pkceCodeChallengeMethod)) {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code challenge method is not configured one");
}
// check whether code challenge is specified
if (codeChallenge == null) {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: code_challenge");
}
// check whether code challenge is formatted along with the PKCE specification
if (!isValidPkceCodeChallenge(codeChallenge)) {
throw new ClientPolicyException(OAuthErrorException.INVALID_REQUEST, "Invalid parameter: code_challenge");
}
}
private void executeOnTokenRequest(
MultivaluedMap<String, String> params,
OAuth2CodeParser.ParseResult parseResult) throws ClientPolicyException {
String codeVerifier = params.getFirst(OAuth2Constants.CODE_VERIFIER);
OAuth2Code codeData = parseResult.getCodeData();
String codeChallenge = codeData.getCodeChallenge();
String codeChallengeMethod = codeData.getCodeChallengeMethod();
checkParamsForPkceEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod);
};
private boolean isValidPkceCodeChallenge(String codeChallenge) {
if (codeChallenge.length() < OIDCLoginProtocol.PKCE_CODE_CHALLENGE_MIN_LENGTH) {
return false;
}
if (codeChallenge.length() > OIDCLoginProtocol.PKCE_CODE_CHALLENGE_MAX_LENGTH) {
return false;
}
Matcher m = VALID_CODE_CHALLENGE_PATTERN.matcher(codeChallenge);
return m.matches();
}
private void checkParamsForPkceEnforcedClient(String codeVerifier, String codeChallenge, String codeChallengeMethod) throws ClientPolicyException {
// check whether code verifier is specified
if (codeVerifier == null) {
throw new ClientPolicyException(Errors.CODE_VERIFIER_MISSING, "PKCE code verifier not specified");
}
verifyCodeVerifier(codeVerifier, codeChallenge, codeChallengeMethod);
}
private void verifyCodeVerifier(String codeVerifier, String codeChallenge, String codeChallengeMethod) throws ClientPolicyException {
// check whether code verifier is formatted along with the PKCE specification
if (!isValidFormattedCodeVerifier(codeVerifier)) {
throw new ClientPolicyException(Errors.INVALID_CODE_VERIFIER, "PKCE invalid code verifier");
}
String codeVerifierEncoded = codeVerifier;
try {
if (codeChallengeMethod != null && codeChallengeMethod.equals(OAuth2Constants.PKCE_METHOD_S256)) {
codeVerifierEncoded = generateS256CodeChallenge(codeVerifier);
} else {
codeVerifierEncoded = codeVerifier;
}
} catch (Exception nae) {
throw new ClientPolicyException(Errors.PKCE_VERIFICATION_FAILED, "PKCE code verification failed, not supported algorithm specified");
}
if (!codeChallenge.equals(codeVerifierEncoded)) {
throw new ClientPolicyException(Errors.PKCE_VERIFICATION_FAILED, "PKCE verification failed");
}
}
private boolean isValidFormattedCodeVerifier(String codeVerifier) {
if (codeVerifier.length() < OIDCLoginProtocol.PKCE_CODE_VERIFIER_MIN_LENGTH) {
return false;
}
if (codeVerifier.length() > OIDCLoginProtocol.PKCE_CODE_VERIFIER_MAX_LENGTH) {
return false;
}
Matcher m = VALID_CODE_VERIFIER_PATTERN.matcher(codeVerifier);
return m.matches();
}
private String generateS256CodeChallenge(String codeVerifier) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(codeVerifier.getBytes("ISO_8859_1"));
byte[] digestBytes = md.digest();
String codeVerifierEncoded = Base64Url.encode(digestBytes);
return codeVerifierEncoded;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright 2020 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.testsuite.services.clientpolicy.executor;
import java.util.List;
import org.keycloak.Config.Scope;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.clientpolicy.executor.AbstractAugumentingClientRegistrationPolicyExecutorFactory;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
public class TestPKCEEnforceExecutorFactory extends AbstractAugumentingClientRegistrationPolicyExecutorFactory {
public static final String PROVIDER_ID = "test-pkce-enforce-executor";
@Override
public ClientPolicyExecutorProvider create(KeycloakSession session, ComponentModel model) {
return new TestPKCEEnforceExecutor(session, model);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return super.getConfigProperties();
}
}

View file

@ -0,0 +1,3 @@
org.keycloak.testsuite.services.clientpolicy.condition.TestAuthnMethodsConditionFactory
org.keycloak.testsuite.services.clientpolicy.condition.TestClientRolesConditionFactory
org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory

View file

@ -0,0 +1,2 @@
org.keycloak.testsuite.services.clientpolicy.executor.TestPKCEEnforceExecutorFactory
org.keycloak.testsuite.services.clientpolicy.executor.TestClientAuthenticationExecutorFactory

View file

@ -0,0 +1,908 @@
/*
* Copyright 2020 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.testsuite.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
import org.hamcrest.Matchers;
import org.jboss.logging.Logger;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator;
import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.common.Profile;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
import org.keycloak.services.clientpolicy.ClientPolicyException;
import org.keycloak.services.clientpolicy.ClientPolicyProvider;
import org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory;
import org.keycloak.services.clientpolicy.condition.ClientPolicyConditionProvider;
import org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
import org.keycloak.testsuite.services.clientpolicy.condition.TestAuthnMethodsConditionFactory;
import org.keycloak.testsuite.services.clientpolicy.condition.TestClientRolesConditionFactory;
import org.keycloak.testsuite.services.clientpolicy.condition.TestRaiseExeptionConditionFactory;
import org.keycloak.testsuite.services.clientpolicy.executor.TestClientAuthenticationExecutorFactory;
import org.keycloak.testsuite.services.clientpolicy.executor.TestPKCEEnforceExecutorFactory;
import org.keycloak.testsuite.util.OAuthClient;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@EnableFeature(value = Profile.Feature.CLIENT_POLICIES, skipRestart = true)
public class ClientPolicyBasicsTest extends AbstractKeycloakTest {
private static final Logger logger = Logger.getLogger(ClientPolicyBasicsTest.class);
static final String REALM_NAME = "test";
static final String TEST_CLIENT = "test-app";
ClientRegistration reg;
@Rule
public AssertEvents events = new AssertEvents(this);
@Before
public void before() throws Exception {
// get initial access token for Dynamic Client Registration with authentication
reg = ClientRegistration.create().url(suiteContext.getAuthServerInfo().getContextRoot() + "/auth", REALM_NAME).build();
ClientInitialAccessPresentation token = adminClient.realm(REALM_NAME).clientInitialAccess().create(new ClientInitialAccessCreatePresentation(0, 10));
reg.auth(Auth.token(token));
}
@After
public void after() throws Exception {
reg.close();
}
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
testRealms.add(realm);
}
@Test
public void testAdminClientRegisterUnacceptableAuthType() {
setupPolicyAcceptableAuthType("MyPolicy");
try {
createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
});
fail();
} catch (ClientPolicyException e) {
assertEquals(Errors.INVALID_REGISTRATION, e.getMessage());
}
}
@Test
public void testAdminClientRegisterAcceptableAuthType() throws ClientPolicyException {
setupPolicyAcceptableAuthType("MyPolicy");
String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
try {
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
} finally {
deleteClientByAdmin(clientId);
}
}
@Test
public void testAdminClientUpdateUnacceptableAuthType() throws ClientPolicyException {
setupPolicyAcceptableAuthType("MyPolicy");
String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
try {
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
try {
updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
});
fail();
} catch (BadRequestException bre) {}
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
} finally {
deleteClientByAdmin(clientId);
}
}
@Test
public void testAdminClientUpdateAcceptableAuthType() throws ClientPolicyException {
setupPolicyAcceptableAuthType("MyPolicy");
String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
try {
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
});
assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
} finally {
deleteClientByAdmin(clientId);
}
}
@Test
public void testAdminClientRegisterDefaultAuthType() {
setupPolicyAcceptableAuthType("MyPolicy");
try {
createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {});
fail();
} catch (ClientPolicyException e) {
assertEquals(Errors.INVALID_REGISTRATION, e.getMessage());
}
}
@Test
public void testAdminClientUpdateDefaultAuthType() throws ClientPolicyException {
setupPolicyAcceptableAuthType("MyPolicy");
String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
try {
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setServiceAccountsEnabled(Boolean.FALSE);
});
assertEquals(JWTClientSecretAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
assertEquals(Boolean.FALSE, getClientByAdmin(clientId).isServiceAccountsEnabled());
} finally {
deleteClientByAdmin(clientId);
}
}
@Test
public void testAdminClientAugmentedAuthType() throws ClientPolicyException {
setupPolicyAcceptableAuthType("MyPolicy");
updateExecutor("TestClientAuthenticationExecutor", (ComponentRepresentation provider) -> {
setExecutorAugmentActivate(provider);
setExecutorAugmentedClientAuthMethod(provider, X509ClientAuthenticator.PROVIDER_ID);
});
String clientId = createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID);
});
try {
assertEquals(X509ClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
updateExecutor("TestClientAuthenticationExecutor", (ComponentRepresentation provider) -> {
setExecutorAugmentedClientAuthMethod(provider, JWTClientAuthenticator.PROVIDER_ID);
});
updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
assertEquals(JWTClientAuthenticator.PROVIDER_ID, getClientByAdmin(clientId).getClientAuthenticatorType());
} finally {
deleteClientByAdmin(clientId);
}
}
@Test
public void testDynamicClientRegisterAndUpdate() throws ClientRegistrationException {
setupPolicyAcceptableAuthType("MyPolicy");
String clientId = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {});
try {
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod());
assertEquals(Boolean.FALSE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens());
updateClientDynamically(clientId, (OIDCClientRepresentation clientRep) -> {
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC);
clientRep.setTlsClientCertificateBoundAccessTokens(Boolean.TRUE);
});
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, getClientDynamically(clientId).getTokenEndpointAuthMethod());
assertEquals(Boolean.TRUE, getClientDynamically(clientId).getTlsClientCertificateBoundAccessTokens());
} finally {
deleteClientDynamically(clientId);
}
}
@Test
public void testAuthzCodeFlowUnderMultiPhasePolicy() throws Exception {
setupPolicyAuthzCodeFlowUnderMultiPhasePolicy("MultiPhasePolicy");
String userName = "test-user@localhost";
String userPassword = "password";
String clientName = "Flughafen-App";
String clientId = createClientDynamically(clientName, (OIDCClientRepresentation clientRep) -> {});
events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent();
OIDCClientRepresentation response = getClientDynamically(clientId);
String clientSecret = response.getClientSecret();
assertEquals(clientName, response.getClientName());
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod());
events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent();
updateClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setDefaultRoles(Arrays.asList("sample-client-role").toArray(new String[1]));
});
oauth.clientId(response.getClientId());
String codeVerifier = "1a345A7890123456r8901c3456789012b45K7890l23"; // 43
String codeChallenge = generateS256CodeChallenge(codeVerifier);
oauth.codeChallenge(codeChallenge);
oauth.codeChallengeMethod(OAuth2Constants.PKCE_METHOD_S256);
oauth.nonce("bjapewiziIE083d");
oauth.doLogin(userName, userPassword);
EventRepresentation loginEvent = events.expectLogin().client(response.getClientId()).assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
oauth.codeVerifier(codeVerifier);
OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret);
assertEquals(200, res.getStatusCode());
events.expectCodeToToken(codeId, sessionId).client(response.getClientId()).assertEvent();
AccessToken token = oauth.verifyToken(res.getAccessToken());
String userId = findUserByUsername(adminClient.realm(REALM_NAME), userName).getId();
assertEquals(userId, token.getSubject());
Assert.assertNotEquals(userName, token.getSubject());
assertEquals(sessionId, token.getSessionState());
assertEquals(response.getClientId(), token.getIssuedFor());
String refreshTokenString = res.getRefreshToken();
RefreshToken refreshToken = oauth.parseRefreshToken(refreshTokenString);
assertEquals(sessionId, refreshToken.getSessionState());
assertEquals(response.getClientId(), refreshToken.getIssuedFor());
OAuthClient.AccessTokenResponse refreshResponse = oauth.doRefreshTokenRequest(refreshTokenString, clientSecret);
assertEquals(200, refreshResponse.getStatusCode());
AccessToken refreshedToken = oauth.verifyToken(refreshResponse.getAccessToken());
RefreshToken refreshedRefreshToken = oauth.parseRefreshToken(refreshResponse.getRefreshToken());
assertEquals(sessionId, refreshedToken.getSessionState());
assertEquals(sessionId, refreshedRefreshToken.getSessionState());
assertEquals(findUserByUsername(adminClient.realm(REALM_NAME), userName).getId(), refreshedToken.getSubject());
events.expectRefresh(refreshToken.getId(), sessionId).client(response.getClientId()).assertEvent();
doIntrospectAccessToken(refreshResponse, userName, clientId, clientSecret);
doTokenRevoke(refreshResponse.getRefreshToken(), clientId, clientSecret, userId, false);
}
@Test
public void testCreateDeletePolicyRuntime() throws ClientRegistrationException {
String clientId = createClientDynamically("Gourmet-App", (OIDCClientRepresentation clientRep) -> {});
try {
OIDCClientRepresentation clientRep = getClientDynamically(clientId);
assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, clientRep.getTokenEndpointAuthMethod());
events.expect(EventType.CLIENT_REGISTER).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent();
events.expect(EventType.CLIENT_INFO).client(clientId).user(Matchers.isEmptyOrNullString()).assertEvent();
updateClientByAdmin(clientId, (ClientRepresentation cr) -> {
cr.setDefaultRoles((String[]) Arrays.asList("sample-client-role").toArray(new String[1]));
});
successfulLoginAndLogout(clientId, clientRep.getClientSecret());
setupPolicyAuthzCodeFlowUnderMultiPhasePolicy("MyPolicy");
failLoginByNotFollowingPKCE(clientId);
deletePolicy("MyPolicy");
logger.info("... Deleted Policy : MyPolicy");
successfulLoginAndLogout(clientId, clientRep.getClientSecret());
} finally {
deleteClientDynamically(clientId);
}
}
@Test
public void testCreateUpdateDeleteConditionRuntime() throws ClientRegistrationException, ClientPolicyException {
String policyName = "MyPolicy";
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyName);
createExecutor("TestPKCEEnforceExecutor", TestPKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAugmentActivate(provider);
});
registerExecutor("TestPKCEEnforceExecutor", policyName);
logger.info("... Registered Executor : TestPKCEEnforceExecutor");
String clientId = "Zahlungs-App";
String clientSecret = "secret";
String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
clientRep.setDefaultRoles((String[]) Arrays.asList("sample-client-role").toArray(new String[1]));
clientRep.setSecret(clientSecret);
});
try {
successfulLoginAndLogout(clientId, clientSecret);
createCondition("TestClientRolesCondition", TestClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role")));
});
registerCondition("TestClientRolesCondition", policyName);
logger.info("... Registered Condition : TestClientRolesCondition");
failLoginByNotFollowingPKCE(clientId);
updateCondition("TestClientRolesCondition", (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("anothor-client-role")));
});
successfulLoginAndLogout(clientId, clientSecret);
deleteCondition("TestClientRolesCondition", policyName);
successfulLoginAndLogout(clientId, clientSecret);
} finally {
deleteClientByAdmin(cid);
}
}
@Test
public void testCreateUpdateDeleteExecutorRuntime() throws ClientRegistrationException, ClientPolicyException {
String policyName = "MyPolicy";
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyName);
createCondition("TestClientRolesCondition", TestClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role")));
});
registerCondition("TestClientRolesCondition", policyName);
logger.info("... Registered Condition : TestClientRolesCondition");
createCondition("TestAuthnMethodsCondition", TestAuthnMethodsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(TestAuthnMethodsConditionFactory.BY_AUTHENTICATED_USER)));
});
registerCondition("TestAuthnMethodsCondition", policyName);
logger.info("... Registered Condition : TestAuthnMethodsCondition");
String clientId = "Zahlungs-App";
String clientSecret = "secret";
String cid = createClientByAdmin(clientId, (ClientRepresentation clientRep) -> {
String[] defaultRoles = {"sample-client-role"};
clientRep.setDefaultRoles(defaultRoles);
clientRep.setSecret(clientSecret);
});
try {
successfulLoginAndLogout(clientId, clientSecret);
createExecutor("TestPKCEEnforceExecutor", TestPKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAugmentDeactivate(provider);
});
registerExecutor("TestPKCEEnforceExecutor", policyName);
logger.info("... Registered Executor : TestPKCEEnforceExecutor");
failLoginByNotFollowingPKCE(clientId);
updateExecutor("TestPKCEEnforceExecutor", (ComponentRepresentation provider) -> {
setExecutorAugmentActivate(provider);
});
updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
clientRep.setServiceAccountsEnabled(Boolean.FALSE);
});
assertEquals(false, getClientByAdmin(cid).isServiceAccountsEnabled());
assertEquals(OAuth2Constants.PKCE_METHOD_S256, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod());
deleteExecutor("TestPKCEEnforceExecutor", policyName);
logger.info("... Deleted Executor : TestPKCEEnforceExecutor");
updateClientByAdmin(cid, (ClientRepresentation clientRep) -> {
OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setPkceCodeChallengeMethod(null);
});
assertEquals(null, OIDCAdvancedConfigWrapper.fromClientRepresentation(getClientByAdmin(cid)).getPkceCodeChallengeMethod());
successfulLoginAndLogout(clientId, clientSecret);
} finally {
deleteClientByAdmin(cid);
}
}
@Test
public void testMultiplePolicies() throws ClientRegistrationException, ClientPolicyException {
String policyAlphaName = "MyPolicy-alpha";
createPolicy(policyAlphaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyAlphaName);
createCondition("TestClientRolesCondition-alpha", TestClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-alpha")));
});
registerCondition("TestClientRolesCondition-alpha", policyAlphaName);
logger.info("... Registered Condition : TestClientRolesCondition-alpha");
createCondition("TestAuthnMethodsCondition-alpha", TestAuthnMethodsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(TestAuthnMethodsConditionFactory.BY_AUTHENTICATED_USER)));
});
registerCondition("TestAuthnMethodsCondition-alpha", policyAlphaName);
logger.info("... Registered Condition : TestAuthnMethodsCondition-alpha");
createExecutor("TestClientAuthenticationExecutor-alpha", TestClientAuthenticationExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID)));
setExecutorAugmentActivate(provider);
setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID);
});
registerExecutor("TestClientAuthenticationExecutor-alpha", policyAlphaName);
logger.info("... Registered Executor : TestClientAuthenticationExecutor-alpha");
String policyBetaName = "MyPolicy-beta";
createPolicy(policyBetaName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyBetaName);
createCondition("TestClientRolesCondition-beta", TestClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role-beta")));
});
registerCondition("TestClientRolesCondition-beta", policyBetaName);
logger.info("... Registered Condition : TestClientRolesCondition-beta");
createExecutor("TestPKCEEnforceExecutor-beta", TestPKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAugmentActivate(provider);
});
registerExecutor("TestPKCEEnforceExecutor-beta", policyBetaName);
logger.info("... Registered Executor : TestPKCEEnforceExecutor-beta");
String clientAlphaId = "Alpha-App";
String clientAlphaSecret = "secretAlpha";
String cAlphaId = createClientByAdmin(clientAlphaId, (ClientRepresentation clientRep) -> {
clientRep.setDefaultRoles((String[]) Arrays.asList("sample-client-role-alpha").toArray(new String[1]));
clientRep.setSecret(clientAlphaSecret);
clientRep.setClientAuthenticatorType(JWTClientSecretAuthenticator.PROVIDER_ID);
});
String clientBetaId = "Beta-App";
String clientBetaSecret = "secretBeta";
String cBetaId = createClientByAdmin(clientBetaId, (ClientRepresentation clientRep) -> {
clientRep.setDefaultRoles((String[]) Arrays.asList("sample-client-role-beta").toArray(new String[1]));
clientRep.setSecret(clientBetaSecret);
});
try {
assertEquals(ClientIdAndSecretAuthenticator.PROVIDER_ID, getClientByAdmin(cAlphaId).getClientAuthenticatorType());
successfulLoginAndLogout(clientAlphaId, clientAlphaSecret);
failLoginByNotFollowingPKCE(clientBetaId);
} finally {
deleteClientByAdmin(cAlphaId);
deleteClientByAdmin(cBetaId);
}
}
@Test
public void testIntentionalExceptionOnCondition() throws ClientRegistrationException, ClientPolicyException {
String policyName = "MyPolicy";
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyName);
createCondition("TestRaiseExeptionCondition", TestRaiseExeptionConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
});
registerCondition("TestRaiseExeptionCondition", policyName);
logger.info("... Registered Condition : TestRaiseExeptionCondition-beta");
try {
createClientByAdmin("Zahlungs-App", (ClientRepresentation clientRep) -> {
});
fail();
} catch (ClientPolicyException e) {
assertEquals(Errors.INVALID_REGISTRATION, e.getMessage());
}
}
private void setupPolicyAcceptableAuthType(String policyName) {
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyName);
createCondition("TestAuthnMethodsCondition", TestAuthnMethodsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(TestAuthnMethodsConditionFactory.BY_AUTHENTICATED_USER)));
});
registerCondition("TestAuthnMethodsCondition", policyName);
logger.info("... Registered Condition : TestAuthnMethodsCondition");
createExecutor("TestClientAuthenticationExecutor", TestClientAuthenticationExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(
JWTClientAuthenticator.PROVIDER_ID, JWTClientSecretAuthenticator.PROVIDER_ID, X509ClientAuthenticator.PROVIDER_ID)));
});
registerExecutor("TestClientAuthenticationExecutor", policyName);
logger.info("... Registered Executor : TestClientAuthenticationExecutor");
}
private void setupPolicyAuthzCodeFlowUnderMultiPhasePolicy(String policyName) {
logger.info("Setup Policy");
createPolicy(policyName, DefaultClientPolicyProviderFactory.PROVIDER_ID, null, null, null);
logger.info("... Created Policy : " + policyName);
createCondition("TestAuthnMethodsCondition", TestAuthnMethodsConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionRegistrationMethods(provider, new ArrayList<>(Arrays.asList(TestAuthnMethodsConditionFactory.BY_INITIAL_ACCESS_TOKEN)));
});
registerCondition("TestAuthnMethodsCondition", policyName);
logger.info("... Registered Condition : TestAuthnMethodsCondition");
createCondition("TestClientRolesCondition", TestClientRolesConditionFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setConditionClientRoles(provider, new ArrayList<>(Arrays.asList("sample-client-role")));
});
registerCondition("TestClientRolesCondition", policyName);
logger.info("... Registered Condition : TestClientRolesCondition");
createExecutor("TestClientAuthenticationExecutor", TestClientAuthenticationExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAcceptedClientAuthMethods(provider, new ArrayList<>(Arrays.asList(ClientIdAndSecretAuthenticator.PROVIDER_ID, JWTClientAuthenticator.PROVIDER_ID)));
setExecutorAugmentedClientAuthMethod(provider, ClientIdAndSecretAuthenticator.PROVIDER_ID);
setExecutorAugmentActivate(provider);
});
registerExecutor("TestClientAuthenticationExecutor", policyName);
logger.info("... Registered Executor : TestClientAuthenticationExecutor");
createExecutor("TestPKCEEnforceExecutor", TestPKCEEnforceExecutorFactory.PROVIDER_ID, null, (ComponentRepresentation provider) -> {
setExecutorAugmentActivate(provider);
});
registerExecutor("TestPKCEEnforceExecutor", policyName);
logger.info("... Registered Executor : TestPKCEEnforceExecutor");
}
private void successfulLoginAndLogout(String clientId, String clientSecret) {
oauth.clientId(clientId);
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().client(clientId).assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse res = oauth.doAccessTokenRequest(code, clientSecret);
assertEquals(200, res.getStatusCode());
events.expectCodeToToken(codeId, sessionId).client(clientId).assertEvent();
oauth.doLogout(res.getRefreshToken(), clientSecret);
events.expectLogout(sessionId).client(clientId).clearDetails().assertEvent();
}
private void failLoginByNotFollowingPKCE(String clientId) {
oauth.clientId(clientId);
oauth.openLoginForm();
assertEquals("invalid_request", oauth.getCurrentQuery().get("error"));
assertEquals("Missing parameter: code_challenge_method", oauth.getCurrentQuery().get("error_description"));
}
private String generateS256CodeChallenge(String codeVerifier) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(codeVerifier.getBytes("ISO_8859_1"));
byte[] digestBytes = md.digest();
String codeChallenge = Base64Url.encode(digestBytes);
return codeChallenge;
}
private void doIntrospectAccessToken(OAuthClient.AccessTokenResponse tokenRes, String username, String clientId, String clientSecret) throws IOException {
String tokenResponse = oauth.introspectAccessTokenWithClientCredential(clientId, clientSecret, tokenRes.getAccessToken());
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(tokenResponse);
assertEquals(true, jsonNode.get("active").asBoolean());
assertEquals(username, jsonNode.get("username").asText());
assertEquals(clientId, jsonNode.get("client_id").asText());
TokenMetadataRepresentation rep = objectMapper.readValue(tokenResponse, TokenMetadataRepresentation.class);
assertEquals(true, rep.isActive());
assertEquals(clientId, rep.getClientId());
assertEquals(clientId, rep.getIssuedFor());
events.expect(EventType.INTROSPECT_TOKEN).client(clientId).user((String)null).clearDetails().assertEvent();
}
private void doTokenRevoke(String refreshToken, String clientId, String clientSecret, String userId, boolean isOfflineAccess) throws IOException {
oauth.clientId(clientId);
oauth.doTokenRevoke(refreshToken, "refresh_token", clientSecret);
// confirm revocation
OAuthClient.AccessTokenResponse tokenRes = oauth.doRefreshTokenRequest(refreshToken, clientSecret);
assertEquals(400, tokenRes.getStatusCode());
assertEquals(OAuthErrorException.INVALID_GRANT, tokenRes.getError());
if (isOfflineAccess) assertEquals("Offline user session not found", tokenRes.getErrorDescription());
else assertEquals("Session not active", tokenRes.getErrorDescription());
events.expect(EventType.REVOKE_GRANT).clearDetails().client(clientId).user(userId).assertEvent();
}
private ComponentRepresentation createComponentInstance(String name, String providerId, String providerType, String subType) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setId(org.keycloak.models.utils.KeycloakModelUtils.generateId());
rep.setName(name);
rep.setParentId(REALM_NAME);
rep.setProviderId(providerId);
rep.setProviderType(providerType);
rep.setSubType(subType);
rep.setConfig(new MultivaluedHashMap<>());
return rep;
}
private String createComponent(ComponentRepresentation cr) {
Response resp = adminClient.realm(REALM_NAME).components().add(cr);
String id = ApiUtil.getCreatedId(resp);
resp.close();
// registered components will be removed automatically
testContext.getOrCreateCleanup(REALM_NAME).addComponentId(id);
return id;
}
private ComponentRepresentation getComponent(String name, String providerType) {
return adminClient.realm(REALM_NAME).components().query(null, providerType, name).get(0);
}
private void updateComponent(ComponentRepresentation cr) {
adminClient.realm(REALM_NAME).components().component(cr.getId()).update(cr);
}
private void deleteComponent(String id) {
adminClient.realm(REALM_NAME).components().component(id).remove();
}
private String createCondition(String name, String providerId, String subType, Consumer<ComponentRepresentation> op) {
ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyConditionProvider.class.getName(), subType);
op.accept(component);
return createComponent(component);
}
private void registerCondition(String conditionName, String policyName) {
ComponentRepresentation policy = getPolicy(policyName);
List<String> conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS);
if (conditionIds == null) conditionIds = new ArrayList<String>();
ComponentRepresentation condition = getCondition(conditionName);
conditionIds.add(condition.getId());
policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds);
updatePolicy(policy);
}
private ComponentRepresentation getCondition(String name) {
return getComponent(name, ClientPolicyConditionProvider.class.getName());
}
private void updateCondition(String name, Consumer<ComponentRepresentation> op) {
ComponentRepresentation condition = getCondition(name);
op.accept(condition);
updateComponent(condition);
}
private void deleteCondition(String conditionName, String policyName) {
ComponentRepresentation policy = getPolicy(policyName);
List<String> conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS);
ComponentRepresentation condition = getCondition(conditionName);
String conditionId = condition.getId();
adminClient.realm(REALM_NAME).components().component(conditionId).remove();
conditionIds.remove(conditionId);
policy.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditionIds);
updatePolicy(policy);
}
private String createExecutor(String name, String providerId, String subType, Consumer<ComponentRepresentation> op) {
ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyExecutorProvider.class.getName(), subType);
op.accept(component);
return createComponent(component);
}
private void registerExecutor(String executorName, String policyName) {
ComponentRepresentation policy = getPolicy(policyName);
List<String> executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS);
if (executorIds == null) executorIds = new ArrayList<String>();
ComponentRepresentation executor = getExecutor(executorName);
executorIds.add(executor.getId());
policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds);
updatePolicy(policy);
}
private ComponentRepresentation getExecutor(String name) {
return getComponent(name, ClientPolicyExecutorProvider.class.getName());
}
private void updateExecutor(String name, Consumer<ComponentRepresentation> op) {
ComponentRepresentation executor = getExecutor(name);
op.accept(executor);
updateComponent(executor);
}
private void deleteExecutor(String executorName, String policyName) {
ComponentRepresentation policy = getPolicy(policyName);
List<String> executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS);
ComponentRepresentation executor = getExecutor(executorName);
String executorId = executor.getId();
adminClient.realm(REALM_NAME).components().component(executorId).remove();
executorIds.remove(executorId);
policy.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executorIds);
updatePolicy(policy);
}
private String createPolicy(String name, String providerId, String subType, List<String> conditions, List<String> executors) {
ComponentRepresentation component = createComponentInstance(name, providerId, ClientPolicyProvider.class.getName(), subType);
component.getConfig().put(DefaultClientPolicyProviderFactory.CONDITION_IDS, conditions);
component.getConfig().put(DefaultClientPolicyProviderFactory.EXECUTOR_IDS, executors);
return createComponent(component);
}
private ComponentRepresentation getPolicy(String name) {
return getComponent(name, ClientPolicyProvider.class.getName());
}
private void updatePolicy(ComponentRepresentation policy) {
updateComponent(policy);
}
private void deletePolicy(String policyName) {
ComponentRepresentation policy = getPolicy(policyName);
List<String> conditionIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.CONDITION_IDS);
List<String> executorIds = policy.getConfig().get(DefaultClientPolicyProviderFactory.EXECUTOR_IDS);
conditionIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove());
executorIds.stream().forEach(i->adminClient.realm(REALM_NAME).components().component(i).remove());
adminClient.realm(REALM_NAME).components().component(policy.getId()).remove();
}
private String createClientByAdmin(String clientName, Consumer<ClientRepresentation> op) throws ClientPolicyException {
ClientRepresentation clientRep = new ClientRepresentation();
clientRep.setClientId(clientName);
clientRep.setName(clientName);
clientRep.setProtocol("openid-connect");
clientRep.setBearerOnly(Boolean.FALSE);
clientRep.setPublicClient(Boolean.FALSE);
clientRep.setServiceAccountsEnabled(Boolean.TRUE);
clientRep.setRedirectUris(Collections.singletonList("https://localhost:8543/auth/realms/master/app/auth"));
op.accept(clientRep);
Response resp = adminClient.realm(REALM_NAME).clients().create(clientRep);
if (resp.getStatus() == Response.Status.BAD_REQUEST.getStatusCode()) {
throw new ClientPolicyException(Errors.INVALID_REGISTRATION, "registration error by admin");
}
resp.close();
assertEquals(Response.Status.CREATED.getStatusCode(), resp.getStatus());
return ApiUtil.getCreatedId(resp);
}
private ClientRepresentation getClientByAdmin(String clientId) {
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId);
return clientResource.toRepresentation();
}
private void updateClientByAdmin(String clientId, Consumer<ClientRepresentation> op) {
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId);
ClientRepresentation clientRep = clientResource.toRepresentation();
op.accept(clientRep);
clientResource.update(clientRep);
}
private void deleteClientByAdmin(String clientId) {
ClientResource clientResource = adminClient.realm(REALM_NAME).clients().get(clientId);
clientResource.remove();
}
private String createClientDynamically(String clientName, Consumer<OIDCClientRepresentation> op) throws ClientRegistrationException {
OIDCClientRepresentation clientRep = new OIDCClientRepresentation();
clientRep.setClientName(clientName);
clientRep.setClientUri("https://localhost:8543");
clientRep.setRedirectUris(Collections.singletonList("https://localhost:8543/auth/realms/master/app/auth"));
op.accept(clientRep);
OIDCClientRepresentation response = reg.oidc().create(clientRep);
reg.auth(Auth.token(response));
return response.getClientId();
}
private OIDCClientRepresentation getClientDynamically(String clientId) throws ClientRegistrationException {
return reg.oidc().get(clientId);
}
private void updateClientDynamically(String clientId, Consumer<OIDCClientRepresentation> op) throws ClientRegistrationException {
OIDCClientRepresentation clientRep = reg.oidc().get(clientId);
op.accept(clientRep);
OIDCClientRepresentation response = reg.oidc().update(clientRep);
reg.auth(Auth.token(response));
}
private void deleteClientDynamically(String clientId) throws ClientRegistrationException {
reg.oidc().delete(clientId);
}
private void setConditionRegistrationMethods(ComponentRepresentation provider, List<String> registrationMethods) {
provider.getConfig().put(TestAuthnMethodsConditionFactory.AUTH_METHOD, registrationMethods);
}
private void setConditionClientRoles(ComponentRepresentation provider, List<String> clientRoles) {
provider.getConfig().put(TestClientRolesConditionFactory.ROLES, clientRoles);
}
private void setExecutorAugmentActivate(ComponentRepresentation provider) {
provider.getConfig().putSingle("is-augment", Boolean.TRUE.toString());
}
private void setExecutorAugmentDeactivate(ComponentRepresentation provider) {
provider.getConfig().putSingle("is-augment", Boolean.FALSE.toString());
}
private void setExecutorAcceptedClientAuthMethods(ComponentRepresentation provider, List<String> acceptedClientAuthMethods) {
provider.getConfig().put(TestClientAuthenticationExecutorFactory.CLIENT_AUTHNS, acceptedClientAuthMethods);
}
private void setExecutorAugmentedClientAuthMethod(ComponentRepresentation provider, String augmentedClientAuthMethod) {
provider.getConfig().putSingle(TestClientAuthenticationExecutorFactory.CLIENT_AUTHNS_AUGMENT, augmentedClientAuthMethod);
}
}

View file

@ -76,6 +76,9 @@ log4j.logger.org.apache.directory.server.core=warn
# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
# log4j.logger.org.keycloak.keys.infinispan=trace
log4j.logger.org.keycloak.services.clientregistration.policy=debug
# log4j.logger.org.keycloak.services.clientpolicy=trace
# log4j.logger.org.keycloak.testsuite.clientpolicy=trace
#log4j.logger.org.keycloak.authentication=debug