Skip to content

Commit

Permalink
chore(kc26): add identity provider storage provider
Browse files Browse the repository at this point in the history
Signed-off-by: opdt <[email protected]>
  • Loading branch information
opdt committed Nov 26, 2024
1 parent f37703c commit 318bc01
Show file tree
Hide file tree
Showing 23 changed files with 402 additions and 112 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ jobs:

- uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'adopt'

- name: Run tests
run: mvn test

- name: Create Jacoco report
run: mvn -f ./core org.jacoco:jacoco-maven-plugin:0.8.8:report -Djacoco.dataFile="$(readlink -f ./tests/target/jacoco.exec)"
run: mvn -f ./core org.jacoco:jacoco-maven-plugin:report -Djacoco.dataFile="$(readlink -f ./tests/target/jacoco.exec)"

- name: Cache SonarCloud packages
uses: actions/cache@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Set up Maven Central Repository
uses: actions/setup-java@v4
with:
java-version: '17'
java-version: '21'
distribution: 'adopt'
server-id: ossrh
server-username: MAVEN_USERNAME
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Keycloak Cassandra is a datastore and caching extension for Keycloak, the Open S

## Building and working with the codebase

To build the codebase you need an installed JDK of at least version 17 and Maven.
To build the codebase you need an installed JDK of at least version 21 and Maven.
The tests use the Testcontainers-framework to start a local Apache Cassandra database instance. For this to work, you need Docker installed on your system as well.

## Contributing to Keycloak Cassandra
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Cassandra storage extension for Keycloak

Uses Apache Cassandra to store and retrieve entities of all storage areas except authorization and events.
Requires Keycloak >= 25.0.0 (older versions may be supported by older versions of this extension).
Requires Keycloak >= 26.0.0 (older versions may be supported by older versions of this extension).

## How to use

Expand Down Expand Up @@ -39,6 +39,10 @@ The following parameters might be needed in addition to the configuration option

## Deviations from standard storage providers

### Organizations

Keycloak Organizations are currently *not* supported and have to be turned off.

### User Lookup
Due to Cassandras query first nature, users can only be looked up by specific fields.
`UserProvider::searchForUserStream` supports the following subset of Keycloaks standard search attributes:
Expand Down
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>de.arbeitsagentur.opdt</groupId>
<artifactId>keycloak-cassandra-extension-parent</artifactId>
<version>4.0.6-25.0-SNAPSHOT</version>
<version>4.0.6-26.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,8 @@
*/
package de.arbeitsagentur.opdt.keycloak.cassandra;

