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 index cf3091d..25af562 100644 --- 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 @@ -1,7 +1,7 @@ package de.ccc.hamburg.keycloak.ssh_key; -import jakarta.ws.rs.ForbiddenException; -import jakarta.ws.rs.NotAuthorizedException; +import java.util.function.Function; + import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -9,7 +9,8 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; -import java.util.function.Function; +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotAuthorizedException; public class AuthHelper { @@ -47,4 +48,4 @@ public class AuthHelper { 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 fb5c2d4..000abd2 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,27 +1,37 @@ 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 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.services.managers.Auth; +import org.keycloak.representations.userprofile.config.UPConfig; import org.keycloak.services.resource.RealmResourceProvider; +import org.keycloak.userprofile.UserProfileProvider; + +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.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; } @@ -40,26 +50,43 @@ public class SSHKeyResourceProvider implements RealmResourceProvider { @Produces(MediaType.APPLICATION_JSON) public Response exportKeys(@PathParam("group_id") String groupId) { UserProvider userProvider = session.users(); + UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class); + UPConfig upconfig = profileProvider.getConfiguration(); + + Stream attributeNames = upconfig.getAttributes() + .stream() + .filter(a -> a.getGroup() != null && a.getGroup().equals("de.ccc.hamburg.keycloak.ssh_key.keys")) + .map(a -> a.getName()); try { - Auth auth = AuthHelper.getAuth(session, + 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)); - }); + List keys = users + .map(user -> { + return attributeNames + .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("hello", auth.getUser().getUsername())).build(); + return Response.ok(Map.of("keys", keys)).build(); } catch (Exception e) { System.err.println(e); return Response.status(401, e.getMessage()).build(); 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 4f615d2..d5ac583 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,11 +1,12 @@ 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 {