♻️ Transform to IDP provider
This commit is contained in:
parent
79104dd02f
commit
7d638e6530
@ -1,59 +0,0 @@
|
||||
package de.tavolio.bootstrap;
|
||||
|
||||
import de.tavolio.realm.user.UserEntity;
|
||||
import de.tavolio.realm.user.UserRepo;
|
||||
import de.tavolio.realm.user.UserStatus;
|
||||
import de.tavolio.bootstrap.model.Account;
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApplicationScoped
|
||||
public class AccountBootstrapper
|
||||
{
|
||||
@Inject
|
||||
UserRepo userRepo;
|
||||
|
||||
public void bootstrap(RealmEntity realm, List<Account> accounts)
|
||||
{
|
||||
for (Account account : accounts)
|
||||
{
|
||||
run(realm, account);
|
||||
}
|
||||
}
|
||||
|
||||
public void run(RealmEntity realm, Account account)
|
||||
{
|
||||
Optional<UserEntity> existingAccount = userRepo.findOptionalByRealmAndEmail(realm, account.email());
|
||||
if (existingAccount.isEmpty())
|
||||
{
|
||||
UserEntity newAccount = new UserEntity();
|
||||
newAccount.setId(UUID.randomUUID().toString());
|
||||
newAccount.setEmail(account.email());
|
||||
newAccount.setFirstname(account.firstname());
|
||||
newAccount.setLastname(account.lastname());
|
||||
newAccount.setRealm(realm);
|
||||
newAccount.setStatus(UserStatus.INIT);
|
||||
newAccount.setPassword(resolvePassword(account));
|
||||
userRepo.persist(newAccount);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolvePassword(Account account)
|
||||
{
|
||||
if (account.passwordFromEnv() != null)
|
||||
{
|
||||
return BcryptUtil.bcryptHash(System.getenv(account.passwordFromEnv()));
|
||||
}
|
||||
if (account.passwordPlain() != null)
|
||||
{
|
||||
return BcryptUtil.bcryptHash(account.passwordPlain());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ClientBootstrapper
|
||||
@ -23,8 +24,23 @@ public class ClientBootstrapper
|
||||
{
|
||||
for (Map.Entry<String, Client> clientEntry : clients.entrySet())
|
||||
{
|
||||
ClientEntity client = clientService.findOrCreate(realm, clientEntry);
|
||||
clientRepo.persist(client);
|
||||
run(realm, clientEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private void run(RealmEntity realm, Map.Entry<String, Client> bootstrap)
|
||||
{
|
||||
Optional<ClientEntity> existingClient = clientRepo.findByRealmAndIdOptional(realm, bootstrap.getKey());
|
||||
if (existingClient.isEmpty())
|
||||
{
|
||||
String id = bootstrap.getKey();
|
||||
Client client = bootstrap.getValue();
|
||||
ClientEntity entity = new ClientEntity().setId(id)
|
||||
.setSecret(Credentials.resolve(client.secret()))
|
||||
.setRedirectURI(client.redirectURI())
|
||||
.setRealm(realm)
|
||||
.setPermissions(client.permissions());
|
||||
clientRepo.persist(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
src/main/java/de/tavolio/bootstrap/Credentials.java
Normal file
36
src/main/java/de/tavolio/bootstrap/Credentials.java
Normal file
@ -0,0 +1,36 @@
|
||||
package de.tavolio.bootstrap;
|
||||
|
||||
import de.tavolio.bootstrap.model.Credential;
|
||||
import de.tavolio.bootstrap.model.User;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
public class Credentials
|
||||
{
|
||||
public static String resolve(Credential credential)
|
||||
{
|
||||
if (!StringUtils.isBlank(credential.bcrypt()))
|
||||
{
|
||||
if (Strings.CI.startsWithAny(credential.bcrypt(), "$2a$", "$2b$", "$2y$"))
|
||||
{
|
||||
return credential.bcrypt();
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!StringUtils.isBlank(credential.env()))
|
||||
{
|
||||
String value = System.getenv(credential.env());
|
||||
if (!StringUtils.isBlank(value))
|
||||
{
|
||||
return BcryptUtil.bcryptHash(value);
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!StringUtils.isBlank(credential.plain()))
|
||||
{
|
||||
return BcryptUtil.bcryptHash(credential.plain());
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find password source.");
|
||||
}
|
||||
}
|
||||
@ -26,14 +26,11 @@ public class RealmBootstrapService
|
||||
@Inject
|
||||
ClientBootstrapper clientBootstrapper;
|
||||
|
||||
@Inject
|
||||
RoleBootstrapper roleBootstrapper;
|
||||
|
||||
@Inject
|
||||
RealmRepo realmRepo;
|
||||
|
||||
@Inject
|
||||
AccountBootstrapper accountBootstrapper;
|
||||
UserBootstrapper userBootstrapper;
|
||||
|
||||
@Inject
|
||||
AudienceStrategyRepo audienceStrategyRepo;
|
||||
@ -42,10 +39,9 @@ public class RealmBootstrapService
|
||||
{
|
||||
Realm realmValue = realmEntry.getValue();
|
||||
RealmEntity realm = run(realmEntry.getKey(), realmEntry.getValue());
|
||||
roleBootstrapper.bootstrap(realm, realmValue.roles());
|
||||
keyBootstrapper.bootstrap(realm, realmValue.key());
|
||||
clientBootstrapper.bootstrap(realm, realmValue.clients());
|
||||
accountBootstrapper.bootstrap(realm, realmValue.accounts());
|
||||
userBootstrapper.bootstrap(realm, realmValue.users());
|
||||
}
|
||||
|
||||
public RealmEntity run(String key, Realm realm)
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
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.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApplicationScoped
|
||||
public class RoleBootstrapper
|
||||
{
|
||||
@Inject
|
||||
RoleRepo roleRepo;
|
||||
|
||||
public void bootstrap(RealmEntity realm, Map<String, Role> roles)
|
||||
{
|
||||
for (Map.Entry<String, Role> roleEntry : roles.entrySet())
|
||||
{
|
||||
run(realm, roleEntry);
|
||||
}
|
||||
}
|
||||
|
||||
public void run(RealmEntity realm, Map.Entry<String, Role> roleEntry)
|
||||
{
|
||||
RoleEntity role = getOrCreateRole(realm, roleEntry.getKey());
|
||||
for (String permission : roleEntry.getValue().permissions())
|
||||
{
|
||||
if (!role.hasPermission(permission))
|
||||
{
|
||||
role.getPermissions().add(permission);
|
||||
}
|
||||
}
|
||||
roleRepo.persist(role);
|
||||
}
|
||||
|
||||
public RoleEntity getOrCreateRole(RealmEntity realm, String role)
|
||||
{
|
||||
return roleRepo.findByNameAndRealmOptional(role, realm).orElse(new RoleEntity().setId(UUID.randomUUID().toString()).setName(role).setRealm(realm));
|
||||
}
|
||||
}
|
||||
@ -20,8 +20,8 @@ public class SuperuserBootstrapper
|
||||
public void bootstrap()
|
||||
{
|
||||
Config config = ConfigProvider.getConfig();
|
||||
String username = config.getValue("dev.dinauer.idp.superuser.username", String.class);
|
||||
String password = config.getValue("dev.dinauer.idp.superuser.password", String.class);
|
||||
String username = config.getValue("io.verifoo.superuser.username", String.class);
|
||||
String password = config.getValue("io.verifoo.superuser.password", String.class);
|
||||
if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password) && superuserRepo.count() == 0)
|
||||
{
|
||||
SuperuserEntity superuser = new SuperuserEntity();
|
||||
|
||||
48
src/main/java/de/tavolio/bootstrap/UserBootstrapper.java
Normal file
48
src/main/java/de/tavolio/bootstrap/UserBootstrapper.java
Normal file
@ -0,0 +1,48 @@
|
||||
package de.tavolio.bootstrap;
|
||||
|
||||
import de.tavolio.realm.user.UserEntity;
|
||||
import de.tavolio.realm.user.UserRepo;
|
||||
import de.tavolio.realm.user.UserStatus;
|
||||
import de.tavolio.bootstrap.model.User;
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApplicationScoped
|
||||
public class UserBootstrapper
|
||||
{
|
||||
@Inject
|
||||
UserRepo userRepo;
|
||||
|
||||
public void bootstrap(RealmEntity realm, List<User> users)
|
||||
{
|
||||
for (User user : users)
|
||||
{
|
||||
run(realm, user);
|
||||
}
|
||||
}
|
||||
|
||||
public void run(RealmEntity realm, User user)
|
||||
{
|
||||
Optional<UserEntity> existingAccount = userRepo.findOptionalByRealmAndEmail(realm, user.email());
|
||||
if (existingAccount.isEmpty())
|
||||
{
|
||||
UserEntity newAccount = new UserEntity();
|
||||
newAccount.setId(UUID.randomUUID().toString());
|
||||
newAccount.setEmail(user.email());
|
||||
newAccount.setFirstname(user.firstname());
|
||||
newAccount.setLastname(user.lastname());
|
||||
newAccount.setRealm(realm);
|
||||
newAccount.setStatus(UserStatus.INIT);
|
||||
newAccount.setPassword(Credentials.resolve(user.password()));
|
||||
userRepo.persist(newAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package de.tavolio.bootstrap.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record Account(String email, @JsonProperty("first-name") String firstname, @JsonProperty("last-name") String lastname, @JsonProperty("password-env") String passwordFromEnv, @JsonProperty("password-plain") String passwordPlain)
|
||||
{
|
||||
}
|
||||
@ -1,9 +1,15 @@
|
||||
package de.tavolio.bootstrap.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.tavolio.realm.user.Permission;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public record Client(@JsonProperty("client-secret") String clientSecret, @JsonProperty("redirect-uri") String redirectURI, List<String> roles)
|
||||
public record Client(@JsonProperty("secret") Credential secret, @JsonProperty("redirect-uri") String redirectURI, @JsonProperty("permissions") Set<String> permissionList)
|
||||
{
|
||||
public Set<Permission> permissions()
|
||||
{
|
||||
return permissionList.stream().map(item -> Permission.valueOf(item.toUpperCase())).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
5
src/main/java/de/tavolio/bootstrap/model/Credential.java
Normal file
5
src/main/java/de/tavolio/bootstrap/model/Credential.java
Normal file
@ -0,0 +1,5 @@
|
||||
package de.tavolio.bootstrap.model;
|
||||
|
||||
public record Credential(String plain, String bcrypt, String env)
|
||||
{
|
||||
}
|
||||
@ -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<String, Client> clients, Map<String, Role> roles, List<String> permissions, List<Account> accounts)
|
||||
public record Realm(String name, Key key, Audience audience, Map<String, Client> clients, Map<String, Role> roles, List<String> permissions, List<User> users)
|
||||
{
|
||||
}
|
||||
|
||||
7
src/main/java/de/tavolio/bootstrap/model/User.java
Normal file
7
src/main/java/de/tavolio/bootstrap/model/User.java
Normal file
@ -0,0 +1,7 @@
|
||||
package de.tavolio.bootstrap.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record User(String email, @JsonProperty("first-name") String firstname, @JsonProperty("last-name") String lastname, Credential password)
|
||||
{
|
||||
}
|
||||
@ -6,7 +6,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
@ApplicationScoped
|
||||
public class IssuerService
|
||||
{
|
||||
@ConfigProperty(name = "dev.dinauer.idp.origin")
|
||||
@ConfigProperty(name = "io.verifoo.http.origin")
|
||||
String origin;
|
||||
|
||||
public String getIssuer(String realmKey)
|
||||
|
||||
@ -16,7 +16,7 @@ public class OidcConfigurationResource
|
||||
@Inject
|
||||
IssuerService issuerService;
|
||||
|
||||
@ConfigProperty(name = "dev.dinauer.idp.origin")
|
||||
@ConfigProperty(name = "io.verifoo.http.origin")
|
||||
String origin;
|
||||
|
||||
@GET
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package de.tavolio.oidc;
|
||||
|
||||
import de.tavolio.oidc.session.AuthorizationService;
|
||||
import de.tavolio.oidc.session.dto.SessionCreation;
|
||||
import de.tavolio.oidc.auth.AuthorizationService;
|
||||
import de.tavolio.oidc.auth.model.AuthorizationCreation;
|
||||
import de.tavolio.oidc.token.ClientTokenService;
|
||||
import de.tavolio.oidc.token.model.TokenResponse;
|
||||
import de.tavolio.oidc.token.TokenService;
|
||||
import de.tavolio.oidc.token.UserTokenService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.enterprise.context.RequestScoped;
|
||||
import jakarta.inject.Inject;
|
||||
@ -26,11 +27,14 @@ public class OidcResource
|
||||
JwksService jwksService;
|
||||
|
||||
@Inject
|
||||
TokenService tokenService;
|
||||
UserTokenService userTokenService;
|
||||
|
||||
@Inject
|
||||
AuthorizationService authorizationService;
|
||||
|
||||
@Inject
|
||||
ClientTokenService clientTokenService;
|
||||
|
||||
@GET
|
||||
@Path("/certs")
|
||||
public Map<String, Object> certs()
|
||||
@ -42,7 +46,7 @@ public class OidcResource
|
||||
@Path("/auth")
|
||||
public Response auth(@QueryParam("client_id") String clientId, @FormParam("email") String email, @FormParam("password") String password)
|
||||
{
|
||||
String code = authorizationService.generateBySessionCreation(realmKey, clientId, new SessionCreation(email, password));
|
||||
String code = authorizationService.generateBySessionCreation(realmKey, clientId, new AuthorizationCreation(email, password));
|
||||
return Response.status(302).location(URI.create("http://localhost:8080/callback?code=" + code + "&state=d")).build();
|
||||
}
|
||||
|
||||
@ -50,15 +54,15 @@ public class OidcResource
|
||||
@Path("/token")
|
||||
@RolesAllowed("CLIENT")
|
||||
@Transactional
|
||||
public TokenResponse token(@FormParam("grant_type") String grantType, @FormParam("client_id") String clientId, @FormParam("code") String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
public TokenResponse token(@FormParam("grant_type") String grantType, @FormParam("code") String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
if (GrantType.AUTH_CODE.equals(GrantType.fromValue(grantType)))
|
||||
{
|
||||
return tokenService.getUserToken(realmKey, code);
|
||||
return userTokenService.getToken(realmKey, code);
|
||||
}
|
||||
if (GrantType.CLIENT_CREDENTIALS.equals(GrantType.fromValue(grantType)))
|
||||
{
|
||||
return tokenService.getClientToken(realmKey);
|
||||
return clientTokenService.getToken(realmKey);
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package de.tavolio.oidc.session;
|
||||
package de.tavolio.oidc.auth;
|
||||
|
||||
import de.tavolio.realm.RealmService;
|
||||
import de.tavolio.realm.user.UserEntity;
|
||||
@ -8,7 +8,7 @@ import de.tavolio.realm.client.ClientService;
|
||||
import de.tavolio.realm.code.CodeEntity;
|
||||
import de.tavolio.realm.code.CodeRepo;
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
import de.tavolio.oidc.session.dto.SessionCreation;
|
||||
import de.tavolio.oidc.auth.model.AuthorizationCreation;
|
||||
import io.quarkus.elytron.security.common.BcryptUtil;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
@ -35,16 +35,16 @@ public class AuthorizationService
|
||||
ClientService clientService;
|
||||
|
||||
@Transactional
|
||||
public String generateBySessionCreation(String realmKey, String clientId, SessionCreation sessionCreation)
|
||||
public String generateBySessionCreation(String realmKey, String clientId, AuthorizationCreation authorizationCreation)
|
||||
{
|
||||
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
ClientEntity client = clientService.findByIdAndRealm(clientId, realm);
|
||||
Optional<UserEntity> accountEntityOptional = userRepo.findOptionalByRealmAndEmail(realm, sessionCreation.email());
|
||||
Optional<UserEntity> accountEntityOptional = userRepo.findOptionalByRealmAndEmail(realm, authorizationCreation.email());
|
||||
if (accountEntityOptional.isPresent())
|
||||
{
|
||||
UserEntity userEntity = accountEntityOptional.get();
|
||||
if (BcryptUtil.matches(sessionCreation.password(), userEntity.getPassword()))
|
||||
if (BcryptUtil.matches(authorizationCreation.password(), userEntity.getPassword()))
|
||||
{
|
||||
CodeEntity code = new CodeEntity().setId(UUID.randomUUID().toString());
|
||||
code.setExpiresAt(ZonedDateTime.now().plusMinutes(1));
|
||||
@ -1,9 +1,9 @@
|
||||
package de.tavolio.oidc.session.dto;
|
||||
package de.tavolio.oidc.auth.model;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record SessionCreation(
|
||||
public record AuthorizationCreation(
|
||||
@Email String email,
|
||||
@NotBlank String password)
|
||||
{
|
||||
@ -1,4 +1,4 @@
|
||||
package de.tavolio.oidc.auth;
|
||||
package de.tavolio.oidc.identityproviders;
|
||||
|
||||
import de.tavolio.Role;
|
||||
import de.tavolio.realm.user.UserRepo;
|
||||
@ -16,20 +16,20 @@ import io.quarkus.security.runtime.QuarkusPrincipal;
|
||||
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
|
||||
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;
|
||||
|
||||
@ApplicationScoped
|
||||
public class OidcClientIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest>
|
||||
@Priority(1)
|
||||
public class BasicAuthIdentityProvider implements IdentityProvider<UsernamePasswordAuthenticationRequest>
|
||||
{
|
||||
@Inject
|
||||
ClientRepo clientRepo;
|
||||
|
||||
@Inject
|
||||
SuperuserRepo superuserRepo;
|
||||
@Inject
|
||||
UserRepo userRepo;
|
||||
|
||||
@Override
|
||||
public Class<UsernamePasswordAuthenticationRequest> getRequestType()
|
||||
@ -0,0 +1,129 @@
|
||||
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.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;
|
||||
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.Permission;
|
||||
import java.security.PublicKey;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApplicationScoped
|
||||
@Priority(1)
|
||||
public class ClientIdentityProvider implements IdentityProvider<TokenAuthenticationRequest>
|
||||
{
|
||||
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<TokenAuthenticationRequest> getRequestType()
|
||||
{
|
||||
return TokenAuthenticationRequest.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActivateRequestContext
|
||||
public Uni<SecurityIdentity> 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.findByIdAndRealm(token.getName(), realm);
|
||||
if (client != null)
|
||||
{
|
||||
return (SecurityIdentity) QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal(client.getId())).addRole(Role.CLIENT.toString()).addAttribute("permissions", new HashSet<>(client.getPermissions())).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<String, Object> getHeader(String raw) throws JsonProcessingException
|
||||
{
|
||||
return OBJECT_MAPPER.readValue(new String(Base64.getUrlDecoder().decode(section(raw))), new TypeReference<Map<String, Object>>(){});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package de.tavolio.oidc.token;
|
||||
|
||||
import de.tavolio.oidc.IssuerService;
|
||||
import io.smallrye.jwt.build.Jwt;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ClientTokenGenerator
|
||||
{
|
||||
@Inject
|
||||
IssuerService issuerService;
|
||||
|
||||
public String generateAccessToken(String realmKey, String clientId, ZonedDateTime expiresAt, PrivateKey key, String keyId)
|
||||
{
|
||||
return Jwt.claims()
|
||||
.upn(clientId)
|
||||
.audience(clientId)
|
||||
.subject(clientId)
|
||||
.claim("realm_key", realmKey)
|
||||
.claim("client_id", clientId)
|
||||
.expiresAt(expiresAt.toInstant())
|
||||
.issuer(issuerService.getIssuer(realmKey)).jws().keyId(keyId)
|
||||
.sign(key);
|
||||
}
|
||||
}
|
||||
48
src/main/java/de/tavolio/oidc/token/ClientTokenService.java
Normal file
48
src/main/java/de/tavolio/oidc/token/ClientTokenService.java
Normal file
@ -0,0 +1,48 @@
|
||||
package de.tavolio.oidc.token;
|
||||
|
||||
import de.tavolio.oidc.token.model.TokenResponse;
|
||||
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.key.KeypairEntity;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
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
|
||||
public class ClientTokenService
|
||||
{
|
||||
@Inject
|
||||
RealmService realmService;
|
||||
|
||||
@Inject
|
||||
ClientService clientService;
|
||||
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
|
||||
@Inject
|
||||
ClientTokenGenerator clientTokenGenerator;
|
||||
|
||||
public TokenResponse getToken(String realmKey)
|
||||
{
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
ClientEntity client = clientService.findByIdAndRealm(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());
|
||||
return new TokenResponse().setAccessToken(token).setTokenType("Bearer").setExpiresAt(expiresAt.toInstant().getEpochSecond());
|
||||
}
|
||||
throw new BadRequestException();
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ import java.security.PrivateKey;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@ApplicationScoped
|
||||
public class TokenGenerator
|
||||
public class UserTokenGenerator
|
||||
{
|
||||
@Inject
|
||||
IssuerService issuerService;
|
||||
@ -2,6 +2,8 @@ package de.tavolio.oidc.token;
|
||||
|
||||
import de.tavolio.oidc.token.model.TokenResponse;
|
||||
import de.tavolio.realm.RealmRepo;
|
||||
import de.tavolio.realm.client.ClientEntity;
|
||||
import de.tavolio.realm.client.ClientService;
|
||||
import de.tavolio.realm.code.CodeEntity;
|
||||
import de.tavolio.realm.code.CodeRepo;
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
@ -11,41 +13,31 @@ import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@ApplicationScoped
|
||||
public class TokenService
|
||||
public class UserTokenService
|
||||
{
|
||||
@Inject
|
||||
CodeRepo codeRepo;
|
||||
|
||||
@Inject
|
||||
TokenGenerator tokenGenerator;
|
||||
UserTokenGenerator userTokenGenerator;
|
||||
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
|
||||
@Inject
|
||||
RealmRepo realmRepo;
|
||||
RealmService realmService;
|
||||
|
||||
@POST
|
||||
public TokenResponse getClientToken(String realmKey)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public TokenResponse getUserToken(String realmKey, String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
public TokenResponse getToken(String realmKey, String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
return generateUserToken(realmKey, code);
|
||||
}
|
||||
@ -53,18 +45,17 @@ public class TokenService
|
||||
private TokenResponse generateUserToken(String realmKey, String code) throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
String principal = identity.getPrincipal().getName();
|
||||
RealmEntity realm = realmRepo.findById(realmKey);
|
||||
RealmEntity realm = realmService.requireByKey(realmKey);
|
||||
CodeEntity entity = codeRepo.findByRealmAndId(realm, code);
|
||||
if (entity != null && !ZonedDateTime.now().isAfter(entity.getExpiresAt()) && principal.equals(entity.getClient().getId()))
|
||||
{
|
||||
KeypairEntity keypair = realm.getKeys().getFirst();
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keypair.getPrivateKey());
|
||||
PrivateKey signingKey = KeypairEntity.toPrivateKey(keypair);
|
||||
ZonedDateTime expiresAt = ZonedDateTime.now().plusYears(1);
|
||||
PrivateKey signingKey = KeyFactory.getInstance(RealmService.EC).generatePrivate(spec);
|
||||
TokenResponse response = new TokenResponse()
|
||||
.setAccessToken(tokenGenerator.generateAccessToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId()))
|
||||
.setAccessToken(userTokenGenerator.generateAccessToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, signingKey, keypair.getId()))
|
||||
.setRefreshToken(UUID.randomUUID().toString())
|
||||
.setIdToken(tokenGenerator.generateIDToken(realm.getKey(), principal, entity.getAccount().getId(), expiresAt, 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);
|
||||
34
src/main/java/de/tavolio/realm/PermissionService.java
Normal file
34
src/main/java/de/tavolio/realm/PermissionService.java
Normal file
@ -0,0 +1,34 @@
|
||||
package de.tavolio.realm;
|
||||
|
||||
import de.tavolio.Role;
|
||||
import de.tavolio.realm.user.Permission;
|
||||
import io.quarkus.security.ForbiddenException;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
import jakarta.enterprise.context.RequestScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@RequestScoped
|
||||
public class PermissionService
|
||||
{
|
||||
@Inject
|
||||
SecurityIdentity identity;
|
||||
|
||||
public void hasPermission(Permission... required)
|
||||
{
|
||||
if (identity.hasRole(Role.ROOT.toString()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Set<Permission> granted = identity.getAttribute("permissions");
|
||||
for (Permission permission : required)
|
||||
{
|
||||
if (granted != null && granted.contains(permission))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
5
src/main/java/de/tavolio/realm/Realm.java
Normal file
5
src/main/java/de/tavolio/realm/Realm.java
Normal file
@ -0,0 +1,5 @@
|
||||
package de.tavolio.realm;
|
||||
|
||||
public record Realm(String key, String name)
|
||||
{
|
||||
}
|
||||
@ -9,6 +9,12 @@ import jakarta.ws.rs.Path;
|
||||
@Path("/realms/{realm-key}")
|
||||
public class RealmResource
|
||||
{
|
||||
@Path("/")
|
||||
public RealmSubResource realms()
|
||||
{
|
||||
return CDI.current().select(RealmSubResource.class).get();
|
||||
}
|
||||
|
||||
@Path("/accounts")
|
||||
public UserResource accounts()
|
||||
{
|
||||
|
||||
25
src/main/java/de/tavolio/realm/RealmSubResource.java
Normal file
25
src/main/java/de/tavolio/realm/RealmSubResource.java
Normal file
@ -0,0 +1,25 @@
|
||||
package de.tavolio.realm;
|
||||
|
||||
import jakarta.enterprise.context.RequestScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
|
||||
@RequestScoped
|
||||
public class RealmSubResource
|
||||
{
|
||||
@Inject
|
||||
RealmRepo repo;
|
||||
|
||||
@GET
|
||||
public Realm get(@PathParam("realm-key") String key)
|
||||
{
|
||||
RealmEntity entity = repo.findById(key);
|
||||
if (entity != null)
|
||||
{
|
||||
return new Realm(entity.getKey(), entity.getName());
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,13 @@ package de.tavolio.realm.client;
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
import de.tavolio.realm.RealmScoped;
|
||||
import de.tavolio.realm.code.CodeEntity;
|
||||
import de.tavolio.realm.user.Permission;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@Table(name = "client")
|
||||
@ -26,6 +30,14 @@ public class ClientEntity implements RealmScoped
|
||||
@OneToMany(mappedBy = "client")
|
||||
private List<CodeEntity> codes;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "client_permission",
|
||||
joinColumns = @JoinColumn(name = "client_id")
|
||||
)
|
||||
@Column(name = "permission")
|
||||
private Set<Permission> permissions = new HashSet<>();
|
||||
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
@ -80,4 +92,15 @@ public class ClientEntity implements RealmScoped
|
||||
this.codes = codes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Set<Permission> getPermissions()
|
||||
{
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public ClientEntity setPermissions(Set<Permission> permissions)
|
||||
{
|
||||
this.permissions = permissions;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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;
|
||||
@ -21,16 +22,6 @@ public class ClientService
|
||||
@Inject
|
||||
RealmRepo realmRepo;
|
||||
|
||||
public ClientEntity findOrCreate(RealmEntity realm, Map.Entry<String, Client> bootstrap)
|
||||
{
|
||||
String secret = null;
|
||||
if (!StringUtils.isBlank(bootstrap.getValue().clientSecret()))
|
||||
{
|
||||
secret = BcryptUtil.bcryptHash(System.getenv(bootstrap.getValue().clientSecret()));
|
||||
}
|
||||
return clientRepo.findByRealmAndIdOptional(realm, bootstrap.getKey()).orElse(new ClientEntity().setId(bootstrap.getKey()).setSecret(secret).setRedirectURI(bootstrap.getValue().redirectURI()).setRealm(realm));
|
||||
}
|
||||
|
||||
public ClientEntity findByIdAndRealm(String clientId, RealmEntity realm)
|
||||
{
|
||||
ClientEntity client = clientRepo.findByRealmAndId(realm, clientId);
|
||||
|
||||
@ -2,8 +2,15 @@ package de.tavolio.realm.key;
|
||||
|
||||
import de.tavolio.realm.RealmEntity;
|
||||
import de.tavolio.realm.RealmScoped;
|
||||
import de.tavolio.realm.RealmService;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
|
||||
@Entity
|
||||
@Table(name = "keypair")
|
||||
public class KeypairEntity implements RealmScoped
|
||||
@ -128,4 +135,17 @@ public class KeypairEntity implements RealmScoped
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public static PrivateKey toPrivateKey(KeypairEntity keypair)
|
||||
{
|
||||
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keypair.getPrivateKey());
|
||||
try
|
||||
{
|
||||
return KeyFactory.getInstance(RealmService.EC).generatePrivate(spec);
|
||||
}
|
||||
catch (InvalidKeySpecException | NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,8 +51,8 @@ public class KeypairService
|
||||
.setUse("sig")
|
||||
.setAlg("ES256")
|
||||
.setCrv("P-256")
|
||||
.setX(Base64.getUrlEncoder().withoutPadding().encodeToString(xBytes))
|
||||
.setY(Base64.getUrlEncoder().withoutPadding().encodeToString(yBytes));
|
||||
.setX(Base64.getEncoder().withoutPadding().encodeToString(xBytes))
|
||||
.setY(Base64.getEncoder().withoutPadding().encodeToString(yBytes));
|
||||
}
|
||||
|
||||
private KeyPair generate()
|
||||
|
||||
6
src/main/java/de/tavolio/realm/user/Permission.java
Normal file
6
src/main/java/de/tavolio/realm/user/Permission.java
Normal file
@ -0,0 +1,6 @@
|
||||
package de.tavolio.realm.user;
|
||||
|
||||
public enum Permission
|
||||
{
|
||||
USER_VIEW, USER_CREATE
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package de.tavolio.realm.user;
|
||||
|
||||
import de.tavolio.realm.PermissionService;
|
||||
import de.tavolio.realm.user.dto.User;
|
||||
import de.tavolio.realm.user.dto.UserCreation;
|
||||
import jakarta.enterprise.context.RequestScoped;
|
||||
@ -20,27 +21,34 @@ public class UserResource
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
@PathParam("realm-key")
|
||||
String realmKey;
|
||||
|
||||
@Inject
|
||||
UserService userService;
|
||||
|
||||
@Inject
|
||||
PermissionService permissionService;
|
||||
|
||||
@POST
|
||||
public User post(@Valid UserCreation account)
|
||||
{
|
||||
User createdUser = userService.create("", account);
|
||||
LOG.infof("Created account successfully: %s", account.email());
|
||||
return createdUser;
|
||||
permissionService.hasPermission(Permission.USER_CREATE);
|
||||
return userService.create(realmKey, account);
|
||||
}
|
||||
|
||||
@GET
|
||||
public User get(@PathParam("id") String id)
|
||||
public List<User> get()
|
||||
{
|
||||
return userService.getUser(id);
|
||||
permissionService.hasPermission(Permission.USER_VIEW);
|
||||
return userService.get();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public User getById(@PathParam("id") String id)
|
||||
{
|
||||
permissionService.hasPermission(Permission.USER_VIEW);
|
||||
return userService.getUser(id);
|
||||
}
|
||||
|
||||
@ -48,6 +56,7 @@ public class UserResource
|
||||
@Path("/search")
|
||||
public Map<String, User> get(List<String> ids)
|
||||
{
|
||||
permissionService.hasPermission(Permission.USER_VIEW);
|
||||
return userService.findByIds(ids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import de.tavolio.verify.jwks.EcPublicKey;
|
||||
import de.tavolio.verify.jwks.JwksKey;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -18,45 +19,24 @@ public class JwksService
|
||||
@Inject
|
||||
KeypairRepo keypairRepo;
|
||||
|
||||
public Optional<JwksKey> findByKid(String kid)
|
||||
{
|
||||
KeypairEntity keypair = keypairRepo.findById(kid);
|
||||
if (keypair != null)
|
||||
public JwksKey findByKid(KeypairEntity keypair)
|
||||
{
|
||||
switch (keypair.getType())
|
||||
{
|
||||
case "EC" ->
|
||||
{
|
||||
return Optional.of(constructPublicKey(keypair));
|
||||
return constructPublicKey(keypair);
|
||||
}
|
||||
case "RSA" ->
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
default ->
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public List<JwksKey> findByRealm(RealmEntity realm)
|
||||
{
|
||||
List<JwksKey> result = new LinkedList<>();
|
||||
for (KeypairEntity keypair : realm.getKeys())
|
||||
{
|
||||
switch (keypair.getType())
|
||||
{
|
||||
case "EC" ->
|
||||
{
|
||||
result.add(constructPublicKey(keypair));
|
||||
}
|
||||
case "RSA" ->
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EcPublicKey constructPublicKey(KeypairEntity entity)
|
||||
{
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
package de.tavolio.verify;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import de.tavolio.verify.jwks.JwksKey;
|
||||
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.mutiny.Uni;
|
||||
import io.smallrye.mutiny.infrastructure.Infrastructure;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.enterprise.context.control.ActivateRequestContext;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@ApplicationScoped
|
||||
public class TokenVerifier implements IdentityProvider<TokenAuthenticationRequest>
|
||||
{
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Inject
|
||||
JwksService jwksService;
|
||||
|
||||
@Override
|
||||
public Class<TokenAuthenticationRequest> getRequestType()
|
||||
{
|
||||
return TokenAuthenticationRequest.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActivateRequestContext
|
||||
public Uni<SecurityIdentity> 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(() -> {
|
||||
Optional<JwksKey> keypair = jwksService.findByKid(keyId);
|
||||
if (keypair.isPresent())
|
||||
{
|
||||
PublicKey publicKey = keypair.get().toPublicKey();
|
||||
return (SecurityIdentity) QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal("")).build();
|
||||
}
|
||||
throw new AuthenticationFailedException();
|
||||
}).runSubscriptionOn(Infrastructure.getDefaultWorkerPool());
|
||||
}
|
||||
return Uni.createFrom().nullItem();
|
||||
}
|
||||
catch (JsonProcessingException e)
|
||||
{
|
||||
return Uni.createFrom().failure(new AuthenticationFailedException());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> getHeader(String raw) throws JsonProcessingException
|
||||
{
|
||||
return OBJECT_MAPPER.readValue(new String(Base64.getDecoder().decode(section(raw))), new TypeReference<Map<String, Object>>(){});
|
||||
}
|
||||
|
||||
private String section(String raw)
|
||||
{
|
||||
String[] sections = raw.split("\\.");
|
||||
if (sections.length == 3)
|
||||
{
|
||||
return sections[0];
|
||||
}
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@ -6,15 +6,6 @@ quarkus.http.test-port=9089
|
||||
quarkus.http.cors.enabled=true
|
||||
%dev.quarkus.http.cors.origins=/.*/
|
||||
|
||||
# JWT
|
||||
%prod.smallrye.jwt.sign.key.location=${PRIVATE_KEY_LOCATION}
|
||||
%prod.mp.jwt.verify.publickey.location=${PUBLIC_KEY_LOCATION}
|
||||
|
||||
mp.jwt.verify.issuer=https://tavolio.de
|
||||
|
||||
%dev.smallrye.jwt.verify.key.location=/home/andreas/Documents/dev/publicKey.pem
|
||||
%dev.smallrye.jwt.sign.key.location=/home/andreas/Documents/dev/privateKey.pem
|
||||
|
||||
# Postgres
|
||||
prod.quarkus.hibernate-orm.validate-in-dev-mode=false
|
||||
quarkus.hibernate-orm.schema-management.strategy=none
|
||||
@ -25,10 +16,6 @@ quarkus.datasource.db-kind=postgresql
|
||||
%dev,test.quarkus.datasource.password=postgres
|
||||
%dev,test.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres?currentSchema=auth
|
||||
|
||||
%prod.quarkus.datasource.username=${DB_USER}
|
||||
%prod.quarkus.datasource.password=${DB_PASSWORD}
|
||||
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_DATABASE}?currentSchema=${DB_SCHEMA}
|
||||
|
||||
# Flyway
|
||||
quarkus.flyway.enabled=false
|
||||
%test.quarkus.flyway.clean-at-start=true
|
||||
@ -37,16 +24,10 @@ quarkus.flyway.enabled=false
|
||||
%test,dev.quarkus.flyway.migrate-at-start=false
|
||||
quarkus.flyway.migrate-at-start=true
|
||||
|
||||
# IAM Superuser
|
||||
%test,dev.iam.user.name=tavolio
|
||||
%test,dev.iam.user.password=tavolio
|
||||
|
||||
|
||||
quarkus.http.access-log.enabled=true
|
||||
quarkus.http.auth.basic=true
|
||||
dev.dinauer.idp.origin=http://localhost:8089
|
||||
|
||||
%dev.dev.dinauer.idp.superuser.username=admin
|
||||
%dev.dev.dinauer.idp.superuser.password=pw
|
||||
io.verifoo.http.origin=http://localhost:8089
|
||||
|
||||
quarkus.log.level=DEBUG
|
||||
%dev.io.verifoo.superuser.username=admin
|
||||
%dev.io.verifoo.superuser.password=pw
|
||||
@ -1,22 +1,22 @@
|
||||
realms:
|
||||
maven:
|
||||
name: My Bootstrap Realm
|
||||
name: MavenVault
|
||||
audience:
|
||||
strategy: realm
|
||||
strategy: static
|
||||
value: https://maven.dinauer.dev/api
|
||||
key:
|
||||
type: EC
|
||||
alg: P256
|
||||
clients:
|
||||
backend:
|
||||
client-secret: MY_SECRET_ENV
|
||||
permissions:
|
||||
- USER:VIEW
|
||||
frontend:
|
||||
secret:
|
||||
bcrypt: $2a$12$1oYS45e/nXP1OeMgdZZAKeEixarRDzbBGZd0xOnEQQMKlOKwVMrX.
|
||||
redirect-uri: http://localhost:8080/callback
|
||||
accounts:
|
||||
permissions:
|
||||
- USER_VIEW
|
||||
users:
|
||||
- email: andreas.j.dinauer@gmail.com
|
||||
first-name: Andreas
|
||||
last-name: Dinauer
|
||||
password-plain: pw
|
||||
roles:
|
||||
- USER:VIEW
|
||||
password:
|
||||
bcrypt: $2a$12$vvHIfs6MWovIulGcuHeDie7yKtZzkBEPpNPiuj3C2HmUASYQ/x5SO
|
||||
Loading…
x
Reference in New Issue
Block a user