import java.util.HashSet;
import java.util.Set;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.models.*;
import org.keycloak.provider.Provider;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.ExportImportManager;
import org.keycloak.storage.MigrationManager;
Expand All @@ -30,8 +27,6 @@
public class CassandraDatastoreProvider extends DefaultDatastoreProvider {
private final KeycloakSession session;

private final Set<Provider> providersToClose = new HashSet<>();

public CassandraDatastoreProvider(KeycloakSession session) {
super(null, session);
this.session = session;
Expand Down Expand Up @@ -87,6 +82,11 @@ public UserSessionProvider userSessions() {
return session.getProvider(UserSessionProvider.class);
}

@Override
public IdentityProviderStorageProvider identityProviders() {
return session.getProvider(IdentityProviderStorageProvider.class);
}

@Override
public ExportImportManager getExportImportManager() {
return new CassandraLegacyExportImportManager(session);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,27 @@ public static ObjectMapper getMapper() {
return mapper;
}

public static String writeValueAsString(Object obj) throws IOException {
return mapper.writeValueAsString(obj);
public static String writeValueAsString(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static <T> T readValue(String bytes, Class<T> type) throws IOException {
return mapper.readValue(bytes, type);
public static <T> T readValue(String bytes, Class<T> type) {
try {
return mapper.readValue(bytes, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static <T> T readValue(String string, TypeReference<T> type) throws IOException {
return mapper.readValue(string, type);
public static <T> T readValue(String string, TypeReference<T> type) {
try {
return mapper.readValue(string, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import de.arbeitsagentur.opdt.keycloak.cassandra.client.persistence.ClientRepository;
import de.arbeitsagentur.opdt.keycloak.cassandra.client.persistence.entities.Client;
import de.arbeitsagentur.opdt.keycloak.cassandra.transaction.TransactionalModelAdapter;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.*;
import java.util.function.Function;
Expand Down Expand Up @@ -773,16 +772,7 @@ private void setSerializedAttributeValues(String name, List<?> values) {
List<String> attributeValues =
values.stream()
.filter(Objects::nonNull)
.map(
value -> {
try {
return CassandraJsonSerialization.writeValueAsString(value);
} catch (IOException e) {
log.errorf(
"Cannot serialize %s (realm: %s, name: %s)", value, entity.getId(), name);
throw new RuntimeException(e);
}
})
.map(CassandraJsonSerialization::writeValueAsString)
.collect(Collectors.toCollection(ArrayList::new));

entity.getAttributes().put(name, attributeValues);
Expand All @@ -800,16 +790,7 @@ private <T> List<T> getDeserializedAttributes(String name, TypeReference<T> type
}

return values.stream()
.map(
value -> {
try {
return CassandraJsonSerialization.readValue(value, type);
} catch (IOException e) {
log.errorf(
"Cannot deserialize %s (realm: %s, name: %s)", value, entity.getId(), name);
throw new RuntimeException(e);
}
})
.map(value -> CassandraJsonSerialization.readValue(value, type))
.filter(Objects::nonNull)
.collect(Collectors.toCollection(ArrayList::new));
}
Expand All @@ -822,17 +803,7 @@ private <T> List<T> getDeserializedAttributes(String name, Class<T> type) {
}

return values.stream()
.map(
value -> {
try {
return CassandraJsonSerialization.readValue(value, type);
} catch (IOException e) {
log.errorf(
"Cannot deserialize %s (realm: %s, name: %s, type: %s)",
value, entity.getId(), name, type.getName());
throw new RuntimeException(e);
}
})
.map(value -> CassandraJsonSerialization.readValue(value, type))
.filter(Objects::nonNull)
.collect(Collectors.toCollection(ArrayList::new));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import de.arbeitsagentur.opdt.keycloak.cassandra.clientScope.persistence.ClientScopeRepository;
import de.arbeitsagentur.opdt.keycloak.cassandra.clientScope.persistence.entities.ClientScopeValue;
import de.arbeitsagentur.opdt.keycloak.cassandra.clientScope.persistence.entities.ClientScopes;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -261,17 +260,7 @@ private List<String> getAttributeValues(String name) {
private void setSerializedAttributeValues(String name, List<?> values) {
List<String> attributeValues =
values.stream()
.map(
value -> {
try {
return CassandraJsonSerialization.writeValueAsString(value);
} catch (IOException e) {
log.errorf(
"Cannot serialize %s (realm: %s, name: %s)",
value, clientScopeEntity.getId(), name);
throw new RuntimeException(e);
}
})
.map(CassandraJsonSerialization::writeValueAsString)
.collect(Collectors.toList());

clientScopeEntity.getAttributes().put(name, attributeValues);
Expand All @@ -282,17 +271,7 @@ private <T> List<T> getDeserializedAttributes(String name, Class<T> type) {
List<String> values = clientScopeEntity.getAttributes().getOrDefault(name, new ArrayList<>());

return values.stream()
.map(
value -> {
try {
return CassandraJsonSerialization.readValue(value, type);
} catch (IOException e) {
log.errorf(
"Cannot deserialize %s (realm: %s, name: %s, type: %s)",
value, clientScopeEntity.getId(), name, type.getName());
throw new RuntimeException(e);
}
})
.map(value -> CassandraJsonSerialization.readValue(value, type))
.collect(Collectors.toCollection(ArrayList::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
import org.cognitor.cassandra.migration.MigrationRepository;
import org.cognitor.cassandra.migration.MigrationTask;
import org.keycloak.Config;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionModel;
Expand Down Expand Up @@ -178,6 +179,7 @@ public void init(Config.Scope scope) {
.withLocalDatacenter(localDatacenter)
.withKeyspace(keyspace)
.addTypeCodecs(new EnumNameCodec<>(UserSessionModel.State.class))
.addTypeCodecs(new EnumNameCodec<>(GroupModel.Type.class))
.addTypeCodecs(new EnumNameCodec<>(UserSessionModel.SessionPersistenceState.class))
.addTypeCodecs(new EnumNameCodec<>(CommonClientSessionModel.ExecutionStatus.class))
.addTypeCodecs(new JsonCodec<>(RoleValue.class, CassandraJsonSerialization.getMapper()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ private Function<GroupValue, GroupModel> entityToAdapterFunc(RealmModel realm) {

@Override
public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) {
return createGroup(realm, id, GroupModel.Type.REALM, name, toParent);
}

@Override
public GroupModel createGroup(
RealmModel realm, String id, GroupModel.Type type, String name, GroupModel toParent) {
log.debugv(
"createGroup(%s, %s, %s, %s)",
realm.getId(), id, name, toParent == null ? "null" : toParent.getId());
Expand All @@ -90,6 +96,7 @@ public GroupModel createGroup(RealmModel realm, String id, String name, GroupMod
.id(id == null ? KeycloakModelUtils.generateId() : id)
.name(name)
.parentId(toParent == null ? null : toParent.getId())
.type(type)
.build();

groups.addRealmGroup(group);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.arbeitsagentur.opdt.keycloak.cassandra.group.persistence.entities;

import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.*;
import lombok.*;
import org.keycloak.models.GroupModel;

@AllArgsConstructor
@NoArgsConstructor
Expand All @@ -13,9 +15,15 @@ public class GroupValue {
private String name;
private String parentId;
private String realmId;
@Builder.Default private GroupModel.Type type = GroupModel.Type.REALM;
@Builder.Default private Map<String, List<String>> attributes = new HashMap<>();
@Builder.Default private Set<String> grantedRoles = new HashSet<>();

@JsonSetter("type")
public void setType(GroupModel.Type type) {
this.type = type == null ? GroupModel.Type.REALM : type;
}

public Map<String, List<String>> getAttributes() {
if (attributes == null) {
attributes = new HashMap<>();
Expand Down
Loading

0 comments on commit 318bc01

Please sign in to comment.