Skip to content

Commit

Permalink
Manage only known Clients Authorization Resources (adorsys#646)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke authored Feb 26, 2022
1 parent 8c68679 commit a33f5d7
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 160 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Support for managing `Client Authorization Resources` like other resources by configuring `import.managed.client-authorization-resources=<full|no-delete>`. This prevents deletion of remote managed resources.

### Changes

- Compile keycloak-config-cli inside docker build to avoid the requirement to run maven before

### Fixed

- Manage `Client Authorization` without define a `clientId` in import realm.

## [4.7.0] - 2022-02-14

### Added
Expand Down
34 changes: 17 additions & 17 deletions docs/MANAGED.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,30 @@ groups will be deleted. If you define `groups` but set an empty array, keycloak

## Supported full managed resources

| Type | Additional Information | Resource Name |
| ------------------------- | -------------------------------------------------------------------------------- | -------------------------- |
| Groups | - | `group` |
| Required Actions | You have to copy the default one to you import json. | `required-action` |
| Client Scopes | - | `client-scope` |
| Scope Mappings | - | `scope-mapping` |
| Client Scope Mappings | - | `client-scope-mapping` |
| Roles | - | `role` |
| Components | You have to copy the default components to you import json. | `component` |
| Sub Components | You have to copy the default components to you import json. | `sub-component` |
| Authentication Flows | You have to copy the default components to you import json, expect bulitin flows | `authentication-flow` |
| Identity Providers | - | `identity-provider` |
| Identity Provider Mappers | - | `identity-provider-mapper` |
| Clients | - | `client` |
| Type | Additional Information | Resource Name |
|---------------------------------|----------------------------------------------------------------------------------|----------------------------------|
| Groups | - | `group` |
| Required Actions | You have to copy the default one to you import json. | `required-action` |
| Client Scopes | - | `client-scope` |
| Scope Mappings | - | `scope-mapping` |
| Client Scope Mappings | - | `client-scope-mapping` |
| Roles | - | `role` |
| Components | You have to copy the default components to you import json. | `component` |
| Sub Components | You have to copy the default components to you import json. | `sub-component` |
| Authentication Flows | You have to copy the default components to you import json, expect builtin flows | `authentication-flow` |
| Identity Providers | - | `identity-provider` |
| Identity Provider Mappers | - | `identity-provider-mapper` |
| Clients | - | `client` |
| Clients Authorization Resources | The 'Default Resource' is always included. | `client-authorization-resources` |

## Disable deletion of managed entities

If you won't delete properties of a specific type, you can disable this behavior by default a properties like `import.managed.<entity>=<full|no-delete>`, e.g.:
If you don't delete properties of a specific type, you can disable this behavior by default a properties like `import.managed.<entity>=<full|no-delete>`, e.g.:
`import.managed.required-actions=no-delete`

## State management

If `import.state` is set to `true` (default value), keycloak-config-cli will purge only resources they created before by keycloak-config-cli.
If `import.state` is set to `false`, keycloak-config-cli will purge all existing entities if they not defined in import json.
If `import.state` is set to `true` (default value), keycloak-config-cli will purge only resources they created before by keycloak-config-cli. If `import.state` is set to `false`, keycloak-config-cli will purge all existing entities if they are not defined in import json.

### Supported resources

Expand Down
25 changes: 23 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,23 @@
<version>${commons-io.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>

<dependency>
<groupId>net.jodah</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down Expand Up @@ -195,13 +207,16 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>

<dependency>
<groupId>net.jodah</groupId>
<artifactId>failsafe</artifactId>
<version>${failsafe.version}</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -235,6 +250,12 @@
</exclusions>
</dependency>

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,17 @@ public static class ImportManagedProperties {
@NotNull
private final ImportManagedPropertiesValues client;

@NotNull
private final ImportManagedPropertiesValues clientAuthorizationResources;

public ImportManagedProperties(
ImportManagedPropertiesValues requiredAction, ImportManagedPropertiesValues group,
ImportManagedPropertiesValues clientScope, ImportManagedPropertiesValues scopeMapping,
ImportManagedPropertiesValues clientScopeMapping,
ImportManagedPropertiesValues component, ImportManagedPropertiesValues subComponent,
ImportManagedPropertiesValues authenticationFlow, ImportManagedPropertiesValues identityProvider,
ImportManagedPropertiesValues identityProviderMapper, ImportManagedPropertiesValues role,
ImportManagedPropertiesValues client) {
ImportManagedPropertiesValues client, ImportManagedPropertiesValues clientAuthorizationResources) {
this.requiredAction = requiredAction;
this.group = group;
this.clientScope = clientScope;
Expand All @@ -285,6 +288,7 @@ public ImportManagedProperties(
this.identityProviderMapper = identityProviderMapper;
this.role = role;
this.client = client;
this.clientAuthorizationResources = clientAuthorizationResources;
}

public ImportManagedPropertiesValues getRequiredAction() {
Expand Down Expand Up @@ -335,6 +339,10 @@ public ImportManagedPropertiesValues getClient() {
return client;
}

public ImportManagedPropertiesValues getClientAuthorizationResources() {
return clientAuthorizationResources;
}

public enum ImportManagedPropertiesValues {
FULL,
NO_DELETE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private Map<String, String> retrieveCustomAttributes(String realmName) {
return existingRealm.getAttributes();
}

public void setState(String entity, List<Object> values) {
public void setState(String entity, List<String> values) {
String valuesAsString = toJson(values);

if (this.importConfigProperties.getStateEncryptionKey() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import de.adorsys.keycloak.config.repository.ClientScopeRepository;
import de.adorsys.keycloak.config.service.state.StateService;
import de.adorsys.keycloak.config.util.*;
import org.apache.commons.lang3.ArrayUtils;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
Expand Down Expand Up @@ -185,7 +186,7 @@ private void updateClientIfNeeded(
ClientRepresentation clientToUpdate,
ClientRepresentation existingClient
) {
String[] propertiesToIgnore = ArrayUtil.concat(propertiesWithDependencies, "id", "access");
String[] propertiesToIgnore = ArrayUtils.addAll(propertiesWithDependencies, "id", "access");
ClientRepresentation mergedClient = CloneUtil.patch(existingClient, clientToUpdate, propertiesToIgnore);

if (!isClientEqual(realmName, existingClient, mergedClient)) {
Expand All @@ -209,7 +210,7 @@ private boolean isClientEqual(
ClientRepresentation existingClient,
ClientRepresentation patchedClient
) {
String[] propertiesToIgnore = ArrayUtil.concat(
String[] propertiesToIgnore = ArrayUtils.addAll(
propertiesWithDependencies, "id", "secret", "access", "protocolMappers"
);

Expand Down Expand Up @@ -263,7 +264,15 @@ private void updateClientAuthorizationSettings(
.collect(Collectors.toList());

for (ClientRepresentation client : clientsWithAuthorization) {
ClientRepresentation existingClient = clientRepository.getByClientId(realmName, client.getClientId());
ClientRepresentation existingClient;
if (client.getClientId() != null) {
existingClient = clientRepository.getByClientId(realmName, client.getClientId());
} else if (client.getName() != null) {
existingClient = clientRepository.getByName(realmName, client.getName());
} else {
throw new ImportProcessingException("clients require client id or name.");
}

updateAuthorization(realmName, existingClient, client.getAuthorizationSettings());
}
}
Expand All @@ -289,18 +298,23 @@ private void updateAuthorization(

createOrUpdateAuthorizationResources(realmName, client,
existingAuthorization.getResources(), authorizationSettingsToImport.getResources());
removeAuthorizationResources(realmName, client,
existingAuthorization.getResources(), authorizationSettingsToImport.getResources());

createOrUpdateAuthorizationScopes(realmName, client,
existingAuthorization.getScopes(), authorizationSettingsToImport.getScopes());
removeAuthorizationScopes(realmName, client,
existingAuthorization.getScopes(), authorizationSettingsToImport.getScopes());

createOrUpdateAuthorizationPolicies(realmName, client,
existingAuthorization.getPolicies(), authorizationSettingsToImport.getPolicies());

if (importConfigProperties.getManaged().getClientAuthorizationResources() == FULL) {
removeAuthorizationResources(realmName, client,
existingAuthorization.getResources(), authorizationSettingsToImport.getResources());
}

removeAuthorizationPolicies(realmName, client,
existingAuthorization.getPolicies(), authorizationSettingsToImport.getPolicies());

removeAuthorizationScopes(realmName, client,
existingAuthorization.getScopes(), authorizationSettingsToImport.getScopes());
}

private void handleAuthorizationSettings(
Expand Down Expand Up @@ -410,11 +424,11 @@ private void removeAuthorizationResources(
.stream().map(ResourceRepresentation::getName)
.collect(Collectors.toList());

for (ResourceRepresentation existingClientAuthorizationResource : existingClientAuthorizationResources) {
if (!authorizationResourceNamesToImport.contains(existingClientAuthorizationResource.getName())) {
removeAuthorizationResource(realmName, client, existingClientAuthorizationResource);
}
}
List<ResourceRepresentation> managedClientAuthorizationResources = getManagedClientResources(client, existingClientAuthorizationResources);

managedClientAuthorizationResources.stream()
.filter(resource -> !authorizationResourceNamesToImport.contains(resource.getName()))
.forEach(resource -> removeAuthorizationResource(realmName, client, resource));
}

private void removeAuthorizationResource(
Expand Down Expand Up @@ -725,7 +739,7 @@ private void updateClientDefaultOptionalClientScopes(
}

private String getClientIdentifier(ClientRepresentation client) {
return client.getClientId() != null ? client.getClientId() : client.getName();
return client.getName() != null ? client.getName() : client.getClientId();
}

// https://github.com/adorsys/keycloak-config-cli/issues/589
Expand All @@ -735,4 +749,17 @@ private void setAuthorizationResourceOwner(ResourceRepresentation representation
representation.getOwner().setName(null);
}
}

private List<ResourceRepresentation> getManagedClientResources(ClientRepresentation client, List<ResourceRepresentation> existingResources) {
if (importConfigProperties.isState()) {
String clientKey = Objects.equals(client.getId(), client.getClientId()) ? "name:" + client.getName() : client.getClientId();
List<String> clientResourcesInState = stateService.getClientAuthorizationResources(clientKey);
// ignore all object there are not in state
return existingResources.stream()
.filter(resource -> clientResourcesInState.contains(resource.getName()) || Objects.equals(resource.getName(), "Default Resource"))
.collect(Collectors.toList());
} else {
return existingResources;
}
}
}
Loading

0 comments on commit a33f5d7

Please sign in to comment.