diff --git a/pom.xml b/pom.xml index 93eb0bf..d2164a8 100644 --- a/pom.xml +++ b/pom.xml @@ -63,12 +63,16 @@ quarkus-hibernate-validator - io.quarkus - quarkus-smallrye-jwt + io.smallrye + smallrye-jwt + 4.6.3 + compile io.quarkus quarkus-smallrye-jwt-build + 3.32.3 + compile io.quarkus diff --git a/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java b/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java index 722a6e1..9627f66 100644 --- a/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java +++ b/src/main/java/de/tavolio/bootstrap/ClientBootstrapper.java @@ -10,13 +10,11 @@ import jakarta.inject.Inject; import java.util.Map; import java.util.Optional; +import java.util.UUID; @ApplicationScoped public class ClientBootstrapper { - @Inject - ClientService clientService; - @Inject ClientRepo clientRepo; @@ -33,9 +31,11 @@ public class ClientBootstrapper Optional existingClient = clientRepo.findByRealmAndIdOptional(realm, bootstrap.getKey()); if (existingClient.isEmpty()) { - String id = bootstrap.getKey(); + String name = bootstrap.getKey(); Client client = bootstrap.getValue(); - ClientEntity entity = new ClientEntity().setId(id) + ClientEntity entity = new ClientEntity() + .setId(UUID.randomUUID().toString().replace("-", "").substring(0, 16)) + .setName(name) .setSecret(Credentials.resolve(client.secret())) .setRedirectURI(client.redirectURI()) .setRealm(realm) diff --git a/src/main/java/de/tavolio/oidc/OidcResource.java b/src/main/java/de/tavolio/oidc/OidcResource.java index 5b80c5a..ea46199 100644 --- a/src/main/java/de/tavolio/oidc/OidcResource.java +++ b/src/main/java/de/tavolio/oidc/OidcResource.java @@ -9,6 +9,7 @@ import de.tavolio.realm.RealmEntity; import de.tavolio.realm.RealmService; import de.tavolio.realm.client.ClientEntity; import de.tavolio.realm.client.ClientService; +import io.quarkus.security.Authenticated; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; @@ -57,7 +58,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.findByIdAndRealm(clientId, realm); + ClientEntity client = clientService.requireByIdAndRealm(clientId, realm); if (client != null) { String code = authorizationService.generateBySessionCreation(realmKey, clientId, new AuthorizationCreation(email, password)); @@ -66,9 +67,9 @@ public class OidcResource throw new BadRequestException(); } + @Authenticated @POST @Path("/token") - @RolesAllowed("CLIENT") @Transactional public TokenResponse token(@FormParam("grant_type") String grantType, @FormParam("code") String code) throws NoSuchAlgorithmException, InvalidKeySpecException { diff --git a/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java b/src/main/java/de/tavolio/oidc/auth/AuthorizationService.java index a98e9e7..f2f3603 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.findByIdAndRealm(clientId, realm); + ClientEntity client = clientService.requireByIdAndRealm(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 index 0158fb6..9cf2855 100644 --- a/src/main/java/de/tavolio/oidc/identityproviders/ClientIdentityProvider.java +++ b/src/main/java/de/tavolio/oidc/identityproviders/ClientIdentityProvider.java @@ -12,9 +12,7 @@ import de.tavolio.realm.key.KeypairEntity; import de.tavolio.realm.key.KeypairRepo; import de.tavolio.realm.user.Permission; import de.tavolio.verify.JwksService; -import de.tavolio.verify.jwks.JwksKey; import io.quarkus.security.AuthenticationFailedException; -import io.quarkus.security.StringPermission; import io.quarkus.security.identity.AuthenticationRequestContext; import io.quarkus.security.identity.IdentityProvider; import io.quarkus.security.identity.SecurityIdentity; @@ -36,7 +34,6 @@ import org.slf4j.LoggerFactory; import java.security.PublicKey; import java.util.*; -import java.util.stream.Collectors; @ApplicationScoped @Priority(1) @@ -82,7 +79,7 @@ public class ClientIdentityProvider implements IdentityProvider permissions = new HashSet<>(client.getPermissions()); diff --git a/src/main/java/de/tavolio/oidc/token/ClientTokenService.java b/src/main/java/de/tavolio/oidc/token/ClientTokenService.java index 5173e4f..71f7cd5 100644 --- a/src/main/java/de/tavolio/oidc/token/ClientTokenService.java +++ b/src/main/java/de/tavolio/oidc/token/ClientTokenService.java @@ -11,9 +11,7 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.BadRequestException; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.spec.InvalidKeySpecException; import java.time.ZonedDateTime; @ApplicationScoped @@ -34,7 +32,7 @@ public class ClientTokenService public TokenResponse getToken(String realmKey) { RealmEntity realm = realmService.requireByKey(realmKey); - ClientEntity client = clientService.findByIdAndRealm(identity.getPrincipal().getName(), realm); + ClientEntity client = clientService.requireByIdAndRealm(identity.getPrincipal().getName(), realm); if (client != null) { KeypairEntity keypair = realm.getKeys().getFirst(); diff --git a/src/main/java/de/tavolio/realm/PermissionService.java b/src/main/java/de/tavolio/realm/PermissionService.java index a6d937e..c3b224b 100644 --- a/src/main/java/de/tavolio/realm/PermissionService.java +++ b/src/main/java/de/tavolio/realm/PermissionService.java @@ -6,6 +6,7 @@ import io.quarkus.security.ForbiddenException; import io.quarkus.security.identity.SecurityIdentity; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; import java.util.Set; @@ -15,8 +16,15 @@ public class PermissionService @Inject SecurityIdentity identity; + @ConfigProperty(name = "quarkus.profile") + String profile; + public void hasPermission(Permission... required) { + if ("dev".equals(profile)) + { + return; + } if (identity.hasRole(Role.ROOT.toString())) { return; diff --git a/src/main/java/de/tavolio/realm/RealmEntity.java b/src/main/java/de/tavolio/realm/RealmEntity.java index cfa3d21..6f6f187 100644 --- a/src/main/java/de/tavolio/realm/RealmEntity.java +++ b/src/main/java/de/tavolio/realm/RealmEntity.java @@ -7,6 +7,8 @@ import de.tavolio.realm.client.ClientEntity; import de.tavolio.realm.code.CodeEntity; import de.tavolio.realm.role.RoleEntity; import jakarta.persistence.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import java.util.ArrayList; import java.util.List; @@ -16,29 +18,49 @@ import java.util.List; public class RealmEntity { @Id + @GeneratedValue(strategy = GenerationType.UUID) + private String id; + private String key; @Column(name = "realm_name") private String name; - @OneToMany(mappedBy = "realm", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private List keys = new ArrayList<>(); @OneToMany(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private List accounts = new ArrayList<>(); @OneToMany(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private List clients = new ArrayList<>(); @OneToMany(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private List codes = new ArrayList<>(); @OneToMany(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private List roles = new ArrayList<>(); @OneToOne(mappedBy = "realm") + @OnDelete(action = OnDeleteAction.CASCADE) private AudienceStrategyEntity audienceStrategy; + public String getId() + { + return id; + } + + public RealmEntity setId(String id) + { + this.id = id; + return this; + } + public String getKey() { return key; diff --git a/src/main/java/de/tavolio/realm/RealmResource.java b/src/main/java/de/tavolio/realm/RealmResource.java index 6a74dfa..4493d68 100644 --- a/src/main/java/de/tavolio/realm/RealmResource.java +++ b/src/main/java/de/tavolio/realm/RealmResource.java @@ -1,35 +1,68 @@ package de.tavolio.realm; +import de.tavolio.Role; import de.tavolio.oidc.OidcConfigurationResource; import de.tavolio.oidc.OidcResource; +import de.tavolio.realm.client.ClientResource; +import de.tavolio.realm.role.RoleResource; import de.tavolio.realm.user.UserResource; +import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.inject.spi.CDI; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; -@Path("/realms/{realm-key}") +import java.util.List; + +@Path("/realms") public class RealmResource { - @Path("/") + @Inject + RealmRepo repo; + + @Path("/{realm-key}") public RealmSubResource realms() { return CDI.current().select(RealmSubResource.class).get(); } - @Path("/accounts") + @RolesAllowed("ROOT") + @Path("/{realm-key}/users") public UserResource accounts() { return CDI.current().select(UserResource.class).get(); } - @Path("/protocol/openid-connect") + @RolesAllowed("ROOT") + @Path("/{realm-key}/clients") + public ClientResource clients() + { + return CDI.current().select(ClientResource.class).get(); + } + + @RolesAllowed("ROOT") + @Path("/{realm-key}/roles") + public RoleResource roles() + { + return CDI.current().select(RoleResource.class).get(); + } + + @Path("/{realm-key}/protocol/openid-connect") public OidcResource oidc() { return CDI.current().select(OidcResource.class).get(); } - @Path("/.well-known/openid-configuration") + @Path("/{realm-key}/.well-known/openid-configuration") public OidcConfigurationResource oidcConfigurationResource() { return CDI.current().select(OidcConfigurationResource.class).get(); } + + @GET + public List get() + { + return repo.listAll().stream().map(item -> new Realm(item.getKey(), item.getName())).toList(); + } } diff --git a/src/main/java/de/tavolio/realm/RealmService.java b/src/main/java/de/tavolio/realm/RealmService.java index 691dcb8..ba8fbcc 100644 --- a/src/main/java/de/tavolio/realm/RealmService.java +++ b/src/main/java/de/tavolio/realm/RealmService.java @@ -24,7 +24,7 @@ public class RealmService public RealmEntity requireByKey(String key) { - RealmEntity realm = repo.findById(key); + RealmEntity realm = repo.findByKey(key); if (realm != null) { return realm; diff --git a/src/main/java/de/tavolio/realm/RealmSubResource.java b/src/main/java/de/tavolio/realm/RealmSubResource.java index 96c93ec..15d64a1 100644 --- a/src/main/java/de/tavolio/realm/RealmSubResource.java +++ b/src/main/java/de/tavolio/realm/RealmSubResource.java @@ -1,25 +1,36 @@ package de.tavolio.realm; +import io.quarkus.security.Authenticated; +import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; +import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; -import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.PathParam; @RequestScoped public class RealmSubResource { + @PathParam("realm-key") + String key; + @Inject RealmRepo repo; + @Inject + RealmService service; + @GET - public Realm get(@PathParam("realm-key") String key) + public Realm get() { - RealmEntity entity = repo.findById(key); - if (entity != null) - { - return new Realm(entity.getKey(), entity.getName()); - } - throw new NotFoundException(); + RealmEntity realm = service.requireByKey(key); + return new Realm(realm.getKey(), realm.getName()); + } + + @RolesAllowed("ROOT") + @DELETE + public void delete() + { + repo.delete(service.requireByKey(key)); } } diff --git a/src/main/java/de/tavolio/realm/client/Client.java b/src/main/java/de/tavolio/realm/client/Client.java new file mode 100644 index 0000000..26a061c --- /dev/null +++ b/src/main/java/de/tavolio/realm/client/Client.java @@ -0,0 +1,7 @@ +package de.tavolio.realm.client; + +import java.util.Set; + +public record Client(String id, String name, String redirectURI, Set allowedGrants) +{ +} diff --git a/src/main/java/de/tavolio/realm/client/ClientCreation.java b/src/main/java/de/tavolio/realm/client/ClientCreation.java index 92d1f19..276acb4 100644 --- a/src/main/java/de/tavolio/realm/client/ClientCreation.java +++ b/src/main/java/de/tavolio/realm/client/ClientCreation.java @@ -1,5 +1,7 @@ package de.tavolio.realm.client; -public record ClientCreation(String id, String secret) +import java.util.Set; + +public record ClientCreation(String name, String secret, String redirectURI, Set allowedGrants) { } diff --git a/src/main/java/de/tavolio/realm/client/ClientEntity.java b/src/main/java/de/tavolio/realm/client/ClientEntity.java index 17f102f..0854f6f 100644 --- a/src/main/java/de/tavolio/realm/client/ClientEntity.java +++ b/src/main/java/de/tavolio/realm/client/ClientEntity.java @@ -19,6 +19,8 @@ public class ClientEntity implements RealmScoped @Id private String id; + private String name; + private String secret; @Column(name = "redirect_uri") @@ -32,12 +34,16 @@ public class ClientEntity implements RealmScoped private List codes; @ElementCollection - @CollectionTable( - name = "client_permission", - joinColumns = @JoinColumn(name = "client_id") - ) + @CollectionTable(name = "client_permission", joinColumns = @JoinColumn(name = "client_id")) @Column(name = "permission") - private Set permissions = new HashSet<>(); + @Enumerated(EnumType.STRING) + private Set permissions = new HashSet<>(); + + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable(name = "allowed_grants", joinColumns = @JoinColumn(name = "client_id")) + @Column(name = "grant_name") + @Enumerated(EnumType.STRING) + private Set allowedGrants = new HashSet(); public String getId() { @@ -50,6 +56,17 @@ public class ClientEntity implements RealmScoped return this; } + public String getName() + { + return name; + } + + public ClientEntity setName(String name) + { + this.name = name; + return this; + } + public String getSecret() { return secret; @@ -96,12 +113,23 @@ public class ClientEntity implements RealmScoped public Set getPermissions() { - return permissions.stream().map(Permission::valueOf).collect(Collectors.toSet()); + return permissions; } public ClientEntity setPermissions(Set permissions) { - this.permissions = permissions.stream().map(Enum::toString).collect(Collectors.toSet()); + this.permissions = permissions; + return this; + } + + public Set getAllowedGrants() + { + return allowedGrants; + } + + public ClientEntity setAllowedGrants(Set allowedGrants) + { + this.allowedGrants = allowedGrants; return this; } } diff --git a/src/main/java/de/tavolio/realm/client/ClientMapper.java b/src/main/java/de/tavolio/realm/client/ClientMapper.java new file mode 100644 index 0000000..f338964 --- /dev/null +++ b/src/main/java/de/tavolio/realm/client/ClientMapper.java @@ -0,0 +1,16 @@ +package de.tavolio.realm.client; + +import java.util.List; + +public class ClientMapper +{ + public static List map(List client) + { + return client.stream().map(ClientMapper::map).toList(); + } + + public static Client map(ClientEntity client) + { + return new Client(client.getId(), client.getName(), client.getRedirectURI(), client.getAllowedGrants()); + } +} diff --git a/src/main/java/de/tavolio/realm/client/ClientRepo.java b/src/main/java/de/tavolio/realm/client/ClientRepo.java index ccbec27..d7751dd 100644 --- a/src/main/java/de/tavolio/realm/client/ClientRepo.java +++ b/src/main/java/de/tavolio/realm/client/ClientRepo.java @@ -5,6 +5,7 @@ import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; import java.util.Optional; @ApplicationScoped @@ -19,4 +20,14 @@ public class ClientRepo implements PanacheRepositoryBase { return find("realm = :realm AND id = :id", Parameters.with("realm", realm).and("id", id)).firstResult(); } + + public List findByRealm(RealmEntity realm) + { + return list("realm = :realm", Parameters.with("realm", realm)); + } + + public ClientEntity 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/client/ClientResource.java b/src/main/java/de/tavolio/realm/client/ClientResource.java new file mode 100644 index 0000000..6fcbd66 --- /dev/null +++ b/src/main/java/de/tavolio/realm/client/ClientResource.java @@ -0,0 +1,54 @@ +package de.tavolio.realm.client; + +import de.tavolio.realm.RealmEntity; +import de.tavolio.realm.RealmService; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; + +import java.util.List; + +@RequestScoped +@Consumes(MediaType.APPLICATION_JSON) +public class ClientResource +{ + @PathParam("realm-key") + String realmKey; + + @Inject + ClientRepo repo; + + @Inject + ClientService clientService; + + @Inject + RealmService realmService; + + @GET + public List get() + { + RealmEntity realm = realmService.requireByKey(realmKey); + return ClientMapper.map(repo.findByRealm(realm)); + } + + @POST + public Client post(ClientCreation creation) + { + return clientService.create(realmKey, creation); + } + + @Path("/{client-name}") + @PUT + public Client put(@PathParam("client-name") String clientName, Client client) + { + return clientService.update(realmKey, clientName, client); + } + + @Path("/{client-name}") + @DELETE + public void delete(@PathParam("client-name") String clientName) + { + clientService.delete(realmKey, clientName); + } +} diff --git a/src/main/java/de/tavolio/realm/client/ClientService.java b/src/main/java/de/tavolio/realm/client/ClientService.java index d352693..ad0d0b2 100644 --- a/src/main/java/de/tavolio/realm/client/ClientService.java +++ b/src/main/java/de/tavolio/realm/client/ClientService.java @@ -1,17 +1,17 @@ package de.tavolio.realm.client; -import de.tavolio.bootstrap.Credentials; -import de.tavolio.bootstrap.model.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; -import java.util.Map; +import java.util.UUID; @ApplicationScoped public class ClientService @@ -20,9 +20,9 @@ public class ClientService ClientRepo clientRepo; @Inject - RealmRepo realmRepo; + RealmService realmService; - public ClientEntity findByIdAndRealm(String clientId, RealmEntity realm) + public ClientEntity requireByIdAndRealm(String clientId, RealmEntity realm) { ClientEntity client = clientRepo.findByRealmAndId(realm, clientId); if (client != null) @@ -33,17 +33,46 @@ public class ClientService } @Transactional - public ClientEntity create(String realmKey, ClientCreation client) + public Client create(String realmKey, ClientCreation client) { - RealmEntity realm = realmRepo.findByKey(realmKey); - if (realm != null) + RealmEntity realm = realmService.requireByKey(realmKey); + ClientEntity entity = new ClientEntity(); + entity.setId(UUID.randomUUID().toString().replace("-", "").substring(0, 16)); + entity.setName(client.name()); + if (!StringUtils.isBlank(client.secret())) { - ClientEntity entity = new ClientEntity(); - entity.setId(client.id()); entity.setSecret(BcryptUtil.bcryptHash(client.secret())); - entity.setRealm(realm); - clientRepo.persist(entity); - return entity; + } + entity.setAllowedGrants(client.allowedGrants()); + entity.setRedirectURI(client.redirectURI()); + entity.setRealm(realm); + clientRepo.persist(entity); + return ClientMapper.map(entity); + } + + @Transactional + public void delete(String realmKey, String clientName) + { + RealmEntity realm = realmService.requireByKey(realmKey); + ClientEntity client = clientRepo.findByRealmAndName(realm, clientName); + if (client != null) + { + clientRepo.delete(client); + } + } + + @Transactional + public Client update(String realmKey, String clientName, Client client) + { + RealmEntity realm = realmService.requireByKey(realmKey); + ClientEntity existing = clientRepo.findByRealmAndName(realm, clientName); + if (existing != null) + { + existing.setName(client.name()); + existing.setRedirectURI(client.redirectURI()); + existing.setAllowedGrants(client.allowedGrants()); + clientRepo.persist(existing); + return ClientMapper.map(existing); } throw new NotFoundException(); } diff --git a/src/main/java/de/tavolio/realm/client/Grant.java b/src/main/java/de/tavolio/realm/client/Grant.java new file mode 100644 index 0000000..818f3dd --- /dev/null +++ b/src/main/java/de/tavolio/realm/client/Grant.java @@ -0,0 +1,6 @@ +package de.tavolio.realm.client; + +public enum Grant +{ + CLIENT_CREDENTIALS, AUTHORIZATION, PASSWORD +} diff --git a/src/main/java/de/tavolio/realm/role/Role.java b/src/main/java/de/tavolio/realm/role/Role.java new file mode 100644 index 0000000..aa87957 --- /dev/null +++ b/src/main/java/de/tavolio/realm/role/Role.java @@ -0,0 +1,5 @@ +package de.tavolio.realm.role; + +public record Role(String id, String name) +{ +} diff --git a/src/main/java/de/tavolio/realm/role/RoleCreation.java b/src/main/java/de/tavolio/realm/role/RoleCreation.java new file mode 100644 index 0000000..47e654b --- /dev/null +++ b/src/main/java/de/tavolio/realm/role/RoleCreation.java @@ -0,0 +1,5 @@ +package de.tavolio.realm.role; + +public record RoleCreation(String name) +{ +} diff --git a/src/main/java/de/tavolio/realm/role/RoleEntity.java b/src/main/java/de/tavolio/realm/role/RoleEntity.java index 8039d75..b79eb11 100644 --- a/src/main/java/de/tavolio/realm/role/RoleEntity.java +++ b/src/main/java/de/tavolio/realm/role/RoleEntity.java @@ -21,14 +21,6 @@ public class RoleEntity implements RealmScoped @JoinColumn(name = "realm_id") private RealmEntity realm; - @ElementCollection - @CollectionTable( - name = "permission", - joinColumns = @JoinColumn(name = "role_id") - ) - @Column(name = "permission") - private List permissions = new ArrayList<>(); - public String getId() { return id; @@ -51,17 +43,6 @@ public class RoleEntity implements RealmScoped return this; } - public List getPermissions() - { - return permissions; - } - - public RoleEntity setPermissions(List permissions) - { - this.permissions = permissions; - return this; - } - public RealmEntity getRealm() { return realm; @@ -72,16 +53,4 @@ public class RoleEntity implements RealmScoped this.realm = realm; return this; } - - public boolean hasPermission(String permission) - { - for (String value : permissions) - { - if (permission.equals(value)) - { - return true; - } - } - return false; - } } diff --git a/src/main/java/de/tavolio/realm/role/RoleRepo.java b/src/main/java/de/tavolio/realm/role/RoleRepo.java index 7650ba4..581704e 100644 --- a/src/main/java/de/tavolio/realm/role/RoleRepo.java +++ b/src/main/java/de/tavolio/realm/role/RoleRepo.java @@ -5,13 +5,14 @@ import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.Parameters; import jakarta.enterprise.context.ApplicationScoped; +import java.util.List; import java.util.Optional; @ApplicationScoped public class RoleRepo implements PanacheRepositoryBase { - public Optional findByNameAndRealmOptional(String name, RealmEntity realm) + public List findByRealm(RealmEntity realm) { - return find("realm = :realm AND name = :name", Parameters.with("realm", realm).and("name", name)).firstResultOptional(); + return list("realm = :realm", Parameters.with("realm", realm)); } } diff --git a/src/main/java/de/tavolio/realm/role/RoleResource.java b/src/main/java/de/tavolio/realm/role/RoleResource.java new file mode 100644 index 0000000..e682b9d --- /dev/null +++ b/src/main/java/de/tavolio/realm/role/RoleResource.java @@ -0,0 +1,31 @@ +package de.tavolio.realm.role; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PathParam; + +import java.util.List; + +@RequestScoped +public class RoleResource +{ + @PathParam("realm-key") + String realmKey; + + @Inject + RoleService roleService; + + @GET + public List get() + { + return roleService.findByRealm(realmKey); + } + + @POST + public Role post(RoleCreation creation) + { + return roleService.create(realmKey, creation); + } +} diff --git a/src/main/java/de/tavolio/realm/role/RoleService.java b/src/main/java/de/tavolio/realm/role/RoleService.java new file mode 100644 index 0000000..01422a4 --- /dev/null +++ b/src/main/java/de/tavolio/realm/role/RoleService.java @@ -0,0 +1,50 @@ +package de.tavolio.realm.role; + +import de.tavolio.realm.RealmEntity; +import de.tavolio.realm.RealmResource; +import de.tavolio.realm.RealmService; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import java.util.List; +import java.util.UUID; + +@ApplicationScoped +public class RoleService +{ + @Inject + RoleRepo repo; + + @Inject + RealmService realmService; + + @Transactional + public Role create(String realmKey, RoleCreation roleCreation) + { + RealmEntity realm = realmService.requireByKey(realmKey); + + RoleEntity role = new RoleEntity(); + role.setId(UUID.randomUUID().toString()); + role.setName(roleCreation.name()); + role.setRealm(realm); + repo.persist(role); + return map(role); + } + + public List findByRealm(String realmKey) + { + RealmEntity realm = realmService.requireByKey(realmKey); + return map(repo.findByRealm(realm)); + } + + private List map(List entities) + { + return entities.stream().map(this::map).toList(); + } + + private Role map(RoleEntity entity) + { + return new Role(entity.getId(), entity.getName()); + } +} diff --git a/src/main/java/de/tavolio/realm/user/UserRepo.java b/src/main/java/de/tavolio/realm/user/UserRepo.java index ca81a37..a006f7c 100644 --- a/src/main/java/de/tavolio/realm/user/UserRepo.java +++ b/src/main/java/de/tavolio/realm/user/UserRepo.java @@ -20,4 +20,14 @@ public class UserRepo implements PanacheRepositoryBase { return list("id IN :ids", Parameters.with("ids", ids)); } + + public List findByRealm(RealmEntity realm) + { + return list("realm = :realm", Parameters.with("realm", realm)); + } + + public UserEntity findByRealmAndId(RealmEntity realm, String id) + { + return find("realm = :realm AND id = :id", Parameters.with("realm", realm).and("id", id)).firstResult(); + } } diff --git a/src/main/java/de/tavolio/realm/user/UserResource.java b/src/main/java/de/tavolio/realm/user/UserResource.java index 2d87e79..4e7c8ec 100644 --- a/src/main/java/de/tavolio/realm/user/UserResource.java +++ b/src/main/java/de/tavolio/realm/user/UserResource.java @@ -6,10 +6,7 @@ import de.tavolio.realm.user.dto.UserCreation; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.validation.Valid; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.*; import org.jboss.logging.Logger; import java.util.List; @@ -41,7 +38,7 @@ public class UserResource public List get() { permissionService.hasPermission(Permission.USER_VIEW); - return userService.get(); + return userService.get(realmKey); } @GET @@ -52,6 +49,14 @@ public class UserResource return userService.getUser(id); } + @DELETE + @Path("/{id}") + public void delete(@PathParam("id") String id) + { + permissionService.hasPermission(Permission.USER_VIEW); + userService.delete(realmKey, id); + } + @POST @Path("/search") public Map get(List ids) diff --git a/src/main/java/de/tavolio/realm/user/UserService.java b/src/main/java/de/tavolio/realm/user/UserService.java index 0a4cdd2..7cc7148 100644 --- a/src/main/java/de/tavolio/realm/user/UserService.java +++ b/src/main/java/de/tavolio/realm/user/UserService.java @@ -1,6 +1,7 @@ package de.tavolio.realm.user; import de.tavolio.AuthenticationService; +import de.tavolio.realm.RealmService; import de.tavolio.realm.user.dto.User; import de.tavolio.realm.user.dto.UserCreation; import de.tavolio.realm.RealmEntity; @@ -35,6 +36,9 @@ public class UserService @Inject RealmRepo realmRepo; + @Inject + RealmService realmService; + @Transactional public User create(String realmId, UserCreation account) { @@ -54,21 +58,15 @@ public class UserService throw new NotFoundException(); } - public List get() + public List get(String realmKey) { - return userMapper.map(userRepo.listAll()); + RealmEntity realm = realmService.requireByKey(realmKey); + return userMapper.map(userRepo.findByRealm(realm)); } public User getUser(String id) { - UserEntity account = authenticationService.requireUser(); - UserEntity requestedAccount = userRepo.findById(id); - if (requestedAccount != null && requestedAccount.getId().equals(account.getId())) - { - return userMapper.map(authenticationService.requireUser()); - } - LOG.errorf("Cannot access account"); - throw new UnauthorizedException(); + return userMapper.map(userRepo.findById(id)); } public Map findByIds(List ids) @@ -80,4 +78,15 @@ public class UserService } return accounts; } + + @Transactional + public void delete(String realmKey, String id) + { + RealmEntity realm = realmService.requireByKey(realmKey); + UserEntity user = userRepo.findByRealmAndId(realm, id); + if (user != null) + { + userRepo.delete(user); + } + } } diff --git a/src/main/resources/bootstrap.yaml b/src/main/resources/bootstrap.yaml index 775a866..691431f 100644 --- a/src/main/resources/bootstrap.yaml +++ b/src/main/resources/bootstrap.yaml @@ -1,6 +1,7 @@ realms: maven: name: MavenVault + lifetime: 3600 audience: strategy: static value: https://maven.dinauer.dev/api @@ -14,6 +15,12 @@ realms: redirect-uri: http://localhost:8080/callback permissions: - USER_VIEW + analytics-backend: + secret: + bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX. + redirect-uri: http://localhost:8080/callback + permissions: + - USER_VIEW users: - email: andreas.j.dinauer@gmail.com first-name: Andreas