Skip to content

Commit

Permalink
Fixed basic auth in gateway (#2512)
Browse files Browse the repository at this point in the history
- backend sends token to gateway
- renamed `AuthenticationUserDetails` to `SaveUserDetails`
  • Loading branch information
nulls authored Aug 30, 2023
1 parent bd704e7 commit ebded6f
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.saveourtool.save.gateway.service

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import com.saveourtool.save.entities.User
import com.saveourtool.save.gateway.config.ConfigurationProperties
import com.saveourtool.save.utils.orNotFound
Expand Down Expand Up @@ -29,7 +29,7 @@ class BackendService(
*/
fun findByName(
username: String,
): Mono<AuthenticationUserDetails> = findAuthenticationUserDetails("/internal/users/find-by-name/$username")
): Mono<SaveUserDetails> = findAuthenticationUserDetails("/internal/users/find-by-name/$username")

/**
* @param source
Expand All @@ -39,15 +39,15 @@ class BackendService(
fun findByOriginalLogin(
source: String,
nameInSource: String,
): Mono<AuthenticationUserDetails> = findAuthenticationUserDetails("/internal/users/find-by-original-login/$source/$nameInSource")
): Mono<SaveUserDetails> = findAuthenticationUserDetails("/internal/users/find-by-original-login/$source/$nameInSource")

private fun findAuthenticationUserDetails(uri: String): Mono<AuthenticationUserDetails> = webClient.get()
private fun findAuthenticationUserDetails(uri: String): Mono<SaveUserDetails> = webClient.get()
.uri(uri)
.retrieve()
.onStatus({ it.is4xxClientError }) {
Mono.error(ResponseStatusException(it.statusCode()))
}
.toEntity<AuthenticationUserDetails>()
.toEntity<SaveUserDetails>()
.flatMap { responseEntity ->
responseEntity.body.toMono().orNotFound { "Authentication body is empty" }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.saveourtool.save.gateway.utils

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import com.saveourtool.save.gateway.service.BackendService
import com.saveourtool.save.utils.switchIfEmptyToResponseException
import org.springframework.cloud.gateway.filter.GatewayFilter
Expand Down Expand Up @@ -43,9 +43,9 @@ class AuthorizationHeadersGatewayFilterFactory(
.flatMap { chain.filter(it) }
}

private fun resolveSaveUser(principal: Principal): Mono<AuthenticationUserDetails> = when (principal) {
private fun resolveSaveUser(principal: Principal): Mono<SaveUserDetails> = when (principal) {
is OAuth2AuthenticationToken -> backendService.findByOriginalLogin(principal.authorizedClientRegistrationId, principal.name)
is UsernamePasswordAuthenticationToken -> (principal.principal as? AuthenticationUserDetails)
is UsernamePasswordAuthenticationToken -> (principal.principal as? SaveUserDetails)
.toMono()
.switchIfEmptyToResponseException(HttpStatus.INTERNAL_SERVER_ERROR) {
"Unexpected principal type ${principal.principal.javaClass} in ${UsernamePasswordAuthenticationToken::class}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

package com.saveourtool.save.authservice.config

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails.Companion.toAuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails.Companion.toSaveUserDetails
import com.saveourtool.save.authservice.utils.roleHierarchy
import com.saveourtool.save.v1

Expand Down Expand Up @@ -40,7 +40,7 @@ class WebSecurityConfig(
return AuthenticationWebFilter(authenticationManager)
.also { authenticationWebFilter ->
authenticationWebFilter.setServerAuthenticationConverter { exchange ->
exchange.request.headers.toAuthenticationUserDetails()?.toAuthenticationToken().toMono()
exchange.request.headers.toSaveUserDetails()?.toPreAuthenticatedAuthenticationToken().toMono()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.saveourtool.save.utils.*

import com.fasterxml.jackson.annotation.JsonIgnore
import org.springframework.http.HttpHeaders
import org.springframework.security.core.CredentialsContainer
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.userdetails.UserDetails
Expand All @@ -14,18 +15,26 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA
* @property id [com.saveourtool.save.entities.User.id]
* @property name [com.saveourtool.save.entities.User.name]
* @property role [com.saveourtool.save.entities.User.role]
* @property token [com.saveourtool.save.entities.User.password]
*/
data class AuthenticationUserDetails(
class SaveUserDetails(
val id: Long,
val name: String,
val role: String,
) : UserDetails {
constructor(user: User) : this(user.requiredId(), user.name, user.role.orEmpty())
var token: String?,
) : UserDetails, CredentialsContainer {
constructor(user: User) : this(
user.requiredId(),
user.name,
user.role.orEmpty(),
user.password,
)

/**
* @return [PreAuthenticatedAuthenticationToken]
*/
fun toAuthenticationToken() = PreAuthenticatedAuthenticationToken(this, NO_CREDENTIALS, authorities)
fun toPreAuthenticatedAuthenticationToken() =
PreAuthenticatedAuthenticationToken(this, null, AuthorityUtils.commaSeparatedStringToAuthorityList(role))

/**
* Populates `X-Authorization-*` headers
Expand All @@ -42,36 +51,40 @@ data class AuthenticationUserDetails(
override fun getAuthorities(): MutableCollection<out GrantedAuthority> = AuthorityUtils.commaSeparatedStringToAuthorityList(role)

@JsonIgnore
override fun getPassword(): String = NO_CREDENTIALS
override fun getPassword(): String? = token

@JsonIgnore
override fun getUsername(): String = name

@JsonIgnore
override fun isAccountNonExpired(): Boolean = false
override fun isAccountNonExpired(): Boolean = true

@JsonIgnore
override fun isAccountNonLocked(): Boolean = false
override fun isAccountNonLocked(): Boolean = true

@JsonIgnore
override fun isCredentialsNonExpired(): Boolean = false
override fun isCredentialsNonExpired(): Boolean = true

@JsonIgnore
override fun isEnabled(): Boolean = true

override fun eraseCredentials() {
token = null
}

companion object {
@Suppress("GENERIC_VARIABLE_WRONG_DECLARATION")
private val log = getLogger<AuthenticationUserDetails>()
private const val NO_CREDENTIALS = "N/A"
private val log = getLogger<SaveUserDetails>()

/**
* @return [AuthenticationUserDetails] created from values in headers
* @return [SaveUserDetails] created from values in headers
*/
fun HttpHeaders.toAuthenticationUserDetails(): AuthenticationUserDetails? {
return AuthenticationUserDetails(
fun HttpHeaders.toSaveUserDetails(): SaveUserDetails? {
return SaveUserDetails(
id = getSingleHeader(AUTHORIZATION_ID)?.toLong() ?: return logWarnAndReturnEmpty(AUTHORIZATION_ID),
name = getSingleHeader(AUTHORIZATION_NAME) ?: return logWarnAndReturnEmpty(AUTHORIZATION_NAME),
role = getSingleHeader(AUTHORIZATION_ROLES) ?: return logWarnAndReturnEmpty(AUTHORIZATION_ROLES),
token = null,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@ import org.springframework.security.core.Authentication

/**
* Extract userId from this [Authentication]
* We assume that the authentication uses [AuthenticationUserDetails] as principal
* We assume that the authentication uses [SaveUserDetails] as principal
*
* @return userId
*/
fun Authentication.userId() = (principal as AuthenticationUserDetails).id
fun Authentication.userId() = (principal as SaveUserDetails).id

/**
* Extract username from this [Authentication].
* We assume that the authentication uses [AuthenticationUserDetails] as principal
* We assume that the authentication uses [SaveUserDetails] as principal
*
* @return username
*/
fun Authentication.username(): String = (principal as AuthenticationUserDetails).name
fun Authentication.username(): String = (principal as SaveUserDetails).name

/**
* Set role hierarchy for spring security
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.saveourtool.save.authservice.utils

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails.Companion.toAuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails.Companion.toSaveUserDetails
import com.saveourtool.save.utils.AUTHORIZATION_ID
import com.saveourtool.save.utils.AUTHORIZATION_NAME
import com.saveourtool.save.utils.AUTHORIZATION_ROLES
Expand All @@ -9,14 +9,14 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.springframework.http.HttpHeaders

class AuthenticationUserDetailsTest {
class SaveUserDetailsTest {
@Test
fun toAuthenticationUserDetailsValid() {
fun toSaveUserDetailsValid() {
val httpHeaders = HttpHeaders()
httpHeaders[AUTHORIZATION_ID] = "123"
httpHeaders[AUTHORIZATION_NAME] = "name"
httpHeaders[AUTHORIZATION_ROLES] = "ROLE"
val result = httpHeaders.toAuthenticationUserDetails()
val result = httpHeaders.toSaveUserDetails()

Assertions.assertNotNull(result)
Assertions.assertEquals(123, result?.id)
Expand All @@ -25,48 +25,53 @@ class AuthenticationUserDetailsTest {
}

@Test
fun toAuthenticationUserDetailsDuplicate() {
fun toSaveUserDetailsDuplicate() {
val httpHeaders = HttpHeaders()
httpHeaders[AUTHORIZATION_ID] = listOf("123", "321")
httpHeaders[AUTHORIZATION_NAME] = "name"
httpHeaders[AUTHORIZATION_ROLES] = "ROLE"

Assertions.assertNull(httpHeaders.toAuthenticationUserDetails())
Assertions.assertNull(httpHeaders.toSaveUserDetails())
}

@Test
fun toAuthenticationUserDetailsMissed() {
fun toSaveUserDetailsMissed() {
val httpHeaders = HttpHeaders()
httpHeaders[AUTHORIZATION_NAME] = "name"
httpHeaders[AUTHORIZATION_ROLES] = "ROLE"

Assertions.assertNull(httpHeaders.toAuthenticationUserDetails())
Assertions.assertNull(httpHeaders.toSaveUserDetails())
}

@Test
fun toAuthenticationUserDetailsInvalid() {
fun toSaveUserDetailsInvalid() {
val httpHeaders = HttpHeaders()
httpHeaders[AUTHORIZATION_ID] = "not_integer"
httpHeaders[AUTHORIZATION_NAME] = "name"
httpHeaders[AUTHORIZATION_ROLES] = "ROLE"

assertThrows<NumberFormatException> {
httpHeaders.toAuthenticationUserDetails()
httpHeaders.toSaveUserDetails()
}
}

@Test
fun populateHeaders() {
val authenticationUserDetails = AuthenticationUserDetails(
val SaveUserDetails = SaveUserDetails(
id = 123,
name = "name",
role = "ROLE"
role = "ROLE",
token = "N/A",
)
val httpHeaders = HttpHeaders()
authenticationUserDetails.populateHeaders(httpHeaders)
SaveUserDetails.populateHeaders(httpHeaders)

Assertions.assertEquals(listOf("123"), httpHeaders[AUTHORIZATION_ID])
Assertions.assertEquals(listOf("name"), httpHeaders[AUTHORIZATION_NAME])
Assertions.assertEquals(listOf("ROLE"), httpHeaders[AUTHORIZATION_ROLES])
Assertions.assertEquals(
setOf(AUTHORIZATION_ID, AUTHORIZATION_NAME, AUTHORIZATION_ROLES),
httpHeaders.keys,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.saveourtool.save.backend.controllers.internal

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import com.saveourtool.save.backend.repository.OriginalLoginRepository
import com.saveourtool.save.backend.service.UserDetailsService
import com.saveourtool.save.domain.Role
Expand All @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono

typealias AuthenticationUserDetailsResponse = ResponseEntity<AuthenticationUserDetails>
typealias SaveUserDetailsResponse = ResponseEntity<SaveUserDetails>

/**
* Controller that handles operation with users
Expand Down Expand Up @@ -61,8 +61,8 @@ class UsersController(
@GetMapping("/find-by-name/{userName}")
fun findByName(
@PathVariable userName: String,
): Mono<AuthenticationUserDetailsResponse> = userService.findByName(userName).map {
ResponseEntity.ok().body(AuthenticationUserDetails(it))
): Mono<SaveUserDetailsResponse> = userService.findByName(userName).map {
ResponseEntity.ok().body(SaveUserDetails(it))
}

/**
Expand All @@ -76,8 +76,8 @@ class UsersController(
fun findByOriginalLogin(
@PathVariable source: String,
@PathVariable nameInSource: String,
): Mono<AuthenticationUserDetailsResponse> = userService.findByOriginalLogin(nameInSource, source).map {
ResponseEntity.ok().body(AuthenticationUserDetails(it))
): Mono<SaveUserDetailsResponse> = userService.findByOriginalLogin(nameInSource, source).map {
ResponseEntity.ok().body(SaveUserDetails(it))
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.saveourtool.save.backend.repository.LnkUserOrganizationRepository
import com.saveourtool.save.backend.repository.UserRepository
import com.saveourtool.save.backend.service.LnkUserOrganizationService
import com.saveourtool.save.backend.service.UserDetailsService
import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import com.saveourtool.save.domain.Role
import com.saveourtool.save.entities.*
import com.saveourtool.save.permission.Permission
Expand Down Expand Up @@ -117,11 +117,12 @@ class OrganizationPermissionEvaluatorTest {
}
}

private fun mockAuth(principal: String, vararg roles: String, id: Long = 99) = AuthenticationUserDetails(
private fun mockAuth(principal: String, vararg roles: String, id: Long = 99) = SaveUserDetails(
id = id,
name = principal,
role = roles.joinToString(","),
).toAuthenticationToken()
token = null,
).toPreAuthenticatedAuthenticationToken()

private fun mockUser(id: Long) = User("mocked", null, null, "").apply { this.id = id }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.saveourtool.save.backend.repository.LnkUserProjectRepository
import com.saveourtool.save.backend.repository.UserRepository
import com.saveourtool.save.backend.service.LnkUserProjectService
import com.saveourtool.save.backend.service.UserDetailsService
import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import com.saveourtool.save.backend.service.LnkUserOrganizationService
import com.saveourtool.save.domain.Role
import com.saveourtool.save.entities.LnkUserProject
Expand Down Expand Up @@ -154,11 +154,12 @@ class ProjectPermissionEvaluatorTest {
}
}

private fun mockAuth(principal: String, vararg roles: String, id: Long = 99) = AuthenticationUserDetails(
private fun mockAuth(principal: String, vararg roles: String, id: Long = 99) = SaveUserDetails(
id = id,
name = principal,
role = roles.joinToString(","),
).toAuthenticationToken()
token = null,
).toPreAuthenticatedAuthenticationToken()

private fun mockUser(id: Long) = User("mocked", null, null, "").apply { this.id = id }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

package com.saveourtool.save.backend.utils

import com.saveourtool.save.authservice.utils.AuthenticationUserDetails
import com.saveourtool.save.authservice.utils.SaveUserDetails
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder

Expand All @@ -13,10 +13,11 @@ import org.springframework.security.core.context.SecurityContextHolder
*/
internal fun mutateMockedUser(id: Long) {
SecurityContextHolder.getContext().apply {
authentication = AuthenticationUserDetails(
id,
authentication.name,
(authentication as UsernamePasswordAuthenticationToken).authorities.joinToString(",") { it.authority }
).toAuthenticationToken()
authentication = SaveUserDetails(
id = id,
name = authentication.name,
role = (authentication as UsernamePasswordAuthenticationToken).authorities.joinToString(",") { it.authority },
token = null,
).toPreAuthenticatedAuthenticationToken()
}
}
2 changes: 2 additions & 0 deletions save-cloud-charts/save-cloud/templates/gateway-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ data:
server.shutdown=graceful
management.endpoints.web.exposure.include=*
management.server.port={{ .Values.gateway.managementPort }}
logging.level.org.springframework=DEBUG
logging.level.com.saveourtool=DEBUG
{{ if .Values.gateway.applicationProperties }}
{{- .Values.gateway.applicationProperties | nindent 4 }}
{{ end }}

0 comments on commit ebded6f

Please sign in to comment.