diff --git a/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/AuthHelper.java b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/AuthHelper.java new file mode 100644 index 0000000..cf3091d --- /dev/null +++ b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/AuthHelper.java @@ -0,0 +1,50 @@ +package de.ccc.hamburg.keycloak.ssh_key; + +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.AppAuthManager; +import org.keycloak.services.managers.Auth; +import org.keycloak.services.managers.AuthenticationManager; + +import java.util.function.Function; + +public class AuthHelper { + + public static AuthenticationManager.AuthResult getAuthResult(KeycloakSession session, Function authFn) { + AuthenticationManager.AuthResult auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); + + if (auth == null) { + throw new NotAuthorizedException("Bearer"); + } else if (!authFn.apply(auth)) { + throw new ForbiddenException(); + } + return auth; + } + + public static Auth getAuth(KeycloakSession session, Function authFn) { + return getAuth(session, getAuthResult(session, authFn)); + } + + public static Auth getAuth(KeycloakSession session, String clientId, Function authFn) { + return getAuth(session, getAuthResult(session, authFn), clientId); + } + + public static Auth getAuth(KeycloakSession session, AuthenticationManager.AuthResult authResult) { + return getAuth(session, authResult, null); + } + + public static Auth getAuth(KeycloakSession session, AuthenticationManager.AuthResult authResult, String clientId) { + RealmModel realm = session.getContext().getRealm(); + ClientModel client; + if (clientId == null) { + client = authResult.getClient(); + } else { + client = realm.getClientByClientId(clientId); + } + return new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), false); + } + +} \ No newline at end of file diff --git a/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProvider.java b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProvider.java index fd91f34..fb5c2d4 100644 --- a/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProvider.java +++ b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProvider.java @@ -1,44 +1,27 @@ package de.ccc.hamburg.keycloak.ssh_key; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Stream; import org.jboss.logging.Logger; -import org.keycloak.models.ClientModel; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.MediaType; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; -import org.keycloak.representations.userprofile.config.UPConfig; -import org.keycloak.services.managers.AppAuthManager; -import org.keycloak.services.managers.AppAuthManager.BearerTokenAuthenticator; import org.keycloak.services.managers.Auth; -import org.keycloak.services.managers.AuthenticationManager.AuthResult; import org.keycloak.services.resource.RealmResourceProvider; -import org.keycloak.userprofile.UserProfileProvider; - -import jakarta.ws.rs.ForbiddenException; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.NotAuthorizedException; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; public class SSHKeyResourceProvider implements RealmResourceProvider { private static final Logger LOG = Logger.getLogger(SSHKeyResourceProvider.class); private final KeycloakSession session; - // taken from: https://github.com/nemchik/ssh-key-regex - private static final Pattern SSH_PUBLIC_KEY = Pattern.compile( - "^(?(ssh-dss AAAAB3NzaC1kc3|ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT|ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzOD|ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1Mj|sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3})(\\s.*)?$"); - public SSHKeyResourceProvider(KeycloakSession keycloakSession) { this.session = keycloakSession; } @@ -56,62 +39,32 @@ public class SSHKeyResourceProvider implements RealmResourceProvider { @Path("export/{group_id}") @Produces(MediaType.APPLICATION_JSON) public Response exportKeys(@PathParam("group_id") String groupId) { + UserProvider userProvider = session.users(); + try { - SSHKeyResourceProvider.getAuth(session); + Auth auth = AuthHelper.getAuth(session, + authResult -> authResult.getToken().getIssuedFor().equals("admin-cli")); + + RealmModel realm = session.getContext().getRealm(); + // TODO: add allowlist check + GroupModel group = realm.getGroupById(groupId); + + LOG.info(String.format("Getting Users from Group \"%s\" with ID %s", group.getName(), group.getId())); + + Stream users = userProvider.getGroupMembersStream(realm, group); + + users.forEach(user -> { + String sshKey = user.getAttributeStream("ssh-key-1").findFirst().get(); + LOG.info(String.format("SSH Key of %s: %s", user.getUsername(), sshKey)); + }); + + + return Response.ok(Map.of("hello", auth.getUser().getUsername())).build(); } catch (Exception e) { System.err.println(e); return Response.status(401, e.getMessage()).build(); } - UserProvider userProvider = session.users(); - UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class); - UPConfig upconfig = profileProvider.getConfiguration(); - List attributeNames = upconfig.getAttributes() - .stream() - .filter(a -> a.getGroup() != null && a.getGroup().equals("de.ccc.hamburg.keycloak.ssh_key.keys")) - .map(a -> a.getName()) - .toList(); - - RealmModel realm = session.getContext().getRealm(); - - // TODO: add allowlist check - GroupModel group = realm.getGroupById(groupId); - - Stream users = userProvider.getGroupMembersStream(realm, group); - - List keys = users - .map(user -> { - return attributeNames - .stream() - .map(attributeName -> user.getAttributeStream(attributeName).findFirst()) - .filter(attribute -> attribute.isPresent()) - .map(attribute -> attribute.get()) - .toList(); - }) - .flatMap(List::stream) - .map(key -> { - final Matcher matcher = SSH_PUBLIC_KEY.matcher(key); - return matcher.find() ? matcher.group("key") : null; - }) - .filter(Objects::nonNull) - .toList(); - - return Response.ok(Map.of("keys", keys)).build(); - - } - - private static Auth getAuth(KeycloakSession session) { - AuthResult auth = new AppAuthManager.BearerTokenAuthenticator(session).authenticate(); - - if (auth == null) { - throw new NotAuthorizedException("Bearer"); - } else if (!auth.getToken().getIssuedFor().equals("admin-cli")) { - throw new ForbiddenException(); - } - - RealmModel realm = session.getContext().getRealm(); - ClientModel client = auth.getClient(); - return new Auth(realm, auth.getToken(), auth.getUser(), client, auth.getSession(), false); } } diff --git a/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProviderFactory.java b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProviderFactory.java index d5ac583..4f615d2 100644 --- a/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProviderFactory.java +++ b/ssh-key-provider/src/main/java/de/ccc/hamburg/keycloak/ssh_key/SSHKeyResourceProviderFactory.java @@ -1,12 +1,11 @@ package de.ccc.hamburg.keycloak.ssh_key; -import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.resource.RealmResourceProviderFactory; - import com.google.auto.service.AutoService; +import org.keycloak.Config; @AutoService(RealmResourceProviderFactory.class) public class SSHKeyResourceProviderFactory implements RealmResourceProviderFactory {