Skip to content

Commit 90f712a

Browse files
[SELC-7901] feat: add call to getIAMUser to obtain roles in exchangeBackofficeAdmin API (#557)
1 parent 37d7c98 commit 90f712a

File tree

9 files changed

+130
-28
lines changed

9 files changed

+130
-28
lines changed

integration-test-config/db/roles.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
"_id": "SUPPORT",
2222
"permissions": [
2323
"read:users",
24-
"write:users"
24+
"write:users",
25+
"Selc:ViewInstitutionData",
26+
"Selc:AccessProductBackofficeAdmin"
2527
]
2628
}
2729
]

integration-test-config/db/userClaims.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
{
33
"_id": "35a78332-d038-4bfa-8e85-2cba7f6b7bf8",
44
"email": "EEFF4l3KRN2cJKm8PDpdMjrVeWG00GeYY6JCbxHlcAPSpQ==",
5-
"name": "user-001",
65
"productRoles": [{
76
"productId": "prod-interop",
87
"roles": [
9-
"ADMIN"
8+
"SUPPORT"
109
]
1110
}]
1211
},
@@ -18,7 +17,7 @@
1817
{
1918
"productId": "product-A",
2019
"roles": [
21-
"OPERATOR"
20+
"ADMIN"
2221
]
2322
},
2423
{

src/main/java/it/pagopa/selfcare/dashboard/client/IamExternalRestClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
import it.pagopa.selfcare.iam.generated.openapi.v1.api.ExternalV2Api;
44
import org.springframework.cloud.openfeign.FeignClient;
55

6-
@FeignClient(name = "${rest-client.iam.serviceCode}", url = "${rest-client.iam.base-url}")
6+
@FeignClient(name = "${rest-client.iam-external.serviceCode}", url = "${rest-client.iam-external.base-url}")
77
public interface IamExternalRestClient extends ExternalV2Api {
88
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package it.pagopa.selfcare.dashboard.config;
2+
3+
import it.pagopa.selfcare.commons.connector.rest.config.RestClientBaseConfig;
4+
import it.pagopa.selfcare.dashboard.client.IamExternalRestClient;
5+
import org.springframework.cloud.openfeign.EnableFeignClients;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.context.annotation.Import;
8+
import org.springframework.context.annotation.PropertySource;
9+
10+
@Configuration
11+
@Import(RestClientBaseConfig.class)
12+
@EnableFeignClients(clients = IamExternalRestClient.class)
13+
@PropertySource("classpath:config/iam-rest-client.properties")
14+
public class IamExternalRestClientConfig {
15+
}

src/main/java/it/pagopa/selfcare/dashboard/model/mapper/InstitutionResourceMapper.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import it.pagopa.selfcare.dashboard.model.UpdateInstitutionDto;
1010
import it.pagopa.selfcare.dashboard.model.institution.*;
1111
import it.pagopa.selfcare.dashboard.security.ExchangeTokenServiceV2;
12+
import it.pagopa.selfcare.iam.generated.openapi.v1.dto.ProductRoles;
1213
import org.mapstruct.Mapper;
1314
import org.mapstruct.Mapping;
1415
import org.mapstruct.Named;
@@ -18,8 +19,10 @@
1819
import org.springframework.util.StringUtils;
1920

2021
import java.util.ArrayList;
22+
import java.util.Collections;
2123
import java.util.List;
2224
import java.util.Optional;
25+
import java.util.stream.Collectors;
2326

2427
import static it.pagopa.selfcare.dashboard.model.institution.RelationshipState.PENDING;
2528
import static it.pagopa.selfcare.dashboard.model.institution.RelationshipState.TOBEVALIDATED;
@@ -107,8 +110,8 @@ default String toUserRole(String userRole) {
107110
@Mapping(target = "subUnitType", source = "institution.subunitType")
108111
@Mapping(target = "subUnitCode", source = "institution.subunitCode")
109112
@Mapping(target = "rootParent", expression = "java(toRootParent(institution))")
110-
@Mapping(target = "roles", expression = "java(toRolesBackofficeAdmin())")
111-
InstitutionBackofficeAdmin toInstitutionBackofficeAdmin(Institution institution);
113+
@Mapping(target = "roles", expression = "java(toRolesBackofficeAdmin(productRoles))")
114+
InstitutionBackofficeAdmin toInstitutionBackofficeAdmin(Institution institution, List<ProductRoles> productRoles);
112115

113116
@Named("toRootParent")
114117
default RootParent toRootParent(Institution institutionInfo) {
@@ -129,12 +132,20 @@ default List<ExchangeTokenServiceV2.Role> toRoles(List<ProductGrantedAuthority>
129132
return roles;
130133
}
131134

132-
default List<RoleBackofficeAdmin> toRolesBackofficeAdmin() {
133-
RoleBackofficeAdmin institutionRole = new RoleBackofficeAdmin();
134-
institutionRole.setPartyRole("SUPPORT");
135-
institutionRole.setProductRole("support");
135+
default List<RoleBackofficeAdmin> toRolesBackofficeAdmin(List<ProductRoles> productRoles) {
136+
return Optional.ofNullable(productRoles)
137+
.orElse(Collections.emptyList())
138+
.stream()
139+
.flatMap(productRole -> productRole.getRoles().stream()
140+
.map(this::constructRoleBackofficeAdmin))
141+
.collect(Collectors.toList());
142+
}
136143

137-
return List.of(institutionRole);
144+
default RoleBackofficeAdmin constructRoleBackofficeAdmin(String role) {
145+
RoleBackofficeAdmin roleBackofficeAdmin = new RoleBackofficeAdmin();
146+
roleBackofficeAdmin.setPartyRole(role);
147+
roleBackofficeAdmin.setProductRole(role.toLowerCase());
148+
return roleBackofficeAdmin;
138149
}
139150

140151
default List<ExchangeTokenServiceV2.Role> constructRole(ProductGrantedAuthority productGrantedAuthority, boolean isBillingToken) {

src/main/java/it/pagopa/selfcare/dashboard/security/ExchangeTokenServiceV2.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22

33
import com.fasterxml.jackson.annotation.JsonInclude;
44
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
57
import io.jsonwebtoken.*;
68
import io.jsonwebtoken.impl.DefaultClaims;
79
import it.pagopa.selfcare.commons.base.logging.LogUtils;
810
import it.pagopa.selfcare.commons.base.security.PartyRole;
911
import it.pagopa.selfcare.commons.base.security.ProductGrantedAuthority;
1012
import it.pagopa.selfcare.commons.base.security.SelfCareUser;
1113
import it.pagopa.selfcare.commons.web.security.JwtService;
14+
import it.pagopa.selfcare.dashboard.client.IamExternalRestClient;
1215
import it.pagopa.selfcare.dashboard.client.UserInstitutionApiRestClient;
1316
import it.pagopa.selfcare.dashboard.model.institution.InstitutionBackofficeAdmin;
14-
import it.pagopa.selfcare.dashboard.model.institution.RoleBackofficeAdmin;
1517
import it.pagopa.selfcare.dashboard.model.institution.RootParent;
1618
import it.pagopa.selfcare.dashboard.model.product.mapper.ProductMapper;
1719
import it.pagopa.selfcare.dashboard.exception.InvalidRequestException;
@@ -28,6 +30,7 @@
2830
import it.pagopa.selfcare.dashboard.config.ExchangeTokenProperties;
2931
import it.pagopa.selfcare.dashboard.model.ExchangedToken;
3032
import it.pagopa.selfcare.dashboard.model.mapper.InstitutionResourceMapper;
33+
import it.pagopa.selfcare.iam.generated.openapi.v1.dto.UserClaims;
3134
import it.pagopa.selfcare.product.entity.Product;
3235
import it.pagopa.selfcare.product.service.ProductService;
3336
import it.pagopa.selfcare.user.generated.openapi.v1.dto.UserInstitutionResponse;
@@ -78,18 +81,20 @@ public class ExchangeTokenServiceV2 {
7881
public final UserV2Service userService;
7982
private final ProductService productService;
8083
private final UserInstitutionApiRestClient userInstitutionApiRestClient;
84+
private final IamExternalRestClient iamExternalRestClient;
8185
private final String issuer;
8286

8387
private final InstitutionResourceMapper institutionResourceMapper;
8488
private final InstitutionMapper institutionMapper;
8589
private final ProductMapper productMapper;
90+
private final ObjectMapper objectMapper;
8691

8792
public ExchangeTokenServiceV2(JwtService jwtService,
8893
InstitutionService institutionService,
8994
UserGroupV2Service groupService,
9095
ExchangeTokenProperties properties,
91-
UserV2Service userService, ProductService productService, UserInstitutionApiRestClient userInstitutionApiRestClient,
92-
InstitutionResourceMapper institutionResourceMapper, InstitutionMapper institutionMapper, ProductMapper productMapper)
96+
UserV2Service userService, ProductService productService, UserInstitutionApiRestClient userInstitutionApiRestClient, IamExternalRestClient iamExternalRestClient,
97+
InstitutionResourceMapper institutionResourceMapper, InstitutionMapper institutionMapper, ProductMapper productMapper, ObjectMapper objectMapper)
9398
throws InvalidKeySpecException, NoSuchAlgorithmException {
9499
this.billingUrl = properties.getBillingUrl();
95100
this.billingAudience = properties.getBillingAudience();
@@ -103,9 +108,11 @@ public ExchangeTokenServiceV2(JwtService jwtService,
103108
this.userService = userService;
104109
this.productService = productService;
105110
this.userInstitutionApiRestClient = userInstitutionApiRestClient;
111+
this.iamExternalRestClient = iamExternalRestClient;
106112
this.institutionResourceMapper = institutionResourceMapper;
107113
this.institutionMapper = institutionMapper;
108114
this.productMapper = productMapper;
115+
this.objectMapper = objectMapper;
109116
}
110117

111118

@@ -151,7 +158,15 @@ public ExchangedToken exchangeBackofficeAdmin(String institutionId, String produ
151158

152159
it.pagopa.selfcare.dashboard.model.institution.Institution institution = institutionService.getInstitutionById(institutionId);
153160
Assert.notNull(institution, INSTITUTION_REQUIRED_MESSAGE);
154-
InstitutionBackofficeAdmin institutionExchange = institutionResourceMapper.toInstitutionBackofficeAdmin(institution);
161+
162+
UserClaims userClaims;
163+
try {
164+
userClaims = objectMapper.readValue(iamExternalRestClient._getIAMUser(selfCareUser.getId(), productId).getBody(), UserClaims.class);
165+
} catch (JsonProcessingException e) {
166+
throw new IllegalArgumentException(String.format("User Claims are required for product '%s' and institution '%s'", productId, institutionId));
167+
}
168+
169+
InstitutionBackofficeAdmin institutionExchange = institutionResourceMapper.toInstitutionBackofficeAdmin(institution, userClaims.getProductRoles());
155170

156171
TokenExchangeClaims claims = retrieveAndSetBackofficeAdminClaims(authentication.getCredentials().toString(), institutionExchange, selfCareUser);
157172

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
rest-client.iam.serviceCode=iam
2-
rest-client.iam.base-url=${IAM_URL::http://localhost:8080}
2+
rest-client.iam.base-url=${IAM_URL:http://localhost:8080}
33
feign.client.config.iam.connectTimeout=${IAM_REST_CLIENT_CONNECT_TIMEOUT:${REST_CLIENT_CONNECT_TIMEOUT:5000}}
44
feign.client.config.iam.errorDecoder=it.pagopa.selfcare.dashboard.decoder.FeignErrorDecoder
55
feign.client.config.iam.readTimeout=${IAM_REST_CLIENT_READ_TIMEOUT:${REST_CLIENT_READ_TIMEOUT:5000}}
66
feign.client.config.iam.loggerLevel=${IAM_REST_CLIENT_LOGGER_LEVEL:${REST_CLIENT_LOGGER_LEVEL:FULL}}
77
feign.client.config.iam.requestInterceptors[0]=it.pagopa.selfcare.commons.connector.rest.interceptor.AuthorizationHeaderInterceptor
8+
rest-client.iam-external.serviceCode=iam-external
9+
rest-client.iam-external.base-url=${IAM_URL:http://localhost:8080}
10+
feign.client.config.iam-external.connectTimeout=${IAM_REST_CLIENT_CONNECT_TIMEOUT:${REST_CLIENT_CONNECT_TIMEOUT:5000}}
11+
feign.client.config.iam-external.errorDecoder=it.pagopa.selfcare.dashboard.decoder.FeignErrorDecoder
12+
feign.client.config.iam-external.readTimeout=${IAM_REST_CLIENT_READ_TIMEOUT:${REST_CLIENT_READ_TIMEOUT:5000}}
13+
feign.client.config.iam-external.loggerLevel=${IAM_REST_CLIENT_LOGGER_LEVEL:${REST_CLIENT_LOGGER_LEVEL:FULL}}
14+
feign.client.config.iam-external.requestInterceptors[0]=it.pagopa.selfcare.commons.connector.rest.interceptor.AuthorizationHeaderInterceptor

src/test/java/it/pagopa/selfcare/dashboard/security/ExchangeTokenServiceV2Test.java

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package it.pagopa.selfcare.dashboard.security;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
35
import io.jsonwebtoken.Jwts;
46
import it.pagopa.selfcare.commons.base.security.ProductGrantedAuthority;
57
import it.pagopa.selfcare.commons.base.security.SelfCareGrantedAuthority;
68
import it.pagopa.selfcare.commons.base.security.SelfCareUser;
79
import it.pagopa.selfcare.commons.web.security.JwtService;
10+
import it.pagopa.selfcare.dashboard.client.IamExternalRestClient;
811
import it.pagopa.selfcare.dashboard.client.UserInstitutionApiRestClient;
912
import it.pagopa.selfcare.dashboard.config.ExchangeTokenProperties;
1013
import it.pagopa.selfcare.dashboard.model.ExchangedToken;
@@ -16,6 +19,8 @@
1619
import it.pagopa.selfcare.dashboard.service.InstitutionService;
1720
import it.pagopa.selfcare.dashboard.service.UserGroupV2Service;
1821
import it.pagopa.selfcare.dashboard.service.UserV2Service;
22+
import it.pagopa.selfcare.iam.generated.openapi.v1.dto.ProductRoles;
23+
import it.pagopa.selfcare.iam.generated.openapi.v1.dto.UserClaims;
1924
import it.pagopa.selfcare.product.entity.Product;
2025
import it.pagopa.selfcare.product.service.ProductService;
2126
import it.pagopa.selfcare.user.generated.openapi.v1.dto.OnboardedProductResponse;
@@ -58,6 +63,9 @@ class ExchangeTokenServiceV2Test {
5863
@Mock
5964
private UserInstitutionApiRestClient userInstitutionApiRestClient;
6065

66+
@Mock
67+
private IamExternalRestClient iamExternalRestClient;
68+
6169
@Mock
6270
private InstitutionService institutionService;
6371

@@ -88,8 +96,11 @@ class ExchangeTokenServiceV2Test {
8896

8997
private ExchangeTokenServiceV2 exchangeTokenServiceV2;
9098

99+
private ObjectMapper objectMapper;
100+
91101
@BeforeEach
92102
void setUp() throws Exception {
103+
objectMapper = new ObjectMapper();
93104
when(exchangeTokenProperties.getBillingAudience()).thenReturn("aud");
94105
when(exchangeTokenProperties.getBillingUrl()).thenReturn("url");
95106
when(exchangeTokenProperties.getDuration()).thenReturn("PT20H30M");
@@ -105,7 +116,7 @@ void setUp() throws Exception {
105116

106117
exchangeTokenServiceV2 = new ExchangeTokenServiceV2(jwtService,
107118
institutionService,userGroupV2Service,exchangeTokenProperties,userV2Service,productService,
108-
userInstitutionApiRestClient,institutionResourceMapper,institutionMapper, productMapper);
119+
userInstitutionApiRestClient,iamExternalRestClient,institutionResourceMapper,institutionMapper, productMapper, objectMapper);
109120
}
110121

111122

@@ -186,7 +197,7 @@ void exchange_noProductGrantedAuthority_throwsIllegalArgumentException() {
186197
}
187198

188199
@Test
189-
void exchangeBackofficeAdmin_validInputs_returnsExchangedToken() {
200+
void exchangeBackofficeAdmin_validInputs_returnsExchangedToken() throws JsonProcessingException {
190201
String jti = "id";
191202
String sub = "subject";
192203
String iss = "PAGOPA";
@@ -196,6 +207,15 @@ void exchangeBackofficeAdmin_validInputs_returnsExchangedToken() {
196207
String productId = "productId";
197208
String credential = "password";
198209
String userId = UUID.randomUUID().toString();
210+
UserClaims userClaims = new UserClaims();
211+
List<ProductRoles> productRoles = List.of(
212+
ProductRoles.builder()
213+
.productId(productId)
214+
.roles(List.of("SUPPORT"))
215+
.build()
216+
);
217+
userClaims.setProductRoles(productRoles);
218+
String userClaimsJson = objectMapper.writeValueAsString(userClaims);
199219

200220
it.pagopa.selfcare.dashboard.model.institution.Institution institution = mock(it.pagopa.selfcare.dashboard.model.institution.Institution.class);
201221
Product product = mock(Product.class);
@@ -211,23 +231,55 @@ void exchangeBackofficeAdmin_validInputs_returnsExchangedToken() {
211231

212232
when(institutionService.getInstitutionById(institutionId)).thenReturn(institution);
213233
when(productService.getProduct(productId)).thenReturn(product);
234+
when(iamExternalRestClient._getIAMUser(userId, productId))
235+
.thenReturn(ResponseEntity.ok(userClaimsJson));
214236

215237
ExchangedToken result = exchangeTokenServiceV2.exchangeBackofficeAdmin(institutionId, productId, Optional.empty());
216238

217239
assertNotNull(result);
218240
assertNotNull(result.getIdentityToken());
219241
}
220242

221-
@Test
222-
void exchangeBackofficeAdmin_noAuth() {
223-
// Arrange
224-
String institutionId = "validInstitutionId";
225-
String productId = "validProductId";
226-
Optional<String> environment = Optional.of("validEnvironment");
227-
SecurityContextHolder.getContext().setAuthentication(null);
243+
@Test
244+
void exchangeBackofficeAdmin_noAuth() {
245+
// Arrange
246+
String institutionId = "validInstitutionId";
247+
String productId = "validProductId";
248+
Optional<String> environment = Optional.of("validEnvironment");
249+
SecurityContextHolder.getContext().setAuthentication(null);
250+
251+
Assertions.assertThrows(IllegalStateException.class, () -> exchangeTokenServiceV2.exchangeBackofficeAdmin(institutionId, productId, environment), "Authentication is required");
252+
}
253+
254+
@Test
255+
void exchangeBackofficeAdmin_noUserClaims_throwsIllegalArgumentException() {
256+
String institutionId = "institutionId";
257+
String productId = "productId";
258+
String credential = "password";
259+
String userId = UUID.randomUUID().toString();
228260

229-
Assertions.assertThrows(IllegalStateException.class, () -> exchangeTokenServiceV2.exchangeBackofficeAdmin(institutionId, productId, environment), "Authentication is required");
230-
}
261+
it.pagopa.selfcare.dashboard.model.institution.Institution institution = mock(it.pagopa.selfcare.dashboard.model.institution.Institution.class);
262+
263+
TestSecurityContextHolder.setAuthentication(new TestingAuthenticationToken(SelfCareUser.builder(userId).build(), credential));
264+
265+
when(institutionService.getInstitutionById(institutionId)).thenReturn(institution);
266+
when(iamExternalRestClient._getIAMUser(userId, productId))
267+
.thenReturn(ResponseEntity.ok("invalid json response"));
268+
269+
IllegalArgumentException exception = assertThrows(
270+
IllegalArgumentException.class,
271+
() -> exchangeTokenServiceV2.exchangeBackofficeAdmin(
272+
institutionId,
273+
productId,
274+
Optional.empty()
275+
)
276+
);
277+
278+
Assertions.assertEquals(
279+
String.format("User Claims are required for product '%s' and institution '%s'", productId, institutionId),
280+
exception.getMessage()
281+
);
282+
}
231283

232284
@Test
233285
void testRetrieveBillingExchangedToken_AuthenticationMissing() {

src/test/resources/application-test.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ rest-client.ms-core.base-url=http://localhost:8082
1313
rest-client.user-groups.base-url=http://localhost:8083
1414
rest-client.onboarding.base-url=http://localhost:8080
1515
rest-client.iam.base-url=http://localhost:8085
16+
rest-client.iam-external.base-url=http://localhost:8085
1617

1718
rest-client.party-management.base-url=http://localhost:8082
1819
rest-client.party-process.base-url=http://localhost:8082

0 commit comments

Comments
 (0)