diff --git a/dbrepo-auth-service/init/Pipfile.lock b/dbrepo-auth-service/init/Pipfile.lock index c8224a7844942ed36a6fef30185dc094f516378d..57631a05559948613a5c9a63b37463c95a48da9a 100644 --- a/dbrepo-auth-service/init/Pipfile.lock +++ b/dbrepo-auth-service/init/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "certifi": { "hashes": [ - "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", - "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2024.12.14" + "version": "==2025.1.31" }, "charset-normalizer": { "hashes": [ @@ -175,11 +175,11 @@ "develop": { "certifi": { "hashes": [ - "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", - "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" ], "markers": "python_version >= '3.6'", - "version": "==2024.12.14" + "version": "==2025.1.31" }, "charset-normalizer": { "hashes": [ diff --git a/dbrepo-auth-service/init/test/test_unit_app.py b/dbrepo-auth-service/init/test/test_unit_app.py index 624b7d8d53e7393d2077c214278bdb98f32297ba..af6aed379a3780157718d760bdabd79475e8d249 100644 --- a/dbrepo-auth-service/init/test/test_unit_app.py +++ b/dbrepo-auth-service/init/test/test_unit_app.py @@ -16,38 +16,6 @@ class AppUnitTest(unittest.TestCase): "session_state": "ae64d2bd-3225-4e05-9943-2bb91fb8fe52", "scope": "profile email" } - user_res = [ - {"id": "5b516520-67cb-4aa0-86a6-d12f8b8f1a20", - "username": "admin", - "firstName": "User1", - "lastName": "Bar1", - "emailVerified": False, - "attributes": {"LDAP_ENTRY_DN": ["cn=admin,ou=users,dc=dbrepo,dc=at"], - "createTimestamp": ["20250120141013Z"], - "modifyTimestamp": ["20250120141013Z"], - "LDAP_ID": ["02b6e096-6b84-103f-81f6-1f6da137f2bb"]}, - "createdTimestamp": 1737382606939, - "enabled": True, - "totp": False, - "federationLink": "c109d473-5ce1-4032-af7b-02e5442f5c07", - "disableableCredentialTypes": [], - "requiredActions": [], - "notBefore": 0, - "access": {"manageGroupMembership": True, - "view": True, - "mapRoles": True, - "impersonate": True, - "manage": True}}] - - def test_fetch_succeeds(self): - with requests_mock.Mocker() as mock: - # mock - mock.post(f'{endpoint}/realms/master/protocol/openid-connect/token', json=self.token_res, status_code=200) - mock.get(f'{endpoint}/admin/realms/dbrepo/users/?username=admin', json=self.user_res, status_code=200) - - # test - user_id = fetch() - self.assertEqual("02b6e096-6b84-103f-81f6-1f6da137f2bb", user_id) def test_fetch_token_bad_request_fails(self): with requests_mock.Mocker() as mock: diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index ac7fa6e613ca2bb5de260b4c0606533b4ebb9bc6..d8850b5589959c0f5241d59870d1b629fcff8b59 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -546,6 +546,7 @@ public abstract class BaseTest { public final static User USER_1 = User.builder() .id(USER_1_ID) + .keycloakId(USER_1_KEYCLOAK_ID) .username(USER_1_USERNAME) .firstname(USER_1_FIRSTNAME) .lastname(USER_1_LASTNAME) @@ -646,6 +647,7 @@ public abstract class BaseTest { public final static User USER_2 = User.builder() .id(USER_2_ID) + .keycloakId(USER_2_KEYCLOAK_ID) .username(USER_2_USERNAME) .firstname(USER_2_FIRSTNAME) .lastname(USER_2_LASTNAME) @@ -734,6 +736,7 @@ public abstract class BaseTest { public final static User USER_3 = User.builder() .id(USER_3_ID) + .keycloakId(USER_3_KEYCLOAK_ID) .username(USER_3_USERNAME) .firstname(USER_3_FIRSTNAME) .lastname(USER_3_LASTNAME) @@ -813,6 +816,7 @@ public abstract class BaseTest { public final static User USER_4 = User.builder() .id(USER_4_ID) + .keycloakId(USER_4_KEYCLOAK_ID) .username(USER_4_USERNAME) .firstname(USER_4_FIRSTNAME) .lastname(USER_4_LASTNAME) @@ -853,7 +857,7 @@ public abstract class BaseTest { USER_4_PASSWORD, USER_4_DETAILS.getAuthorities()); public final static UUID USER_5_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); - public final static UUID USER_5_LDAP_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); + public final static UUID USER_5_KEYCLOAK_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); public final static String USER_5_USERNAME = "nobody"; public final static String USER_5_FIRSTNAME = "No"; public final static String USER_5_LASTNAME = "Body"; @@ -908,6 +912,7 @@ public abstract class BaseTest { public final static User USER_5 = User.builder() .id(USER_5_ID) + .keycloakId(USER_5_KEYCLOAK_ID) .username(USER_5_USERNAME) .firstname(USER_5_FIRSTNAME) .lastname(USER_5_LASTNAME) diff --git a/dbrepo-upload-service/pre-create.sh b/dbrepo-upload-service/pre-create.sh index 27b05a914619e655980ed1b33f00023806f6b608..2d6eb4f861297db095e9dcf5268ed858fde7cd6d 100755 --- a/dbrepo-upload-service/pre-create.sh +++ b/dbrepo-upload-service/pre-create.sh @@ -21,7 +21,7 @@ END exit 0 fi -echo "[DEBUG] [pre-create hook] request has 'Authorization' header present" >&2 +echo "[DEBUG] [pre-create hook] request has 'Authorization' header p resent" >&2 BEARER="$(echo "$REQUEST_RAW" | jq -r '.Event.HTTPRequest.Header.Authorization[0]')" diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java b/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java index e360cecfdc106386a1229cfd12de189a8b57d60c..601229b5e5ae64da214820b2f91396a926d77884 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/config/GatewayConfig.java @@ -1,13 +1,11 @@ package at.tuwien.config; -import at.tuwien.interceptor.KeycloakInterceptor; import lombok.Getter; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.DefaultUriBuilderFactory; @Log4j2 @Getter @@ -28,13 +26,4 @@ public class GatewayConfig { return new RestTemplate(); } - @Bean("keycloakRestTemplate") - public RestTemplate keycloakRestTemplate() { - final RestTemplate restTemplate = new RestTemplate(); - restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(keycloakEndpoint)); - restTemplate.getInterceptors() - .add(new KeycloakInterceptor(restTemplate(), keycloakUsername, keycloakPassword, keycloakEndpoint)); - return restTemplate; - } - } diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java b/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java index 01be743daa98d258391d3d9c4e6bdec7aa4f8773..6243cb3ab5ecffc614cf71391886cae1852d30a2 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/config/KeycloakConfig.java @@ -1,25 +1,11 @@ package at.tuwien.config; -import at.tuwien.api.auth.KeycloakErrorDto; -import at.tuwien.api.keycloak.UserCreateDto; -import at.tuwien.api.keycloak.UserDto; -import at.tuwien.exception.AuthServiceConnectionException; -import at.tuwien.exception.AuthServiceException; -import at.tuwien.exception.EmailExistsException; -import at.tuwien.exception.UserExistsException; import lombok.Getter; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; +import org.keycloak.admin.client.Keycloak; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestTemplate; @Log4j2 @Getter @@ -29,55 +15,9 @@ public class KeycloakConfig { @Value("${dbrepo.endpoints.keycloak}") private String keycloakEndpoint; - @Autowired - @Qualifier("keycloakRestTemplate") - private RestTemplate keycloakRestTemplate; - - public Boolean existsByUsername(String username) throws AuthServiceException, AuthServiceConnectionException { - final String path = "/admin/realms/dbrepo/users/?username=" + username; - final ResponseEntity<UserDto[]> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.GET, HttpEntity.EMPTY, UserDto[].class); - } catch (HttpServerErrorException e) { - log.error("Failed to find user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (Exception e) { - log.error("Failed to find user: unexpected response: {}", e.getMessage()); - throw new AuthServiceException("Unexpected result", e); - } - final UserDto[] body = response.getBody(); - if (body == null || body.length != 1) { - log.error("Failed to find user with username {}", username); - return false; - } - return true; - } - - public void createUser(UserCreateDto data) throws UserExistsException, EmailExistsException, - AuthServiceConnectionException, AuthServiceException { - final String path = "/admin/realms/dbrepo/users"; - final ResponseEntity<Void> response; - try { - response = keycloakRestTemplate.exchange(path, HttpMethod.POST, new HttpEntity<>(data), Void.class); - } catch (HttpServerErrorException e) { - log.error("Failed to create user: {}", e.getMessage()); - throw new AuthServiceConnectionException("Service unavailable", e); - } catch (HttpClientErrorException.Conflict e) { - if (e.getResponseBodyAsByteArray() != null && e.getResponseBodyAsByteArray().length > 0) { - final KeycloakErrorDto error = e.getResponseBodyAs(KeycloakErrorDto.class); - if (error != null && error.getErrorMessage().contains("same email")) { - log.error("Failed to create user: email exists: {}", e.getMessage()); - throw new EmailExistsException("E-Mail exists", e); - } - } - log.error("Failed to create user: user exists: {}", e.getMessage()); - throw new UserExistsException("User exists", e); - } - if (!response.getStatusCode().equals(HttpStatus.CREATED)) { - log.error("Failed to create user: unexpected status: {}", response.getStatusCode().value()); - throw new AuthServiceException("Unexpected status: " + response.getStatusCode().value()); - } - log.debug("Created user {} at auth service", data.getUsername()); + @Bean + public Keycloak keycloak() { + return Keycloak.getInstance(keycloakEndpoint, "master", "admin", "admin", "admin-cli"); } } diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..c6b5d429e7b87b1a3a4759167f794ad183311883 --- /dev/null +++ b/dbrepo-upload-service/src/test/java/at/tuwien/mapper/MetadataMapper.java @@ -0,0 +1,22 @@ +package at.tuwien.mapper; + + +import at.tuwien.api.keycloak.UserCreateDto; +import at.tuwien.api.user.external.ExternalResultType; +import org.keycloak.representations.idm.UserRepresentation; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +import java.util.LinkedList; + +@Mapper(componentModel = "spring", imports = {LinkedList.class, ExternalResultType.class}) +public interface MetadataMapper { + + org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); + + @Mappings({ + @Mapping(target = "attributes", ignore = true) + }) + UserRepresentation userCreateDtoToUserRepresentation(UserCreateDto data); +} diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java b/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java index 30fd6752c3b37a20487aa4133aefdefcbdf3e4b0..052d028d39478f3f8a61b2639bf8bfe8487af9ba 100644 --- a/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java +++ b/dbrepo-upload-service/src/test/java/at/tuwien/service/UploadServiceIntegrationTest.java @@ -7,11 +7,9 @@ import at.tuwien.api.keycloak.UserCreateDto; import at.tuwien.config.KeycloakConfig; import at.tuwien.config.TusdConfig; import at.tuwien.config.TusdContainerConfig; -import at.tuwien.exception.AuthServiceConnectionException; import at.tuwien.exception.AuthServiceException; -import at.tuwien.exception.EmailExistsException; -import at.tuwien.exception.UserExistsException; import at.tuwien.interceptor.KeycloakInterceptor; +import at.tuwien.util.KeycloakUtil; import com.github.dockerjava.api.model.ExposedPort; import dasniko.testcontainers.keycloak.KeycloakContainer; import lombok.extern.log4j.Log4j2; @@ -20,7 +18,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.*; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -33,7 +34,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @Log4j2 @@ -48,6 +48,9 @@ public class UploadServiceIntegrationTest { @Autowired private TusdConfig tusdConfig; + @Autowired + private KeycloakUtil keycloakUtil; + @Autowired private KeycloakConfig keycloakConfig; @@ -55,7 +58,7 @@ public class UploadServiceIntegrationTest { private static TusdContainerConfig.TusdContainer tusdContainer = TusdContainerConfig.TusdContainer.getInstance(); @Container - private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:24.0") + private static KeycloakContainer keycloakContainer = new KeycloakContainer("quay.io/keycloak/keycloak:26.0") .withImagePullPolicy(PullPolicy.alwaysPull()) .withRealmImportFile("init/dbrepo-realm.json") .withEnv("KC_HOSTNAME_STRICT_HTTPS", "false") @@ -69,9 +72,8 @@ public class UploadServiceIntegrationTest { } @BeforeEach - public void beforeEach() throws UserExistsException, AuthServiceException, AuthServiceConnectionException, - EmailExistsException { - if (keycloakConfig.existsByUsername(keycloakContainer.getAdminUsername())) { + public void beforeEach() throws AuthServiceException { + if (keycloakUtil.existsByUsername(keycloakContainer.getAdminUsername())) { return; } final UserCreateDto payload = UserCreateDto.builder() @@ -82,7 +84,7 @@ public class UploadServiceIntegrationTest { .value(keycloakContainer.getAdminPassword()) .build())) .build(); - keycloakConfig.createUser(payload); + keycloakUtil.createUser(payload); } @Test diff --git a/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java b/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b7c24cab9d5221f893e2e1fceb3f378b59e6d684 --- /dev/null +++ b/dbrepo-upload-service/src/test/java/at/tuwien/util/KeycloakUtil.java @@ -0,0 +1,46 @@ +package at.tuwien.util; + +import at.tuwien.api.keycloak.UserCreateDto; +import at.tuwien.exception.AuthServiceException; +import at.tuwien.mapper.MetadataMapper; +import jakarta.ws.rs.core.Response; +import lombok.extern.log4j.Log4j2; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +@Log4j2 +public class KeycloakUtil { + + + private final MetadataMapper metadataMapper; + private final Keycloak keycloak; + + @Autowired + public KeycloakUtil(MetadataMapper metadataMapper, Keycloak keycloak) { + this.metadataMapper = metadataMapper; + this.keycloak = keycloak; + } + + public void createUser(UserCreateDto data) throws AuthServiceException { + final UserRepresentation user = metadataMapper.userCreateDtoToUserRepresentation(data); + try (Response response = keycloak.realm("dbrepo") + .users() + .create(user)) { + if (response.getStatus() != 200) { + log.error("Failed to delete user: unexpected response status: {}", response.getStatus()); + throw new AuthServiceException("Unexpected response status: " + response.getStatus()); + } + } + log.info("Created user at auth service"); + } + + public boolean existsByUsername(String username) { + return keycloak.realm("dbrepo") + .users() + .search(username) + .isEmpty(); + } +}