Skip to content

Commit

Permalink
feat: protect endpoints and conditional configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter committed Dec 4, 2024
1 parent 924f57c commit c11e9a2
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 233 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import java.util.*
Expand All @@ -13,6 +14,7 @@ import java.util.*
@ConfigurationPropertiesScan
@EnableScheduling
@EnableJpaRepositories
@EnableMethodSecurity
class Application

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.aamdigital.aambackendservice.security

import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import org.springframework.stereotype.Component

@Component
class AamAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
override fun convert(source: Jwt): AbstractAuthenticationToken {
return JwtAuthenticationToken(source, getClientAuthorities(source))
}

private fun getClientAuthorities(jwt: Jwt): Collection<GrantedAuthority> {
val realmAccessClaim = jwt.getClaimAsMap("realm_access") ?: return emptyList()

val roles: List<String> = if (realmAccessClaim.containsKey("roles")) {
when (val rolesClaim = realmAccessClaim["roles"]) {
is List<*> -> rolesClaim.filterIsInstance<String>()
else -> emptyList()
}
} else {
emptyList()
}

return roles
.filter { it.startsWith("aam_") }
.map {
SimpleGrantedAuthority("ROLE_$it")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import org.springframework.security.web.SecurityFilterChain
class SecurityConfiguration {

@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
fun filterChain(
http: HttpSecurity,
aamAuthenticationConverter: AamAuthenticationConverter,
): SecurityFilterChain {
http {
authorizeRequests {
authorize(HttpMethod.GET, "/", permitAll)
Expand Down Expand Up @@ -45,6 +48,7 @@ class SecurityConfiguration {
}
oauth2ResourceServer {
jwt {
jwtAuthenticationConverter = aamAuthenticationConverter
authenticationEntryPoint =
AamAuthenticationEntryPoint(
parentEntryPoint = BearerTokenAuthenticationEntryPoint(),
Expand All @@ -55,7 +59,4 @@ class SecurityConfiguration {
}
return http.build()
}


}

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import com.aamdigital.aambackendservice.skill.core.FetchUserProfileUpdatesReques
import com.aamdigital.aambackendservice.skill.core.FetchUserProfileUpdatesUseCase
import com.aamdigital.aambackendservice.skill.repository.SkillLabUserProfileSyncRepository
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
Expand All @@ -27,15 +29,21 @@ enum class SyncModeDto {

@RestController
@RequestMapping("/v1/skill")
@ConditionalOnProperty(
prefix = "features.skill-api",
name = ["mode"],
havingValue = "skilllab",
matchIfMissing = false
)
class SkillAdminController(
private val skillLabFetchUserProfileUpdatesUseCase: FetchUserProfileUpdatesUseCase,
private val skillLabUserProfileSyncRepository: SkillLabUserProfileSyncRepository,
) {

private val logger = LoggerFactory.getLogger(javaClass)


@GetMapping("/sync")
@PreAuthorize("hasAuthority('ROLE_aam_skill_admin')")
fun fetchSyncStatus(): ResponseEntity<List<SkillDto>> {
val result = skillLabUserProfileSyncRepository.findAll().mapNotNull {
SkillDto(
Expand All @@ -48,6 +56,7 @@ class SkillAdminController(
}

@PostMapping("/sync/{projectId}")
@PreAuthorize("hasAuthority('ROLE_aam_skill_admin')")
fun triggerSync(
@PathVariable projectId: String,
syncMode: SyncModeDto = SyncModeDto.DELTA,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,36 @@ package com.aamdigital.aambackendservice.skill.controller

import com.aamdigital.aambackendservice.domain.UseCaseOutcome
import com.aamdigital.aambackendservice.error.HttpErrorDto
import com.aamdigital.aambackendservice.skill.core.FetchUserProfileUpdatesRequest
import com.aamdigital.aambackendservice.skill.core.FetchUserProfileUpdatesUseCase
import com.aamdigital.aambackendservice.skill.core.SearchUserProfileRequest
import com.aamdigital.aambackendservice.skill.core.SearchUserProfileUseCase
import com.aamdigital.aambackendservice.skill.domain.EscoSkill
import com.aamdigital.aambackendservice.skill.domain.SkillUsage
import com.aamdigital.aambackendservice.skill.domain.UserProfile
import com.aamdigital.aambackendservice.skill.repository.SkillLabUserProfileRepository
import com.aamdigital.aambackendservice.skill.skilllab.SkillLabFetchUserProfileUpdatesErrorCode
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/v1/skill")
@ConditionalOnProperty(
prefix = "features.skill-api",
name = ["mode"],
havingValue = "skilllab",
matchIfMissing = false
)
class SkillController(
private val fetchUserProfileUpdatesUseCase: FetchUserProfileUpdatesUseCase, // todo needs no-op implementation
private val searchUserProfileUseCase: SearchUserProfileUseCase, // todo needs no-op implementation
private val userProfileRepository: SkillLabUserProfileRepository
private val searchUserProfileUseCase: SearchUserProfileUseCase,
private val userProfileRepository: SkillLabUserProfileRepository,
) {

@GetMapping("/user-profile")
@PreAuthorize("hasAnyAuthority('ROLE_aam_skill_reader')")
fun fetchUserProfiles(
fullName: String = "",
email: String = "",
Expand All @@ -45,14 +48,6 @@ class SkillController(
return when (result) {
is UseCaseOutcome.Failure<*> -> {
when (result.errorCode) {
// SkillLabFetchUserProfileUpdatesErrorCode.EXTERNAL_SYSTEM_ERROR
// -> ResponseEntity.internalServerError().body(
// HttpErrorDto(
// errorCode = result.errorCode.toString(),
// errorMessage = result.errorMessage
// )
// )

else -> ResponseEntity.badRequest().body(
ResponseEntity.internalServerError().body(
HttpErrorDto(
Expand All @@ -72,6 +67,7 @@ class SkillController(
}

@GetMapping("/user-profile/{id}")
@PreAuthorize("hasAuthority('ROLE_aam_skill_reader')")
fun fetchUserProfile(
@PathVariable id: String,
): ResponseEntity<Any> {
Expand Down Expand Up @@ -103,41 +99,4 @@ class SkillController(
)
}
}

@PostMapping
fun fetchUserProfileUpdates(
@RequestBody request: FetchUserProfileUpdatesRequest,
): ResponseEntity<Any> {
val result = fetchUserProfileUpdatesUseCase.run(
request = request
)

return when (result) {
is UseCaseOutcome.Failure<*> -> {
when (result.errorCode) {
SkillLabFetchUserProfileUpdatesErrorCode.EXTERNAL_SYSTEM_ERROR
-> ResponseEntity.internalServerError().body(
HttpErrorDto(
errorCode = result.errorCode.toString(),
errorMessage = result.errorMessage
)
)

else -> ResponseEntity.badRequest().body(
ResponseEntity.internalServerError().body(
HttpErrorDto(
errorCode = result.errorCode.toString(),
errorMessage = result.errorMessage
)
)
)
}
ResponseEntity.badRequest().body(
result.errorMessage
)
}

is UseCaseOutcome.Success<*> -> ResponseEntity.ok().body(result.data)
}
}
}

This file was deleted.

Loading

0 comments on commit c11e9a2

Please sign in to comment.