diff --git a/src/main/java/de/tavolio/auth/httpmechanism/CustomHttpAuthenticationMechanism.java b/src/main/java/de/tavolio/auth/httpmechanism/CustomHttpAuthenticationMechanism.java new file mode 100644 index 0000000..b41b2ca --- /dev/null +++ b/src/main/java/de/tavolio/auth/httpmechanism/CustomHttpAuthenticationMechanism.java @@ -0,0 +1,79 @@ +package de.tavolio.auth.httpmechanism; + +import de.tavolio.auth.request.JWTRequest; +import de.tavolio.auth.request.UsernamePasswordRequest; +import io.quarkus.security.AuthenticationFailedException; +import io.quarkus.security.identity.IdentityProviderManager; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.vertx.http.runtime.security.ChallengeData; +import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils; +import io.smallrye.mutiny.Uni; +import io.vertx.core.http.HttpHeaders; +import io.vertx.ext.web.RoutingContext; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.commons.lang3.Strings; + +import java.util.Base64; + +@Priority(50) +@ApplicationScoped +public class CustomHttpAuthenticationMechanism implements HttpAuthenticationMechanism +{ + private static final String BASIC = "Basic "; + private static final String BEARER = "Bearer "; + + @Override + public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) + { + String realmKey = getRealmKey(context.request().uri()); + context.put(CustomHttpAuthenticationMechanism.class.getName(), this); + String auth = context.request().headers().get(HttpHeaders.AUTHORIZATION); + if (Strings.CI.startsWith(auth, BASIC)) + { + String decoded = new String(Base64.getDecoder().decode(Strings.CI.removeStart(auth, BASIC))); + String[] sections = decoded.split(":"); + if (sections.length == 2) + { + UsernamePasswordRequest request = new UsernamePasswordRequest(realmKey, sections[0], sections[1]); + HttpSecurityUtils.setRoutingContextAttribute(request, context); + return identityProviderManager.authenticate(request); + } + return Uni.createFrom().failure(new AuthenticationFailedException()); + } + if (Strings.CI.startsWith(auth, BEARER)) + { + JWTRequest request = new JWTRequest(realmKey, Strings.CI.removeStart(auth, BEARER)); + HttpSecurityUtils.setRoutingContextAttribute(request, context); + return identityProviderManager.authenticate(request); + } + return Uni.createFrom().nullItem(); + } + + private String getRealmKey(String uri) + { + String[] sections = uri.split("/"); + for (int i = 0; i < sections.length; i++) + { + if (sections[i].equals("realms")) + { + try + { + return sections[i + 1]; + } + catch (Exception e) + { + return null; + } + } + } + return null; + } + + @Override + public Uni getChallenge(RoutingContext context) + { + return null; + } +} diff --git a/src/main/java/de/tavolio/oidc/identityproviders/BasicAuthIdentityProvider.java b/src/main/java/de/tavolio/auth/identityproviders/BasicAuthIdentityProvider.java similarity index 74% rename from src/main/java/de/tavolio/oidc/identityproviders/BasicAuthIdentityProvider.java rename to src/main/java/de/tavolio/auth/identityproviders/BasicAuthIdentityProvider.java index 5d67b71..9ea2e65 100644 --- a/src/main/java/de/tavolio/oidc/identityproviders/BasicAuthIdentityProvider.java +++ b/src/main/java/de/tavolio/auth/identityproviders/BasicAuthIdentityProvider.java @@ -1,7 +1,8 @@ -package de.tavolio.oidc.identityproviders; +package de.tavolio.auth.identityproviders; import de.tavolio.Role; -import de.tavolio.realm.user.UserRepo; +import de.tavolio.auth.request.UsernamePasswordRequest; +import de.tavolio.realm.RealmService; import de.tavolio.realm.client.ClientEntity; import de.tavolio.realm.client.ClientRepo; import de.tavolio.superuser.SuperuserEntity; @@ -11,7 +12,6 @@ import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.identity.AuthenticationRequestContext; import io.quarkus.security.identity.IdentityProvider; import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.identity.request.UsernamePasswordAuthenticationRequest; import io.quarkus.security.runtime.QuarkusPrincipal; import io.quarkus.security.runtime.QuarkusSecurityIdentity; import io.smallrye.mutiny.Uni; @@ -23,7 +23,7 @@ import jakarta.inject.Inject; @ApplicationScoped @Priority(1) -public class BasicAuthIdentityProvider implements IdentityProvider +public class BasicAuthIdentityProvider implements IdentityProvider { @Inject ClientRepo clientRepo; @@ -31,19 +31,22 @@ public class BasicAuthIdentityProvider implements IdentityProvider getRequestType() + public Class getRequestType() { - return UsernamePasswordAuthenticationRequest.class; + return UsernamePasswordRequest.class; } @Override @ActivateRequestContext - public Uni authenticate(UsernamePasswordAuthenticationRequest usernamePasswordAuthenticationRequest, AuthenticationRequestContext authenticationRequestContext) + public Uni authenticate(UsernamePasswordRequest usernamePasswordAuthenticationRequest, AuthenticationRequestContext authenticationRequestContext) { return Uni.createFrom().item(() -> { String username = usernamePasswordAuthenticationRequest.getUsername(); - String credential = new String(usernamePasswordAuthenticationRequest.getPassword().getPassword()); + String credential = usernamePasswordAuthenticationRequest.getPassword(); SuperuserEntity superuser = superuserRepo.findById(username); if (superuser != null) { @@ -52,7 +55,7 @@ public class BasicAuthIdentityProvider implements IdentityProvider +{ + private static final Logger LOG = LoggerFactory.getLogger(ClientIdentityProvider.class); + + @Inject + JwksService jwksService; + + @Inject + KeypairRepo keypairRepo; + + @Inject + IssuerService issuerService; + + @Inject + ClientService clientService; + + @Override + public Class getRequestType() + { + return JWTRequest.class; + } + + @Override + @ActivateRequestContext + public Uni authenticate(JWTRequest tokenAuthenticationRequest, AuthenticationRequestContext authenticationRequestContext) + { + String raw = tokenAuthenticationRequest.getToken(); + return Uni.createFrom().item(() -> { + String kid = JwtUtils.parseHeader(raw).getKid(); + KeypairEntity keypairEntity = keypairRepo.findById(kid); + if (keypairEntity != null) + { + PublicKey publicKey = jwksService.findByKid(keypairEntity).toPublicKey(); + RealmEntity realm = keypairEntity.getRealm(); + try + { + JsonWebToken token = new DefaultJWTParser(getContextForRealm(realm.getKey())).verify(raw, publicKey); + ClientEntity client = clientService.requireByNameAndRealm(token.getName(), realm); + if (client != null) + { + Set permissions = new HashSet<>(client.getPermissions()); + return (SecurityIdentity) QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal(client.getId())).addRole(Role.CLIENT.toString()).addAttribute("permissions", permissions).build(); + } + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + LOG.error("Cannot find key with id {}", kid); + throw new AuthenticationFailedException(); + }).runSubscriptionOn(Infrastructure.getDefaultWorkerPool()); + } + + private JWTAuthContextInfo getContextForRealm(String realmKey) + { + JWTAuthContextInfo info = new JWTAuthContextInfo(); + info.setIssuedBy(issuerService.getIssuer(realmKey)); + return info; + } +} \ No newline at end of file diff --git a/src/main/java/de/tavolio/auth/request/JWTRequest.java b/src/main/java/de/tavolio/auth/request/JWTRequest.java new file mode 100644 index 0000000..54ccb90 --- /dev/null +++ b/src/main/java/de/tavolio/auth/request/JWTRequest.java @@ -0,0 +1,39 @@ +package de.tavolio.auth.request; + +import io.quarkus.security.identity.request.AuthenticationRequest; + +import java.util.Map; + +public class JWTRequest extends RealmScoped implements AuthenticationRequest +{ + private final String token; + + public JWTRequest(String realmKey, String token) + { + super(realmKey); + this.token = token; + } + + public String getToken() + { + return token; + } + + @Override + public T getAttribute(String name) + { + return null; + } + + @Override + public void setAttribute(String name, Object value) + { + + } + + @Override + public Map getAttributes() + { + return Map.of(); + } +} diff --git a/src/main/java/de/tavolio/auth/request/RealmScoped.java b/src/main/java/de/tavolio/auth/request/RealmScoped.java new file mode 100644 index 0000000..309be8c --- /dev/null +++ b/src/main/java/de/tavolio/auth/request/RealmScoped.java @@ -0,0 +1,16 @@ +package de.tavolio.auth.request; + +public class RealmScoped +{ + private final String realmKey; + + public RealmScoped(String realmKey) + { + this.realmKey = realmKey; + } + + public String getRealmKey() + { + return realmKey; + } +} diff --git a/src/main/java/de/tavolio/auth/request/UsernamePasswordRequest.java b/src/main/java/de/tavolio/auth/request/UsernamePasswordRequest.java new file mode 100644 index 0000000..c4fa21d --- /dev/null +++ b/src/main/java/de/tavolio/auth/request/UsernamePasswordRequest.java @@ -0,0 +1,46 @@ +package de.tavolio.auth.request; + +import io.quarkus.security.identity.request.AuthenticationRequest; + +import java.util.Map; + +public class UsernamePasswordRequest extends RealmScoped implements AuthenticationRequest +{ + private final String username; + private final String password; + + public UsernamePasswordRequest(String realmKey, String username, String password) + { + super(realmKey); + this.username = username; + this.password = password; + } + + public String getUsername() + { + return username; + } + + public String getPassword() + { + return password; + } + + @Override + public T getAttribute(String name) + { + return null; + } + + @Override + public void setAttribute(String name, Object value) + { + + } + + @Override + public Map getAttributes() + { + return Map.of(); + } +} diff --git a/src/main/java/de/tavolio/auth/utils/JwtHeader.java b/src/main/java/de/tavolio/auth/utils/JwtHeader.java new file mode 100644 index 0000000..00ab089 --- /dev/null +++ b/src/main/java/de/tavolio/auth/utils/JwtHeader.java @@ -0,0 +1,17 @@ +package de.tavolio.auth.utils; + +public class JwtHeader +{ + public String kid; + + public String getKid() + { + return kid; + } + + public JwtHeader setKid(String kid) + { + this.kid = kid; + return this; + } +} diff --git a/src/main/java/de/tavolio/auth/utils/JwtUtils.java b/src/main/java/de/tavolio/auth/utils/JwtUtils.java new file mode 100644 index 0000000..02659af --- /dev/null +++ b/src/main/java/de/tavolio/auth/utils/JwtUtils.java @@ -0,0 +1,32 @@ +package de.tavolio.auth.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.security.AuthenticationFailedException; + +import java.util.Base64; +import java.util.Map; + +public class JwtUtils +{ + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + public static JwtHeader parseHeader(String token) + { + String[] sections = token.split("\\."); + if (sections.length == 3) + { + try + { + Map result = OBJECT_MAPPER.readValue(new String(Base64.getUrlDecoder().decode(sections[0])), new TypeReference>(){}); + return new JwtHeader().setKid((String) result.get("kid")); + } + catch (JsonProcessingException e) + { + throw new RuntimeException(e); + } + } + throw new AuthenticationFailedException(); + } +} diff --git a/src/main/java/de/tavolio/bootstrap/Bootstrapper.java b/src/main/java/de/tavolio/bootstrap/Bootstrapper.java new file mode 100644 index 0000000..57aacf6 --- /dev/null +++ b/src/main/java/de/tavolio/bootstrap/Bootstrapper.java @@ -0,0 +1,6 @@ +package de.tavolio.bootstrap; + +public interface Bootstrapper +{ + void run(String key, O object); +} diff --git a/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java b/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java index 9627f66..58de82b 100644 --- a/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java +++ b/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java @@ -39,7 +39,8 @@ public class ClientBootstrapper .setSecret(Credentials.resolve(client.secret())) .setRedirectURI(client.redirectURI()) .setRealm(realm) - .setPermissions(client.permissions()); + .setPermissions(client.permissions()) + .setAllowedGrants(client.allowedGrants()); clientRepo.persist(entity); } } diff --git a/src/main/java/de/tavolio/bootstrap/RealmBootstrapService.java b/src/main/java/de/tavolio/bootstrap/RealmBootstrapService.java index 1b60841..dc6ee85 100644 --- a/src/main/java/de/tavolio/bootstrap/RealmBootstrapService.java +++ b/src/main/java/de/tavolio/bootstrap/RealmBootstrapService.java @@ -4,7 +4,6 @@ import de.tavolio.bootstrap.model.Audience; import de.tavolio.bootstrap.model.Realm; import de.tavolio.realm.RealmEntity; import de.tavolio.realm.RealmRepo; -import de.tavolio.realm.RealmService; import de.tavolio.realm.audience.AudienceStrategyEntity; import de.tavolio.realm.audience.AudienceStrategyRepo; import jakarta.enterprise.context.ApplicationScoped; @@ -17,9 +16,6 @@ import java.util.UUID; @ApplicationScoped public class RealmBootstrapService { - @Inject - RealmService realmService; - @Inject KeyBootstrapper keyBootstrapper; @@ -34,6 +30,8 @@ public class RealmBootstrapService @Inject AudienceStrategyRepo audienceStrategyRepo; + @Inject + RoleBootstrapper roleBootstrapper; public void bootstrap(Map.Entry realmEntry) { @@ -42,6 +40,7 @@ public class RealmBootstrapService keyBootstrapper.bootstrap(realm, realmValue.key()); clientBootstrapper.bootstrap(realm, realmValue.clients()); userBootstrapper.bootstrap(realm, realmValue.users()); + roleBootstrapper.run(realm, realmValue.roles()); } public RealmEntity run(String key, Realm realm) @@ -53,7 +52,7 @@ public class RealmBootstrapService } else { - RealmEntity newRealm = new RealmEntity().setKey(key).setName(realm.name()); + RealmEntity newRealm = new RealmEntity().setKey(key).setName(realm.name()).setLifetime(Optional.ofNullable(realm.lifetime()).orElse(60 * 30)); realmRepo.persist(newRealm); bootstrapAudience(newRealm, realm.audience()); return newRealm; diff --git a/src/main/java/de/tavolio/bootstrap/RoleBootstrapper.java b/src/main/java/de/tavolio/bootstrap/RoleBootstrapper.java new file mode 100644 index 0000000..0709691 --- /dev/null +++ b/src/main/java/de/tavolio/bootstrap/RoleBootstrapper.java @@ -0,0 +1,38 @@ +package de.tavolio.bootstrap; + +import de.tavolio.bootstrap.model.Role; +import de.tavolio.realm.RealmEntity; +import de.tavolio.realm.role.RoleEntity; +import de.tavolio.realm.role.RoleRepo; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.util.List; +import java.util.UUID; + +@ApplicationScoped +public class RoleBootstrapper +{ + @Inject + RoleRepo roleRepo; + + public void run(RealmEntity realm, List roles) + { + for (Role role : roles) + { + run(realm, role); + } + } + + private void run(RealmEntity realm, Role role) + { + RoleEntity existingRole = roleRepo.findByRealmAndName(realm, role.name()); + if (existingRole == null) + { + RoleEntity newRole = new RoleEntity().setId(UUID.randomUUID().toString()); + newRole.setName(role.name()); + newRole.setRealm(realm); + roleRepo.persist(newRole); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/tavolio/bootstrap/model/Client.java b/src/main/java/de/tavolio/bootstrap/model/Client.java index a0622e4..9b7f1bf 100644 --- a/src/main/java/de/tavolio/bootstrap/model/Client.java +++ b/src/main/java/de/tavolio/bootstrap/model/Client.java @@ -1,12 +1,13 @@ package de.tavolio.bootstrap.model; import com.fasterxml.jackson.annotation.JsonProperty; +import de.tavolio.realm.client.Grant; import de.tavolio.realm.user.Permission; import java.util.Set; import java.util.stream.Collectors; -public record Client(@JsonProperty("secret") Credential secret, @JsonProperty("redirect-uri") String redirectURI, @JsonProperty("permissions") Set permissionList) +public record Client(@JsonProperty("secret") Credential secret, @JsonProperty("redirect-uri") String redirectURI, @JsonProperty("permissions") Set permissionList, @JsonProperty("allowed-grants") Set allowedGrants) { public Set permissions() { diff --git a/src/main/java/de/tavolio/bootstrap/model/Realm.java b/src/main/java/de/tavolio/bootstrap/model/Realm.java index 07b97d6..f1db2e9 100644 --- a/src/main/java/de/tavolio/bootstrap/model/Realm.java +++ b/src/main/java/de/tavolio/bootstrap/model/Realm.java @@ -3,6 +3,6 @@ package de.tavolio.bootstrap.model; import java.util.List; import java.util.Map; -public record Realm(String name, Key key, Audience audience, Map clients, Map roles, List permissions, List users) +public record Realm(String name, Key key, Integer lifetime, Audience audience, Map clients, List roles, List permissions, List users) { } diff --git a/src/main/java/de/tavolio/bootstrap/model/Role.java b/src/main/java/de/tavolio/bootstrap/model/Role.java index 32bc2d6..c7bf0de 100644 --- a/src/main/java/de/tavolio/bootstrap/model/Role.java +++ b/src/main/java/de/tavolio/bootstrap/model/Role.java @@ -1,7 +1,5 @@ package de.tavolio.bootstrap.model; -import java.util.List; - -public record Role(List permissions) +public record Role(String name) { -} +} \ No newline at end of file diff --git a/src/main/java/de/tavolio/oidc/GrantType.java b/src/main/java/de/tavolio/oidc/GrantType.java deleted file mode 100644 index a4552e1..0000000 --- a/src/main/java/de/tavolio/oidc/GrantType.java +++ /dev/null @@ -1,30 +0,0 @@ -package de.tavolio.oidc; - -public enum GrantType -{ - AUTH_CODE("authorization_code"), - CLIENT_CREDENTIALS("client_credentials"); - - private final String value; - - GrantType(String value) - { - this.value = value; - } - - public String getValue() - { - return value; - } - - public static GrantType fromValue(String value) - { - for (GrantType type : GrantType.values()) - { - if (type.value.equalsIgnoreCase(value)) - { - return type; - } - } throw new IllegalArgumentException("Unknown grant type: " + value); - } -} \ No newline at end of file diff --git a/src/main/java/de/tavolio/oidc/OidcResource.java b/src/main/java/de/tavolio/oidc/OidcResource.java index ea46199..f2fe7bc 100644 --- a/src/main/java/de/tavolio/oidc/OidcResource.java +++ b/src/main/java/de/tavolio/oidc/OidcResource.java @@ -9,8 +9,9 @@ import de.tavolio.realm.RealmEntity; import de.tavolio.realm.RealmService; import de.tavolio.realm.client.ClientEntity; import de.tavolio.realm.client.ClientService; +import de.tavolio.realm.client.Grant; import io.quarkus.security.Authenticated; -import jakarta.annotation.security.RolesAllowed; +import io.quarkus.security.identity.SecurityIdentity; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; @@ -46,6 +47,9 @@ public class OidcResource @Inject RealmService realmService; + @Inject + SecurityIdentity identity; + @GET @Path("/certs") public Map certs() @@ -58,7 +62,7 @@ public class OidcResource public Response auth(@QueryParam("client_id") String clientId, @FormParam("email") String email, @FormParam("password") String password) { RealmEntity realm = realmService.requireByKey(realmKey); - ClientEntity client = clientService.requireByIdAndRealm(clientId, realm); + ClientEntity client = clientService.requireByNameAndRealm(clientId, realm); if (client != null) { String code = authorizationService.generateBySessionCreation(realmKey, clientId, new AuthorizationCreation(email, password)); @@ -73,14 +77,36 @@ public class OidcResource @Transactional public TokenResponse token(@FormParam("grant_type") String grantType, @FormParam("code") String code) throws NoSuchAlgorithmException, InvalidKeySpecException { - if (GrantType.AUTH_CODE.equals(GrantType.fromValue(grantType))) + Grant grant = getGrant(grantType); + + RealmEntity realm = realmService.getCurrent(); + ClientEntity client = clientService.requireByNameAndRealm(identity.getPrincipal().getName(), realm); + if (!client.getAllowedGrants().contains(grant)) + { + throw new ForbiddenException(); + } + + if (Grant.AUTHORIZATION_CODE.equals(grant)) { return userTokenService.getToken(realmKey, code); } - if (GrantType.CLIENT_CREDENTIALS.equals(GrantType.fromValue(grantType))) + if (Grant.CLIENT_CREDENTIALS.equals(grant)) { return clientTokenService.getToken(realmKey); } throw new RuntimeException(); } + + private Grant getGrant(String grantType) + { + if ("authorization_code".equals(grantType)) + { + return Grant.AUTHORIZATION_CODE; + } + if ("client_credentials".equals(grantType)) + { + return Grant.CLIENT_CREDENTIALS; + } + throw new RuntimeException(); + } } diff --git a/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java b/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java index f2f3603..58bf542 100644 --- a/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java +++ b/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java @@ -39,7 +39,7 @@ public class AuthorizationService { RealmEntity realm = realmService.requireByKey(realmKey); - ClientEntity client = clientService.requireByIdAndRealm(clientId, realm); + ClientEntity client = clientService.requireByNameAndRealm(clientId, realm); Optional accountEntityOptional = userRepo.findOptionalByRealmAndEmail(realm, authorizationCreation.email()); if (accountEntityOptional.isPresent()) { diff --git a/src/main/java/de/tavolio/oidc/identityproviders/ClientIdentityProvider.java b/src/main/java/de/tavolio/oidc/identityproviders/ClientIdentityProvider.java deleted file mode 100644 index 9cf2855..0000000 --- a/src/main/java/de/tavolio/oidc/identityproviders/ClientIdentityProvider.java +++ /dev/null @@ -1,127 +0,0 @@ -package de.tavolio.oidc.identityproviders; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.tavolio.Role; -import de.tavolio.oidc.IssuerService; -import de.tavolio.realm.RealmEntity; -import de.tavolio.realm.client.ClientEntity; -import de.tavolio.realm.client.ClientService; -import de.tavolio.realm.key.KeypairEntity; -import de.tavolio.realm.key.KeypairRepo; -import de.tavolio.realm.user.Permission; -import de.tavolio.verify.JwksService; -import io.quarkus.security.AuthenticationFailedException; -import io.quarkus.security.identity.AuthenticationRequestContext; -import io.quarkus.security.identity.IdentityProvider; -import io.quarkus.security.identity.SecurityIdentity; -import io.quarkus.security.identity.request.TokenAuthenticationRequest; -import io.quarkus.security.runtime.QuarkusPrincipal; -import io.quarkus.security.runtime.QuarkusSecurityIdentity; -import io.smallrye.jwt.auth.principal.DefaultJWTParser; -import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; -import io.smallrye.jwt.auth.principal.ParseException; -import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.infrastructure.Infrastructure; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.control.ActivateRequestContext; -import jakarta.inject.Inject; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.PublicKey; -import java.util.*; - -@ApplicationScoped -@Priority(1) -public class ClientIdentityProvider implements IdentityProvider -{ - private static final Logger LOG = LoggerFactory.getLogger(ClientIdentityProvider.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - @Inject - JwksService jwksService; - - @Inject - KeypairRepo keypairRepo; - - @Inject - IssuerService issuerService; - - @Inject - ClientService clientService; - - @Override - public Class getRequestType() - { - return TokenAuthenticationRequest.class; - } - - @Override - @ActivateRequestContext - public Uni authenticate(TokenAuthenticationRequest tokenAuthenticationRequest, AuthenticationRequestContext authenticationRequestContext) - { - String raw = tokenAuthenticationRequest.getToken().getToken(); - try - { - Object kid = getHeader(raw).get("kid"); - if (kid instanceof String keyId) - { - return Uni.createFrom().item(() -> { - KeypairEntity keypairEntity = keypairRepo.findById(keyId); - if (keypairEntity != null) - { - PublicKey publicKey = jwksService.findByKid(keypairEntity).toPublicKey(); - RealmEntity realm = keypairEntity.getRealm(); - try - { - JsonWebToken token = new DefaultJWTParser(getContextForRealm(realm.getKey())).verify(raw, publicKey); - ClientEntity client = clientService.requireByIdAndRealm(token.getName(), realm); - if (client != null) - { - Set permissions = new HashSet<>(client.getPermissions()); - return (SecurityIdentity) QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal(client.getId())).addRole(Role.CLIENT.toString()).addAttribute("permissions", permissions).build(); - } - } - catch (ParseException e) - { - throw new RuntimeException(e); - } - } - LOG.error("Cannot find key with id {}", kid); - throw new AuthenticationFailedException(); - }).runSubscriptionOn(Infrastructure.getDefaultWorkerPool()); - } - return Uni.createFrom().nullItem(); - } - catch (JsonProcessingException e) - { - return Uni.createFrom().failure(new AuthenticationFailedException()); - } - } - - private Map getHeader(String raw) throws JsonProcessingException - { - return OBJECT_MAPPER.readValue(new String(Base64.getUrlDecoder().decode(section(raw))), new TypeReference>(){}); - } - - private String section(String raw) - { - String[] sections = raw.split("\\."); - if (sections.length == 3) - { - return sections[0]; - } - throw new RuntimeException(); - } - - private JWTAuthContextInfo getContextForRealm(String realmKey) - { - JWTAuthContextInfo info = new JWTAuthContextInfo(); - info.setIssuedBy(issuerService.getIssuer(realmKey)); - return info; - } -} \ No newline at end of file diff --git a/src/main/java/de/tavolio/oidc/token/ClientTokenService.java b/src/main/java/de/tavolio/oidc/token/ClientTokenService.java index 71f7cd5..777b1a2 100644 --- a/src/main/java/de/tavolio/oidc/token/ClientTokenService.java +++ b/src/main/java/de/tavolio/oidc/token/ClientTokenService.java @@ -32,13 +32,13 @@ public class ClientTokenService public TokenResponse getToken(String realmKey) { RealmEntity realm = realmService.requireByKey(realmKey); - ClientEntity client = clientService.requireByIdAndRealm(identity.getPrincipal().getName(), realm); + ClientEntity client = clientService.requireByNameAndRealm(identity.getPrincipal().getName(), realm); if (client != null) { KeypairEntity keypair = realm.getKeys().getFirst(); PrivateKey signingKey = KeypairEntity.toPrivateKey(keypair); ZonedDateTime expiresAt = ZonedDateTime.now().plusYears(1); - String token = clientTokenGenerator.generateAccessToken(realmKey, client.getId(), expiresAt, signingKey, keypair.getId()); + String token = clientTokenGenerator.generateAccessToken(realmKey, client.getName(), expiresAt, signingKey, keypair.getId()); return new TokenResponse().setAccessToken(token).setTokenType("Bearer").setExpiresAt(expiresAt.toInstant().getEpochSecond()); } throw new BadRequestException(); diff --git a/src/main/java/de/tavolio/oidc/token/UserTokenService.java b/src/main/java/de/tavolio/oidc/token/UserTokenService.java index 3e0e9a3..719edb4 100644 --- a/src/main/java/de/tavolio/oidc/token/UserTokenService.java +++ b/src/main/java/de/tavolio/oidc/token/UserTokenService.java @@ -47,11 +47,11 @@ public class UserTokenService String principal = identity.getPrincipal().getName(); RealmEntity realm = realmService.requireByKey(realmKey); CodeEntity entity = codeRepo.findByRealmAndId(realm, code); - if (entity != null && !ZonedDateTime.now().isAfter(entity.getExpiresAt()) && principal.equals(entity.getClient().getId())) + if (entity != null && !ZonedDateTime.now().isAfter(entity.getExpiresAt()) && principal.equals(entity.getClient().getName())) { KeypairEntity keypair = realm.getKeys().getFirst(); PrivateKey signingKey = KeypairEntity.toPrivateKey(keypair); - ZonedDateTime expiresAt = ZonedDateTime.now().plusYears(1); + ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(realm.getLifetime()); TokenResponse response = new TokenResponse() .setAccessToken(userTokenGenerator.generateAccessToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId())) .setRefreshToken(UUID.randomUUID().toString()) diff --git a/src/main/java/de/tavolio/realm/Realm.java b/src/main/java/de/tavolio/realm/Realm.java index 95037bd..c6d9f89 100644 --- a/src/main/java/de/tavolio/realm/Realm.java +++ b/src/main/java/de/tavolio/realm/Realm.java @@ -1,5 +1,5 @@ package de.tavolio.realm; -public record Realm(String key, String name) +public record Realm(String key, String name, int lifetime) { } diff --git a/src/main/java/de/tavolio/realm/RealmEntity.java b/src/main/java/de/tavolio/realm/RealmEntity.java index 6f6f187..fff5727 100644 --- a/src/main/java/de/tavolio/realm/RealmEntity.java +++ b/src/main/java/de/tavolio/realm/RealmEntity.java @@ -6,6 +6,7 @@ import de.tavolio.realm.user.UserEntity; import de.tavolio.realm.client.ClientEntity; import de.tavolio.realm.code.CodeEntity; import de.tavolio.realm.role.RoleEntity; +import jakarta.enterprise.inject.Vetoed; import jakarta.persistence.*; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; @@ -15,6 +16,7 @@ import java.util.List; @Entity @Table(name = "realm") +@Vetoed public class RealmEntity { @Id @@ -26,6 +28,8 @@ public class RealmEntity @Column(name = "realm_name") private String name; + private int lifetime; + @OneToMany(mappedBy = "realm") @OnDelete(action = OnDeleteAction.CASCADE) private List keys = new ArrayList<>(); @@ -83,6 +87,17 @@ public class RealmEntity return this; } + public int getLifetime() + { + return lifetime; + } + + public RealmEntity setLifetime(int lifetime) + { + this.lifetime = lifetime; + return this; + } + public List getKeys() { return keys; diff --git a/src/main/java/de/tavolio/realm/RealmMapper.java b/src/main/java/de/tavolio/realm/RealmMapper.java new file mode 100644 index 0000000..f2dde1d --- /dev/null +++ b/src/main/java/de/tavolio/realm/RealmMapper.java @@ -0,0 +1,16 @@ +package de.tavolio.realm; + +import java.util.List; + +public class RealmMapper +{ + public static List map(List entities) + { + return entities.stream().map(RealmMapper::map).toList(); + } + + public static Realm map(RealmEntity entity) + { + return new Realm(entity.getKey(), entity.getName(), entity.getLifetime()); + } +} diff --git a/src/main/java/de/tavolio/realm/RealmResource.java b/src/main/java/de/tavolio/realm/RealmResource.java index 4493d68..2d3b9a9 100644 --- a/src/main/java/de/tavolio/realm/RealmResource.java +++ b/src/main/java/de/tavolio/realm/RealmResource.java @@ -3,6 +3,8 @@ package de.tavolio.realm; import de.tavolio.Role; import de.tavolio.oidc.OidcConfigurationResource; import de.tavolio.oidc.OidcResource; +import de.tavolio.realm.assignment.Assignment; +import de.tavolio.realm.assignment.AssignmentResource; import de.tavolio.realm.client.ClientResource; import de.tavolio.realm.role.RoleResource; import de.tavolio.realm.user.UserResource; @@ -11,6 +13,7 @@ import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.inject.spi.CDI; import jakarta.inject.Inject; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import java.util.List; @@ -21,19 +24,28 @@ public class RealmResource @Inject RealmRepo repo; + @Inject + RealmService realmService; + @Path("/{realm-key}") public RealmSubResource realms() { return CDI.current().select(RealmSubResource.class).get(); } - @RolesAllowed("ROOT") + @RolesAllowed({"ROOT", "CLIENT"}) @Path("/{realm-key}/users") public UserResource accounts() { return CDI.current().select(UserResource.class).get(); } + @Path("/{realm-key}/users/{user-id}/role-assignments") + public AssignmentResource assignments() + { + return CDI.current().select(AssignmentResource.class).get(); + } + @RolesAllowed("ROOT") @Path("/{realm-key}/clients") public ClientResource clients() @@ -63,6 +75,13 @@ public class RealmResource @GET public List get() { - return repo.listAll().stream().map(item -> new Realm(item.getKey(), item.getName())).toList(); + return RealmMapper.map(repo.listAll()); + } + + @POST + @RolesAllowed("ROOT") + public Realm post(RealmCreation realmCreation) + { + return realmService.create(realmCreation); } } diff --git a/src/main/java/de/tavolio/realm/RealmService.java b/src/main/java/de/tavolio/realm/RealmService.java index ba8fbcc..333c829 100644 --- a/src/main/java/de/tavolio/realm/RealmService.java +++ b/src/main/java/de/tavolio/realm/RealmService.java @@ -1,11 +1,13 @@ package de.tavolio.realm; -import de.tavolio.bootstrap.model.Realm; import de.tavolio.realm.key.KeypairEntity; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; +import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -22,6 +24,9 @@ public class RealmService @Inject RealmRepo repo; + @Inject + UriInfo context; + public RealmEntity requireByKey(String key) { RealmEntity realm = repo.findByKey(key); @@ -32,13 +37,23 @@ public class RealmService throw new NotFoundException(); } + public RealmEntity getCurrent() + { + String realmKey = context.getPathParameters().getFirst("realm-key"); + if (!StringUtils.isBlank(realmKey)) + { + return requireByKey(realmKey); + } + throw new InternalServerErrorException(); + } + @Transactional - public RealmEntity create(RealmCreation creation) + public Realm create(RealmCreation creation) { RealmEntity realm = new RealmEntity(); realm.setKey(creation.key()); realm.setName(creation.name()); repo.persist(realm); - return realm; + return RealmMapper.map(realm); } } diff --git a/src/main/java/de/tavolio/realm/RealmSubResource.java b/src/main/java/de/tavolio/realm/RealmSubResource.java index 15d64a1..3634f1d 100644 --- a/src/main/java/de/tavolio/realm/RealmSubResource.java +++ b/src/main/java/de/tavolio/realm/RealmSubResource.java @@ -4,16 +4,15 @@ import io.quarkus.security.Authenticated; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; +import jakarta.transaction.Transactional; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.PathParam; @RequestScoped public class RealmSubResource { - @PathParam("realm-key") - String key; - @Inject RealmRepo repo; @@ -23,14 +22,14 @@ public class RealmSubResource @GET public Realm get() { - RealmEntity realm = service.requireByKey(key); - return new Realm(realm.getKey(), realm.getName()); + return RealmMapper.map(service.getCurrent()); } @RolesAllowed("ROOT") @DELETE + @Transactional public void delete() { - repo.delete(service.requireByKey(key)); + repo.delete(service.getCurrent()); } } diff --git a/src/main/java/de/tavolio/realm/assignment/Assignment.java b/src/main/java/de/tavolio/realm/assignment/Assignment.java new file mode 100644 index 0000000..d671717 --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/Assignment.java @@ -0,0 +1,9 @@ +package de.tavolio.realm.assignment; + +import de.tavolio.realm.role.Role; + +import java.util.List; + +public record Assignment(List assigned, List unassigned) +{ +} \ No newline at end of file diff --git a/src/main/java/de/tavolio/realm/assignment/AssignmentEntity.java b/src/main/java/de/tavolio/realm/assignment/AssignmentEntity.java new file mode 100644 index 0000000..d68907f --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/AssignmentEntity.java @@ -0,0 +1,54 @@ +package de.tavolio.realm.assignment; + +import de.tavolio.realm.role.RoleEntity; +import de.tavolio.realm.user.UserEntity; +import jakarta.persistence.*; + +@Entity +@Table(name = "assignment") +public class AssignmentEntity +{ + @Id + private String id; + + @ManyToOne + @JoinColumn(name = "user_id") + private UserEntity user; + + @ManyToOne + @JoinColumn(name = "role_id") + private RoleEntity role; + + public String getId() + { + return id; + } + + public AssignmentEntity setId(String id) + { + this.id = id; + return this; + } + + public UserEntity getUser() + { + return user; + } + + public AssignmentEntity setUser(UserEntity user) + { + this.user = user; + return this; + } + + public RoleEntity getRole() + { + return role; + } + + public AssignmentEntity setRole(RoleEntity role) + { + this.role = role; + return this; + } +} diff --git a/src/main/java/de/tavolio/realm/assignment/AssignmentRepo.java b/src/main/java/de/tavolio/realm/assignment/AssignmentRepo.java new file mode 100644 index 0000000..2361718 --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/AssignmentRepo.java @@ -0,0 +1,19 @@ +package de.tavolio.realm.assignment; + +import de.tavolio.realm.RealmEntity; +import de.tavolio.realm.role.RoleEntity; +import de.tavolio.realm.user.UserEntity; +import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; +import io.quarkus.panache.common.Parameters; +import jakarta.enterprise.context.ApplicationScoped; + +import java.util.List; + +@ApplicationScoped +public class AssignmentRepo implements PanacheRepositoryBase +{ + public AssignmentEntity findByRoleAndUser(RoleEntity role, UserEntity user) + { + return find("role = :role AND user = :user", Parameters.with("role", role).and("user", user)).firstResult(); + } +} diff --git a/src/main/java/de/tavolio/realm/assignment/AssignmentRequest.java b/src/main/java/de/tavolio/realm/assignment/AssignmentRequest.java new file mode 100644 index 0000000..6aaad55 --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/AssignmentRequest.java @@ -0,0 +1,7 @@ +package de.tavolio.realm.assignment; + +import java.util.List; + +public record AssignmentRequest(List assign, List unassign) +{ +} diff --git a/src/main/java/de/tavolio/realm/assignment/AssignmentResource.java b/src/main/java/de/tavolio/realm/assignment/AssignmentResource.java new file mode 100644 index 0000000..1e484f3 --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/AssignmentResource.java @@ -0,0 +1,34 @@ +package de.tavolio.realm.assignment; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; + +import java.util.List; + +@ApplicationScoped +public class AssignmentResource +{ + @Inject + AssignmentService assignmentService; + + @GET + public Assignment get(@PathParam("user-id") String userId) + { + return assignmentService.get(userId); + } + + @PUT + public void post(@PathParam("user-id") String userId, AssignmentRequest request) + { + if (request.assign() != null) + { + assignmentService.assign(userId, request.assign()); + } + if (request.unassign() != null) + { + assignmentService.unassign(userId, request.unassign()); + } + } +} diff --git a/src/main/java/de/tavolio/realm/assignment/AssignmentService.java b/src/main/java/de/tavolio/realm/assignment/AssignmentService.java new file mode 100644 index 0000000..25eba97 --- /dev/null +++ b/src/main/java/de/tavolio/realm/assignment/AssignmentService.java @@ -0,0 +1,98 @@ +package de.tavolio.realm.assignment; + +import de.tavolio.realm.RealmEntity; +import de.tavolio.realm.RealmService; +import de.tavolio.realm.role.Role; +import de.tavolio.realm.role.RoleEntity; +import de.tavolio.realm.role.RoleRepo; +import de.tavolio.realm.role.RoleService; +import de.tavolio.realm.user.UserEntity; +import de.tavolio.realm.user.UserRepo; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +@ApplicationScoped +public class AssignmentService +{ + @Inject + RoleRepo roleRepo; + + @Inject + RealmService realmService; + + @Inject + UserRepo userRepo; + + @Inject + AssignmentRepo assignmentRepo; + + public Assignment get(String userId) + { + RealmEntity realm = realmService.getCurrent(); + UserEntity user = userRepo.findByRealmAndId(realm, userId); + List assigned = new LinkedList<>(); + List unassigned = new LinkedList<>(); + for (RoleEntity role : roleRepo.findByRealm(realm)) + { + if (isAssigned(user, role)) + { + assigned.add(new Role(role.getId(), role.getName())); + } + else + { + unassigned.add(new Role(role.getId(), role.getName())); + } + } + return new Assignment(assigned, unassigned); + } + + @Transactional + public void assign(String userId, List roles) + { + RealmEntity realm = realmService.getCurrent(); + UserEntity user = userRepo.findByRealmAndId(realm, userId); + for (String roleName : roles) + { + RoleEntity role = roleRepo.findByRealmAndName(realm, roleName); + assign(user, role); + } + } + + public void assign(UserEntity user, RoleEntity role) + { + AssignmentEntity assignment = new AssignmentEntity().setId(UUID.randomUUID().toString()); + assignment.setUser(user); + assignment.setRole(role); + assignmentRepo.persist(assignment); + } + + @Transactional + public void unassign(String userId, List roles) + { + RealmEntity realm = realmService.getCurrent(); + UserEntity user = userRepo.findByRealmAndId(realm, userId); + for (String roleName : roles) + { + RoleEntity role = roleRepo.findByRealmAndName(realm, roleName); + AssignmentEntity assignment = assignmentRepo.findByRoleAndUser(role, user); + assignmentRepo.delete(assignment); + } + } + + private boolean isAssigned(UserEntity user, RoleEntity role) + { + for (AssignmentEntity assignment : role.getAssignments()) + { + if (assignment.getUser().getId().equals(user.getId())) + { + return true; + } + } + return false; + } +} diff --git a/src/main/java/de/tavolio/realm/client/ClientEntity.java b/src/main/java/de/tavolio/realm/client/ClientEntity.java index 0854f6f..455e485 100644 --- a/src/main/java/de/tavolio/realm/client/ClientEntity.java +++ b/src/main/java/de/tavolio/realm/client/ClientEntity.java @@ -43,7 +43,7 @@ public class ClientEntity implements RealmScoped @CollectionTable(name = "allowed_grants", joinColumns = @JoinColumn(name = "client_id")) @Column(name = "grant_name") @Enumerated(EnumType.STRING) - private Set allowedGrants = new HashSet(); + private Set allowedGrants = new HashSet<>(); public String getId() { diff --git a/src/main/java/de/tavolio/realm/client/ClientService.java b/src/main/java/de/tavolio/realm/client/ClientService.java index ad0d0b2..561c781 100644 --- a/src/main/java/de/tavolio/realm/client/ClientService.java +++ b/src/main/java/de/tavolio/realm/client/ClientService.java @@ -1,13 +1,11 @@ package de.tavolio.realm.client; import de.tavolio.realm.RealmEntity; -import de.tavolio.realm.RealmRepo; import de.tavolio.realm.RealmService; import io.quarkus.elytron.security.common.BcryptUtil; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.transaction.Transactional; -import jakarta.ws.rs.DELETE; import jakarta.ws.rs.NotFoundException; import org.apache.commons.lang3.StringUtils; @@ -22,9 +20,9 @@ public class ClientService @Inject RealmService realmService; - public ClientEntity requireByIdAndRealm(String clientId, RealmEntity realm) + public ClientEntity requireByNameAndRealm(String clientId, RealmEntity realm) { - ClientEntity client = clientRepo.findByRealmAndId(realm, clientId); + ClientEntity client = clientRepo.findByRealmAndName(realm, clientId); if (client != null) { return client; diff --git a/src/main/java/de/tavolio/realm/client/Grant.java b/src/main/java/de/tavolio/realm/client/Grant.java index 818f3dd..938fa3b 100644 --- a/src/main/java/de/tavolio/realm/client/Grant.java +++ b/src/main/java/de/tavolio/realm/client/Grant.java @@ -2,5 +2,5 @@ package de.tavolio.realm.client; public enum Grant { - CLIENT_CREDENTIALS, AUTHORIZATION, PASSWORD + CLIENT_CREDENTIALS, AUTHORIZATION_CODE, PASSWORD } diff --git a/src/main/java/de/tavolio/realm/role/RoleEntity.java b/src/main/java/de/tavolio/realm/role/RoleEntity.java index b79eb11..a9bdfe4 100644 --- a/src/main/java/de/tavolio/realm/role/RoleEntity.java +++ b/src/main/java/de/tavolio/realm/role/RoleEntity.java @@ -2,6 +2,7 @@ package de.tavolio.realm.role; import de.tavolio.realm.RealmEntity; import de.tavolio.realm.RealmScoped; +import de.tavolio.realm.assignment.AssignmentEntity; import jakarta.persistence.*; import java.util.ArrayList; @@ -21,6 +22,9 @@ public class RoleEntity implements RealmScoped @JoinColumn(name = "realm_id") private RealmEntity realm; + @OneToMany(mappedBy = "role") + private List assignments; + public String getId() { return id; @@ -53,4 +57,15 @@ public class RoleEntity implements RealmScoped this.realm = realm; return this; } + + public List getAssignments() + { + return assignments; + } + + public RoleEntity setAssignments(List assignments) + { + this.assignments = assignments; + return this; + } } diff --git a/src/main/java/de/tavolio/realm/role/RoleRepo.java b/src/main/java/de/tavolio/realm/role/RoleRepo.java index 581704e..4051a83 100644 --- a/src/main/java/de/tavolio/realm/role/RoleRepo.java +++ b/src/main/java/de/tavolio/realm/role/RoleRepo.java @@ -15,4 +15,9 @@ public class RoleRepo implements PanacheRepositoryBase { return list("realm = :realm", Parameters.with("realm", realm)); } + + public RoleEntity findByRealmAndName(RealmEntity realm, String name) + { + return find("realm = :realm AND name = :name", Parameters.with("realm", realm).and("name", name)).firstResult(); + } } diff --git a/src/main/java/de/tavolio/realm/user/Permission.java b/src/main/java/de/tavolio/realm/user/Permission.java index 138ab93..41b149d 100644 --- a/src/main/java/de/tavolio/realm/user/Permission.java +++ b/src/main/java/de/tavolio/realm/user/Permission.java @@ -2,5 +2,5 @@ package de.tavolio.realm.user; public enum Permission { - USER_VIEW, USER_CREATE + USER_VIEW, USER_DELETE, USER_CREATE } diff --git a/src/main/java/de/tavolio/realm/user/UserEntity.java b/src/main/java/de/tavolio/realm/user/UserEntity.java index d09908f..4dcf59c 100644 --- a/src/main/java/de/tavolio/realm/user/UserEntity.java +++ b/src/main/java/de/tavolio/realm/user/UserEntity.java @@ -1,6 +1,7 @@ package de.tavolio.realm.user; import de.tavolio.realm.RealmScoped; +import de.tavolio.realm.assignment.AssignmentEntity; import de.tavolio.realm.code.CodeEntity; import de.tavolio.realm.RealmEntity; import jakarta.persistence.*; @@ -34,6 +35,9 @@ public class UserEntity implements RealmScoped @OneToMany(mappedBy = "account") private List codes; + @OneToMany(mappedBy = "user") + private List assignments; + public static UserEntity init() { return new UserEntity().setId(UUID.randomUUID().toString()); @@ -126,4 +130,15 @@ public class UserEntity implements RealmScoped this.codes = codes; return this; } + + public List getAssignments() + { + return assignments; + } + + public UserEntity setAssignments(List assignments) + { + this.assignments = assignments; + return this; + } } diff --git a/src/main/java/de/tavolio/realm/user/UserResource.java b/src/main/java/de/tavolio/realm/user/UserResource.java index 4e7c8ec..036610b 100644 --- a/src/main/java/de/tavolio/realm/user/UserResource.java +++ b/src/main/java/de/tavolio/realm/user/UserResource.java @@ -53,7 +53,7 @@ public class UserResource @Path("/{id}") public void delete(@PathParam("id") String id) { - permissionService.hasPermission(Permission.USER_VIEW); + permissionService.hasPermission(Permission.USER_DELETE); userService.delete(realmKey, id); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 76b43f1..02cab90 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,7 +25,7 @@ quarkus.flyway.enabled=false quarkus.flyway.migrate-at-start=true quarkus.http.access-log.enabled=true -quarkus.http.auth.basic=true +quarkus.http.auth.basic=false %dev.io.verifoo.http.origin=http://localhost:8089 diff --git a/src/main/resources/bootstrap.yaml b/src/main/resources/bootstrap.yaml index 691431f..8c0c5c2 100644 --- a/src/main/resources/bootstrap.yaml +++ b/src/main/resources/bootstrap.yaml @@ -8,17 +8,22 @@ realms: key: type: EC alg: P256 + roles: + - name: ADMIN + - name: USER clients: backend: secret: bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX. - redirect-uri: http://localhost:8080/callback + redirect-uri: http://localhost:8080/auth/callback permissions: - USER_VIEW + allowed-grants: + - AUTHORIZATION_CODE analytics-backend: secret: bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX. - redirect-uri: http://localhost:8080/callback + redirect-uri: http://localhost:8080/auth/callback permissions: - USER_VIEW users: