Skip to content
This repository has been archived by the owner on Dec 31, 2021. It is now read-only.

Commit

Permalink
Support multiple Keycloak Realms
Browse files Browse the repository at this point in the history
  • Loading branch information
flytreeleft committed Apr 26, 2019
1 parent 7aef7b2 commit bc49aea
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
*/
package org.github.flytreeleft.nexus3.keycloak.plugin;

import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
Expand All @@ -24,13 +27,10 @@
import org.apache.shiro.subject.PrincipalCollection;
import org.eclipse.sisu.Description;
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.NexusKeycloakClient;
import org.github.flytreeleft.nexus3.keycloak.plugin.internal.NexusKeycloakClientLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

@Singleton
@Named
@Description("Keycloak Authentication Realm")
Expand All @@ -40,9 +40,8 @@ public class KeycloakAuthenticatingRealm extends AuthorizingRealm {

private NexusKeycloakClient client;

@Inject
public KeycloakAuthenticatingRealm(final NexusKeycloakClient client) {
this.client = client;
public KeycloakAuthenticatingRealm() {
this.client = NexusKeycloakClientLoader.loadClient();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
*/
package org.github.flytreeleft.nexus3.keycloak.plugin.internal;

import java.util.Collections;
import java.util.Set;
import javax.enterprise.inject.Typed;
import javax.inject.Named;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.nexus.security.authz.AbstractReadOnlyAuthorizationManager;
Expand All @@ -21,13 +27,6 @@
import org.sonatype.nexus.security.role.NoSuchRoleException;
import org.sonatype.nexus.security.role.Role;

import javax.enterprise.inject.Typed;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.Set;

@Singleton
@Typed(AuthorizationManager.class)
@Named("Keycloak")
Expand All @@ -36,10 +35,9 @@ public class KeycloakAuthorizationManager extends AbstractReadOnlyAuthorizationM

private NexusKeycloakClient client;

@Inject
public KeycloakAuthorizationManager(NexusKeycloakClient client) {
public KeycloakAuthorizationManager() {
LOGGER.info("KeycloakAuthorizationManager is starting...");
this.client = client;
this.client = NexusKeycloakClientLoader.loadClient();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import javax.inject.Named;
import javax.inject.Singleton;

import com.google.inject.Inject;
import org.github.flytreeleft.nexus3.keycloak.plugin.KeycloakAuthenticatingRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -38,10 +37,9 @@ public class KeycloakUserManager extends AbstractReadOnlyUserManager {

private NexusKeycloakClient client;

@Inject
public KeycloakUserManager(NexusKeycloakClient client) {
public KeycloakUserManager() {
LOGGER.info("KeycloakUserManager is starting...");
this.client = client;
this.client = NexusKeycloakClientLoader.loadClient();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.util.StringUtils;
Expand All @@ -23,14 +21,22 @@
import org.sonatype.nexus.security.user.User;
import org.sonatype.nexus.security.user.UserSearchCriteria;

@Singleton
@Named("NexusKeycloakClient")
public class NexusKeycloakClient {
private static final String CONFIG_FILE = "keycloak.json";
private static final Logger LOGGER = LoggerFactory.getLogger(NexusKeycloakClient.class);

private Path config;
private boolean roleWithRealm;
private transient KeycloakAdminClient keycloakAdminClient;

public NexusKeycloakClient(Path config) {
this(config, false);
}

public NexusKeycloakClient(Path config, boolean roleWithRealm) {
this.config = config;
this.roleWithRealm = roleWithRealm;
}

public boolean authenticate(UsernamePasswordToken token) {
AccessTokenResponse accessTokenResponse = getKeycloakAdminClient().obtainAccessToken(token.getUsername(),
new String(token.getPassword()));
Expand All @@ -47,7 +53,7 @@ public Set<String> findRoleIdsByUserId(String userId) {
List<GroupRepresentation> realmGroups = getKeycloakAdminClient().getRealmGroupsOfUser(user);

// Convert to compatible roles to make sure the existing role-mappings are still working
return KeycloakMapper.toCompatibleRoleIds(clientRoles, realmRoles, realmGroups);
return KeycloakMapper.toCompatibleRoleIds(getRoleRealm(), clientRoles, realmRoles, realmGroups);
}

public User findUserByUserId(String userId) {
Expand All @@ -57,24 +63,27 @@ public User findUserByUserId(String userId) {
}

public Role findRoleByRoleId(String roleId) {
RoleRepresentation role;
String[] splits = roleId.split(":");
String roleType = splits.length > 1 ? splits[0] : null;
String roleRealm = splits.length > 2 ? splits[1] : null;
String roleName = splits[splits.length - 1];

if (roleId.startsWith(KeycloakMapper.REALM_GROUP_PREFIX)) {
String groupPath = roleId.substring(KeycloakMapper.REALM_GROUP_PREFIX.length() + 1);
GroupRepresentation group = getKeycloakAdminClient().getRealmGroupByGroupPath(groupPath);
if (roleRealm != null && !getKeycloakAdminClient().getConfig().getRealm().equals(roleRealm)) {
return null;
}

RoleRepresentation role;
if (KeycloakMapper.REALM_GROUP_PREFIX.equals(roleType)) {
GroupRepresentation group = getKeycloakAdminClient().getRealmGroupByGroupPath(roleName);

return KeycloakMapper.toRole(group);
} else if (roleId.startsWith(KeycloakMapper.REALM_ROLE_PREFIX)) {
String roleName = roleId.substring(KeycloakMapper.REALM_ROLE_PREFIX.length() + 1);
return KeycloakMapper.toRole(getRoleRealm(), group);
} else if (KeycloakMapper.REALM_ROLE_PREFIX.equals(roleType)) {
role = getKeycloakAdminClient().getRealmRoleByRoleName(roleName);
} else {
String roleName = roleId.startsWith(KeycloakMapper.CLIENT_ROLE_PREFIX)
? roleId.substring(KeycloakMapper.CLIENT_ROLE_PREFIX.length() + 1)
: roleId;
String client = getKeycloakAdminClient().getConfig().getResource();
role = getKeycloakAdminClient().getRealmClientRoleByRoleName(client, roleName);
}
return KeycloakMapper.toRole(role);
return KeycloakMapper.toRole(getRoleRealm(), role);
}

public Set<String> findAllUserIds() {
Expand Down Expand Up @@ -109,7 +118,12 @@ public Set<Role> findRoles() {
List<RoleRepresentation> realmRoles = getKeycloakAdminClient().getRealmRoles();
List<GroupRepresentation> realmGroups = getKeycloakAdminClient().getRealmGroups();

return KeycloakMapper.toRoles(clientRoles, realmRoles, realmGroups);
return KeycloakMapper.toRoles(getRoleRealm(), clientRoles, realmRoles, realmGroups);
}

protected String getRoleRealm() {
String realm = getKeycloakAdminClient().getConfig().getRealm();
return this.roleWithRealm ? realm : null;
}

private synchronized KeycloakAdminClient getKeycloakAdminClient() {
Expand All @@ -124,6 +138,6 @@ private synchronized KeycloakAdminClient getKeycloakAdminClient() {
}

private InputStream getKeycloakJson() throws IOException {
return Files.newInputStream(Paths.get(".", "etc", CONFIG_FILE));
return Files.newInputStream(this.config);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package org.github.flytreeleft.nexus3.keycloak.plugin.internal;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.nexus.security.role.Role;
import org.sonatype.nexus.security.user.User;
import org.sonatype.nexus.security.user.UserSearchCriteria;

public class NexusKeycloakClientLoader {
private static final String PLUGIN_CONFIG_FILE = "keycloak-plugin.properties";
private static final String CONFIG_FILE = "keycloak.json";

private static final String PLUGIN_CONFIG_KEY_KEYCLOAK_AUTH_CONFIG = "keycloak.auth.config";

private static final Logger LOGGER = LoggerFactory.getLogger(NexusKeycloakClientLoader.class);

private static NexusKeycloakClient client;

public static NexusKeycloakClient loadClient() {
if (client == null) {
Properties props = getPluginProperties();
String keycloakJSON = props.getProperty(PLUGIN_CONFIG_KEY_KEYCLOAK_AUTH_CONFIG, "").trim();
String[] jsonFiles = keycloakJSON.isEmpty()
? new String[] { CONFIG_FILE }
: keycloakJSON.split("\\s*(,|;)\\s*");

if (jsonFiles.length == 1) {
client = new NexusKeycloakClient(Paths.get(".", "etc", jsonFiles[0]));

LOGGER.info(String.format("Create NexusKeycloakClient with %s", jsonFiles[0]));
} else {
List<NexusKeycloakClient> clients = new ArrayList<>();
for (String jsonFile : jsonFiles) {
NexusKeycloakClient c = new NexusKeycloakClient(Paths.get(".", "etc", jsonFile), true);
clients.add(c);
}

client = new CompositeNexusKeycloakClient(clients);

LOGGER.info(String.format("Create CompositeNexusKeycloakClient with %d keycloak.json files: %s",
jsonFiles.length,
keycloakJSON));
}
}
return client;
}

public static Properties getPluginProperties() {
Properties props = new Properties();

File config = FileUtils.getFile(".", "etc", PLUGIN_CONFIG_FILE);
if (config.exists()) {
try {
props.load(FileUtils.openInputStream(config));
} catch (IOException e) {
throw new IllegalStateException("Can not read the plugin properties", e);
}
}
return props;
}

static class CompositeNexusKeycloakClient extends NexusKeycloakClient {
private List<NexusKeycloakClient> clients;

public CompositeNexusKeycloakClient(List<NexusKeycloakClient> clients) {
super(null);
this.clients = clients != null ? clients : new ArrayList<>();
}

@Override
public boolean authenticate(UsernamePasswordToken token) {
for (NexusKeycloakClient client : this.clients) {
// Do authenticate for the first matching
if (client.findUserByUserId(token.getUsername()) != null) {
return client.authenticate(token);
}
}
return false;
}

@Override
public Set<String> findRoleIdsByUserId(String userId) {
for (NexusKeycloakClient client : this.clients) {
if (client.findUserByUserId(userId) != null) {
return client.findRoleIdsByUserId(userId);
}
}
return new HashSet<>();
}

@Override
public User findUserByUserId(String userId) {
for (NexusKeycloakClient client : this.clients) {
User user = client.findUserByUserId(userId);
if (user != null) {
return user;
}
}
return null;
}

@Override
public Role findRoleByRoleId(String roleId) {
for (NexusKeycloakClient client : this.clients) {
Role role = client.findRoleByRoleId(roleId);
if (role != null) {
return role;
}
}
return null;
}

@Override
public Set<String> findAllUserIds() {
Set<String> userIds = new HashSet<>();

for (NexusKeycloakClient client : this.clients) {
Set<String> set = client.findAllUserIds();
// Remove the existing users
set.removeAll(userIds);
// Add the new users to the result
userIds.addAll(set);
}
return userIds;
}

@Override
public Set<User> findUsers() {
Set<User> users = new HashSet<>();

for (NexusKeycloakClient client : this.clients) {
Set<User> set = client.findUsers();
// Remove the existing users
set.removeAll(users);
// Add the new users to the result
users.addAll(set);
}
return users;
}

@Override
public Set<User> findUserByCriteria(UserSearchCriteria criteria) {
Set<User> users = new HashSet<>();

for (NexusKeycloakClient client : this.clients) {
Set<User> set = client.findUserByCriteria(criteria);
// Remove the existing users
set.removeAll(users);
// Add the new users to the result
users.addAll(set);
}
return users;
}

@Override
public Set<Role> findRoles() {
Set<Role> roles = new HashSet<>();

for (NexusKeycloakClient client : this.clients) {
Set<Role> set = client.findRoles();
// Remove the existing roles
set.removeAll(roles);
// Add the new roles to the result
roles.addAll(set);
}
return roles;
}
}
}
Loading

0 comments on commit bc49aea

Please sign in to comment.