diff --git a/build.gradle b/build.gradle index 3df25a3..7330a5c 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.kafka:spring-kafka' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' @@ -38,11 +39,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.postgresql:postgresql' - implementation 'org.flywaydb:flyway-core' + implementation 'org.flywaydb:flyway-core' implementation 'no.fintlabs:fint-kafka:4.0.1' + implementation 'no.fintlabs:fint-flyt-resource-server:2.1.0' + + implementation 'javax.validation:validation-api' + implementation 'org.hibernate.validator:hibernate-validator' - implementation 'no.fintlabs:fint-flyt-resource-server:2.1.0-rc-3' + implementation 'com.azure:azure-identity:1.10.2' + implementation 'com.microsoft.graph:microsoft-graph:5.80.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'io.micrometer:micrometer-registry-prometheus' diff --git a/kustomize/base/flais.yaml b/kustomize/base/flais.yaml index f16121d..5b1c022 100644 --- a/kustomize/base/flais.yaml +++ b/kustomize/base/flais.yaml @@ -41,6 +41,8 @@ spec: } - name: fint.flyt.resource-server.security.api.internal.enabled value: 'true' + - name: fint.flyt.azure-ad-gateway.enabled + value: 'true' onePassword: itemPath: path envFrom: [] diff --git a/kustomize/base/kustomization.yaml b/kustomize/base/kustomization.yaml index 732a662..efc6227 100644 --- a/kustomize/base/kustomization.yaml +++ b/kustomize/base/kustomization.yaml @@ -1,4 +1,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - - flais.yaml \ No newline at end of file + - flais.yaml + - onePassword.yaml \ No newline at end of file diff --git a/kustomize/base/onePassword.yaml b/kustomize/base/onePassword.yaml new file mode 100644 index 0000000..1639654 --- /dev/null +++ b/kustomize/base/onePassword.yaml @@ -0,0 +1,7 @@ +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: fint-flyt-authorization-service +spec: + itemPath: "path set in overlay" + diff --git a/kustomize/overlays/afk-no/api/kustomization.yaml b/kustomize/overlays/afk-no/api/kustomization.yaml index d21cd97..7e83a5f 100644 --- a/kustomize/overlays/afk-no/api/kustomization.yaml +++ b/kustomize/overlays/afk-no/api/kustomization.yaml @@ -41,6 +41,18 @@ patches: value: secretRef: name: fint-flyt-acos-oauth2-client + - op: add + path: "/spec/envFrom/1" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-afk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/agderfk-no/api/kustomization.yaml b/kustomize/overlays/agderfk-no/api/kustomization.yaml index 72e2340..8453b04 100644 --- a/kustomize/overlays/agderfk-no/api/kustomization.yaml +++ b/kustomize/overlays/agderfk-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/bfk-no/api/kustomization.yaml b/kustomize/overlays/bfk-no/api/kustomization.yaml index 901c792..6413af8 100644 --- a/kustomize/overlays/bfk-no/api/kustomization.yaml +++ b/kustomize/overlays/bfk-no/api/kustomization.yaml @@ -41,6 +41,18 @@ patches: value: secretRef: name: fint-flyt-acos-oauth2-client + - op: add + path: "/spec/envFrom/1" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-bfk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/ffk-no/api/kustomization.yaml b/kustomize/overlays/ffk-no/api/kustomization.yaml index 6fb48d2..16882db 100644 --- a/kustomize/overlays/ffk-no/api/kustomization.yaml +++ b/kustomize/overlays/ffk-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/fintlabs-no/beta/kustomization.yaml b/kustomize/overlays/fintlabs-no/beta/kustomization.yaml index c8a6829..7a00527 100644 --- a/kustomize/overlays/fintlabs-no/beta/kustomization.yaml +++ b/kustomize/overlays/fintlabs-no/beta/kustomization.yaml @@ -51,6 +51,18 @@ patches: value: secretRef: name: fint-flyt-vigo-oauth2-client + - op: add + path: "/spec/envFrom/2" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-beta-vault/items/fint-flyt-authorization-service-fintlabs-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/innlandetfylke-no/api/kustomization.yaml b/kustomize/overlays/innlandetfylke-no/api/kustomization.yaml index f558d80..a281ed7 100644 --- a/kustomize/overlays/innlandetfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/innlandetfylke-no/api/kustomization.yaml @@ -34,6 +34,18 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: add + path: "/spec/envFrom/0" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-innlandetfylke-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/mrfylke-no/api/kustomization.yaml b/kustomize/overlays/mrfylke-no/api/kustomization.yaml index c96f083..9f98ce9 100644 --- a/kustomize/overlays/mrfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/mrfylke-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/nfk-no/api/kustomization.yaml b/kustomize/overlays/nfk-no/api/kustomization.yaml index 2ede0c2..49cf0b9 100644 --- a/kustomize/overlays/nfk-no/api/kustomization.yaml +++ b/kustomize/overlays/nfk-no/api/kustomization.yaml @@ -34,6 +34,18 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: add + path: "/spec/envFrom/0" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-nfk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/ofk-no/api/kustomization.yaml b/kustomize/overlays/ofk-no/api/kustomization.yaml index 69151f2..152a456 100644 --- a/kustomize/overlays/ofk-no/api/kustomization.yaml +++ b/kustomize/overlays/ofk-no/api/kustomization.yaml @@ -47,6 +47,18 @@ patches: value: secretRef: name: fint-flyt-vigo-oauth2-client + - op: add + path: "/spec/envFrom/2" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-ofk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/ofk-no/beta/kustomization.yaml b/kustomize/overlays/ofk-no/beta/kustomization.yaml index 9b540aa..56c0acb 100644 --- a/kustomize/overlays/ofk-no/beta/kustomization.yaml +++ b/kustomize/overlays/ofk-no/beta/kustomization.yaml @@ -47,6 +47,18 @@ patches: value: secretRef: name: fint-flyt-vigo-oauth2-client + - op: add + path: "/spec/envFrom/2" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-beta-vault/items/fint-flyt-authorization-service-ofk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/rogfk-no/api/kustomization.yaml b/kustomize/overlays/rogfk-no/api/kustomization.yaml index 7d6758c..aa2fe68 100644 --- a/kustomize/overlays/rogfk-no/api/kustomization.yaml +++ b/kustomize/overlays/rogfk-no/api/kustomization.yaml @@ -34,6 +34,18 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: add + path: "/spec/envFrom/0" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-rogfk-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/telemarkfylke-no/api/kustomization.yaml b/kustomize/overlays/telemarkfylke-no/api/kustomization.yaml index 0485255..cef99ac 100644 --- a/kustomize/overlays/telemarkfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/telemarkfylke-no/api/kustomization.yaml @@ -34,6 +34,18 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: add + path: "/spec/envFrom/0" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-telemarkfylke-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/tromsfylke-no/api/kustomization.yaml b/kustomize/overlays/tromsfylke-no/api/kustomization.yaml index 869bad8..74cea06 100644 --- a/kustomize/overlays/tromsfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/tromsfylke-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/tromsfylke-no/beta/kustomization.yaml b/kustomize/overlays/tromsfylke-no/beta/kustomization.yaml index dbed73c..9833d13 100644 --- a/kustomize/overlays/tromsfylke-no/beta/kustomization.yaml +++ b/kustomize/overlays/tromsfylke-no/beta/kustomization.yaml @@ -35,6 +35,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' - op: add path: "/spec/envFrom/0" value: diff --git a/kustomize/overlays/trondelagfylke-no/api/kustomization.yaml b/kustomize/overlays/trondelagfylke-no/api/kustomization.yaml index f4bd862..c42cb99 100644 --- a/kustomize/overlays/trondelagfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/trondelagfylke-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/trondelagfylke-no/beta/kustomization.yaml b/kustomize/overlays/trondelagfylke-no/beta/kustomization.yaml index 196f363..355e48b 100644 --- a/kustomize/overlays/trondelagfylke-no/beta/kustomization.yaml +++ b/kustomize/overlays/trondelagfylke-no/beta/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/vestfoldfylke-no/api/kustomization.yaml b/kustomize/overlays/vestfoldfylke-no/api/kustomization.yaml index 06c911c..411fae4 100644 --- a/kustomize/overlays/vestfoldfylke-no/api/kustomization.yaml +++ b/kustomize/overlays/vestfoldfylke-no/api/kustomization.yaml @@ -34,6 +34,18 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: add + path: "/spec/envFrom/0" + value: + secretRef: + name: fint-flyt-authorization-service target: kind: Application + name: fint-flyt-authorization-service + - patch: |- + - op: replace + path: "/spec/itemPath" + value: "vaults/aks-api-vault/items/fint-flyt-authorization-service-vestfoldfylke-no" + target: + kind: OnePasswordItem name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/vlfk-no/api/kustomization.yaml b/kustomize/overlays/vlfk-no/api/kustomization.yaml index 3fe25e6..418be41 100644 --- a/kustomize/overlays/vlfk-no/api/kustomization.yaml +++ b/kustomize/overlays/vlfk-no/api/kustomization.yaml @@ -34,6 +34,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' target: kind: Application name: fint-flyt-authorization-service \ No newline at end of file diff --git a/kustomize/overlays/vlfk-no/beta/kustomization.yaml b/kustomize/overlays/vlfk-no/beta/kustomization.yaml index fe927d2..0a3746d 100644 --- a/kustomize/overlays/vlfk-no/beta/kustomization.yaml +++ b/kustomize/overlays/vlfk-no/beta/kustomization.yaml @@ -35,6 +35,9 @@ patches: "vigo.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"], "novari.no":["https://role-catalog.vigoiks.no/vigo/flyt/developer"] } + - op: replace + path: "/spec/env/3/value" + value: 'false' - op: add path: "/spec/env/-" value: diff --git a/src/main/java/no/fintlabs/Application.java b/src/main/java/no/fintlabs/Application.java index 27a753b..4887207 100644 --- a/src/main/java/no/fintlabs/Application.java +++ b/src/main/java/no/fintlabs/Application.java @@ -2,7 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling +@ConfigurationPropertiesScan @SpringBootApplication public class Application { diff --git a/src/main/java/no/fintlabs/authorization/AuthorizationUtil.java b/src/main/java/no/fintlabs/authorization/AuthorizationUtil.java deleted file mode 100644 index 41938c3..0000000 --- a/src/main/java/no/fintlabs/authorization/AuthorizationUtil.java +++ /dev/null @@ -1,11 +0,0 @@ -package no.fintlabs.authorization; - -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.stereotype.Service; - -@Service -public class AuthorizationUtil { - public String getObjectIdentifierFromToken(JwtAuthenticationToken jwtAuthenticationToken) { - return jwtAuthenticationToken.getTokenAttributes().get("objectidentifier").toString(); - } -} diff --git a/src/main/java/no/fintlabs/authorization/adminuser/AdminUser.java b/src/main/java/no/fintlabs/authorization/adminuser/AdminUser.java deleted file mode 100644 index bc09ef4..0000000 --- a/src/main/java/no/fintlabs/authorization/adminuser/AdminUser.java +++ /dev/null @@ -1,10 +0,0 @@ -package no.fintlabs.authorization.adminuser; - -import lombok.Builder; -import lombok.Getter; - -@Builder -@Getter -public class AdminUser { - private boolean admin; -} diff --git a/src/main/java/no/fintlabs/authorization/adminuser/AdminUserController.java b/src/main/java/no/fintlabs/authorization/adminuser/AdminUserController.java deleted file mode 100644 index 3481c13..0000000 --- a/src/main/java/no/fintlabs/authorization/adminuser/AdminUserController.java +++ /dev/null @@ -1,111 +0,0 @@ -package no.fintlabs.authorization.adminuser; - -import no.fintlabs.authorization.user.UserPermission; -import no.fintlabs.authorization.user.UserPermissionDto; -import no.fintlabs.authorization.user.UserPermissionRepository; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.*; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import java.util.List; -import java.util.Optional; - -import static no.fintlabs.resourceserver.UrlPaths.INTERNAL_API; - -@RestController -@RequestMapping(INTERNAL_API + "/authorization/adminuser") -public class AdminUserController { - - private final UserPermissionRepository userPermissionRepository; - - public AdminUserController( - UserPermissionRepository userPermissionRepository) { - this.userPermissionRepository = userPermissionRepository; - } - - - @GetMapping("check-is-admin") - public Mono> checkAdminUser( - @AuthenticationPrincipal Mono authenticationMono - ) { - return isAdmin(authenticationMono) - .map(isAdmin -> ResponseEntity.ok(AdminUser.builder().admin(isAdmin).build())); - } - - @GetMapping("userpermissions") - public Mono>> getUserPermissions( - @AuthenticationPrincipal Mono authenticationMono - ) { - return isAdmin(authenticationMono) - .flatMap(isAdmin -> { - if (isAdmin) { - return Mono.fromCallable(userPermissionRepository::findAll) - .subscribeOn(Schedulers.boundedElastic()) - .map(userPermissions -> { - List userPermissionDtos = new java.util.ArrayList<>(List.of()); - userPermissions.forEach(userPermission -> userPermissionDtos.add(buildUserPermissionDto(userPermission))); - return ResponseEntity.ok().body(userPermissionDtos); - }); - } else { - return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); - } - }); - } - - @PostMapping("userpermissions") - public Mono>> setUserPermissions( - @RequestBody List userPermissionDtos, - @AuthenticationPrincipal Mono authenticationMono - ) { - return isAdmin(authenticationMono) - .flatMap(isAdmin -> { - if (isAdmin) { - return Flux.fromIterable(userPermissionDtos) - .flatMap(userPermissionDto -> Mono.fromCallable(() -> { - Optional userPermissionOptional = userPermissionRepository - .findByObjectIdentifier(userPermissionDto.getObjectIdentifier()); - if (userPermissionOptional.isPresent()) { - UserPermission userPermission = userPermissionOptional.get(); - userPermission.setSourceApplicationIds(userPermissionDto - .getSourceApplicationIds()); - userPermissionRepository.save(userPermission); - - return buildUserPermissionDto(userPermission); - } else { - UserPermission newUserPermission = UserPermission.builder() - .objectIdentifier(userPermissionDto.getObjectIdentifier()) - .sourceApplicationIds(userPermissionDto.getSourceApplicationIds()) - .build(); - userPermissionRepository.save(newUserPermission); - - return buildUserPermissionDto(newUserPermission); - } - }).subscribeOn(Schedulers.boundedElastic())) - .collectList() - .map(ResponseEntity::ok); - } else { - return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); - } - }); - } - - private UserPermissionDto buildUserPermissionDto(UserPermission userPermission) { - return UserPermissionDto - .builder() - .objectIdentifier(userPermission.getObjectIdentifier()) - .sourceApplicationIds(userPermission.getSourceApplicationIds()) - .build(); - } - - private Mono isAdmin(Mono authenticationMono) { - return authenticationMono - .map(authentication -> authentication != null && authentication.getAuthorities().stream() - .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_ADMIN"))); - } - -} diff --git a/src/main/java/no/fintlabs/authorization/user/UserPermission.java b/src/main/java/no/fintlabs/authorization/user/UserPermission.java deleted file mode 100644 index ca3eaeb..0000000 --- a/src/main/java/no/fintlabs/authorization/user/UserPermission.java +++ /dev/null @@ -1,32 +0,0 @@ -package no.fintlabs.authorization.user; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.*; - -import javax.persistence.*; -import java.util.List; - -@Getter -@Builder -@Entity -@NoArgsConstructor -@AllArgsConstructor -public class UserPermission { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @JsonIgnore - @Setter(AccessLevel.NONE) - private long id; - @Setter - @Column(unique = true, nullable = false) - private String objectIdentifier; - @ElementCollection - @CollectionTable( - name = "user_permission_source_application_ids", - joinColumns = @JoinColumn(name = "user_permission_id"), - uniqueConstraints = @UniqueConstraint(columnNames = {"user_permission_id", "source_application_ids"}) - ) - @Setter - @Column(name = "source_application_ids") - private List sourceApplicationIds; -} diff --git a/src/main/java/no/fintlabs/authorization/user/UserPermissionController.java b/src/main/java/no/fintlabs/authorization/user/UserPermissionController.java deleted file mode 100644 index 6dc5fb2..0000000 --- a/src/main/java/no/fintlabs/authorization/user/UserPermissionController.java +++ /dev/null @@ -1,57 +0,0 @@ -package no.fintlabs.authorization.user; - -import lombok.extern.slf4j.Slf4j; -import no.fintlabs.authorization.AuthorizationUtil; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -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 reactor.core.scheduler.Schedulers; - -import static no.fintlabs.resourceserver.UrlPaths.INTERNAL_API; - -@Slf4j -@RequestMapping(INTERNAL_API + "/authorization/user") -@RestController -public class UserPermissionController { - - private final UserPermissionRepository userPermissionRepository; - private final AuthorizationUtil authorizationUtil; - - public UserPermissionController( - UserPermissionRepository userPermissionRepository, - AuthorizationUtil authorizationUtil - ) { - this.userPermissionRepository = userPermissionRepository; - this.authorizationUtil = authorizationUtil; - } - - @GetMapping("check-authorized") - public ResponseEntity checkAuthorization() { - return ResponseEntity.ok("User authorized"); - } - - @GetMapping("permission") - public Mono> getSourceApplications( - @AuthenticationPrincipal Mono authenticationMono - ) { - return authenticationMono - .map(authentication -> authorizationUtil - .getObjectIdentifierFromToken((JwtAuthenticationToken) authentication)) - .publishOn(Schedulers.boundedElastic()) - .map(userPermissionRepository::findByObjectIdentifier) - .map(optionalUserPermission -> optionalUserPermission.map(userPermission -> ResponseEntity.ok( - UserPermissionDto - .builder() - .objectIdentifier(userPermission.getObjectIdentifier()) - .sourceApplicationIds(userPermission.getSourceApplicationIds()) - .build() - )).orElseGet(() -> ResponseEntity.notFound().build()) - ); - } - -} diff --git a/src/main/java/no/fintlabs/authorization/user/UserPermissionDto.java b/src/main/java/no/fintlabs/authorization/user/UserPermissionDto.java deleted file mode 100644 index 2e8fe8c..0000000 --- a/src/main/java/no/fintlabs/authorization/user/UserPermissionDto.java +++ /dev/null @@ -1,21 +0,0 @@ -package no.fintlabs.authorization.user; - -import com.sun.istack.NotNull; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.extern.jackson.Jacksonized; - -import java.util.List; - -@Getter -@Builder -@EqualsAndHashCode -@Jacksonized -public class UserPermissionDto { - @NotNull - private String objectIdentifier; - private String email; - @NotNull - private List sourceApplicationIds; -} diff --git a/src/main/java/no/fintlabs/authorization/user/UserPermissionRepository.java b/src/main/java/no/fintlabs/authorization/user/UserPermissionRepository.java deleted file mode 100644 index e844733..0000000 --- a/src/main/java/no/fintlabs/authorization/user/UserPermissionRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package no.fintlabs.authorization.user; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface UserPermissionRepository extends JpaRepository { - - Optional findByObjectIdentifier(String sub); - -} diff --git a/src/main/java/no/fintlabs/ClientAuthorization.java b/src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorization.java similarity index 65% rename from src/main/java/no/fintlabs/ClientAuthorization.java rename to src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorization.java index 8b87ef6..663d2f8 100644 --- a/src/main/java/no/fintlabs/ClientAuthorization.java +++ b/src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorization.java @@ -1,4 +1,4 @@ -package no.fintlabs; +package no.fintlabs.flyt.authorization.client; import lombok.Builder; import lombok.Getter; @@ -8,5 +8,5 @@ public class ClientAuthorization { private final boolean authorized; private final String clientId; - private final String sourceApplicationId; + private final Long sourceApplicationId; } diff --git a/src/main/java/no/fintlabs/ClientAuthorizationProducerRecordBuilder.java b/src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorizationProducerRecordBuilder.java similarity index 75% rename from src/main/java/no/fintlabs/ClientAuthorizationProducerRecordBuilder.java rename to src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorizationProducerRecordBuilder.java index 8bdc9fc..0cefbee 100644 --- a/src/main/java/no/fintlabs/ClientAuthorizationProducerRecordBuilder.java +++ b/src/main/java/no/fintlabs/flyt/authorization/client/ClientAuthorizationProducerRecordBuilder.java @@ -1,18 +1,16 @@ -package no.fintlabs; +package no.fintlabs.flyt.authorization.client; -import lombok.extern.slf4j.Slf4j; +import no.fintlabs.flyt.authorization.client.sourceapplications.AcosSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.DigisakSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.EgrunnervervSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.VigoSourceApplication; import no.fintlabs.kafka.requestreply.ReplyProducerRecord; -import no.fintlabs.models.sourceapplication.AcosSourceApplication; -import no.fintlabs.models.sourceapplication.EgrunnervervSourceApplication; -import no.fintlabs.models.sourceapplication.DigisakSourceApplication; -import no.fintlabs.models.sourceapplication.VigoSourceApplication; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.stereotype.Component; import java.util.Objects; @Component -@Slf4j public class ClientAuthorizationProducerRecordBuilder { public ReplyProducerRecord apply(ConsumerRecord consumerRecord) { @@ -22,10 +20,8 @@ public ReplyProducerRecord apply(ConsumerRecord permittedUsersGraphUserInfo) { + log.info("Updating users based on {} permitted user info", permittedUsersGraphUserInfo.size()); + + List usersToUpdate = permittedUsersGraphUserInfo.stream() + .map(graphUserInfo -> User + .builder() + .objectIdentifier(graphUserInfo.getId()) + .name(graphUserInfo.getDisplayName()) + .email(graphUserInfo.getMail()) + .build() + ).toList(); + + userService.updateUsers(usersToUpdate); + log.info("Successfully updated users"); + } + + public Optional getUser(UUID objectIdentifier) { + return userService.find(objectIdentifier); + } + + public Page getUsers(Pageable pageable) { + return userService.getAll(pageable); + } + + public void batchPutUserPermissions(List users) { + userService.putAll(users); + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/UserRepository.java b/src/main/java/no/fintlabs/flyt/authorization/user/UserRepository.java new file mode 100644 index 0000000..8d83523 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/UserRepository.java @@ -0,0 +1,19 @@ +package no.fintlabs.flyt.authorization.user; + +import no.fintlabs.flyt.authorization.user.model.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.*; + + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByObjectIdentifier(UUID sub); + + List findAllByObjectIdentifierIn(Collection objectIdentifiers); + + void deleteByObjectIdentifierNotIn(Set objectIdentifiers); + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java b/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java new file mode 100644 index 0000000..dddeff1 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/UserService.java @@ -0,0 +1,119 @@ +package no.fintlabs.flyt.authorization.user; + +import lombok.extern.slf4j.Slf4j; +import no.fintlabs.flyt.authorization.user.model.User; +import no.fintlabs.flyt.authorization.user.model.UserEntity; +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 static java.util.stream.Collectors.toMap; + +@Service +@Slf4j +public class UserService { + + // TODO eivindmorch 27/06/2024 : Azure sync og tjeneste som leverer til frontend burde være separate + // ettersom horisontal skalering av synkingen vil skape problemer + + private final UserRepository userRepository; + + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @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 Optional find(UUID objectIdentifier) { + return this.userRepository.findByObjectIdentifier(objectIdentifier) + .map(this::mapFromEntity); + } + + public Page getAll(Pageable pageable) { + return this.userRepository.findAll(pageable) + .map(this::mapFromEntity); + } + + public void putAll(List users) { + Map> sourceApplicationIdsPerObjectIdentifier = users.stream() + .collect(toMap( + User::getObjectIdentifier, + User::getSourceApplicationIds + )); + + List entities = userRepository.findAllByObjectIdentifierIn( + users + .stream() + .map(User::getObjectIdentifier) + .toList() + ); + + entities.forEach(entity -> entity.setSourceApplicationIds( + sourceApplicationIdsPerObjectIdentifier.get(entity.getObjectIdentifier()) + )); + + userRepository.saveAll(entities); + } + + private User mapFromEntity(UserEntity userEntity) { + return User + .builder() + .objectIdentifier(userEntity.getObjectIdentifier()) + .name(userEntity.getName()) + .email(userEntity.getEmail()) + .sourceApplicationIds(userEntity.getSourceApplicationIds()) + .build(); + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureAdGatewayConfiguration.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureAdGatewayConfiguration.java new file mode 100644 index 0000000..07e7446 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureAdGatewayConfiguration.java @@ -0,0 +1,36 @@ +package no.fintlabs.flyt.authorization.user.azure.configuration; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + + +@Getter +@Setter +@Validated +@EnableAutoConfiguration +@Configuration +@ConfigurationProperties(prefix = "fint.flyt.azure-ad-gateway") +public class AzureAdGatewayConfiguration { + + @NotNull + private Boolean enabled; + + @Valid + private PermittedAppRolesProperties permittedAppRoles; + + @Getter + @Setter + public static class PermittedAppRolesProperties { + @NotEmpty + private String flytUser; + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureCredentialsConfiguration.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureCredentialsConfiguration.java new file mode 100644 index 0000000..3db5426 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/AzureCredentialsConfiguration.java @@ -0,0 +1,28 @@ +package no.fintlabs.flyt.authorization.user.azure.configuration; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.NotEmpty; + + +@Getter +@Setter +@Validated +@EnableAutoConfiguration +@Configuration +@ConfigurationProperties(prefix = "azure.credentials") +public class AzureCredentialsConfiguration { + @NotEmpty + private String clientId; + @NotEmpty + private String clientSecret; + @NotEmpty + private String tenantId; + @NotEmpty + private String appId; +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/GraphServiceConfiguration.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/GraphServiceConfiguration.java new file mode 100644 index 0000000..a077d0c --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/configuration/GraphServiceConfiguration.java @@ -0,0 +1,100 @@ +package no.fintlabs.flyt.authorization.user.azure.configuration; + +import com.azure.identity.ClientSecretCredential; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.microsoft.graph.authentication.TokenCredentialAuthProvider; +import com.microsoft.graph.requests.GraphServiceClient; +import lombok.Getter; +import lombok.Setter; +import no.fintlabs.flyt.authorization.user.azure.services.*; +import okhttp3.Request; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + + +@Getter +@Setter +@Validated +@EnableAutoConfiguration +@Configuration +@ConditionalOnProperty(value = "fint.flyt.azure-ad-gateway.enabled", havingValue = "true") +public class GraphServiceConfiguration { + + @Bean + public GraphService graphService( + AzureCredentialsConfiguration azureCredentialsConfiguration, + AzureAdGatewayConfiguration azureAdGatewayConfiguration, + GraphServicePrincipalService graphServicePrincipalService, + GraphAppRoleService graphAppRoleService, + GraphGroupService graphGroupService, + GraphUserService graphUserService + ) { + return new GraphService( + azureCredentialsConfiguration, + azureAdGatewayConfiguration, + graphServicePrincipalService, + graphAppRoleService, + graphGroupService, + graphUserService + ); + } + + @Bean + public GraphUserService graphUserService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + return new GraphUserService(graphServiceClient, graphPageWalkerService); + } + + @Bean + public GraphAppRoleService graphAppRoleService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + return new GraphAppRoleService(graphServiceClient, graphPageWalkerService); + } + + @Bean + public GraphGroupService graphGroupService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + return new GraphGroupService(graphServiceClient, graphPageWalkerService); + } + + @Bean + public GraphServicePrincipalService graphServicePrincipalService(GraphServiceClient graphServiceClient) { + return new GraphServicePrincipalService(graphServiceClient); + } + + @Bean + public GraphPageWalkerService graphPageWalkerService() { + return new GraphPageWalkerService(); + } + + @Bean + public GraphServiceClient graphServiceClient(AzureCredentialsConfiguration azureCredentialsConfiguration) { + ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder() + .clientId(azureCredentialsConfiguration.getClientId()) + .clientSecret(azureCredentialsConfiguration.getClientSecret()) + .tenantId(azureCredentialsConfiguration.getTenantId()) + .build(); + + TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider( + List.of("https://graph.microsoft.com/.default"), + clientSecretCredential + ); + + return GraphServiceClient + .builder() + .authenticationProvider(tokenCredentialAuthProvider) + .buildClient(); + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/models/GraphUserInfo.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/models/GraphUserInfo.java new file mode 100644 index 0000000..9a0ae02 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/models/GraphUserInfo.java @@ -0,0 +1,14 @@ +package no.fintlabs.flyt.authorization.user.azure.models; + +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +@Builder +@Getter +public class GraphUserInfo { + private final UUID id; + private final String displayName; + private final String mail; +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphAppRoleService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphAppRoleService.java new file mode 100644 index 0000000..5d162f1 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphAppRoleService.java @@ -0,0 +1,104 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import com.microsoft.graph.models.AppRoleAssignment; +import com.microsoft.graph.models.ServicePrincipal; +import com.microsoft.graph.requests.GraphServiceClient; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; + +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toMap; + + +@Slf4j +public class GraphAppRoleService { + + private final GraphServiceClient graphServiceClient; + + private final GraphPageWalkerService graphPageWalkerService; + + public GraphAppRoleService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + this.graphServiceClient = graphServiceClient; + this.graphPageWalkerService = graphPageWalkerService; + } + + public Set getAppRoleIdsFromAppRoleValues(UUID servicePrincipalId, Set appRoleValues) { + log.info("Retrieving app role ids from app role values: {}", appRoleValues); + ServicePrincipal servicePrincipal = graphServiceClient.servicePrincipals(servicePrincipalId.toString()) + .buildRequest() + .select("appRoles") + .get(); + + if (servicePrincipal == null) { + throw new IllegalStateException("Service principal is null"); + } + if (servicePrincipal.appRoles == null) { + throw new IllegalStateException("Service principal has no roles"); + } + + Map appRoleIdPerValue = servicePrincipal.appRoles + .stream() + .filter(appRole -> Objects.nonNull(appRole.id)) + .filter(appRole -> Objects.nonNull(appRole.value)) + .filter(appRole -> appRoleValues.contains(appRole.value)) + .collect(toMap( + appRole -> appRole.value, + appRole -> appRole.id.toString() + )); + + Set appRoleValuesThatCouldNotBeFound = new HashSet<>(appRoleValues); + appRoleValuesThatCouldNotBeFound.removeAll(appRoleIdPerValue.keySet()); + if (!appRoleValuesThatCouldNotBeFound.isEmpty()) { + throw new IllegalStateException("Could not find id for app roles with values: " + appRoleValuesThatCouldNotBeFound); + } + + Set appRoleIds = appRoleIdPerValue.values().stream() + .map(UUID::fromString) + .collect(Collectors.toSet()); + log.info("Successfully retrieved {} app role ids: {}", appRoleIds.size(), appRoleIds); + return appRoleIds; + } + + @Getter + @AllArgsConstructor + public static class AppRoleIdAndPrincipalSelection { + private final UUID appRoleId; + private final String principalType; + private final UUID principalId; + } + + public List getAppRoleAssignmentsWithAppRoleIds(UUID servicePrincipalId, Set appRoleIds) { + log.info("Retrieving app role assignments with app role ids: {}", appRoleIds); + List appRoleAssignments = graphPageWalkerService.getContentFromCurrentAndNextPages( + graphServiceClient + .servicePrincipals(servicePrincipalId.toString()) + .appRoleAssignedTo() + .buildRequest() + .select("appRoleId,principalType,principalId"), + pageContent -> pageContent.stream() + .filter(appRoleAssignment -> appRoleIds.contains(appRoleAssignment.appRoleId)) + .toList() + ) + .stream() + .map(this::fromAppRoleAssignment) + .toList(); + log.info("Successfully retrieved {} app role assignments", appRoleAssignments.size()); + return appRoleAssignments; + } + + private AppRoleIdAndPrincipalSelection fromAppRoleAssignment(AppRoleAssignment appRoleAssignment) { + return new AppRoleIdAndPrincipalSelection( + appRoleAssignment.appRoleId, + appRoleAssignment.principalType, + appRoleAssignment.principalId + ); + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphGroupService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphGroupService.java new file mode 100644 index 0000000..d940743 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphGroupService.java @@ -0,0 +1,57 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import com.microsoft.graph.requests.GraphServiceClient; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +public class GraphGroupService { + + private final GraphServiceClient graphServiceClient; + private final GraphPageWalkerService graphPageWalkerService; + + public GraphGroupService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + this.graphServiceClient = graphServiceClient; + this.graphPageWalkerService = graphPageWalkerService; + } + + public Set getGroupUserMemberIds(Collection groupIds) { + log.info("Retrieving member IDs for groups with the following IDs: {}", groupIds); + Set groupUserMemberIds = groupIds.stream() + .map(this::getGroupUserMemberIds) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + log.info("Successfully retrieved member IDs for {} groups, totaling {} member IDs", groupIds.size(), groupUserMemberIds.size()); + return groupUserMemberIds; + } + + public Set getGroupUserMemberIds(UUID groupId) { + log.info("Retrieving member IDs for group with ID: {}", groupId); + Set groupMemberIds = graphPageWalkerService.getContentFromCurrentAndNextPages( + graphServiceClient + .groups(groupId.toString()) + .membersAsUser() + .buildRequest() + .select("id"), + pageContent -> pageContent.stream() + .map(user -> user.id) + .filter(Objects::nonNull) + .toList() + ) + .stream() + .map(UUID::fromString) + .collect(Collectors.toSet()); + log.info("Successfully retrieved {} member IDs for group with ID: {}", groupMemberIds.size(), groupId); + return groupMemberIds; + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphPageWalkerService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphPageWalkerService.java new file mode 100644 index 0000000..287a6dd --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphPageWalkerService.java @@ -0,0 +1,92 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import com.microsoft.graph.http.*; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +@Slf4j +public class GraphPageWalkerService { + + public < + T, + R, + REQUEST_BUILDER extends BaseRequestBuilder, + COLLECTION_RESPONSE extends BaseCollectionResponse, + COLLECTION_REQUEST_BUILDER extends BaseCollectionRequestBuilder< + T, + REQUEST_BUILDER, + COLLECTION_RESPONSE, + COLLECTION_PAGE, + COLLECTION_REQUEST + >, + COLLECTION_PAGE extends BaseCollectionPage, + COLLECTION_REQUEST extends BaseEntityCollectionRequest + > + List getContentFromCurrentAndNextPages( + COLLECTION_REQUEST collectionRequest, + Function, List> contentProcessing + ) { + return getContentFromCurrentAndNextPages(collectionRequest, BaseEntityCollectionRequest::get, contentProcessing); + } + + public < + T, + R, + REQUEST_BUILDER extends BaseRequestBuilder, + COLLECTION_RESPONSE extends BaseCollectionResponse, + COLLECTION_REQUEST_BUILDER extends BaseCollectionRequestBuilder< + T, + REQUEST_BUILDER, + COLLECTION_RESPONSE, + COLLECTION_PAGE, + COLLECTION_REQUEST + >, + COLLECTION_PAGE extends BaseCollectionPage, + COLLECTION_REQUEST extends BaseActionCollectionRequest + > + List getContentFromCurrentAndNextPages( + COLLECTION_REQUEST collectionRequest, + Function, List> contentProcessing + ) { + return getContentFromCurrentAndNextPages(collectionRequest, BaseActionCollectionRequest::post, contentProcessing); + } + + public < + T, + R, + REQUEST_BUILDER extends BaseRequestBuilder, + COLLECTION_RESPONSE extends BaseCollectionResponse, + COLLECTION_REQUEST_BUILDER extends BaseCollectionRequestBuilder< + T, + REQUEST_BUILDER, + COLLECTION_RESPONSE, + COLLECTION_PAGE, + COLLECTION_REQUEST + >, + COLLECTION_PAGE extends BaseCollectionPage, + COLLECTION_REQUEST extends BaseCollectionRequest + > + List getContentFromCurrentAndNextPages( + COLLECTION_REQUEST collectionRequest, + Function performRequest, + Function, List> contentProcessing + ) { + COLLECTION_PAGE collectionPage = performRequest.apply(collectionRequest); + if (collectionPage == null) { + throw new IllegalStateException("Page is null"); + } + List content = new ArrayList<>(contentProcessing.apply(new ArrayList<>(collectionPage.getCurrentPage()))); + COLLECTION_REQUEST_BUILDER nextBuilder = collectionPage.getNextPage(); + if (nextBuilder != null) { + content.addAll(getContentFromCurrentAndNextPages( + nextBuilder.buildRequest(), + performRequest, + contentProcessing + )); + } + return content; + } +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphService.java new file mode 100644 index 0000000..116f9cc --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphService.java @@ -0,0 +1,101 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import lombok.extern.slf4j.Slf4j; +import no.fintlabs.flyt.authorization.user.azure.configuration.AzureAdGatewayConfiguration; +import no.fintlabs.flyt.authorization.user.azure.configuration.AzureCredentialsConfiguration; +import no.fintlabs.flyt.authorization.user.azure.models.GraphUserInfo; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +public class GraphService { + + private final AzureCredentialsConfiguration azureCredentialsConfiguration; + + private final AzureAdGatewayConfiguration azureAdGatewayConfiguration; + + private final GraphServicePrincipalService graphServicePrincipalService; + + private final GraphAppRoleService graphAppRoleService; + + private final GraphGroupService graphGroupService; + + private final GraphUserService graphUserService; + + public GraphService( + AzureCredentialsConfiguration azureCredentialsConfiguration, + AzureAdGatewayConfiguration azureAdGatewayConfiguration, + GraphServicePrincipalService graphServicePrincipalService, + GraphAppRoleService graphAppRoleService, + GraphGroupService graphGroupService, + GraphUserService graphUserService + ) { + this.azureCredentialsConfiguration = azureCredentialsConfiguration; + this.azureAdGatewayConfiguration = azureAdGatewayConfiguration; + this.graphServicePrincipalService = graphServicePrincipalService; + this.graphAppRoleService = graphAppRoleService; + this.graphGroupService = graphGroupService; + this.graphUserService = graphUserService; + } + + public boolean areCredentialsAvailable() { + String appId = azureCredentialsConfiguration.getAppId(); + if (appId == null || appId.isBlank() || "null".equals(appId)) { + log.error("AppId cannot be null, blank, or the string 'null'"); + return false; + } + return true; + } + + public List getPermittedUsersInfo() { + log.info("Retrieving permitted users info"); + + // TODO eivindmorch 27/06/2024 : Hvorfor bruker vi app id og ikke service principal id direkte? +// UUID servicePrincipalId = UUID.fromString(""); // TODO eivindmorch 27/06/2024 : Get from config + UUID servicePrincipalId = graphServicePrincipalService.getServicePrincipalId( + UUID.fromString(azureCredentialsConfiguration.getAppId()) + ); + + Set permittedAppRoleIds = graphAppRoleService.getAppRoleIdsFromAppRoleValues( + servicePrincipalId, + Set.of(azureAdGatewayConfiguration.getPermittedAppRoles().getFlytUser()) + ); + + Set permittedUserIds = getPermittedUserIds( + servicePrincipalId, + permittedAppRoleIds + ); + + List userInfo = graphUserService.getUserInfo(permittedUserIds); + log.info("Successfully retrieved {} permitted users", permittedUserIds.size()); + return userInfo; + } + + private Set getPermittedUserIds(UUID servicePrincipalId, Set permittedAppRoleIds) { + List appRoleAssignments = + graphAppRoleService.getAppRoleAssignmentsWithAppRoleIds(servicePrincipalId, permittedAppRoleIds); + + Map> idsOfPrincipalsWithPermittedAppRoleAssignmentsPerPrincipalType = + appRoleAssignments.stream() + .collect(Collectors.groupingBy( + GraphAppRoleService.AppRoleIdAndPrincipalSelection::getPrincipalType, + Collectors.mapping( + GraphAppRoleService.AppRoleIdAndPrincipalSelection::getPrincipalId, + Collectors.toSet() + ) + )); + + return Stream.concat( + graphGroupService.getGroupUserMemberIds( + idsOfPrincipalsWithPermittedAppRoleAssignmentsPerPrincipalType.getOrDefault("Group", Set.of()) + ).stream(), + idsOfPrincipalsWithPermittedAppRoleAssignmentsPerPrincipalType.getOrDefault("User", Set.of()).stream() + ).collect(Collectors.toSet()); + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphServicePrincipalService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphServicePrincipalService.java new file mode 100644 index 0000000..c5d7695 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphServicePrincipalService.java @@ -0,0 +1,44 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import com.microsoft.graph.options.QueryOption; +import com.microsoft.graph.requests.GraphServiceClient; +import com.microsoft.graph.requests.ServicePrincipalCollectionPage; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; + +import java.util.UUID; + +@Slf4j +public class GraphServicePrincipalService { + + private final GraphServiceClient graphServiceClient; + + public GraphServicePrincipalService(GraphServiceClient graphServiceClient) { + this.graphServiceClient = graphServiceClient; + } + + public UUID getServicePrincipalId(UUID appId) { + log.info("Retrieving service principal id for application with id: {}", appId); + ServicePrincipalCollectionPage servicePrincipalSearchResult = graphServiceClient + .servicePrincipals() + .buildRequest(new QueryOption("$filter", "appId eq '" + appId.toString() + "'")) + .select("id") + .get(); + + if (servicePrincipalSearchResult == null || servicePrincipalSearchResult.getCurrentPage().isEmpty()) { + throw new IllegalStateException("No principal for application found"); + } + if (servicePrincipalSearchResult.getCurrentPage().size() > 1 || servicePrincipalSearchResult.getNextPage() != null) { + throw new IllegalStateException("Found multiple service principals for application"); + } + String servicePrincipalIdString = servicePrincipalSearchResult.getCurrentPage().get(0).id; + if (servicePrincipalIdString == null) { + throw new IllegalStateException("Service principal id is null"); + } + + UUID servicePrincipalId = UUID.fromString(servicePrincipalIdString); + log.info("Successfully retrieved service principal id: {}", servicePrincipalId); + return servicePrincipalId; + } + +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphUserService.java b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphUserService.java new file mode 100644 index 0000000..c4c41ae --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/azure/services/GraphUserService.java @@ -0,0 +1,57 @@ +package no.fintlabs.flyt.authorization.user.azure.services; + +import com.microsoft.graph.models.DirectoryObjectGetByIdsParameterSet; +import com.microsoft.graph.models.User; +import com.microsoft.graph.requests.GraphServiceClient; +import lombok.extern.slf4j.Slf4j; +import no.fintlabs.flyt.authorization.user.azure.models.GraphUserInfo; +import okhttp3.Request; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +@Slf4j +public class GraphUserService { + + private final GraphServiceClient graphServiceClient; + + private final GraphPageWalkerService graphPageWalkerService; + + public GraphUserService( + GraphServiceClient graphServiceClient, + GraphPageWalkerService graphPageWalkerService + ) { + this.graphServiceClient = graphServiceClient; + this.graphPageWalkerService = graphPageWalkerService; + } + + public List getUserInfo(Collection userIds) { + log.info("Retrieving user information for {} user ids", userIds.size()); + List graphUserInfos = + graphPageWalkerService.getContentFromCurrentAndNextPages( + graphServiceClient.users() + .getByIds(DirectoryObjectGetByIdsParameterSet + .newBuilder() + .withIds(userIds.stream().map(UUID::toString).toList()) + .build() + ).buildRequest() + .select("id,mail,displayName"), + pageContent -> pageContent.stream() + .filter(user -> user instanceof User) + .map(user -> (User) user) + .filter(user -> Objects.nonNull(user.id)) + .map(user -> GraphUserInfo + .builder() + .id(UUID.fromString(user.id)) + .displayName(user.displayName) + .mail(user.mail) + .build() + ).toList() + ); + log.info("Successfully retrieved user information for {} users", graphUserInfos.size()); + return graphUserInfos; + } + +} 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 new file mode 100644 index 0000000..1ee777f --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/MeController.java @@ -0,0 +1,113 @@ +package no.fintlabs.flyt.authorization.user.controller; + +import no.fintlabs.flyt.authorization.client.sourceapplications.AcosSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.DigisakSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.EgrunnervervSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.VigoSourceApplication; +import no.fintlabs.flyt.authorization.user.AzureAdUserAuthorizationComponent; +import no.fintlabs.flyt.authorization.user.controller.utils.TokenParsingUtils; +import no.fintlabs.flyt.authorization.user.model.RestrictedPageAuthorization; +import no.fintlabs.flyt.authorization.user.model.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +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; +import java.util.UUID; + +import static no.fintlabs.resourceserver.UrlPaths.INTERNAL_API; + +@RestController +@RequestMapping(INTERNAL_API + "/authorization/me") +public class MeController { + + private final TokenParsingUtils tokenParsingUtils; + + private final Boolean azureAdUserAuthorizationEnabled; + private final AzureAdUserAuthorizationComponent azureAdUserAuthorizationComponent; + + public MeController( + TokenParsingUtils tokenParsingUtils, + @Value("${fint.flyt.azure-ad-gateway.enabled}") Boolean azureAdUserAuthorizationEnabled, + @Autowired(required = false) AzureAdUserAuthorizationComponent azureAdUserAuthorizationComponent + ) { + this.tokenParsingUtils = tokenParsingUtils; + this.azureAdUserAuthorizationEnabled = azureAdUserAuthorizationEnabled; + this.azureAdUserAuthorizationComponent = azureAdUserAuthorizationComponent; + } + + @GetMapping("is-authorized") + public ResponseEntity checkAuthorization() { + return ResponseEntity.ok("User authorized"); + } + + @GetMapping("restricted-page-authorization") + public Mono> getRestrictedPageAuthorization( + @AuthenticationPrincipal Mono authenticationMono + ) { + return (azureAdUserAuthorizationEnabled + ? tokenParsingUtils.isAdmin(authenticationMono) + .map(isAdmin -> RestrictedPageAuthorization + .builder() + .userPermissionPage(isAdmin) + .build()) + : Mono.just(RestrictedPageAuthorization.builder().build()) + ).map(ResponseEntity::ok); + } + + @GetMapping + public Mono> get( + @AuthenticationPrincipal Mono authenticationMono + ) { + 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 -> + azureAdUserAuthorizationEnabled + ? getUserFromUserAuthorizationComponent(authentication) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()) + : ResponseEntity.ok(createUserWithAccessToAllApplications(authentication)) + ); + } + }); + } + + private Optional getUserFromUserAuthorizationComponent(JwtAuthenticationToken token) { + return azureAdUserAuthorizationComponent.getUser( + UUID.fromString(tokenParsingUtils.getObjectIdentifierFromToken(token)) + ); + } + + private User createUserWithAccessToAllApplications(JwtAuthenticationToken token) { + return tokenParsingUtils.getUserFromToken(token) + .toBuilder() + .sourceApplicationIds(sourceApplicationsWithoutUserPermissionSetup()) + .build(); + } + + private List sourceApplicationsWithoutUserPermissionSetup() { + return List.of( + AcosSourceApplication.SOURCE_APPLICATION_ID, + DigisakSourceApplication.SOURCE_APPLICATION_ID, + EgrunnervervSourceApplication.SOURCE_APPLICATION_ID, + VigoSourceApplication.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 new file mode 100644 index 0000000..0a16a8d --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/UserController.java @@ -0,0 +1,72 @@ +package no.fintlabs.flyt.authorization.user.controller; + +import no.fintlabs.flyt.authorization.user.AzureAdUserAuthorizationComponent; +import no.fintlabs.flyt.authorization.user.controller.utils.TokenParsingUtils; +import no.fintlabs.flyt.authorization.user.model.User; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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; + +import static no.fintlabs.resourceserver.UrlPaths.INTERNAL_API; + +@ConditionalOnProperty(value = "fint.flyt.azure-ad-gateway.enabled", havingValue = "true") +@RestController +@RequestMapping(INTERNAL_API + "/authorization/users") +public class UserController { + + private final TokenParsingUtils tokenParsingUtils; + private final AzureAdUserAuthorizationComponent azureAdUserAuthorizationComponent; + + public UserController( + TokenParsingUtils tokenParsingUtils, + AzureAdUserAuthorizationComponent azureAdUserAuthorizationComponent + ) { + this.tokenParsingUtils = tokenParsingUtils; + this.azureAdUserAuthorizationComponent = azureAdUserAuthorizationComponent; + } + + @GetMapping + public Mono>> get( + @AuthenticationPrincipal Mono authenticationMono, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(defaultValue = "name") String sort + ) { + 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( + () -> azureAdUserAuthorizationComponent.getUsers(pageable) + ).map(ResponseEntity::ok); + }); + } + + @PostMapping("actions/userPermissionBatchPut") + public Mono> postUserPermissionBatchPutAction( + @AuthenticationPrincipal Mono authenticationMono, + @RequestBody List users + ) { + return tokenParsingUtils.isAdmin(authenticationMono) + .flatMap(isAdmin -> { + if (!isAdmin) { + return Mono.just(ResponseEntity.status(HttpStatus.FORBIDDEN).build()); + } + azureAdUserAuthorizationComponent.batchPutUserPermissions(users); + return Mono.just(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 new file mode 100644 index 0000000..55d02af --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/controller/utils/TokenParsingUtils.java @@ -0,0 +1,33 @@ +package no.fintlabs.flyt.authorization.user.controller.utils; + +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.Map; +import java.util.UUID; + +@Service +public class TokenParsingUtils { + public String getObjectIdentifierFromToken(JwtAuthenticationToken jwtAuthenticationToken) { + return jwtAuthenticationToken.getTokenAttributes().get("objectidentifier").toString(); + } + + public User getUserFromToken(JwtAuthenticationToken jwtAuthenticationToken) { + Map tokenAttributes = jwtAuthenticationToken.getTokenAttributes(); + return User + .builder() + .objectIdentifier(UUID.fromString(tokenAttributes.get("objectidentifier").toString())) + .name(tokenAttributes.get("displayname").toString()) + .email(tokenAttributes.get("email").toString()) + .build(); + } + + public Mono isAdmin(Mono authenticationMono) { + return authenticationMono + .map(authentication -> authentication != null && authentication.getAuthorities().stream() + .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals("ROLE_ADMIN"))); + } +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/model/RestrictedPageAuthorization.java b/src/main/java/no/fintlabs/flyt/authorization/user/model/RestrictedPageAuthorization.java new file mode 100644 index 0000000..4ca53e2 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/model/RestrictedPageAuthorization.java @@ -0,0 +1,10 @@ +package no.fintlabs.flyt.authorization.user.model; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class RestrictedPageAuthorization { + private boolean userPermissionPage; +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/model/User.java b/src/main/java/no/fintlabs/flyt/authorization/user/model/User.java new file mode 100644 index 0000000..a477dc4 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/model/User.java @@ -0,0 +1,28 @@ +package no.fintlabs.flyt.authorization.user.model; + +import com.sun.istack.NotNull; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@Builder(toBuilder = true) +@EqualsAndHashCode +@Jacksonized +public class User { + @NotNull + private UUID objectIdentifier; + + private String email; + + private String name; + + @NotNull + @Builder.Default + private List sourceApplicationIds = new ArrayList<>(); +} diff --git a/src/main/java/no/fintlabs/flyt/authorization/user/model/UserEntity.java b/src/main/java/no/fintlabs/flyt/authorization/user/model/UserEntity.java new file mode 100644 index 0000000..63a1986 --- /dev/null +++ b/src/main/java/no/fintlabs/flyt/authorization/user/model/UserEntity.java @@ -0,0 +1,45 @@ +package no.fintlabs.flyt.authorization.user.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.*; +import org.hibernate.annotations.NaturalId; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Getter +@Builder +@Entity +@NoArgsConstructor +@AllArgsConstructor +public class UserEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @JsonIgnore + @Setter(AccessLevel.NONE) + private long id; + + @Setter + @NaturalId + @Column(nullable = false, unique = true) + private UUID objectIdentifier; + + @Setter + private String email; + + @Setter + private String name; + + @ElementCollection + @CollectionTable( + name = "user_entity_source_application_ids", + joinColumns = @JoinColumn(name = "user_entity_id"), + uniqueConstraints = @UniqueConstraint(columnNames = {"user_entity_id", "source_application_ids"}) + ) + @Setter + @Column(name = "source_application_ids") + @Builder.Default + private List sourceApplicationIds = new ArrayList<>(); +} diff --git a/src/main/resources/application-flyt-azure.yaml b/src/main/resources/application-flyt-azure.yaml new file mode 100644 index 0000000..869c93a --- /dev/null +++ b/src/main/resources/application-flyt-azure.yaml @@ -0,0 +1,30 @@ +azure: + credentials: + clientid: ${fint.flyt.azure.client-id:null} + clientsecret: ${fint.flyt.azure.client-secret:null} + tenantid: ${fint.flyt.azure.tenant-id:null} + appid: ${fint.flyt.azure.app-id:null} + +spring: + security: + oauth2: + client: + registration: + azure: + client-id: ${azure.credentials.clientid} + client-secret: ${azure.credentials.clientsecret} + authorization-grant-type: client_credentials + scope: "https://graph.microsoft.com/.default" + provider: + azure: + authorization-uri: "https://login.microsoftonline.com/${azure.credentials.tenantid}/oauth2/v2.0/authorize" + token-uri: "https://login.microsoftonline.com/${azure.credentials.tenantid}/oauth2/v2.0/token" + +fint: + flyt: + azure-ad-gateway: + permitted-approles: + flyt-user: "https://role-catalog.vigoiks.no/vigo/flyt/user" + sync-schedule: + initial-delay-ms: 1000 + fixed-delay-ms: 900000 \ No newline at end of file diff --git a/src/main/resources/application-local-staging.yaml b/src/main/resources/application-local-staging.yaml index d1bcec0..fae5dbe 100644 --- a/src/main/resources/application-local-staging.yaml +++ b/src/main/resources/application-local-staging.yaml @@ -1,6 +1,8 @@ fint: org-id: fintlabs.no flyt: + azure-ad-gateway: + enabled: true resource-server: security: api: @@ -20,4 +22,9 @@ spring: username: postgres password: password server: - port: 8086 \ No newline at end of file + port: 8086 + +logging: + level: + com.microsoft.aad.msal4j: warn + com.azure.identity: warn diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4cfb947..1d1f680 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -3,6 +3,7 @@ fint: spring: profiles: include: + - flyt-azure - flyt-kafka - flyt-logging - flyt-postgres diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql index ed6d580..ec97888 100644 --- a/src/main/resources/db/migration/V1__init.sql +++ b/src/main/resources/db/migration/V1__init.sql @@ -1,17 +1,19 @@ -create table user_permission_source_application_ids +create table user_entity_source_application_ids ( - user_permission_id int8 not null, - source_application_ids int4 + user_entity_id int8 not null, + source_application_ids int8 ); -create table user_permission +create table user_entity ( - id bigserial not null, - object_identifier varchar(255) not null, + id bigserial not null, + email varchar(255), + name varchar(255), + object_identifier uuid not null, primary key (id) ); -alter table user_permission_source_application_ids - add constraint UK5w20jnmqeej62rhqsgyjahown unique (user_permission_id, source_application_ids); -alter table user_permission - add constraint UK_3j9pvu47o30sbeqolxqgjsnj unique (object_identifier); -alter table user_permission_source_application_ids - add constraint FKjpup6xop1xa25bcs8333uhsm8 foreign key (user_permission_id) references user_permission; +alter table user_entity_source_application_ids + add constraint UKo3tx39i7fw3q0sr3f0rjp679f unique (user_entity_id, source_application_ids); +alter table user_entity + add constraint UK_td2dvdf4t2le4cydfk7a1x17i unique (object_identifier); +alter table user_entity_source_application_ids + add constraint FKisf0n8x0vsfohl0wkenll537s foreign key (user_entity_id) references user_entity; diff --git a/src/test/java/no/fintlabs/ClientAuthorizationProducerRecordBuilderTest.java b/src/test/java/no/fintlabs/ClientAuthorizationProducerRecordBuilderTest.java index cab37e0..9ca583e 100644 --- a/src/test/java/no/fintlabs/ClientAuthorizationProducerRecordBuilderTest.java +++ b/src/test/java/no/fintlabs/ClientAuthorizationProducerRecordBuilderTest.java @@ -1,12 +1,15 @@ package no.fintlabs; +import no.fintlabs.flyt.authorization.client.ClientAuthorization; +import no.fintlabs.flyt.authorization.client.ClientAuthorizationProducerRecordBuilder; +import no.fintlabs.flyt.authorization.client.sourceapplications.AcosSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.DigisakSourceApplication; +import no.fintlabs.flyt.authorization.client.sourceapplications.EgrunnervervSourceApplication; import no.fintlabs.kafka.requestreply.ReplyProducerRecord; -import no.fintlabs.models.sourceapplication.AcosSourceApplication; -import no.fintlabs.models.sourceapplication.EgrunnervervSourceApplication; -import no.fintlabs.models.sourceapplication.DigisakSourceApplication; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.*; class ClientAuthorizationProducerRecordBuilderTest { @@ -21,7 +24,7 @@ void setUp() { @Test void testApply_AcosSourceApplicationClientId() { String clientId = "acosClientId"; - String sourceAppId = "1"; + long sourceAppId = 1L; AcosSourceApplication.CLIENT_ID = clientId; ConsumerRecord record = new ConsumerRecord<>("topic", 0, 0, "", clientId); @@ -35,7 +38,7 @@ void testApply_AcosSourceApplicationClientId() { @Test void testApply_EgrunnervervSourceApplicationClientId() { String clientId = "egrunnervervClientId"; - String sourceAppId = "2"; + long sourceAppId = 2L; EgrunnervervSourceApplication.CLIENT_ID = clientId; ConsumerRecord record = new ConsumerRecord<>("topic", 0, 0, "", clientId); @@ -49,7 +52,7 @@ void testApply_EgrunnervervSourceApplicationClientId() { @Test void testApply_DigisakSourceApplicationClientId() { String clientId = "digisakClientId"; - String sourceAppId = "3"; + long sourceAppId = 3L; DigisakSourceApplication.CLIENT_ID = clientId; ConsumerRecord record = new ConsumerRecord<>("topic", 0, 0, "", clientId);