From f667006003f26bca3bd3a9dce8a3e0c22a52ee62 Mon Sep 17 00:00:00 2001 From: Egil Ballestad Date: Thu, 8 Aug 2024 14:53:30 +0200 Subject: [PATCH] store user from token if they dont exist and if they have permitted role and refactor code --- .../user/AccessControlProperties.java | 16 ++++ .../flyt/authorization/user/UserService.java | 70 +++++------------ .../user/controller/MeController.java | 78 ++++++++++--------- .../user/controller/UserController.java | 38 ++++----- .../controller/utils/TokenParsingUtils.java | 39 ++++++++-- src/main/resources/application.yaml | 1 + 6 files changed, 127 insertions(+), 115 deletions(-) create mode 100644 src/main/java/no/fintlabs/flyt/authorization/user/AccessControlProperties.java diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/AccessControlProperties.java b/src/main/java/no/fintlabs/flyt/authorization/user/AccessControlProperties.java new file mode 100644 index 0000000..4d751c3 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/AccessControlProperties.java @@ -0,0 +1,16 @@ +package no.fintlabs.flyt.authorization.user; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +@Getter +@Setter +@Configuration +@ConfigurationProperties(prefix = "fint.flyt.authorization.access-control") +public class AccessControlProperties { + private Map permittedAppRoles; +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java b/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java index 67491b0..0da016a 100644 --- a/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java +++ b/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java @@ -9,11 +9,11 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Stream; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import static java.util.stream.Collectors.toMap; @@ -25,54 +25,6 @@ public class UserService { private final UserRepository userRepository; private final UserPermissionEntityProducerService userPermissionEntityProducerService; - @Transactional - public void updateUsers(Collection users) { - log.info("Updating {} user entities", users.size()); - - Map usersToUpdatePerObjectIdentifier = users.stream() - .collect(toMap( - User::getObjectIdentifier, - Function.identity() - )); - - userRepository.deleteByObjectIdentifierNotIn(usersToUpdatePerObjectIdentifier.keySet()); - - Map updatedUserEntitiesPerObjectIdentifier = - userRepository.findAllByObjectIdentifierIn(usersToUpdatePerObjectIdentifier.keySet()).stream() - .peek(userEntity -> { - User newUser = usersToUpdatePerObjectIdentifier.get(userEntity.getObjectIdentifier()); - userEntity.setName(newUser.getName()); - userEntity.setEmail(newUser.getEmail()); - }) - .collect(toMap( - UserEntity::getObjectIdentifier, - Function.identity() - )); - - Set objectIdentifiersForUsersNotExisting = usersToUpdatePerObjectIdentifier.keySet(); - objectIdentifiersForUsersNotExisting.removeAll(updatedUserEntitiesPerObjectIdentifier.keySet()); - - List newUserEntities = objectIdentifiersForUsersNotExisting.stream() - .map(usersToUpdatePerObjectIdentifier::get) - .map(user -> UserEntity - .builder() - .objectIdentifier(user.getObjectIdentifier()) - .name(user.getName()) - .email(user.getEmail()) - .build() - ) - .toList(); - - userRepository.saveAll( - Stream.concat( - updatedUserEntitiesPerObjectIdentifier.values().stream(), - newUserEntities.stream() - ).toList() - ); - - log.info("Successfully updated user entities"); - } - public void publishUsers() { log.info("Starting publishing users"); try { @@ -91,6 +43,10 @@ public void publishUsers() { } } + public void save(User user) { + this.userRepository.save(mapFromUser(user)); + } + public Optional find(UUID objectIdentifier) { return this.userRepository.findByObjectIdentifier(objectIdentifier) .map(this::mapFromEntity); @@ -122,6 +78,16 @@ public void putAll(List users) { userRepository.saveAll(entities); } + private UserEntity mapFromUser(User user) { + return UserEntity + .builder() + .objectIdentifier(user.getObjectIdentifier()) + .name(user.getName()) + .email(user.getEmail()) + .sourceApplicationIds(user.getSourceApplicationIds()) + .build(); + } + private User mapFromEntity(UserEntity userEntity) { return User .builder() diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/controller/MeController.java b/src/main/java/no/fintlabs/flyt/authorization/user/controller/MeController.java index d61a4d8..08e374b 100644 --- a/src/main/java/no/fintlabs/flyt/authorization/user/controller/MeController.java +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/MeController.java @@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Mono; import java.util.List; import java.util.Optional; @@ -30,7 +29,7 @@ public class MeController { private final TokenParsingUtils tokenParsingUtils; - private final Boolean acesscontrolEnabled; + private final Boolean accessControlEnabled; private final UserService userService; public MeController( @@ -39,52 +38,55 @@ public MeController( UserService userService ) { this.tokenParsingUtils = tokenParsingUtils; - this.acesscontrolEnabled = accessControlEnabled; + this.accessControlEnabled = accessControlEnabled; this.userService = userService; } @GetMapping("is-authorized") - public ResponseEntity checkAuthorization() { + public ResponseEntity checkAuthorization( + @AuthenticationPrincipal Authentication authentication + ) { + if (accessControlEnabled) { + JwtAuthenticationToken jwtAuthToken = (JwtAuthenticationToken) authentication; + Optional userOptional = getUserFromUserAuthorizationComponent(jwtAuthToken); + + if (userOptional.isEmpty() && tokenParsingUtils.hasPermittedRole(jwtAuthToken)) { + User newUser = createUserWithAccessToNoApplications(jwtAuthToken); + userService.save(newUser); + } + } return ResponseEntity.ok("User authorized"); } @GetMapping("restricted-page-authorization") - public Mono> getRestrictedPageAuthorization( - @AuthenticationPrincipal Mono authenticationMono + public ResponseEntity getRestrictedPageAuthorization( + @AuthenticationPrincipal Authentication authentication ) { - return (acesscontrolEnabled - ? tokenParsingUtils.isAdmin(authenticationMono) - .map(isAdmin -> RestrictedPageAuthorization - .builder() - .userPermissionPage(isAdmin) - .build()) - : Mono.just(RestrictedPageAuthorization.builder().build()) - ).map(ResponseEntity::ok); + boolean isAdmin = tokenParsingUtils.isAdmin(authentication); + RestrictedPageAuthorization authorization = RestrictedPageAuthorization + .builder() + .userPermissionPage(isAdmin) + .build(); + return ResponseEntity.ok(authorization); } @GetMapping - public Mono> get( - @AuthenticationPrincipal Mono authenticationMono + public ResponseEntity get( + @AuthenticationPrincipal Authentication authentication ) { - return tokenParsingUtils.isAdmin(authenticationMono) - .flatMap(isAdmin -> { - if (isAdmin) { - return authenticationMono - .map(authentication -> (JwtAuthenticationToken) authentication) - .flatMap(authentication -> - Mono.just(ResponseEntity.ok(createUserWithAccessToAllApplications(authentication)))); - } else { - return authenticationMono - .map(authentication -> (JwtAuthenticationToken) authentication) - .map(authentication -> - acesscontrolEnabled - ? getUserFromUserAuthorizationComponent(authentication) - .map(ResponseEntity::ok) - .orElse(ResponseEntity.notFound().build()) - : ResponseEntity.ok(createUserWithAccessToAllApplications(authentication)) - ); - } - }); + JwtAuthenticationToken jwtAuthToken = (JwtAuthenticationToken) authentication; + if (tokenParsingUtils.isAdmin(authentication)) { + return ResponseEntity.ok(createUserWithAccessToAllApplications(jwtAuthToken)); + } else { + Optional userOptional = getUserFromUserAuthorizationComponent(jwtAuthToken); + if (userOptional.isPresent()) { + return ResponseEntity.ok(userOptional.get()); + } else { + User newUser = createUserWithAccessToAllApplications(jwtAuthToken); + userService.save(newUser); + return ResponseEntity.ok(newUser); + } + } } private Optional getUserFromUserAuthorizationComponent(JwtAuthenticationToken token) { @@ -98,6 +100,12 @@ private User createUserWithAccessToAllApplications(JwtAuthenticationToken token) .build(); } + private User createUserWithAccessToNoApplications(JwtAuthenticationToken token) { + return tokenParsingUtils.getUserFromToken(token) + .toBuilder() + .build(); + } + private List sourceApplicationsWithoutUserPermissionSetup() { return List.of( AcosSourceApplication.SOURCE_APPLICATION_ID, diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/controller/UserController.java b/src/main/java/no/fintlabs/flyt/authorization/user/controller/UserController.java index fc389fe..9bd6b37 100644 --- a/src/main/java/no/fintlabs/flyt/authorization/user/controller/UserController.java +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/UserController.java @@ -14,7 +14,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Mono; import java.util.List; @@ -30,38 +29,31 @@ public class UserController { private final UserService userService; @GetMapping - public Mono>> get( - @AuthenticationPrincipal Mono authenticationMono, + public ResponseEntity> get( + @AuthenticationPrincipal Authentication authentication, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "name") String sort ) { + if (!tokenParsingUtils.isAdmin(authentication)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } Pageable pageable = PageRequest.of(page, size, Sort.by(sort)); - return tokenParsingUtils.isAdmin(authenticationMono) - .flatMap(isAdmin -> { - if (!isAdmin) { - return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); - } - return Mono.fromCallable( - () -> userService.getAll(pageable) - ).map(ResponseEntity::ok); - }); + Page users = userService.getAll(pageable); + return ResponseEntity.ok(users); } @PostMapping("actions/userPermissionBatchPut") - public Mono> postUserPermissionBatchPutAction( - @AuthenticationPrincipal Mono authenticationMono, + public ResponseEntity postUserPermissionBatchPutAction( + @AuthenticationPrincipal Authentication authentication, @RequestBody List users ) { - return tokenParsingUtils.isAdmin(authenticationMono) - .flatMap(isAdmin -> { - if (!isAdmin) { - return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); - } - userService.putAll(users); - userService.publishUsers(); - return Mono.just(ResponseEntity.ok().build()); - }); + if (!tokenParsingUtils.isAdmin(authentication)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + userService.putAll(users); + userService.publishUsers(); + return ResponseEntity.ok().build(); } } diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/controller/utils/TokenParsingUtils.java b/src/main/java/no/fintlabs/flyt/authorization/user/controller/utils/TokenParsingUtils.java index 55d02af..e432010 100644 --- a/src/main/java/no/fintlabs/flyt/authorization/user/controller/utils/TokenParsingUtils.java +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/utils/TokenParsingUtils.java @@ -1,16 +1,24 @@ package no.fintlabs.flyt.authorization.user.controller.utils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import no.fintlabs.flyt.authorization.user.AccessControlProperties; import no.fintlabs.flyt.authorization.user.model.User; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.stereotype.Service; -import reactor.core.publisher.Mono; +import java.util.List; import java.util.Map; import java.util.UUID; @Service +@RequiredArgsConstructor +@Slf4j public class TokenParsingUtils { + + private final AccessControlProperties accessControlProperties; + public String getObjectIdentifierFromToken(JwtAuthenticationToken jwtAuthenticationToken) { return jwtAuthenticationToken.getTokenAttributes().get("objectidentifier").toString(); } @@ -25,9 +33,30 @@ public User getUserFromToken(JwtAuthenticationToken jwtAuthenticationToken) { .build(); } - public Mono isAdmin(Mono authenticationMono) { - return authenticationMono - .map(authentication -> authentication != null && authentication.getAuthorities().stream() - .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_ADMIN"))); + public List getRolesFromToken(JwtAuthenticationToken jwtAuthenticationToken) { + return ((List) jwtAuthenticationToken.getTokenAttributes().get("roles")) + .stream() + .map(Object::toString) + .toList(); + } + + public boolean hasPermittedRole(JwtAuthenticationToken jwtAuthenticationToken) { + List roles = getRolesFromToken(jwtAuthenticationToken); + if (roles == null || roles.isEmpty()) { + log.warn("Roles are null or empty in token"); + return false; + } + Map permittedRolesMap = accessControlProperties.getPermittedAppRoles(); + if (permittedRolesMap == null || permittedRolesMap.isEmpty()) { + log.warn("Permitted app roles are not configured or empty"); + return false; + } + List permittedRoles = permittedRolesMap.values().stream().toList(); + return roles.stream().anyMatch(permittedRoles::contains); + } + + public boolean isAdmin(Authentication authentication) { + return authentication != null && authentication.getAuthorities().stream() + .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_ADMIN")); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index a5e3f6e..74ee637 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -6,6 +6,7 @@ fint: enabled: true permitted-approles: flyt-user: "https://role-catalog.vigoiks.no/vigo/flyt/user" + flyt-developer: "https://role-catalog.vigoiks.no/vigo/flyt/developer" sync-schedule: initial-delay-ms: 1000 fixed-delay-ms: 900000