Skip to content

Commit

Permalink
[SELC-4970] Feat: Refactor "/authorize" API to have institutionId as …
Browse files Browse the repository at this point in the history
…optional query param (#136)
  • Loading branch information
flaminiaScarciofolo authored May 28, 2024
1 parent 9995632 commit 2915245
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 76 deletions.
8 changes: 3 additions & 5 deletions apps/user-ms/src/main/docs/openapi.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"openapi" : "3.0.3",
"info" : {
"title" : "user-ms API",
"title" : "User API",
"version" : "1.0.0"
},
"servers" : [ {
Expand All @@ -12,16 +12,14 @@
"description" : "Auto generated value"
} ],
"paths" : {
"/authorize/{institutionId}" : {
"/authorize" : {
"get" : {
"tags" : [ "User Permission Controller" ],
"summary" : "Get permission for a user in an institution",
"parameters" : [ {
"name" : "institutionId",
"in" : "path",
"required" : true,
"in" : "query",
"schema" : {
"minLength" : 1,
"type" : "string"
}
}, {
Expand Down
8 changes: 3 additions & 5 deletions apps/user-ms/src/main/docs/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
---
openapi: 3.0.3
info:
title: user-ms API
title: User API
version: 1.0.0
servers:
- url: http://localhost:8080
description: Auto generated value
- url: http://0.0.0.0:8080
description: Auto generated value
paths:
/authorize/{institutionId}:
/authorize:
get:
tags:
- User Permission Controller
summary: Get permission for a user in an institution
parameters:
- name: institutionId
in: path
required: true
in: query
schema:
minLength: 1
type: string
- name: permission
in: query
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package it.pagopa.selfcare.user.constant;

import lombok.Getter;

import java.util.List;
import java.util.stream.Stream;

@Getter
public enum SelfCareRole {
MANAGER(SelfCareAuthority.ADMIN),
DELEGATE(SelfCareAuthority.ADMIN),
SUB_DELEGATE(SelfCareAuthority.ADMIN),
OPERATOR(SelfCareAuthority.LIMITED);

private SelfCareAuthority selfCareAuthority;
private final SelfCareAuthority selfCareAuthority;

private SelfCareRole(SelfCareAuthority selfCareAuthority) {
this.selfCareAuthority = selfCareAuthority;
}

public SelfCareAuthority getSelfCareAuthority() {
return this.selfCareAuthority;
public static List<SelfCareRole> fromSelfCareAuthority(String selfCareAuthority) {
return Stream.of(SelfCareRole.values())
.filter(role -> role.getSelfCareAuthority().name().equals(selfCareAuthority))
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ public class UserPermissionController {
*/
@Operation(summary = "Get permission for a user in an institution")
@GET
@Path("/{institutionId}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<Boolean> getPermission(
@NotEmpty(message = "institutionId is required") @PathParam("institutionId") String institutionId,
@QueryParam("institutionId") String institutionId,
@QueryParam("productId") String productId,
@NotNull(message = "Permission type is required") @QueryParam("permission") PermissionTypeEnum permission,
@Context SecurityContext ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.onboarding.common.PartyRole;
import it.pagopa.selfcare.user.constant.OnboardedProductState;
import it.pagopa.selfcare.user.constant.PermissionTypeEnum;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.entity.UserInstitution;

Expand Down Expand Up @@ -36,4 +37,6 @@ public interface UserInstitutionService {
Uni<UserInstitution> findByUserIdAndInstitutionId(String userId, String institutionId);

Uni<UserInstitution> persistOrUpdate(UserInstitution userInstitution);

Uni<Boolean> existsValidUserProduct(String userId, String institutionId, String productId, PermissionTypeEnum permission);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.onboarding.common.PartyRole;
import it.pagopa.selfcare.user.constant.OnboardedProductState;
import it.pagopa.selfcare.user.constant.PermissionTypeEnum;
import it.pagopa.selfcare.user.constant.SelfCareRole;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.entity.OnboardedProduct;
import it.pagopa.selfcare.user.entity.UserInstitution;
Expand All @@ -23,7 +25,7 @@
import java.util.*;

import static it.pagopa.selfcare.user.constant.CollectionUtil.*;
import static it.pagopa.selfcare.user.constant.OnboardedProductState.ACTIVE;
import static it.pagopa.selfcare.user.constant.OnboardedProductState.*;
import static it.pagopa.selfcare.user.entity.filter.OnboardedProductFilter.OnboardedProductEnum.*;
import static it.pagopa.selfcare.user.util.GeneralUtils.formatQueryParameterList;

Expand Down Expand Up @@ -184,6 +186,21 @@ public Uni<UserInstitution> persistOrUpdate(UserInstitution userInstitution) {
.replaceWith(userInstitution);
}

@Override
public Uni<Boolean> existsValidUserProduct(String userId, String institutionId, String productId, PermissionTypeEnum permission) {

Map<String, Object> userInstitutionFilterMap = UserInstitutionFilter.builder().userId(userId).institutionId(institutionId).build().constructMap();
OnboardedProductFilter.OnboardedProductFilterBuilder builder = OnboardedProductFilter.builder().productId(productId).status(List.of(ACTIVE, PENDING, TOBEVALIDATED));
if (permission.equals(PermissionTypeEnum.ADMIN)) {
builder.role(SelfCareRole.fromSelfCareAuthority(permission.name()));
}
Map<String, Object> onboardedProductFilterMap = builder.build().constructMap();
Map<String, Object> filterMap = userUtils.retrieveMapForFilter(userInstitutionFilterMap, onboardedProductFilterMap);
Document query = queryUtils.buildQueryDocument(filterMap, USER_INSTITUTION_COLLECTION);

return runUserInstitutionCountQuery(query);
}


private boolean productFilterIsEmpty(Map<String, Object> filterMap) {
return !filterMap.containsKey(PRODUCT_ID.getChild())
Expand All @@ -194,4 +211,9 @@ private boolean productFilterIsEmpty(Map<String, Object> filterMap) {
public ReactivePanacheQuery<UserInstitution> runUserInstitutionFindQuery(Document query, Document sort) {
return UserInstitution.find(query, sort);
}

public Uni<Boolean> runUserInstitutionCountQuery(Document query) {
Uni<Long> count = UserInstitution.count(query);
return count.onItem().transform(c -> c > 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
import it.pagopa.selfcare.user.constant.PermissionTypeEnum;

public interface UserPermissionService {
Uni<Boolean>hasPermission(String institutionId, String productId, PermissionTypeEnum permission, String userId);
Uni<Boolean> hasPermission(String institutionId, String productId, PermissionTypeEnum permission, String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,22 @@

import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.user.constant.PermissionTypeEnum;
import it.pagopa.selfcare.user.constant.SelfCareRole;
import it.pagopa.selfcare.user.entity.UserInstitution;
import it.pagopa.selfcare.user.entity.filter.OnboardedProductFilter;
import it.pagopa.selfcare.user.entity.filter.UserInstitutionFilter;
import it.pagopa.selfcare.user.exception.ResourceNotFoundException;
import it.pagopa.selfcare.user.util.UserUtils;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

import static it.pagopa.selfcare.user.constant.CustomError.USER_INSTITUTION_NOT_FOUND_ERROR;
import static it.pagopa.selfcare.user.constant.CustomError.USER_NOT_FOUND_ERROR;

@Slf4j
@ApplicationScoped
@RequiredArgsConstructor
public class UserPermissionServiceImpl implements UserPermissionService {

private final UserInstitutionService userInstitutionService;
private final UserUtils userUtils;

@Override
public Uni<Boolean> hasPermission(String institutionId, String productId, PermissionTypeEnum permission, String userId) {
log.trace("hasPermission start");
log.debug("check permission {} for userId: {}, institutionId: {} and productId: {}", permission, userId, institutionId, productId);

return retrievePerson(userId, productId, institutionId)
.onItem().ifNotNull().invoke(userInstitution -> log.debug("UserInstitution founded for given parameters"))
.onItem().transform(userInstitution -> PermissionTypeEnum.ANY.equals(permission) || checkProductRole(userInstitution, productId, permission))
.onFailure(ResourceNotFoundException.class).recoverWithItem(false);
}

private boolean checkProductRole(UserInstitution userInstitution, String productId, PermissionTypeEnum permission) {
return userInstitution.getProducts().stream()
.anyMatch(product -> permission.name().equalsIgnoreCase(SelfCareRole.valueOf(product.getRole().name()).getSelfCareAuthority().name()) &&
(StringUtils.isBlank(productId) || product.getProductId().equalsIgnoreCase(productId)));
}

private Uni<UserInstitution> retrievePerson(String userId, String productId, String institutionId) {
var userInstitutionFilters = UserInstitutionFilter.builder().userId(userId).institutionId(institutionId).build().constructMap();
var productFilters = OnboardedProductFilter.builder().productId(productId).build().constructMap();
Map<String, Object> queryParameter = userUtils.retrieveMapForFilter(userInstitutionFilters, productFilters);
return userInstitutionService.retrieveFirstFilteredUserInstitution(queryParameter)
.onItem().ifNull().failWith(() -> {
log.error(String.format(USER_INSTITUTION_NOT_FOUND_ERROR.getMessage(), userId, institutionId));
return new ResourceNotFoundException(String.format(USER_INSTITUTION_NOT_FOUND_ERROR.getMessage(), userId, institutionId), USER_NOT_FOUND_ERROR.getCode());
});
return userInstitutionService.existsValidUserProduct(userId, institutionId, productId, permission);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ void testGetPermissionUnhauthorize() {

// Perform the API call without providing the permission query parameter
given()
.pathParam(institutionIdField, institutionId)
.queryParam(institutionIdField, institutionId)
.queryParam(productIdField, productId)
.when()
.get("/{institutionId}")
.get("/")
.then()
.statusCode(401);

Expand All @@ -70,16 +70,45 @@ void testGetPermission() {

// Perform the API call
given()
.pathParam(institutionIdField, institutionId)
.queryParam(institutionIdField, institutionId)
.queryParam(productIdField, productId)
.queryParam("permission", permission)
.when()
.get("/{institutionId}")
.get("/")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.contentType(MediaType.APPLICATION_JSON);

// Verify that the methods were called with the expected parameters
verify(userPermissionService).hasPermission(institutionId, productId, permission, userId);
}

@Test
@TestSecurity(user = "userJwt")
void testGetPermissionWithoutProductId() {
// Mock input parameters
PermissionTypeEnum permission = ADMIN;

SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getUserPrincipal()).thenReturn(() -> "userJwt");

// Mock userUtils.readUserIdFromToken() method
when(userUtils.readUserIdFromToken(any())).thenReturn(Uni.createFrom().item(LoggedUser.builder().uid(userId).build()));

// Mock userPermissionService.hasPermission() method
when(userPermissionService.hasPermission(null, productId, permission,userId)).thenReturn(Uni.createFrom().item(Boolean.TRUE));

// Perform the API call
given()
.queryParam(productIdField, productId)
.queryParam("permission", permission)
.when()
.get("/")
.then()
.statusCode(Response.Status.OK.getStatusCode())
.contentType(MediaType.APPLICATION_JSON);

// Verify that the methods were called with the expected parameters
verify(userPermissionService).hasPermission(null, productId, permission, userId);
}
}
Loading

0 comments on commit 2915245

Please sign in to comment.