Allowing client registration access token rotation deactivation
This commit is contained in:
parent
e374e309c6
commit
dbe0c27bcf
6 changed files with 189 additions and 2 deletions
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.models;
|
||||||
|
|
||||||
|
public class ClientRegistrationAccessTokenConstants {
|
||||||
|
|
||||||
|
public static final String ROTATION_ENABLED = "client.registration.access.token.enabled";
|
||||||
|
|
||||||
|
private ClientRegistrationAccessTokenConstants(){}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.models.ClientRegistrationAccessTokenConstants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.representations.idm.ClientPolicyExecutorConfigurationRepresentation;
|
||||||
|
import org.keycloak.services.clientpolicy.ClientPolicyContext;
|
||||||
|
import org.keycloak.services.clientpolicy.ClientPolicyException;
|
||||||
|
|
||||||
|
public class RegistrationAccessTokenRotationDisabledExecutor implements ClientPolicyExecutorProvider<ClientPolicyExecutorConfigurationRepresentation> {
|
||||||
|
|
||||||
|
private final String providerId;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
|
public RegistrationAccessTokenRotationDisabledExecutor(String providerId, KeycloakSession session) {
|
||||||
|
this.providerId = providerId;
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderId() {
|
||||||
|
return providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeOnEvent(ClientPolicyContext context) throws ClientPolicyException {
|
||||||
|
if (session.getAttribute(ClientRegistrationAccessTokenConstants.ROTATION_ENABLED) == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
session.setAttribute(ClientRegistrationAccessTokenConstants.ROTATION_ENABLED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 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.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
|
public class RegistrationAccessTokenRotationDisabledExecutorFactory implements ClientPolicyExecutorProviderFactory {
|
||||||
|
|
||||||
|
public static final String PROVIDER_ID = "registration-access-token-rotation-disabled";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHelpText() {
|
||||||
|
return "Disables registration access rotation for the client.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientPolicyExecutorProvider create(KeycloakSession session) {
|
||||||
|
return new RegistrationAccessTokenRotationDisabledExecutor(getId(), session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return PROVIDER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.ClientInitialAccessModel;
|
import org.keycloak.models.ClientInitialAccessModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientRegistrationAccessTokenConstants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.ModelDuplicateException;
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -142,6 +143,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
||||||
event.event(EventType.CLIENT_UPDATE).client(clientId);
|
event.event(EventType.CLIENT_UPDATE).client(clientId);
|
||||||
|
|
||||||
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||||
|
session.setAttribute(ClientRegistrationAccessTokenConstants.ROTATION_ENABLED, true);
|
||||||
RegistrationAuth registrationAuth = auth.requireUpdate(context, client);
|
RegistrationAuth registrationAuth = auth.requireUpdate(context, client);
|
||||||
|
|
||||||
if (!client.getClientId().equals(rep.getClientId())) {
|
if (!client.getClientId().equals(rep.getClientId())) {
|
||||||
|
@ -165,9 +167,15 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth.isRegistrationAccessToken()) {
|
if (auth.isRegistrationAccessToken()) {
|
||||||
String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client, auth.getRegistrationAuth());
|
String registrationAccessToken;
|
||||||
|
if ((boolean) session.getAttribute(ClientRegistrationAccessTokenConstants.ROTATION_ENABLED)) {
|
||||||
|
registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, client, auth.getRegistrationAuth());
|
||||||
|
} else {
|
||||||
|
registrationAccessToken = ClientRegistrationTokenUtils.updateTokenSignature(session, auth);
|
||||||
|
}
|
||||||
rep.setRegistrationAccessToken(registrationAccessToken);
|
rep.setRegistrationAccessToken(registrationAccessToken);
|
||||||
}
|
}
|
||||||
|
session.removeAttribute(ClientRegistrationAccessTokenConstants.ROTATION_ENABLED);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.getContext().setClient(client);
|
session.getContext().setClient(client);
|
||||||
|
|
|
@ -18,4 +18,5 @@ org.keycloak.services.clientpolicy.executor.RejectResourceOwnerPasswordCredentia
|
||||||
org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutorFactory
|
org.keycloak.services.clientpolicy.executor.ClientSecretRotationExecutorFactory
|
||||||
org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory
|
org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory
|
||||||
org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory
|
org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory
|
||||||
org.keycloak.services.clientpolicy.executor.SuppressRefreshTokenRotationExecutorFactory
|
org.keycloak.services.clientpolicy.executor.SuppressRefreshTokenRotationExecutorFactory
|
||||||
|
org.keycloak.services.clientpolicy.executor.RegistrationAccessTokenRotationDisabledExecutorFactory
|
||||||
|
|
|
@ -111,6 +111,7 @@ import org.keycloak.services.clientpolicy.executor.FullScopeDisabledExecutorFact
|
||||||
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.HolderOfKeyEnforcerExecutorFactory;
|
||||||
import org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.IntentClientBindCheckExecutorFactory;
|
||||||
import org.keycloak.services.clientpolicy.executor.PKCEEnforcerExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.PKCEEnforcerExecutorFactory;
|
||||||
|
import org.keycloak.services.clientpolicy.executor.RegistrationAccessTokenRotationDisabledExecutorFactory;
|
||||||
import org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.RejectRequestExecutorFactory;
|
||||||
import org.keycloak.services.clientpolicy.executor.RejectResourceOwnerPasswordCredentialsGrantExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.RejectResourceOwnerPasswordCredentialsGrantExecutorFactory;
|
||||||
import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
|
import org.keycloak.services.clientpolicy.executor.SecureClientAuthenticatorExecutorFactory;
|
||||||
|
@ -3391,6 +3392,38 @@ public class ClientPoliciesTest extends AbstractClientPoliciesTest {
|
||||||
assertEquals("no claim for an intent value for ID token" , oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
|
assertEquals("no claim for an intent value for ID token" , oauth.getCurrentFragment().get(OAuth2Constants.ERROR_DESCRIPTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegistrationAccessTokenRotationDisabledExecutor() throws Exception {
|
||||||
|
// register profiles - client autoConfigured to disable registration access token rotation
|
||||||
|
String json = new ClientProfilesBuilder().addProfile(
|
||||||
|
new ClientProfileBuilder().createProfile(PROFILE_NAME, "Test Profile")
|
||||||
|
.addExecutor(
|
||||||
|
RegistrationAccessTokenRotationDisabledExecutorFactory.PROVIDER_ID,
|
||||||
|
new ClientPolicyExecutorConfigurationRepresentation()
|
||||||
|
)
|
||||||
|
.toRepresentation()
|
||||||
|
).toString();
|
||||||
|
updateProfiles(json);
|
||||||
|
|
||||||
|
// register policies
|
||||||
|
json = new ClientPoliciesBuilder().addPolicy(
|
||||||
|
new ClientPolicyBuilder().createPolicy(POLICY_NAME, "Test Policy", Boolean.TRUE)
|
||||||
|
.addCondition(AnyClientConditionFactory.PROVIDER_ID,
|
||||||
|
createAnyClientConditionConfig())
|
||||||
|
.addProfile(PROFILE_NAME)
|
||||||
|
.toRepresentation()
|
||||||
|
).toString();
|
||||||
|
updatePolicies(json);
|
||||||
|
|
||||||
|
String clientId = createClientDynamically(generateSuffixedName(CLIENT_NAME), r -> {});
|
||||||
|
OIDCClientRepresentation createdClient = getClientDynamically(clientId);
|
||||||
|
|
||||||
|
updateClientDynamically(clientId, clientRep ->
|
||||||
|
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.CLIENT_SECRET_BASIC));
|
||||||
|
|
||||||
|
assertEquals(createdClient.getRegistrationAccessToken(), getClientDynamically(clientId).getRegistrationAccessToken());
|
||||||
|
}
|
||||||
|
|
||||||
private void openVerificationPage(String verificationUri) {
|
private void openVerificationPage(String verificationUri) {
|
||||||
driver.navigate().to(verificationUri);
|
driver.navigate().to(verificationUri);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue