-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
authentication + authorization backend
- Loading branch information
1 parent
3c4589b
commit b692bef
Showing
56 changed files
with
1,114 additions
and
80 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/OIDCProperties.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package fr.gouv.cacem.monitorenv.config | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
@ConfigurationProperties(prefix = "monitorenv.oidc") | ||
class OIDCProperties { | ||
var enabled: Boolean? = false | ||
var userinfoEndpoint: String? = null | ||
var issuerUri: String? = null | ||
} |
13 changes: 13 additions & 0 deletions
13
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/ProtectedPathsAPIProperties.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package fr.gouv.cacem.monitorenv.config | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
@ConfigurationProperties(prefix = "monitorenv.api.protected") | ||
class ProtectedPathsAPIProperties { | ||
var paths: List<String>? = listOf() | ||
var superUserPaths: List<String>? = listOf() | ||
var publicPaths: List<String>? = listOf() | ||
var apiKey: String = "" | ||
} |
89 changes: 89 additions & 0 deletions
89
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SecurityConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package fr.gouv.cacem.monitorenv.config | ||
|
||
import fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.log.CustomAuthenticationEntryPoint | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
import org.springframework.security.config.Customizer | ||
import org.springframework.security.config.annotation.web.builders.HttpSecurity | ||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity | ||
import org.springframework.security.web.SecurityFilterChain | ||
import org.springframework.web.cors.CorsConfiguration | ||
import org.springframework.web.cors.CorsConfigurationSource | ||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource | ||
|
||
@Configuration | ||
@EnableWebSecurity | ||
class SecurityConfig( | ||
val oidcProperties: OIDCProperties, | ||
val authenticationEntryPoint: CustomAuthenticationEntryPoint, | ||
) { | ||
private val logger: Logger = LoggerFactory.getLogger(SecurityConfig::class.java) | ||
|
||
@Bean | ||
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { | ||
http | ||
.csrf { it.disable() } | ||
.authorizeHttpRequests { authorize -> | ||
if (oidcProperties.enabled == null || oidcProperties.enabled == false) { | ||
logger.warn( | ||
""" | ||
⚠️ WARNING ⚠️ - OIDC Authentication is NOT enabled. | ||
""".trimIndent(), | ||
) | ||
|
||
authorize.requestMatchers("/**").permitAll() | ||
} else { | ||
logger.warn( | ||
""" | ||
✅ OIDC Authentication is enabled. | ||
""".trimIndent(), | ||
) | ||
|
||
authorize.requestMatchers( | ||
"/", | ||
"/index.html", | ||
"/*.js", | ||
"/*.png", | ||
"/*.svg", | ||
"/static/**", | ||
"/assets/**", | ||
"/map-icons/**", | ||
"/flags/**", | ||
"/robots.txt", | ||
"/favicon-32.ico", | ||
"/asset-manifest.json", | ||
"/swagger-ui/**", | ||
// Used to redirect to the frontend SPA, see SpaController.kt | ||
"/error", | ||
"/api/**", | ||
"/version", | ||
).permitAll() | ||
.anyRequest() | ||
.authenticated() | ||
} | ||
}.oauth2ResourceServer { | ||
oauth2ResourceServer -> | ||
oauth2ResourceServer | ||
.jwt(Customizer.withDefaults()) | ||
.authenticationEntryPoint(authenticationEntryPoint) | ||
} | ||
|
||
return http.build() | ||
} | ||
|
||
@Bean | ||
fun corsConfigurationSource(): CorsConfigurationSource { | ||
val configuration = CorsConfiguration().apply { | ||
allowedOrigins = listOf("*") | ||
allowedMethods = listOf("HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS") | ||
allowedHeaders = listOf("Authorization", "Cache-Control", "Content-Type") | ||
} | ||
|
||
val source = UrlBasedCorsConfigurationSource() | ||
source.registerCorsConfiguration("/**", configuration) | ||
|
||
return source | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/SuperUserAPIProperties.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package fr.gouv.cacem.monitorenv.config | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
@ConfigurationProperties(prefix = "monitorenv.api.super-user") | ||
data class SuperUserAPIProperties( | ||
var paths: List<String>? = listOf(), | ||
) |
39 changes: 0 additions & 39 deletions
39
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/config/WebSecurityConfig.kt
This file was deleted.
Oops, something went wrong.
6 changes: 6 additions & 0 deletions
6
...c/main/kotlin/fr/gouv/cacem/monitorenv/domain/entities/authorization/UserAuthorization.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package fr.gouv.cacem.monitorenv.domain.entities.authorization | ||
|
||
data class UserAuthorization( | ||
val hashedEmail: String, | ||
val isSuperUser: Boolean, | ||
) |
9 changes: 9 additions & 0 deletions
9
.../main/kotlin/fr/gouv/cacem/monitorenv/domain/repositories/IUserAuthorizationRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package fr.gouv.cacem.monitorenv.domain.repositories | ||
|
||
import fr.gouv.cacem.monitorenv.domain.entities.authorization.UserAuthorization | ||
|
||
interface IUserAuthorizationRepository { | ||
fun findByHashedEmail(hashedEmail: String): UserAuthorization | ||
fun save(user: UserAuthorization) | ||
fun delete(hashedEmail: String) | ||
} |
16 changes: 16 additions & 0 deletions
16
...end/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/authorization/DeleteUser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package fr.gouv.cacem.monitorenv.domain.use_cases.authorization | ||
|
||
import fr.gouv.cacem.monitorenv.config.UseCase | ||
import fr.gouv.cacem.monitorenv.domain.hash | ||
import fr.gouv.cacem.monitorenv.domain.repositories.IUserAuthorizationRepository | ||
|
||
@UseCase | ||
class DeleteUser( | ||
private val userAuthorizationRepository: IUserAuthorizationRepository, | ||
) { | ||
fun execute(email: String) { | ||
val hashedEmail = hash(email) | ||
|
||
userAuthorizationRepository.delete(hashedEmail) | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
.../main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/authorization/GetAuthorizedUser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package fr.gouv.cacem.monitorenv.domain.use_cases.authorization | ||
|
||
import fr.gouv.cacem.monitorenv.config.UseCase | ||
import fr.gouv.cacem.monitorenv.domain.entities.authorization.UserAuthorization | ||
import fr.gouv.cacem.monitorenv.domain.hash | ||
import fr.gouv.cacem.monitorenv.domain.repositories.IUserAuthorizationRepository | ||
import org.slf4j.LoggerFactory | ||
|
||
@UseCase | ||
class GetAuthorizedUser( | ||
private val userAuthorizationRepository: IUserAuthorizationRepository, | ||
) { | ||
private val logger = LoggerFactory.getLogger(GetAuthorizedUser::class.java) | ||
|
||
fun execute(email: String): UserAuthorization { | ||
val hashedEmail = hash(email) | ||
|
||
return try { | ||
userAuthorizationRepository.findByHashedEmail(hashedEmail) | ||
} catch (e: Throwable) { | ||
logger.info("User $hashedEmail not found, defaulting to super-user=false") | ||
|
||
// By default, a user not found is not super-user | ||
UserAuthorization( | ||
hashedEmail = hashedEmail, | ||
isSuperUser = false, | ||
) | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
...ain/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/authorization/GetIsAuthorizedUser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package fr.gouv.cacem.monitorenv.domain.use_cases.authorization | ||
|
||
import fr.gouv.cacem.monitorenv.config.UseCase | ||
import fr.gouv.cacem.monitorenv.domain.hash | ||
import fr.gouv.cacem.monitorenv.domain.repositories.IUserAuthorizationRepository | ||
import org.slf4j.LoggerFactory | ||
|
||
@UseCase | ||
class GetIsAuthorizedUser( | ||
private val userAuthorizationRepository: IUserAuthorizationRepository, | ||
) { | ||
private val logger = LoggerFactory.getLogger(GetIsAuthorizedUser::class.java) | ||
|
||
fun execute(email: String, isSuperUserPath: Boolean): Boolean { | ||
/** | ||
* If the path is not super-user protected, authorize any logged user | ||
*/ | ||
if (!isSuperUserPath) { | ||
return true | ||
} | ||
|
||
val hashedEmail = hash(email) | ||
|
||
val userAuthorization = try { | ||
userAuthorizationRepository.findByHashedEmail(hashedEmail) | ||
} catch (e: Throwable) { | ||
/** | ||
* If the user is not found in the `UserAuthorizationRepository` and the path | ||
* is super-user protected, reject | ||
*/ | ||
return false | ||
} | ||
|
||
return userAuthorization.isSuperUser | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/use_cases/authorization/SaveUser.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package fr.gouv.cacem.monitorenv.domain.use_cases.authorization | ||
|
||
import fr.gouv.cacem.monitorenv.config.UseCase | ||
import fr.gouv.cacem.monitorenv.domain.entities.authorization.UserAuthorization | ||
import fr.gouv.cacem.monitorenv.domain.hash | ||
import fr.gouv.cacem.monitorenv.domain.repositories.IUserAuthorizationRepository | ||
|
||
@UseCase | ||
class SaveUser( | ||
private val userAuthorizationRepository: IUserAuthorizationRepository, | ||
) { | ||
fun execute(email: String, isSuperUser: Boolean) { | ||
val user = UserAuthorization( | ||
hashedEmail = hash(email), | ||
isSuperUser = isSuperUser, | ||
) | ||
|
||
userAuthorizationRepository.save(user) | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
backend/src/main/kotlin/fr/gouv/cacem/monitorenv/domain/utils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package fr.gouv.cacem.monitorenv.domain | ||
|
||
import java.security.MessageDigest | ||
|
||
fun hash(toHash: String) = MessageDigest | ||
.getInstance("SHA-256") | ||
.digest(toHash.toByteArray()) | ||
.fold("") { str, it -> str + "%02x".format(it) } |
15 changes: 15 additions & 0 deletions
15
...v/cacem/monitorenv/infrastructure/api/adapters/bff/outputs/UserAuthorizationDataOutput.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs | ||
|
||
import fr.gouv.cacem.monitorenv.domain.entities.authorization.UserAuthorization | ||
|
||
data class UserAuthorizationDataOutput( | ||
val isSuperUser: Boolean, | ||
) { | ||
companion object { | ||
fun fromUserAuthorization(userAuthorization: UserAuthorization): UserAuthorizationDataOutput { | ||
return UserAuthorizationDataOutput( | ||
isSuperUser = userAuthorization.isSuperUser, | ||
) | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...fr/gouv/cacem/monitorenv/infrastructure/api/adapters/publicapi/inputs/AddUserDataInput.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package fr.gouv.cacem.monitorenv.infrastructure.api.adapters.publicapi.inputs | ||
|
||
data class AddUserDataInput( | ||
val email: String, | ||
val isSuperUser: Boolean, | ||
) |
6 changes: 6 additions & 0 deletions
6
...rc/main/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/FilterPrecedences.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints | ||
|
||
const val CORRELATION_ID_PRECEDENCE = -1 | ||
const val USER_AUTH_FILTER_PRECEDENCE = 1 | ||
const val API_KEY_FILTER_PRECEDENCE = 2 | ||
const val LOG_REQUEST_PRECEDENCE = 0 |
52 changes: 52 additions & 0 deletions
52
...ain/kotlin/fr/gouv/cacem/monitorenv/infrastructure/api/endpoints/bff/UserAuthorization.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.bff | ||
|
||
import fr.gouv.cacem.monitorenv.domain.hash | ||
import fr.gouv.cacem.monitorenv.domain.use_cases.authorization.GetAuthorizedUser | ||
import fr.gouv.cacem.monitorenv.infrastructure.api.adapters.bff.outputs.UserAuthorizationDataOutput | ||
import fr.gouv.cacem.monitorenv.infrastructure.api.endpoints.security.UserAuthorizationCheckFilter | ||
import io.swagger.v3.oas.annotations.Operation | ||
import io.swagger.v3.oas.annotations.tags.Tag | ||
import jakarta.servlet.http.HttpServletRequest | ||
import jakarta.servlet.http.HttpServletResponse | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.web.bind.annotation.GetMapping | ||
import org.springframework.web.bind.annotation.RequestMapping | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestController | ||
@RequestMapping("/bff/authorization") | ||
@Tag(name = "APIs for authorization") | ||
class UserAuthorization( | ||
private val getAuthorizedUser: GetAuthorizedUser, | ||
) { | ||
private val logger = LoggerFactory.getLogger(UserAuthorization::class.java) | ||
|
||
/** | ||
* This controller will | ||
* - return 200 with the UserAuthorization object if the user authorization is found | ||
* (it passes the filter `UserAuthorizationCheckFilter` - the endpoint is not super-user protected) | ||
* - return an 200 with `isSuperUser=false` if the user authorization is not found | ||
*/ | ||
@GetMapping("current") | ||
@Operation(summary = "Get current logged user authorization") | ||
fun getCurrentUserAuthorization( | ||
request: HttpServletRequest, | ||
response: HttpServletResponse, | ||
): UserAuthorizationDataOutput? { | ||
val email: String? = response.getHeader(UserAuthorizationCheckFilter.EMAIL_HEADER) | ||
if (email == null) { | ||
logger.error("Email not found. Rejecting authentication.") | ||
|
||
response.status = HttpServletResponse.SC_UNAUTHORIZED | ||
|
||
return null | ||
} | ||
|
||
val authorizedUser = getAuthorizedUser.execute(email) | ||
|
||
// The email is hashed as we don't want to have a clear email in the header | ||
response.setHeader(UserAuthorizationCheckFilter.EMAIL_HEADER, hash(email)) | ||
|
||
return UserAuthorizationDataOutput.fromUserAuthorization(authorizedUser) | ||
} | ||
} |
Oops, something went wrong.