KEYCLOAK-14189 Client Policy : Basics
This commit is contained in:
parent
6d5495141d
commit
e0fbfa722e
62 changed files with 3657 additions and 9 deletions
|
@ -53,7 +53,8 @@ public class Profile {
|
||||||
SCRIPTS(Type.PREVIEW),
|
SCRIPTS(Type.PREVIEW),
|
||||||
TOKEN_EXCHANGE(Type.PREVIEW),
|
TOKEN_EXCHANGE(Type.PREVIEW),
|
||||||
UPLOAD_SCRIPTS(DEPRECATED),
|
UPLOAD_SCRIPTS(DEPRECATED),
|
||||||
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW);
|
WEB_AUTHN(Type.DEFAULT, Type.PREVIEW),
|
||||||
|
CLIENT_POLICIES(Type.PREVIEW);
|
||||||
|
|
||||||
private Type typeProject;
|
private Type typeProject;
|
||||||
private Type typeProduct;
|
private Type typeProduct;
|
||||||
|
|
|
@ -21,8 +21,8 @@ public class ProfileTest {
|
||||||
@Test
|
@Test
|
||||||
public void checkDefaultsKeycloak() {
|
public void checkDefaultsKeycloak() {
|
||||||
Assert.assertEquals("community", Profile.getName());
|
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.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);
|
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);
|
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
|
||||||
|
|
||||||
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
||||||
|
@ -37,8 +37,8 @@ public class ProfileTest {
|
||||||
Profile.init();
|
Profile.init();
|
||||||
|
|
||||||
Assert.assertEquals("product", Profile.getName());
|
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.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);
|
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);
|
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
|
||||||
|
|
||||||
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
Assert.assertTrue(Profile.Feature.WEB_AUTHN.hasDifferentProductType());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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> {
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -80,3 +80,6 @@ org.keycloak.crypto.CekManagementSpi
|
||||||
org.keycloak.crypto.ContentEncryptionSpi
|
org.keycloak.crypto.ContentEncryptionSpi
|
||||||
org.keycloak.validation.ClientValidationSPI
|
org.keycloak.validation.ClientValidationSPI
|
||||||
org.keycloak.headers.SecurityHeadersSpi
|
org.keycloak.headers.SecurityHeadersSpi
|
||||||
|
org.keycloak.services.clientpolicy.ClientPolicySpi
|
||||||
|
org.keycloak.services.clientpolicy.condition.ClientPolicyConditionSpi
|
||||||
|
org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorSpi
|
|
@ -20,6 +20,7 @@ package org.keycloak.models;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.services.clientpolicy.ClientPolicyManager;
|
||||||
import org.keycloak.sessions.AuthenticationSessionProvider;
|
import org.keycloak.sessions.AuthenticationSessionProvider;
|
||||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||||
import org.keycloak.vault.VaultTranscriber;
|
import org.keycloak.vault.VaultTranscriber;
|
||||||
|
@ -201,4 +202,10 @@ public interface KeycloakSession {
|
||||||
* Vault transcriber
|
* Vault transcriber
|
||||||
*/
|
*/
|
||||||
VaultTranscriber vault();
|
VaultTranscriber vault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client Policy Manager
|
||||||
|
*/
|
||||||
|
ClientPolicyManager clientPolicy();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -44,6 +44,10 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.services.ErrorPageException;
|
import org.keycloak.services.ErrorPageException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.Urls;
|
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.messages.Messages;
|
||||||
import org.keycloak.services.resources.LoginActionsService;
|
import org.keycloak.services.resources.LoginActionsService;
|
||||||
import org.keycloak.services.util.CacheControlUtil;
|
import org.keycloak.services.util.CacheControlUtil;
|
||||||
|
@ -153,6 +157,12 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
||||||
return errorResponse;
|
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());
|
authenticationSession = createAuthenticationSession(client, request.getState());
|
||||||
updateAuthenticationSession();
|
updateAuthenticationSession();
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,9 @@ import org.keycloak.representations.IDToken;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
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.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
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);
|
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;
|
RefreshToken token = null;
|
||||||
try {
|
try {
|
||||||
// KEYCLOAK-6771 Certificate Bound Token
|
// KEYCLOAK-6771 Certificate Bound Token
|
||||||
|
|
|
@ -83,6 +83,10 @@ import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
import org.keycloak.services.CorsErrorResponseException;
|
import org.keycloak.services.CorsErrorResponseException;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.Urls;
|
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.AppAuthManager;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||||
|
@ -402,6 +406,13 @@ public class TokenEndpoint {
|
||||||
checkParamsForPkceNotEnforcedClient(codeVerifier, codeChallenge, codeChallengeMethod, authUserId, authUsername);
|
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);
|
updateClientSession(clientSession);
|
||||||
updateUserSessionFromClientAuth(userSession);
|
updateUserSessionFromClientAuth(userSession);
|
||||||
|
|
||||||
|
@ -521,6 +532,13 @@ public class TokenEndpoint {
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST);
|
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;
|
AccessTokenResponse res;
|
||||||
try {
|
try {
|
||||||
// KEYCLOAK-6771 Certificate Bound Token
|
// KEYCLOAK-6771 Certificate Bound Token
|
||||||
|
|
|
@ -18,6 +18,7 @@ package org.keycloak.protocol.oidc.endpoints;
|
||||||
|
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.common.ClientConnection;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
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.AccessTokenIntrospectionProviderFactory;
|
||||||
import org.keycloak.protocol.oidc.TokenIntrospectionProvider;
|
import org.keycloak.protocol.oidc.TokenIntrospectionProvider;
|
||||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
|
import org.keycloak.services.CorsErrorResponseException;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
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.POST;
|
||||||
import javax.ws.rs.core.Context;
|
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);
|
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 {
|
try {
|
||||||
|
|
||||||
Response response = provider.introspect(token);
|
Response response = provider.introspect(token);
|
||||||
|
|
|
@ -44,6 +44,9 @@ import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
import org.keycloak.representations.RefreshToken;
|
import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.services.CorsErrorResponseException;
|
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.UserSessionCrossDCManager;
|
||||||
import org.keycloak.services.managers.UserSessionManager;
|
import org.keycloak.services.managers.UserSessionManager;
|
||||||
import org.keycloak.services.resources.Cors;
|
import org.keycloak.services.resources.Cors;
|
||||||
|
@ -93,6 +96,13 @@ public class TokenRevocationEndpoint {
|
||||||
|
|
||||||
formParams = request.getDecodedFormParameters();
|
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();
|
checkToken();
|
||||||
checkIssuedFor();
|
checkIssuedFor();
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,8 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.Urls;
|
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.AppAuthManager;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
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.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -128,6 +131,13 @@ public class UserInfoEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response issueUserInfo(String tokenString) {
|
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)
|
EventBuilder event = new EventBuilder(realm, session, clientConnection)
|
||||||
.event(EventType.USER_INFO_REQUEST)
|
.event(EventType.USER_INFO_REQUEST)
|
||||||
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
|
.detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN);
|
||||||
|
|
|
@ -37,6 +37,8 @@ import org.keycloak.models.cache.CacheRealmProvider;
|
||||||
import org.keycloak.models.cache.UserCache;
|
import org.keycloak.models.cache.UserCache;
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
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.sessions.AuthenticationSessionProvider;
|
||||||
import org.keycloak.storage.ClientStorageManager;
|
import org.keycloak.storage.ClientStorageManager;
|
||||||
import org.keycloak.storage.UserStorageManager;
|
import org.keycloak.storage.UserStorageManager;
|
||||||
|
@ -75,6 +77,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
private ThemeManager themeManager;
|
private ThemeManager themeManager;
|
||||||
private TokenManager tokenManager;
|
private TokenManager tokenManager;
|
||||||
private VaultTranscriber vaultTranscriber;
|
private VaultTranscriber vaultTranscriber;
|
||||||
|
private ClientPolicyManager clientPolicyManager;
|
||||||
|
|
||||||
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
|
@ -321,6 +324,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
return this.vaultTranscriber;
|
return this.vaultTranscriber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientPolicyManager clientPolicy() {
|
||||||
|
if (clientPolicyManager == null) {
|
||||||
|
clientPolicyManager = new DefaultClientPolicyManager(this);
|
||||||
|
}
|
||||||
|
return clientPolicyManager;
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
Consumer<? super Provider> safeClose = p -> {
|
Consumer<? super Provider> safeClose = p -> {
|
||||||
try {
|
try {
|
||||||
|
@ -332,4 +343,5 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
||||||
providers.values().forEach(safeClose);
|
providers.values().forEach(safeClose);
|
||||||
closable.forEach(safeClose);
|
closable.forEach(safeClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -36,6 +36,9 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
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.ClientRegistrationPolicyException;
|
||||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
|
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager;
|
||||||
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
||||||
|
@ -149,8 +152,9 @@ public class ClientRegistrationAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
session.clientPolicy().triggerOnEvent(new DynamicClientRegisterContext(context, jwt, realm));
|
||||||
ClientRegistrationPolicyManager.triggerBeforeRegister(context, registrationAuth);
|
ClientRegistrationPolicyManager.triggerBeforeRegister(context, registrationAuth);
|
||||||
} catch (ClientRegistrationPolicyException crpe) {
|
} catch (ClientRegistrationPolicyException | ClientPolicyException crpe) {
|
||||||
throw forbidden(crpe.getMessage());
|
throw forbidden(crpe.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,8 +214,9 @@ public class ClientRegistrationAuth {
|
||||||
RegistrationAuth regAuth = requireUpdateAuth(client);
|
RegistrationAuth regAuth = requireUpdateAuth(client);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
session.clientPolicy().triggerOnEvent(new DynamicClientUpdateContext(context, client, jwt, realm));
|
||||||
ClientRegistrationPolicyManager.triggerBeforeUpdate(context, regAuth, client);
|
ClientRegistrationPolicyManager.triggerBeforeUpdate(context, regAuth, client);
|
||||||
} catch (ClientRegistrationPolicyException crpe) {
|
} catch (ClientRegistrationPolicyException | ClientPolicyException crpe) {
|
||||||
throw forbidden(crpe.getMessage());
|
throw forbidden(crpe.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ public class ClientRegistrationPolicyManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void triggerAfterRegister(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) {
|
public static void triggerAfterRegister(ClientRegistrationContext context, RegistrationAuth authType, ClientModel client) {
|
||||||
try {
|
try {
|
||||||
triggerPolicies(context.getSession(), context.getProvider(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
triggerPolicies(context.getSession(), context.getProvider(), authType, "after register client " + client.getClientId(), (ClientRegistrationPolicy policy) -> {
|
||||||
|
|
|
@ -51,6 +51,10 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
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.ClientRegistrationTokenUtils;
|
||||||
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
import org.keycloak.services.clientregistration.policy.RegistrationAuth;
|
||||||
import org.keycloak.services.managers.ClientManager;
|
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 {
|
try {
|
||||||
updateClientFromRep(rep, client, session);
|
updateClientFromRep(rep, client, session);
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,9 @@ import org.keycloak.representations.idm.authorization.ResourceServerRepresentati
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.ForbiddenException;
|
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.ClientManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
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 {
|
try {
|
||||||
ClientModel clientModel = ClientManager.createClient(session, realm, rep, true);
|
ClientModel clientModel = ClientManager.createClient(session, realm, rep, true);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.services.clientpolicy.DefaultClientPolicyProviderFactory
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.keycloak.testsuite.services.clientpolicy.executor.TestPKCEEnforceExecutorFactory
|
||||||
|
org.keycloak.testsuite.services.clientpolicy.executor.TestClientAuthenticationExecutorFactory
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
|
||||||
# log4j.logger.org.keycloak.keys.infinispan=trace
|
# log4j.logger.org.keycloak.keys.infinispan=trace
|
||||||
log4j.logger.org.keycloak.services.clientregistration.policy=debug
|
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
|
#log4j.logger.org.keycloak.authentication=debug
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue