Provide an embedded Docker container for local development #1
79
src/main/java/se/su/dsv/oauth2/JDBCClientRepository.java
Normal file
79
src/main/java/se/su/dsv/oauth2/JDBCClientRepository.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package se.su.dsv.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||||
|
import se.su.dsv.oauth2.admin.repository.ClientRepository;
|
||||||
|
import se.su.dsv.oauth2.admin.repository.ClientRow;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class JDBCClientRepository implements ClientRepository {
|
||||||
|
public final JdbcClient jdbc;
|
||||||
|
|
||||||
|
public JDBCClientRepository(final JdbcClient jdbc) {
|
||||||
|
this.jdbc = jdbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JdbcClient getJdbc() {
|
||||||
|
return jdbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addClientOwner(final String principalName, final String id) {
|
||||||
|
getJdbc().sql("INSERT INTO client_owner (client_id, owner) VALUES (:clientId, :owner)")
|
||||||
|
.param("clientId", id)
|
||||||
|
.param("owner", principalName)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ClientRow> getClients(final Principal owner) {
|
||||||
|
return getJdbc().sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE id IN (SELECT client_id FROM client_owner WHERE owner = :owner)")
|
||||||
|
.param("owner", owner.getName())
|
||||||
|
.query(ClientRow.class)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeOwner(final String id, final String owner) {
|
||||||
|
getJdbc().sql("DELETE FROM client_owner WHERE client_id = :id AND owner = :owner")
|
||||||
|
.param("id", id)
|
||||||
|
.param("owner", owner)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ClientRow> getClientRowById(final String id) {
|
||||||
|
return getJdbc().sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE id = :id")
|
||||||
|
.param("id", id)
|
||||||
|
.query(ClientRow.class)
|
||||||
|
.optional();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ClientRow> getClientRowByClientId(final String clientId) {
|
||||||
|
return getJdbc().sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE client_id = :clientId")
|
||||||
|
.param("clientId", clientId)
|
||||||
|
.query(ClientRow.class)
|
||||||
|
.optional();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addNewClient(final ClientRow clientRow) {
|
||||||
|
getJdbc().sql("""
|
||||||
|
INSERT INTO client (id, client_id, client_secret, name, redirect_uri, contact_email, scopes)
|
||||||
|
VALUES (:id, :clientId, :clientSecret, :name, :redirectUri, :contactEmail, :scopes)
|
||||||
|
""")
|
||||||
|
.paramSource(clientRow)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getOwners(final String id) {
|
||||||
|
return getJdbc().sql("SELECT owner FROM client_owner WHERE client_id = :clientId")
|
||||||
|
.param("clientId", id)
|
||||||
|
.query(String.class)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/java/se/su/dsv/oauth2/PersistentConfiguration.java
Normal file
13
src/main/java/se/su/dsv/oauth2/PersistentConfiguration.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package se.su.dsv.oauth2;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class PersistentConfiguration {
|
||||||
|
@Bean
|
||||||
|
public JDBCClientRepository jdbcClientRepository(JdbcClient jdbcClient) {
|
||||||
|
return new JDBCClientRepository(jdbcClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package se.su.dsv.oauth2.admin;
|
package se.su.dsv.oauth2.admin;
|
||||||
|
|
||||||
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||||
@ -10,29 +9,28 @@ import org.springframework.security.oauth2.server.authorization.settings.ClientS
|
|||||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||||
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import se.su.dsv.oauth2.admin.repository.ClientRepository;
|
||||||
|
import se.su.dsv.oauth2.admin.repository.ClientRow;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ClientManager implements RegisteredClientRepository, ClientManagementService {
|
public class ClientManager implements RegisteredClientRepository, ClientManagementService {
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final JdbcClient jdbc;
|
private final ClientRepository clientRepository;
|
||||||
|
|
||||||
public ClientManager(
|
public ClientManager(
|
||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
JdbcClient jdbc)
|
ClientRepository clientRepository)
|
||||||
{
|
{
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.jdbc = jdbc;
|
this.clientRepository = clientRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,49 +40,32 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
String clientSecret = clientData.isPublic() ? null : Util.generateAlphanumericString(32);
|
String clientSecret = clientData.isPublic() ? null : Util.generateAlphanumericString(32);
|
||||||
String encodedClientSecret = clientSecret == null ? null : passwordEncoder.encode(clientSecret);
|
String encodedClientSecret = clientSecret == null ? null : passwordEncoder.encode(clientSecret);
|
||||||
String redirectURI = clientData.redirectURI() != null ? clientData.redirectURI().toString() : null;
|
String redirectURI = clientData.redirectURI() != null ? clientData.redirectURI().toString() : null;
|
||||||
|
String scopeString = String.join(" ", clientData.scopes());
|
||||||
|
|
||||||
jdbc.sql("""
|
ClientRow clientRow = new ClientRow(id, clientId, clientData.clientName(), clientData.contactEmail(),
|
||||||
INSERT INTO client (id, client_id, client_secret, name, redirect_uri, contact_email, scopes)
|
redirectURI, scopeString, encodedClientSecret);
|
||||||
VALUES (:id, :clientId, :clientSecret, :name, :redirectUri, :contactEmail, :scopes)
|
|
||||||
""")
|
|
||||||
.param("id", id)
|
|
||||||
.param("clientId", clientId)
|
|
||||||
.param("clientSecret", encodedClientSecret)
|
|
||||||
.param("name", clientData.clientName())
|
|
||||||
.param("redirectUri", redirectURI)
|
|
||||||
.param("contactEmail", clientData.contactEmail())
|
|
||||||
.param("scopes", String.join(" ", clientData.scopes()))
|
|
||||||
.update();
|
|
||||||
|
|
||||||
addClientOwner(owner.getName(), id);
|
clientRepository.addNewClient(clientRow);
|
||||||
|
|
||||||
|
clientRepository.addClientOwner(owner.getName(), id);
|
||||||
|
|
||||||
return new NewClient(id, clientId, clientSecret);
|
return new NewClient(id, clientId, clientSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addClientOwner(final String principalName, final String id) {
|
|
||||||
jdbc.sql("INSERT INTO client_owner (client_id, owner) VALUES (:clientId, :owner)")
|
|
||||||
.param("clientId", id)
|
|
||||||
.param("owner", principalName)
|
|
||||||
.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<Client> getClient(final Principal principal, final String id) {
|
public Optional<Client> getClient(final Principal principal, final String id) {
|
||||||
boolean ownsClient = getOwners(id).contains(principal.getName());
|
boolean ownsClient = clientRepository.getOwners(id).contains(principal.getName());
|
||||||
|
|
||||||
if (!ownsClient) {
|
if (!ownsClient) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
return getClientRowById(id)
|
return clientRepository.getClientRowById(id)
|
||||||
.map(this::toClient);
|
.map(this::toClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Client> getClients(final Principal owner) {
|
public List<Client> getClients(final Principal owner) {
|
||||||
return jdbc.sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE id IN (SELECT client_id FROM client_owner WHERE owner = :owner)")
|
return clientRepository.getClients(owner)
|
||||||
.param("owner", owner.getName())
|
|
||||||
.query(ClientRow.class)
|
|
||||||
.list()
|
|
||||||
.stream()
|
.stream()
|
||||||
.map(this::toClient)
|
.map(this::toClient)
|
||||||
.toList();
|
.toList();
|
||||||
@ -92,48 +73,28 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addOwner(final Principal currentUser, final String id, final String newOwnerPrincipal) {
|
public void addOwner(final Principal currentUser, final String id, final String newOwnerPrincipal) {
|
||||||
if (!getOwners(id).contains(currentUser.getName())) {
|
if (!clientRepository.getOwners(id).contains(currentUser.getName())) {
|
||||||
throw new IllegalStateException(currentUser.getName() + " is not an owner of the client");
|
throw new IllegalStateException(currentUser.getName() + " is not an owner of the client");
|
||||||
}
|
}
|
||||||
jdbc.sql("INSERT INTO client_owner (client_id, owner) VALUES (:id, :owner) ON DUPLICATE KEY UPDATE owner = owner")
|
clientRepository.addClientOwner(newOwnerPrincipal, id);
|
||||||
.param("id", id)
|
|
||||||
.param("owner", newOwnerPrincipal)
|
|
||||||
.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean removeOwner(final Principal currentUser, final String id, final String owner) {
|
public boolean removeOwner(final Principal currentUser, final String id, final String owner) {
|
||||||
if (!getOwners(id).contains(currentUser.getName())) {
|
if (!clientRepository.getOwners(id).contains(currentUser.getName())) {
|
||||||
throw new IllegalStateException(currentUser.getName() + " is not an owner of the client");
|
throw new IllegalStateException(currentUser.getName() + " is not an owner of the client");
|
||||||
}
|
}
|
||||||
if (currentUser.getName().equals(owner)) {
|
if (currentUser.getName().equals(owner)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
jdbc.sql("DELETE FROM client_owner WHERE client_id = :id AND owner = :owner")
|
clientRepository.removeOwner(id, owner);
|
||||||
.param("id", id)
|
|
||||||
.param("owner", owner)
|
|
||||||
.update();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ClientRow> getClientRowById(final String id) {
|
|
||||||
return jdbc.sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE id = :id")
|
|
||||||
.param("id", id)
|
|
||||||
.query(ClientRow.class)
|
|
||||||
.optional();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Optional<ClientRow> getClientRowByClientId(final String clientId) {
|
|
||||||
return jdbc.sql("SELECT id, client_id, name, contact_email, redirect_uri, scopes, client_secret FROM client WHERE client_id = :clientId")
|
|
||||||
.param("clientId", clientId)
|
|
||||||
.query(ClientRow.class)
|
|
||||||
.optional();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Client toClient(final ClientRow clientRow) {
|
private Client toClient(final ClientRow clientRow) {
|
||||||
List<String> owners = getOwners(clientRow.id());
|
List<String> owners = clientRepository.getOwners(clientRow.id());
|
||||||
owners.sort(Comparator.naturalOrder());
|
owners.sort(Comparator.naturalOrder());
|
||||||
Set<String> scopes = clientRow.scopeSet();
|
Set<String> scopes = clientRow.scopeSet();
|
||||||
boolean isPublic = clientRow.isPublic();
|
boolean isPublic = clientRow.isPublic();
|
||||||
@ -148,13 +109,6 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
owners);
|
owners);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getOwners(final String id) {
|
|
||||||
return jdbc.sql("SELECT owner FROM client_owner WHERE client_id = :clientId")
|
|
||||||
.param("clientId", id)
|
|
||||||
.query(String.class)
|
|
||||||
.list();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by various components of the OAuth 2.0 infrastructure to upgrade
|
// Used by various components of the OAuth 2.0 infrastructure to upgrade
|
||||||
// the client secret if necessary based on the PasswordEncoder bean.
|
// the client secret if necessary based on the PasswordEncoder bean.
|
||||||
@Override
|
@Override
|
||||||
@ -164,14 +118,14 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RegisteredClient findById(final String id) {
|
public RegisteredClient findById(final String id) {
|
||||||
return getClientRowById(id)
|
return clientRepository.getClientRowById(id)
|
||||||
.map(ClientManager::toRegisteredClient)
|
.map(ClientManager::toRegisteredClient)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RegisteredClient findByClientId(final String clientId) {
|
public RegisteredClient findByClientId(final String clientId) {
|
||||||
return getClientRowByClientId(clientId)
|
return clientRepository.getClientRowByClientId(clientId)
|
||||||
.map(ClientManager::toRegisteredClient)
|
.map(ClientManager::toRegisteredClient)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
@ -216,24 +170,4 @@ public class ClientManager implements RegisteredClientRepository, ClientManageme
|
|||||||
.scopes(currentScopes -> currentScopes.addAll(clientRow.scopeSet()))
|
.scopes(currentScopes -> currentScopes.addAll(clientRow.scopeSet()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ClientRow(
|
|
||||||
String id,
|
|
||||||
String clientId,
|
|
||||||
String name,
|
|
||||||
String contactEmail,
|
|
||||||
String redirectUri,
|
|
||||||
String scopes,
|
|
||||||
String clientSecret)
|
|
||||||
{
|
|
||||||
private Set<String> scopeSet() {
|
|
||||||
return Arrays.stream(this.scopes.split(" "))
|
|
||||||
.filter(Predicate.not(String::isBlank))
|
|
||||||
.collect(Collectors.toUnmodifiableSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPublic() {
|
|
||||||
return clientSecret == null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
package se.su.dsv.oauth2.admin.repository;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface ClientRepository {
|
||||||
|
void addNewClient(ClientRow clientRow);
|
||||||
|
|
||||||
|
List<ClientRow> getClients(Principal owner);
|
||||||
|
|
||||||
|
void addClientOwner(String principalName, String id);
|
||||||
|
|
||||||
|
void removeOwner(String id, String owner);
|
||||||
|
|
||||||
|
List<String> getOwners(String id);
|
||||||
|
|
||||||
|
Optional<ClientRow> getClientRowById(String id);
|
||||||
|
|
||||||
|
Optional<ClientRow> getClientRowByClientId(String clientId);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package se.su.dsv.oauth2.admin.repository;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public record ClientRow(
|
||||||
|
String id,
|
||||||
|
String clientId,
|
||||||
|
String name,
|
||||||
|
String contactEmail,
|
||||||
|
String redirectUri,
|
||||||
|
String scopes,
|
||||||
|
String clientSecret)
|
||||||
|
{
|
||||||
|
public Set<String> scopeSet() {
|
||||||
|
return Arrays.stream(this.scopes.split(" "))
|
||||||
|
.filter(Predicate.not(String::isBlank))
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPublic() {
|
||||||
|
return clientSecret == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user