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 c47d1a9
Show file tree
Hide file tree
Showing 20 changed files with 398 additions and 108 deletions.
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 c47d1a9

Please sign in to comment.