diff --git a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/AbstractEndpoint.java b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/AbstractEndpoint.java index e42e546..d68ccb2 100644 --- a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/AbstractEndpoint.java +++ b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/AbstractEndpoint.java @@ -26,7 +26,7 @@ public abstract class AbstractEndpoint implements AutoCloseable { private final String handlerTag; private final CleverMicroAmqpClient client; - private final ObjectMapper objectMapper; + protected final ObjectMapper objectMapper; private final String queueName; diff --git a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointErrorResponse.java b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointErrorResponse.java index 7e68126..b0e99f0 100644 --- a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointErrorResponse.java +++ b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointErrorResponse.java @@ -2,20 +2,38 @@ package com.cleverthis.authservice.controller.rabbitmq.model; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Builder; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; /** * The model for replied response. */ -@Data +@EqualsAndHashCode(callSuper = true) +@Getter +@Setter +@ToString @Builder -public class EndpointErrorResponse { - @JsonProperty("type") - private final String type = "ERROR"; +public final class EndpointErrorResponse extends EndpointResponse { @JsonProperty("exception") private final String exception; @JsonProperty("message") private final String message; @JsonProperty("additional_info") private final Object additionalInfo; + + /** + * Create an error response. + */ + public EndpointErrorResponse( + final String exception, + final String message, + final Object additionalInfo + ) { + super("ERROR"); + this.exception = exception; + this.message = message; + this.additionalInfo = additionalInfo; + } } diff --git a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointOkResponse.java b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointOkResponse.java index 1780700..8aadf33 100644 --- a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointOkResponse.java +++ b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointOkResponse.java @@ -3,17 +3,25 @@ package com.cleverthis.authservice.controller.rabbitmq.model; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; import lombok.Builder; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; /** * The model for replied response. */ -@Data +@EqualsAndHashCode(callSuper = true) +@Getter +@Setter +@ToString @Builder -public class EndpointOkResponse { - @JsonProperty("type") - private final String type = "OK"; +public class EndpointOkResponse extends EndpointResponse { @JsonProperty("result") private final List result; + public EndpointOkResponse(final List result) { + super("OK"); + this.result = result; + } } diff --git a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointResponse.java b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointResponse.java new file mode 100644 index 0000000..a8027c5 --- /dev/null +++ b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/model/EndpointResponse.java @@ -0,0 +1,19 @@ +package com.cleverthis.authservice.controller.rabbitmq.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +/** + * The model for replied response. + */ +@Getter +@Setter +public abstract class EndpointResponse { + @JsonProperty("type") + protected final String type; + + protected EndpointResponse(final String type) { + this.type = type; + } +} diff --git a/src/main/java/com/cleverthis/authservice/controller/rabbitmq/v1/user/UserEndpointV1.java b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/v1/user/UserEndpointV1.java new file mode 100644 index 0000000..9392f6c --- /dev/null +++ b/src/main/java/com/cleverthis/authservice/controller/rabbitmq/v1/user/UserEndpointV1.java @@ -0,0 +1,71 @@ +package com.cleverthis.authservice.controller.rabbitmq.v1.user; + +import com.cleverthis.authservice.controller.rabbitmq.AbstractEndpoint; +import com.cleverthis.authservice.controller.rabbitmq.exception.EndpointBadRequestException; +import com.cleverthis.authservice.controller.rabbitmq.exception.EndpointException; +import com.cleverthis.authservice.controller.rabbitmq.model.EndpointRequest; +import com.cleverthis.authservice.controller.rabbitmq.model.EndpointResponses; +import com.cleverthis.authservice.service.impl.KeycloakAdminReactiveClient; +import com.cleverthis.clevermicro.client.amqp.CleverMicroAmqpClient; +import com.cleverthis.clevermicro.client.amqp.handler.HandlerContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.NoSuchElementException; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.stereotype.Component; + +/** + * Serve endpoints on `user-service-v1`. + */ +@Component +public class UserEndpointV1 extends AbstractEndpoint { + private static final String ROUTING_KEY = "user-service-v1"; + + private final KeycloakAdminReactiveClient keycloakAdminClient; + + public UserEndpointV1( + final CleverMicroAmqpClient client, + final ObjectMapper objectMapper, + final KeycloakAdminReactiveClient keycloakAdminClient + ) throws IOException { + super(client, objectMapper, ROUTING_KEY); + this.keycloakAdminClient = keycloakAdminClient; + } + + @Override + protected void handleRequest( + final HandlerContext ctx, final EndpointRequest request + ) throws EndpointException { + //noinspection SwitchStatementWithTooFewBranches + final Object result = switch (request.getMethod()) { + case "queryUserById" -> queryUserById(request); + default -> throw new EndpointBadRequestException("Method not found"); + }; + reply(ctx, EndpointResponses.ok(result)); + } + + private UserRepresentation queryUserById( + final EndpointRequest request + ) throws EndpointException { + final var argId = request.getArgs().get("id"); + if (argId == null) { + throw new EndpointBadRequestException("Missing parameter 'id'"); + } + if (!argId.isTextual()) { + throw new EndpointBadRequestException( + "Invalid type for parameter 'id', required non-null string"); + } + // this should be non-null + final var id = argId.textValue(); + assert id != null; + try { + return this.keycloakAdminClient.getUserDetails(id).block(); + } catch (final NoSuchElementException ex) { + throw new EndpointException( + "cleverthis.clevermicro.auth.user_not_found", + "User id " + id + "not found", + ex + ); + } + } +} diff --git a/src/main/java/com/cleverthis/authservice/service/impl/KeycloakAdminReactiveClient.java b/src/main/java/com/cleverthis/authservice/service/impl/KeycloakAdminReactiveClient.java index 4c1f46f..5fcec58 100644 --- a/src/main/java/com/cleverthis/authservice/service/impl/KeycloakAdminReactiveClient.java +++ b/src/main/java/com/cleverthis/authservice/service/impl/KeycloakAdminReactiveClient.java @@ -6,9 +6,11 @@ import com.cleverthis.authservice.dto.keycloakadmin.KeycloakInvitationRequest; import com.cleverthis.authservice.exception.RegistrationException; import com.cleverthis.authservice.exception.ServiceUnavailableException; import com.cleverthis.authservice.exception.UserAlreadyExistsException; +import jakarta.ws.rs.NotFoundException; import jakarta.ws.rs.core.Response; import java.util.Collections; import java.util.List; +import java.util.NoSuchElementException; import lombok.extern.slf4j.Slf4j; import org.keycloak.admin.client.Keycloak; import org.keycloak.representations.idm.ClientRepresentation; @@ -460,9 +462,18 @@ public class KeycloakAdminReactiveClient { } + /** + * Get user details with id. + * + * @throws NoSuchElementException in mono if keycloak returns 404. + */ public Mono getUserDetails(String userId) { - return Mono.just(this.keycloak.realm(this.realm) - .users().get(userId).toRepresentation()); + try { + return Mono.just(this.keycloak.realm(this.realm) + .users().get(userId).toRepresentation()); + } catch (final NotFoundException ex) { + return Mono.error(new NoSuchElementException("User with id " + userId + " not exists")); + } } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 292fa36..3680720 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: name: auth-service cloud: consul: - host: ${CONSUL_HOST:localhost} + host: ${CONSUL_HOST:127.0.0.1} port: ${CONSUL_PORT:8500} config: import-check: @@ -15,8 +15,8 @@ spring: # Keycloak Configuration (adjust values as needed) keycloak: - keycloak-host: ${KEYCLOAK_AUTH_SERVER_URL:http://localhost:9100} # Base URL of your Keycloak instance (NO trailing slash) - keycloak-admin-host: ${KEYCLOAK_AUTH_ADMIN_SERVER_URL:http://localhost:9100} # Base URL of your Keycloak instance (NO trailing slash) + keycloak-host: ${KEYCLOAK_AUTH_SERVER_URL:http://keycloak.dev.localhost} # Base URL of your Keycloak instance (NO trailing slash) + keycloak-admin-host: ${KEYCLOAK_AUTH_ADMIN_SERVER_URL:http://keycloak.dev.localhost} # Base URL of your Keycloak instance (NO trailing slash) realm: ${KEYCLOAK_AUTH_REALM:clevermicro-dev} # The realm name you are using client-id: ${KEYCLOAK_AUTH_SERVICE_CLIENT:auth-service} # Client ID created in Keycloak for this service client-secret: ${KEYCLOAK_AUTH_SERVICE_SECRET:UJ1sMcWciIRREfjjAGoLHUDynLT4aYdD} # Client Secret from Keycloak (use secrets management!)