🚧 Improvements
This commit is contained in:
parent
38bed80c5a
commit
6444931d73
@ -34,10 +34,10 @@ public class ClientBootstrapper
|
||||
String name = bootstrap.getKey();
|
||||
Client client = bootstrap.getValue();
|
||||
ClientEntity entity = new ClientEntity()
|
||||
.setId(UUID.randomUUID().toString().replace("-", "").substring(0, 16))
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.setName(name)
|
||||
.setSecret(Credentials.resolve(client.secret()))
|
||||
.setRedirectURI(client.redirectURI())
|
||||
.setAllowedRedirectURIs(client.redirectURI())
|
||||
.setRealm(realm)
|
||||
.setPermissions(client.permissions())
|
||||
.setAllowedGrants(client.allowedGrants());
|
||||
|
||||
@ -4,10 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.tavolio.realm.client.Grant;
|
||||
import de.tavolio.realm.user.Permission;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record Client(@JsonProperty("secret") Credential secret, @JsonProperty("redirect-uri") String redirectURI, @JsonProperty("permissions") Set<String> permissionList, @JsonProperty("allowed-grants") Set<Grant> allowedGrants)
|
||||
public record Client(@JsonProperty("secret") Credential secret, @JsonProperty("redirect-uris") Set<String> redirectURI, @JsonProperty("permissions") Set<String> permissionList, @JsonProperty("allowed-grants") Set<Grant> allowedGrants)
|
||||
{
|
||||
public Set<Permission> permissions()
|
||||
{
|
||||
|
||||
@ -17,6 +17,8 @@ import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URI;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -26,6 +28,8 @@ import java.util.Map;
|
||||
@RequestScoped
|
||||
public class OidcResource
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OidcResource.class);
|
||||
|
||||
@PathParam("realm-key")
|
||||
String realmKey;
|
||||
|
||||
@ -59,16 +63,20 @@ public class OidcResource
|
||||
|
||||
@POST
|
||||
@Path("/auth")
|
||||
public Response auth(@QueryParam("client_id") String clientId, @FormParam("email") String email, @FormParam("password") String password)
|
||||
public Response auth(@QueryParam("client_id") String clientId, @QueryParam("redirect_uri") String redirectURI, @FormParam("email") String email, @FormParam("password") String password)
|
||||
{
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
ClientEntity client = clientService.requireByNameAndRealm(clientId, realm);
|
||||
if (client != null)
|
||||
if (client.getAllowedRedirectURIs().contains(redirectURI))
|
||||
{
|
||||
String code = authorizationService.generateBySessionCreation(realmKey, clientId, new AuthorizationCreation(email, password));
|
||||
return Response.status(302).location(URI.create(client.getRedirectURI() + "?code=" + code + "&state=d")).build();
|
||||
return Response.status(302).location(URI.create(redirectURI + "?code=" + code)).build();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Invalid redirect uri provided: {}", redirectURI);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
@Authenticated
|
||||
@ -83,6 +91,7 @@ public class OidcResource
|
||||
ClientEntity client = clientService.requireByNameAndRealm(identity.getPrincipal().getName(), realm);
|
||||
if (!client.getAllowedGrants().contains(grant))
|
||||
{
|
||||
LOG.error("Client {} has no permission for grant {}", client.getName(), grant);
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
@ -107,6 +116,7 @@ public class OidcResource
|
||||
{
|
||||
return Grant.CLIENT_CREDENTIALS;
|
||||
}
|
||||
LOG.error("Invalid grant {} provided", grantType);
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,6 @@ public class AuthorizationService
|
||||
@Transactional
|
||||
public String generateBySessionCreation(String realmKey, String clientId, AuthorizationCreation authorizationCreation)
|
||||
{
|
||||
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
ClientEntity client = clientService.requireByNameAndRealm(clientId, realm);
|
||||
Optional<UserEntity> accountEntityOptional = userRepo.findOptionalByRealmAndEmail(realm, authorizationCreation.email());
|
||||
|
||||
@ -26,6 +26,17 @@ public class UserTokenGenerator
|
||||
.sign(key);
|
||||
}
|
||||
|
||||
public String generateRefreshToken(String realmKey, String clientId, String upn, ZonedDateTime expiresAt, PrivateKey key, String keyId)
|
||||
{
|
||||
return Jwt.claims()
|
||||
.upn(upn)
|
||||
.claim("realm_key", realmKey)
|
||||
.claim("client_id", clientId)
|
||||
.expiresAt(expiresAt.toInstant())
|
||||
.issuer(issuerService.getIssuer(realmKey)).jws().keyId(keyId)
|
||||
.sign(key);
|
||||
}
|
||||
|
||||
public String generateIDToken(String realmKey, String clientId, String upn, ZonedDateTime expiresAt, PrivateKey key, String keyId)
|
||||
{
|
||||
return Jwt.claims()
|
||||
|
||||
@ -13,6 +13,8 @@ import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@ -25,6 +27,8 @@ import java.util.UUID;
|
||||
@ApplicationScoped
|
||||
public class UserTokenService
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UserTokenService.class);
|
||||
|
||||
@Inject
|
||||
CodeRepo codeRepo;
|
||||
|
||||
@ -37,29 +41,35 @@ public class UserTokenService
|
||||
@Inject
|
||||
RealmService realmService;
|
||||
|
||||
public TokenResponse getToken(String realmKey, String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
public TokenResponse getToken(String realmKey, String code)
|
||||
{
|
||||
return generateUserToken(realmKey, code);
|
||||
}
|
||||
|
||||
private TokenResponse generateUserToken(String realmKey, String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
private TokenResponse generateUserToken(String realmKey, String code)
|
||||
{
|
||||
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().getName()))
|
||||
if (entity != null)
|
||||
{
|
||||
KeypairEntity keypair = realm.getKeys().getFirst();
|
||||
PrivateKey signingKey = KeypairEntity.toPrivateKey(keypair);
|
||||
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())
|
||||
.setIdToken(userTokenGenerator.generateIDToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId()))
|
||||
.setTokenType("Bearer")
|
||||
.setExpiresAt(expiresAt.toInstant().getEpochSecond());
|
||||
codeRepo.delete(entity);
|
||||
return response;
|
||||
if (!ZonedDateTime.now().isAfter(entity.getExpiresAt()) && principal.equals(entity.getClient().getName()))
|
||||
{
|
||||
KeypairEntity keypair = realm.getKeys().getFirst();
|
||||
PrivateKey signingKey = KeypairEntity.toPrivateKey(keypair);
|
||||
ZonedDateTime expiresAt = ZonedDateTime.now().plusSeconds(realm.getLifetime());
|
||||
ZonedDateTime refreshTokenExpiresAt = ZonedDateTime.now().plusDays(3);
|
||||
TokenResponse response = new TokenResponse()
|
||||
.setAccessToken(userTokenGenerator.generateAccessToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId()))
|
||||
.setRefreshToken(userTokenGenerator.generateRefreshToken(realm.getKey(), principal, entity.getAccount().getId(), refreshTokenExpiresAt, signingKey, keypair.getId()))
|
||||
.setIdToken(userTokenGenerator.generateIDToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId()))
|
||||
.setTokenType("Bearer")
|
||||
.setExpiresAt(expiresAt.toInstant().getEpochSecond());
|
||||
codeRepo.delete(entity);
|
||||
return response;
|
||||
}
|
||||
LOG.error("Code is expired or requesting client is not allowed to exchange code.");
|
||||
throw new BadRequestException();
|
||||
}
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
@ -2,6 +2,6 @@ package de.tavolio.realm.client;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record Client(String id, String name, String redirectURI, Set<Grant> allowedGrants)
|
||||
public record Client(String id, String name, Set<String> redirectURI, Set<Grant> allowedGrants)
|
||||
{
|
||||
}
|
||||
|
||||
@ -2,6 +2,6 @@ package de.tavolio.realm.client;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record ClientCreation(String name, String secret, String redirectURI, Set<Grant> allowedGrants)
|
||||
public record ClientCreation(String name, String secret, Set<String> redirectURI, Set<Grant> allowedGrants)
|
||||
{
|
||||
}
|
||||
|
||||
@ -23,9 +23,6 @@ public class ClientEntity implements RealmScoped
|
||||
|
||||
private String secret;
|
||||
|
||||
@Column(name = "redirect_uri")
|
||||
private String redirectURI;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "realm_id")
|
||||
private RealmEntity realm;
|
||||
@ -45,6 +42,11 @@ public class ClientEntity implements RealmScoped
|
||||
@Enumerated(EnumType.STRING)
|
||||
private Set<Grant> allowedGrants = new HashSet<>();
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "allowed_redirect_uri", joinColumns = @JoinColumn(name = "client_id"))
|
||||
@Column(name = "redirect_uri")
|
||||
private Set<String> allowedRedirectURIs = new HashSet<>();
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
@ -89,17 +91,6 @@ public class ClientEntity implements RealmScoped
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getRedirectURI()
|
||||
{
|
||||
return redirectURI;
|
||||
}
|
||||
|
||||
public ClientEntity setRedirectURI(String redirectURI)
|
||||
{
|
||||
this.redirectURI = redirectURI;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<CodeEntity> getCodes()
|
||||
{
|
||||
return codes;
|
||||
@ -132,4 +123,15 @@ public class ClientEntity implements RealmScoped
|
||||
this.allowedGrants = allowedGrants;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<String> getAllowedRedirectURIs()
|
||||
{
|
||||
return allowedRedirectURIs;
|
||||
}
|
||||
|
||||
public ClientEntity setAllowedRedirectURIs(Set<String> allowedRedirectURIs)
|
||||
{
|
||||
this.allowedRedirectURIs = allowedRedirectURIs;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,6 @@ public class ClientMapper
|
||||
|
||||
public static Client map(ClientEntity client)
|
||||
{
|
||||
return new Client(client.getId(), client.getName(), client.getRedirectURI(), client.getAllowedGrants());
|
||||
return new Client(client.getId(), client.getName(), client.getAllowedRedirectURIs(), client.getAllowedGrants());
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,14 +35,14 @@ public class ClientService
|
||||
{
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
ClientEntity entity = new ClientEntity();
|
||||
entity.setId(UUID.randomUUID().toString().replace("-", "").substring(0, 16));
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setName(client.name());
|
||||
if (!StringUtils.isBlank(client.secret()))
|
||||
{
|
||||
entity.setSecret(BcryptUtil.bcryptHash(client.secret()));
|
||||
}
|
||||
entity.setAllowedGrants(client.allowedGrants());
|
||||
entity.setRedirectURI(client.redirectURI());
|
||||
entity.setAllowedRedirectURIs(client.redirectURI());
|
||||
entity.setRealm(realm);
|
||||
clientRepo.persist(entity);
|
||||
return ClientMapper.map(entity);
|
||||
@ -67,7 +67,7 @@ public class ClientService
|
||||
if (existing != null)
|
||||
{
|
||||
existing.setName(client.name());
|
||||
existing.setRedirectURI(client.redirectURI());
|
||||
existing.setAllowedRedirectURIs(client.redirectURI());
|
||||
existing.setAllowedGrants(client.allowedGrants());
|
||||
clientRepo.persist(existing);
|
||||
return ClientMapper.map(existing);
|
||||
|
||||
@ -9,7 +9,7 @@ quarkus.http.cors.enabled=true
|
||||
# Postgres
|
||||
prod.quarkus.hibernate-orm.validate-in-dev-mode=false
|
||||
quarkus.hibernate-orm.schema-management.strategy=none
|
||||
%test,dev.quarkus.hibernate-orm.schema-management.strategy=drop-and-create
|
||||
%test,dev.quarkus.hibernate-orm.schema-management.strategy=none
|
||||
quarkus.datasource.db-kind=postgresql
|
||||
|
||||
%dev,test.quarkus.datasource.username=postgres
|
||||
@ -17,14 +17,11 @@ quarkus.datasource.db-kind=postgresql
|
||||
%dev,test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=auth
|
||||
|
||||
# Flyway
|
||||
quarkus.flyway.enabled=false
|
||||
%test.quarkus.flyway.clean-at-start=true
|
||||
%dev.quarkus.flyway.clean-at-start=true
|
||||
quarkus.flyway.enabled=true
|
||||
quarkus.flyway.clean-at-start=true
|
||||
%dev.quarkus.flyway.locations=db/migration,db/dev
|
||||
%test,dev.quarkus.flyway.migrate-at-start=false
|
||||
quarkus.flyway.migrate-at-start=true
|
||||
|
||||
quarkus.http.access-log.enabled=true
|
||||
quarkus.http.auth.basic=false
|
||||
|
||||
%dev.io.verifoo.http.origin=http://localhost:8089
|
||||
|
||||
@ -15,11 +15,13 @@ realms:
|
||||
backend:
|
||||
secret:
|
||||
bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX.
|
||||
redirect-uri: http://localhost:8080/auth/callback
|
||||
redirect-uris:
|
||||
- http://localhost:8080/auth/callback
|
||||
permissions:
|
||||
- USER_VIEW
|
||||
allowed-grants:
|
||||
- AUTHORIZATION_CODE
|
||||
- CLIENT_CREDENTIALS
|
||||
analytics-backend:
|
||||
secret:
|
||||
bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX.
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
INSERT INTO account (id, firstname, lastname, email, account_password, status)
|
||||
VALUES ('66b261fe-4c5a-4728-9857-67717f02d4e1', 'Andreas', 'Dinauer', 'andreas.j.dinauer@gmail.com', '$2a$12$cdrzIY4sMFAXiz29uo9Ul.MPy0RN0FGS2yjVzb5BTe6bSijn4eGQy', 'REGISTERED');
|
||||
@ -1,10 +1,168 @@
|
||||
CREATE TABLE account (
|
||||
email VARCHAR(255) NULL,
|
||||
firstname VARCHAR(255) NULL,
|
||||
id VARCHAR(255) NOT NULL,
|
||||
lastname VARCHAR(255) NULL,
|
||||
account_password VARCHAR(255) NULL,
|
||||
status VARCHAR(255) NULL,
|
||||
CONSTRAINT account_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT account_status_check CHECK (((status)::text = ANY ((ARRAY['INIT'::character varying, 'REGISTERED'::character varying])::text[])))
|
||||
create table realm
|
||||
(
|
||||
lifetime integer not null,
|
||||
id varchar(255) not null primary key,
|
||||
key varchar(255),
|
||||
realm_name varchar(255)
|
||||
);
|
||||
|
||||
alter table realm
|
||||
owner to postgres;
|
||||
|
||||
create table audience_strategy
|
||||
(
|
||||
id varchar(255) not null primary key,
|
||||
realm_id varchar(255) unique
|
||||
constraint fkth1885jeywykkufsp36n5tc56 references realm,
|
||||
strategy varchar(255),
|
||||
value varchar(255)
|
||||
);
|
||||
|
||||
alter table audience_strategy
|
||||
owner to postgres;
|
||||
|
||||
create table client
|
||||
(
|
||||
id varchar(255) not null primary key,
|
||||
name varchar(255),
|
||||
realm_id varchar(255)
|
||||
constraint fk1bi8c7s4u3s44vw3w8le6xau1 references realm on delete cascade,
|
||||
secret varchar(255)
|
||||
);
|
||||
|
||||
create table allowed_grants
|
||||
(
|
||||
client_id varchar(255) not null
|
||||
constraint fkpuqnyqud9ft1tn18qrcy7457c references client,
|
||||
grant_name varchar(255)
|
||||
constraint allowed_grants_grant_name_check check ((grant_name)::text = ANY ((ARRAY ['CLIENT_CREDENTIALS':: character varying, 'AUTHORIZATION_CODE':: character varying, 'PASSWORD':: character varying])::text[])
|
||||
)
|
||||
);
|
||||
|
||||
alter table allowed_grants
|
||||
owner to postgres;
|
||||
|
||||
create table allowed_redirect_uri
|
||||
(
|
||||
client_id varchar(255) not null
|
||||
constraint fkamn8o2599ky2nactoc7umiltg references client,
|
||||
redirect_uri varchar(255)
|
||||
);
|
||||
|
||||
alter table allowed_redirect_uri
|
||||
owner to postgres;
|
||||
|
||||
create table client_permission
|
||||
(
|
||||
client_id varchar(255) not null
|
||||
constraint fk1kqfe7d8w5bor8vp0aoaloptt
|
||||
references client,
|
||||
permission varchar(255)
|
||||
constraint client_permission_permission_check
|
||||
check ((permission)::text = ANY
|
||||
((ARRAY ['USER_VIEW':: character varying, 'USER_DELETE':: character varying, 'USER_CREATE':: character varying])::text[])
|
||||
)
|
||||
);
|
||||
|
||||
alter table client_permission
|
||||
owner to postgres;
|
||||
|
||||
create table keypair
|
||||
(
|
||||
alg varchar(255),
|
||||
crv varchar(255),
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
realm_id varchar(255)
|
||||
constraint fk1ceq9els4s8jqs96xnmi2y3bi
|
||||
references realm
|
||||
on delete cascade,
|
||||
type varchar(255),
|
||||
use varchar(255),
|
||||
x varchar(255),
|
||||
y varchar(255),
|
||||
privatekey bytea
|
||||
);
|
||||
|
||||
alter table keypair
|
||||
owner to postgres;
|
||||
|
||||
create table role
|
||||
(
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
realm_id varchar(255)
|
||||
constraint fkocsid53ns4d9sngf84ab78bsr
|
||||
references realm
|
||||
on delete cascade,
|
||||
role_name varchar(255)
|
||||
);
|
||||
|
||||
alter table role
|
||||
owner to postgres;
|
||||
|
||||
create table user_regular
|
||||
(
|
||||
account_password varchar(255),
|
||||
email varchar(255),
|
||||
firstname varchar(255),
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
lastname varchar(255),
|
||||
realm_id varchar(255)
|
||||
constraint fki4jjeaqdf1op17hosriwn55t2
|
||||
references realm
|
||||
on delete cascade,
|
||||
status varchar(255)
|
||||
constraint user_regular_status_check
|
||||
check ((status)::text = ANY ((ARRAY ['INIT':: character varying, 'REGISTERED':: character varying])::text[])
|
||||
)
|
||||
);
|
||||
|
||||
alter table user_regular
|
||||
owner to postgres;
|
||||
|
||||
create table assignment
|
||||
(
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
role_id varchar(255)
|
||||
constraint fk90vsrx78n7k7tjr42g7xx6hmy
|
||||
references role,
|
||||
user_id varchar(255)
|
||||
constraint fk3gqvh1h60fkikoc5ix1hvaovl
|
||||
references user_regular
|
||||
);
|
||||
|
||||
alter table assignment
|
||||
owner to postgres;
|
||||
|
||||
create table code
|
||||
(
|
||||
expiresat timestamp(6) with time zone,
|
||||
account_id varchar(255)
|
||||
constraint fk7kkfrwxn8bry4boaorheikjcu
|
||||
references user_regular,
|
||||
client_id varchar(255)
|
||||
constraint fkgs7a5cpyl2slw4j5ageba4hlv
|
||||
references client,
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
realm_id varchar(255)
|
||||
constraint fkqp7vcsghxg1dag4t89ys7mmfq
|
||||
references realm
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
alter table code
|
||||
owner to postgres;
|
||||
|
||||
create table user_super
|
||||
(
|
||||
id varchar(255) not null
|
||||
primary key,
|
||||
password varchar(255)
|
||||
);
|
||||
|
||||
alter table user_super
|
||||
owner to postgres;
|
||||
Loading…
x
Reference in New Issue
Block a user