From c405b1d5f6f395d4392d5b1d133cf5228849bef7 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 30 Aug 2023 14:49:03 +0300 Subject: [PATCH] Fixed basic auth in gateway ### What's done: - backend sends token to gateway - renamed AuthenticationUserDetails to SaveUserDetails --- .../save/gateway/service/BackendService.kt | 10 ++--- ...uthorizationHeadersGatewayFilterFactory.kt | 6 +-- .../authservice/config/WebSecurityConfig.kt | 4 +- ...ationUserDetails.kt => SaveUserDetails.kt} | 39 ++++++++++++------- .../save/authservice/utils/SecurityUtils.kt | 8 ++-- ...rDetailsTest.kt => SaveUserDetailsTest.kt} | 31 ++++++++------- .../controllers/internal/UsersController.kt | 12 +++--- .../OrganizationPermissionEvaluatorTest.kt | 7 ++-- .../ProjectPermissionEvaluatorTest.kt | 7 ++-- .../backend/utils/SecurityTestingUtils.kt | 13 ++++--- 10 files changed, 79 insertions(+), 58 deletions(-) rename authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/{AuthenticationUserDetails.kt => SaveUserDetails.kt} (68%) rename authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/{AuthenticationUserDetailsTest.kt => SaveUserDetailsTest.kt} (69%) diff --git a/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/service/BackendService.kt b/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/service/BackendService.kt index 5e72ee7a9b..9fdf0ec986 100644 --- a/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/service/BackendService.kt +++ b/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/service/BackendService.kt @@ -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 @@ -29,7 +29,7 @@ class BackendService( */ fun findByName( username: String, - ): Mono = findAuthenticationUserDetails("/internal/users/find-by-name/$username") + ): Mono = findAuthenticationUserDetails("/internal/users/find-by-name/$username") /** * @param source @@ -39,15 +39,15 @@ class BackendService( fun findByOriginalLogin( source: String, nameInSource: String, - ): Mono = findAuthenticationUserDetails("/internal/users/find-by-original-login/$source/$nameInSource") + ): Mono = findAuthenticationUserDetails("/internal/users/find-by-original-login/$source/$nameInSource") - private fun findAuthenticationUserDetails(uri: String): Mono = webClient.get() + private fun findAuthenticationUserDetails(uri: String): Mono = webClient.get() .uri(uri) .retrieve() .onStatus({ it.is4xxClientError }) { Mono.error(ResponseStatusException(it.statusCode())) } - .toEntity() + .toEntity() .flatMap { responseEntity -> responseEntity.body.toMono().orNotFound { "Authentication body is empty" } } diff --git a/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/utils/AuthorizationHeadersGatewayFilterFactory.kt b/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/utils/AuthorizationHeadersGatewayFilterFactory.kt index dc6fb4f087..b7cfe26605 100644 --- a/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/utils/AuthorizationHeadersGatewayFilterFactory.kt +++ b/api-gateway/src/main/kotlin/com/saveourtool/save/gateway/utils/AuthorizationHeadersGatewayFilterFactory.kt @@ -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 @@ -43,9 +43,9 @@ class AuthorizationHeadersGatewayFilterFactory( .flatMap { chain.filter(it) } } - private fun resolveSaveUser(principal: Principal): Mono = when (principal) { + private fun resolveSaveUser(principal: Principal): Mono = 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}" diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt index 72b7a33b41..2b899f3aa9 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/config/WebSecurityConfig.kt @@ -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 @@ -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() } } } diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetails.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetails.kt similarity index 68% rename from authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetails.kt rename to authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetails.kt index e06fee1e31..71fc11e86a 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetails.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetails.kt @@ -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 @@ -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 @@ -42,36 +51,40 @@ data class AuthenticationUserDetails( override fun getAuthorities(): MutableCollection = 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() - private const val NO_CREDENTIALS = "N/A" + private val log = getLogger() /** - * @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, ) } diff --git a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SecurityUtils.kt b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SecurityUtils.kt index f90e1f3341..e9e657c6d6 100644 --- a/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SecurityUtils.kt +++ b/authentication-service/src/main/kotlin/com/saveourtool/save/authservice/utils/SecurityUtils.kt @@ -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 diff --git a/authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetailsTest.kt b/authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetailsTest.kt similarity index 69% rename from authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetailsTest.kt rename to authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetailsTest.kt index 67283708e1..30d7803c75 100644 --- a/authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/AuthenticationUserDetailsTest.kt +++ b/authentication-service/src/test/kotlin/com/saveourtool/save/authservice/utils/SaveUserDetailsTest.kt @@ -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 @@ -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) @@ -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 { - 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, + ) } } diff --git a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/internal/UsersController.kt b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/internal/UsersController.kt index 596d33992b..05b18e7054 100644 --- a/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/internal/UsersController.kt +++ b/save-backend/src/main/kotlin/com/saveourtool/save/backend/controllers/internal/UsersController.kt @@ -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 @@ -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 +typealias SaveUserDetailsResponse = ResponseEntity /** * Controller that handles operation with users @@ -61,8 +61,8 @@ class UsersController( @GetMapping("/find-by-name/{userName}") fun findByName( @PathVariable userName: String, - ): Mono = userService.findByName(userName).map { - ResponseEntity.ok().body(AuthenticationUserDetails(it)) + ): Mono = userService.findByName(userName).map { + ResponseEntity.ok().body(SaveUserDetails(it)) } /** @@ -76,8 +76,8 @@ class UsersController( fun findByOriginalLogin( @PathVariable source: String, @PathVariable nameInSource: String, - ): Mono = userService.findByOriginalLogin(nameInSource, source).map { - ResponseEntity.ok().body(AuthenticationUserDetails(it)) + ): Mono = userService.findByOriginalLogin(nameInSource, source).map { + ResponseEntity.ok().body(SaveUserDetails(it)) } companion object { diff --git a/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/OrganizationPermissionEvaluatorTest.kt b/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/OrganizationPermissionEvaluatorTest.kt index f1c982aef1..98d2ab152c 100644 --- a/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/OrganizationPermissionEvaluatorTest.kt +++ b/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/OrganizationPermissionEvaluatorTest.kt @@ -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 @@ -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 } } diff --git a/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/ProjectPermissionEvaluatorTest.kt b/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/ProjectPermissionEvaluatorTest.kt index 0b238e8ac0..d3104f7cc4 100644 --- a/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/ProjectPermissionEvaluatorTest.kt +++ b/save-backend/src/test/kotlin/com/saveourtool/save/backend/security/ProjectPermissionEvaluatorTest.kt @@ -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 @@ -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 } } diff --git a/save-backend/src/test/kotlin/com/saveourtool/save/backend/utils/SecurityTestingUtils.kt b/save-backend/src/test/kotlin/com/saveourtool/save/backend/utils/SecurityTestingUtils.kt index 64bce4ced6..ecf837419c 100644 --- a/save-backend/src/test/kotlin/com/saveourtool/save/backend/utils/SecurityTestingUtils.kt +++ b/save-backend/src/test/kotlin/com/saveourtool/save/backend/utils/SecurityTestingUtils.kt @@ -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 @@ -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() } }