Skip to content

Commit

Permalink
Merge pull request #22 from FINTLabs/FFS-1146-oppdatere-authorization…
Browse files Browse the repository at this point in the history
…-service-til-a-lagre-bruker-i-db-nar-requesten-kommer-inn

store user from token if they dont exist and if they have permitted r…
  • Loading branch information
Battlestad authored Aug 8, 2024
2 parents d33d88d + f667006 commit 2ef60f3
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -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<String, String> permittedAppRoles;
}
70 changes: 18 additions & 52 deletions src/main/java/no/fintlabs/flyt/authorization/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -25,54 +25,6 @@ public class UserService {
private final UserRepository userRepository;
private final UserPermissionEntityProducerService userPermissionEntityProducerService;

@Transactional
public void updateUsers(Collection<User> users) {
log.info("Updating {} user entities", users.size());

Map<UUID, User> usersToUpdatePerObjectIdentifier = users.stream()
.collect(toMap(
User::getObjectIdentifier,
Function.identity()
));

userRepository.deleteByObjectIdentifierNotIn(usersToUpdatePerObjectIdentifier.keySet());

Map<UUID, UserEntity> 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<UUID> objectIdentifiersForUsersNotExisting = usersToUpdatePerObjectIdentifier.keySet();
objectIdentifiersForUsersNotExisting.removeAll(updatedUserEntitiesPerObjectIdentifier.keySet());

List<UserEntity> 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 {
Expand All @@ -91,6 +43,10 @@ public void publishUsers() {
}
}

public void save(User user) {
this.userRepository.save(mapFromUser(user));
}

public Optional<User> find(UUID objectIdentifier) {
return this.userRepository.findByObjectIdentifier(objectIdentifier)
.map(this::mapFromEntity);
Expand Down Expand Up @@ -122,6 +78,16 @@ public void putAll(List<User> 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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(
Expand All @@ -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<User> 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<ResponseEntity<RestrictedPageAuthorization>> getRestrictedPageAuthorization(
@AuthenticationPrincipal Mono<Authentication> authenticationMono
public ResponseEntity<RestrictedPageAuthorization> 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<ResponseEntity<User>> get(
@AuthenticationPrincipal Mono<Authentication> authenticationMono
public ResponseEntity<User> 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<User> 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<User> getUserFromUserAuthorizationComponent(JwtAuthenticationToken token) {
Expand All @@ -98,6 +100,12 @@ private User createUserWithAccessToAllApplications(JwtAuthenticationToken token)
.build();
}

private User createUserWithAccessToNoApplications(JwtAuthenticationToken token) {
return tokenParsingUtils.getUserFromToken(token)
.toBuilder()
.build();
}

private List<Long> sourceApplicationsWithoutUserPermissionSetup() {
return List.of(
AcosSourceApplication.SOURCE_APPLICATION_ID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,38 +29,31 @@ public class UserController {
private final UserService userService;

@GetMapping
public Mono<ResponseEntity<Page<User>>> get(
@AuthenticationPrincipal Mono<Authentication> authenticationMono,
public ResponseEntity<Page<User>> 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<User> users = userService.getAll(pageable);
return ResponseEntity.ok(users);
}

@PostMapping("actions/userPermissionBatchPut")
public Mono<ResponseEntity<?>> postUserPermissionBatchPutAction(
@AuthenticationPrincipal Mono<Authentication> authenticationMono,
public ResponseEntity<?> postUserPermissionBatchPutAction(
@AuthenticationPrincipal Authentication authentication,
@RequestBody List<User> 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();
}

}
Original file line number Diff line number Diff line change
@@ -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();
}
Expand All @@ -25,9 +33,30 @@ public User getUserFromToken(JwtAuthenticationToken jwtAuthenticationToken) {
.build();
}

public Mono<Boolean> isAdmin(Mono<Authentication> authenticationMono) {
return authenticationMono
.map(authentication -> authentication != null && authentication.getAuthorities().stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_ADMIN")));
public List<String> getRolesFromToken(JwtAuthenticationToken jwtAuthenticationToken) {
return ((List<?>) jwtAuthenticationToken.getTokenAttributes().get("roles"))
.stream()
.map(Object::toString)
.toList();
}

public boolean hasPermittedRole(JwtAuthenticationToken jwtAuthenticationToken) {
List<String> roles = getRolesFromToken(jwtAuthenticationToken);
if (roles == null || roles.isEmpty()) {
log.warn("Roles are null or empty in token");
return false;
}
Map<String, String> permittedRolesMap = accessControlProperties.getPermittedAppRoles();
if (permittedRolesMap == null || permittedRolesMap.isEmpty()) {
log.warn("Permitted app roles are not configured or empty");
return false;
}
List<String> 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"));
}
}
1 change: 1 addition & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2ef60f3

Please sign in to comment.