From 29d1af0930f80cd9b371c9353e6c26fc7dbe58d0 Mon Sep 17 00:00:00 2001 From: Ahoo Wang Date: Fri, 24 Mar 2023 14:23:28 +0800 Subject: [PATCH] feat: add `AttributeValue` for CoSecPrincipal.attributes (#120) --- .../cosec/api/configuration/Configuration.kt | 4 +- .../cosec/api/principal/AttributeValue.kt | 22 +++++++++ .../cosec/api/principal/CoSecPrincipal.kt | 2 +- .../cosec/api/principal/CoSecPrincipalTest.kt | 4 +- .../cosec/configuration/JsonConfiguration.kt | 4 +- .../cosec/context/RequestSecurityContexts.kt | 29 +++++++++++ .../cosec/context/SecurityContextParser.kt | 1 - .../policy/condition/BoolConditionMatcher.kt | 6 +-- .../policy/condition/part/PartExtractor.kt | 2 +- .../cosec/principal/ObjectAttributeValue.kt | 39 +++++++++++++++ .../ahoo/cosec/principal/SimplePrincipal.kt | 3 +- .../cosec/principal/TextAttributeValue.kt | 30 ++++++++++++ .../part/DefaultPartExtractorTest.kt | 3 +- .../principal/ObjectAttributeValueTest.kt | 49 +++++++++++++++++++ .../JsonConfigurationSerializerTest.kt | 4 +- .../me/ahoo/cosec/jwt/ClaimAttributeValue.kt | 33 +++++++++++++ .../me/ahoo/cosec/jwt/JwtTokenConverter.kt | 4 ++ .../src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt | 7 +-- .../ahoo/cosec/jwt/JwtTokenConverterTest.kt | 18 ++++++- .../DirectOAuthClientPrincipalConverter.kt | 3 +- .../cosec/webflux/ReactiveSecurityFilter.kt | 2 + .../AbstractAuthorizationInterceptor.kt | 19 ++++--- gradle.properties | 2 +- 23 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/AttributeValue.kt create mode 100644 cosec-core/src/main/kotlin/me/ahoo/cosec/context/RequestSecurityContexts.kt create mode 100644 cosec-core/src/main/kotlin/me/ahoo/cosec/principal/ObjectAttributeValue.kt create mode 100644 cosec-core/src/main/kotlin/me/ahoo/cosec/principal/TextAttributeValue.kt create mode 100644 cosec-core/src/test/kotlin/me/ahoo/cosec/principal/ObjectAttributeValueTest.kt create mode 100644 cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/ClaimAttributeValue.kt diff --git a/cosec-api/src/main/kotlin/me/ahoo/cosec/api/configuration/Configuration.kt b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/configuration/Configuration.kt index 58223762..c38bfc89 100644 --- a/cosec-api/src/main/kotlin/me/ahoo/cosec/api/configuration/Configuration.kt +++ b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/configuration/Configuration.kt @@ -26,7 +26,7 @@ interface Configuration { fun asInt(): Int fun asLong(): Long fun asDouble(): Double - fun asPojo(pojoClass: Class): T + fun asObject(objectClass: Class): T fun has(key: String): Boolean = get(key) != null fun asStringList(): List = asList().map { it.asString() } @@ -41,4 +41,4 @@ interface Configuration { val isObject: Boolean } -inline fun Configuration.asPojo(): T = asPojo(T::class.java) +inline fun Configuration.asObject(): T = asObject(T::class.java) diff --git a/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/AttributeValue.kt b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/AttributeValue.kt new file mode 100644 index 00000000..5416ba16 --- /dev/null +++ b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/AttributeValue.kt @@ -0,0 +1,22 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.api.principal + +interface AttributeValue { + val value: V + + fun asString(): String + + fun asObject(objectClass: Class): T +} diff --git a/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipal.kt b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipal.kt index 00c39f4b..64debba7 100644 --- a/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipal.kt +++ b/cosec-api/src/main/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipal.kt @@ -32,7 +32,7 @@ interface CoSecPrincipal : Principal, PolicyCapable, RoleCapable { return id } - val attributes: Map + val attributes: Map> fun anonymous(): Boolean { return ANONYMOUS_ID == id } diff --git a/cosec-api/src/test/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipalTest.kt b/cosec-api/src/test/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipalTest.kt index 3a810faa..7d0ec5c7 100644 --- a/cosec-api/src/test/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipalTest.kt +++ b/cosec-api/src/test/kotlin/me/ahoo/cosec/api/principal/CoSecPrincipalTest.kt @@ -26,7 +26,7 @@ class CoSecPrincipalTest { override val id: String get() = ANONYMOUS_ID - override val attributes: Map + override val attributes: Map> get() = throw UnsupportedOperationException() override val policies: Set get() = throw UnsupportedOperationException() @@ -45,7 +45,7 @@ class CoSecPrincipalTest { override val id: String get() = "id" - override val attributes: Map + override val attributes: Map> get() = throw UnsupportedOperationException() override val policies: Set get() = throw UnsupportedOperationException() diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/configuration/JsonConfiguration.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/configuration/JsonConfiguration.kt index 014202bd..de1054eb 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/configuration/JsonConfiguration.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/configuration/JsonConfiguration.kt @@ -90,9 +90,9 @@ class JsonConfiguration( return delegate.asDouble() } - override fun asPojo(pojoClass: Class): T { + override fun asObject(objectClass: Class): T { return delegate.traverse(objectCodec).use { - it.readValueAs(pojoClass) + it.readValueAs(objectClass) } } diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/context/RequestSecurityContexts.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/context/RequestSecurityContexts.kt new file mode 100644 index 00000000..c647f9b3 --- /dev/null +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/context/RequestSecurityContexts.kt @@ -0,0 +1,29 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.context + +import me.ahoo.cosec.api.context.SecurityContext +import me.ahoo.cosec.api.context.request.Request + +object RequestSecurityContexts { + const val KEY = "COSEC_SECURITY_CONTEXT_REQUEST" + + fun SecurityContext.setRequest(request: R) { + setAttributeValue(KEY, request) + } + + fun SecurityContext.getRequest(): R? { + return getAttributeValue(KEY) + } +} diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/context/SecurityContextParser.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/context/SecurityContextParser.kt index be4e720e..411db2c8 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/context/SecurityContextParser.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/context/SecurityContextParser.kt @@ -15,7 +15,6 @@ package me.ahoo.cosec.context import me.ahoo.cosec.api.context.SecurityContext import me.ahoo.cosec.token.TokenExpiredException import org.slf4j.LoggerFactory -import kotlin.jvm.Throws /** * Security Context Parser . diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/BoolConditionMatcher.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/BoolConditionMatcher.kt index 82883ccb..9132e016 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/BoolConditionMatcher.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/BoolConditionMatcher.kt @@ -14,7 +14,7 @@ package me.ahoo.cosec.policy.condition import me.ahoo.cosec.api.configuration.Configuration -import me.ahoo.cosec.api.configuration.asPojo +import me.ahoo.cosec.api.configuration.asObject import me.ahoo.cosec.api.context.SecurityContext import me.ahoo.cosec.api.context.request.Request import me.ahoo.cosec.api.policy.ConditionMatcher @@ -29,11 +29,11 @@ class BoolConditionMatcher(configuration: Configuration) : AbstractConditionMatc val and: List = configuration.get(BOOL_CONDITION_MATCHER_AND_KEY) ?.asList() - ?.map { it.asPojo() }.orEmpty() + ?.map { it.asObject() }.orEmpty() val or: List = configuration.get(BOOL_CONDITION_MATCHER_OR_KEY) ?.asList() - ?.map { it.asPojo() }.orEmpty() + ?.map { it.asObject() }.orEmpty() override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean { and.any { diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PartExtractor.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PartExtractor.kt index dcdb2e38..56eae1be 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PartExtractor.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PartExtractor.kt @@ -63,7 +63,7 @@ data class DefaultPartExtractor(val part: String) : PartExtractor { } if (part.startsWith(SecurityContextParts.PRINCIPAL_ATTRIBUTES_PREFIX)) { val headerKey = part.substring(SecurityContextParts.PRINCIPAL_ATTRIBUTES_PREFIX.length) - return securityContext.principal.attributes[headerKey].orEmpty() + return securityContext.principal.attributes[headerKey]?.asString().orEmpty() } throw IllegalArgumentException("Unsupported part: $part") diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/ObjectAttributeValue.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/ObjectAttributeValue.kt new file mode 100644 index 00000000..27dae778 --- /dev/null +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/ObjectAttributeValue.kt @@ -0,0 +1,39 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.principal + +import me.ahoo.cosec.api.principal.AttributeValue +import me.ahoo.cosec.serialization.CoSecJsonSerializer + +class ObjectAttributeValue(override val value: V) : AttributeValue { + companion object { + @Suppress("UNCHECKED_CAST") + fun V.asAttributeValue(): AttributeValue { + return when (this) { + is AttributeValue<*> -> this + is String -> TextAttributeValue(this) + else -> ObjectAttributeValue(this) + } as AttributeValue + } + } + + override fun asString(): String { + return CoSecJsonSerializer.writeValueAsString(value) + } + + override fun asObject(objectClass: Class): T { + @Suppress("UNCHECKED_CAST") + return value as T + } +} diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/SimplePrincipal.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/SimplePrincipal.kt index 00bff5b1..943610c2 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/SimplePrincipal.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/SimplePrincipal.kt @@ -12,6 +12,7 @@ */ package me.ahoo.cosec.principal +import me.ahoo.cosec.api.principal.AttributeValue import me.ahoo.cosec.api.principal.CoSecPrincipal /** @@ -23,7 +24,7 @@ data class SimplePrincipal( override val id: String, override val policies: Set = emptySet(), override val roles: Set = emptySet(), - override val attributes: Map = emptyMap(), + override val attributes: Map> = emptyMap(), ) : CoSecPrincipal { companion object { diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/TextAttributeValue.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/TextAttributeValue.kt new file mode 100644 index 00000000..de14e33f --- /dev/null +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/principal/TextAttributeValue.kt @@ -0,0 +1,30 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.principal + +import me.ahoo.cosec.api.principal.AttributeValue +import me.ahoo.cosec.serialization.CoSecJsonSerializer + +class TextAttributeValue( + override val value: String +) : AttributeValue { + + override fun asString(): String { + return value + } + + override fun asObject(objectClass: Class): T { + return CoSecJsonSerializer.readValue(value, objectClass) + } +} diff --git a/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/DefaultPartExtractorTest.kt b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/DefaultPartExtractorTest.kt index 9fc35602..666d6069 100644 --- a/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/DefaultPartExtractorTest.kt +++ b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/DefaultPartExtractorTest.kt @@ -17,6 +17,7 @@ import io.mockk.every import io.mockk.mockk import me.ahoo.cosec.api.context.SecurityContext import me.ahoo.cosec.api.context.request.Request +import me.ahoo.cosec.principal.ObjectAttributeValue.Companion.asAttributeValue import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* import org.junit.jupiter.api.Assertions @@ -113,7 +114,7 @@ class DefaultPartExtractorTest { @Test fun extractContextPrincipalAttributes() { val context: SecurityContext = mockk { - every { principal.attributes["key"] } returns "value" + every { principal.attributes["key"] } returns "value".asAttributeValue() every { principal.attributes["not_exist"] } returns null } assertThat( diff --git a/cosec-core/src/test/kotlin/me/ahoo/cosec/principal/ObjectAttributeValueTest.kt b/cosec-core/src/test/kotlin/me/ahoo/cosec/principal/ObjectAttributeValueTest.kt new file mode 100644 index 00000000..1c992cc3 --- /dev/null +++ b/cosec-core/src/test/kotlin/me/ahoo/cosec/principal/ObjectAttributeValueTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.principal + +import me.ahoo.cosec.principal.ObjectAttributeValue.Companion.asAttributeValue +import org.junit.jupiter.api.Test + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* + +class ObjectAttributeValueTest { + + @Test + fun duplicateAsAttributeValue() { + val attributeValue = "value".asAttributeValue() + assertThat(attributeValue, sameInstance(attributeValue.asAttributeValue())) + } + + @Test + fun stringAsAttributeValue() { + val value = "1" + val attributeValue = value.asAttributeValue() + assertThat(attributeValue, instanceOf(TextAttributeValue::class.java)) + assertThat(attributeValue.value, equalTo(value)) + assertThat(attributeValue.asString(), equalTo(value)) + assertThat(attributeValue.asObject(Int::class.java), equalTo(1)) + } + + @Test + fun objectAsAttributeValue() { + val value = this + val attributeValue = value.asAttributeValue() + assertThat(attributeValue, instanceOf(ObjectAttributeValue::class.java)) + assertThat(attributeValue.value, equalTo(value)) + assertThat(attributeValue.asString(), equalTo("{}")) + assertThat(attributeValue.asObject(ObjectAttributeValueTest::class.java), equalTo(this)) + } +} \ No newline at end of file diff --git a/cosec-core/src/test/kotlin/me/ahoo/cosec/serialization/JsonConfigurationSerializerTest.kt b/cosec-core/src/test/kotlin/me/ahoo/cosec/serialization/JsonConfigurationSerializerTest.kt index 2eb8dd2e..7e5b67e8 100644 --- a/cosec-core/src/test/kotlin/me/ahoo/cosec/serialization/JsonConfigurationSerializerTest.kt +++ b/cosec-core/src/test/kotlin/me/ahoo/cosec/serialization/JsonConfigurationSerializerTest.kt @@ -13,7 +13,7 @@ package me.ahoo.cosec.serialization -import me.ahoo.cosec.api.configuration.asPojo +import me.ahoo.cosec.api.configuration.asObject import me.ahoo.cosec.configuration.JsonConfiguration import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.* @@ -38,7 +38,7 @@ class JsonConfigurationSerializerTest { equalTo(mapOf("kind" to "request", "name" to "remoteIp")), ) assertThat( - jsonConfiguration.getRequired("part").asPojo(), + jsonConfiguration.getRequired("part").asObject(), equalTo(Part("request", "remoteIp")), ) val jsonString = CoSecJsonSerializer.writeValueAsString(jsonConfiguration) diff --git a/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/ClaimAttributeValue.kt b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/ClaimAttributeValue.kt new file mode 100644 index 00000000..a726db28 --- /dev/null +++ b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/ClaimAttributeValue.kt @@ -0,0 +1,33 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosec.jwt + +import com.auth0.jwt.interfaces.Claim +import me.ahoo.cosec.api.principal.AttributeValue + +class ClaimAttributeValue(override val value: Claim) : AttributeValue { + companion object { + fun Claim.asClaimAttributeValue(): ClaimAttributeValue { + return ClaimAttributeValue(this) + } + } + + override fun asString(): String { + return value.asString() + } + + override fun asObject(objectClass: Class): T { + return value.`as`(objectClass) + } +} diff --git a/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt index f1ad96e9..46d693a3 100644 --- a/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt +++ b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/JwtTokenConverter.kt @@ -43,9 +43,13 @@ class JwtTokenConverter( val now = Date() val accessTokenExp = Date(System.currentTimeMillis() + accessTokenValidity.toMillis()) val payloadClaims: Map = principal.attributes + .asSequence() .filter { !Jwts.isRegisteredClaim(it.key) } + .map { + it.key to it.value.value + } .toMap() val accessTokenBuilder = JWT.create() diff --git a/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt index 223ec553..51a3ad55 100644 --- a/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt +++ b/cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt @@ -20,6 +20,7 @@ import me.ahoo.cosec.api.principal.RoleCapable import me.ahoo.cosec.api.tenant.Tenant.Companion.TENANT_ID_KEY import me.ahoo.cosec.api.token.TokenPrincipal import me.ahoo.cosec.api.token.TokenTenantPrincipal +import me.ahoo.cosec.jwt.ClaimAttributeValue.Companion.asClaimAttributeValue import me.ahoo.cosec.principal.SimplePrincipal import me.ahoo.cosec.tenant.SimpleTenant import me.ahoo.cosec.token.SimpleAccessToken @@ -67,10 +68,10 @@ object Jwts { val principalId = decodedAccessToken.subject val attributes = decodedAccessToken .claims + .asSequence() .filter { !isRegisteredClaim(it.key) } - .mapValues { - it.value.asString() - } + .associateBy({ it.key }, { it.value.asClaimAttributeValue() }) + val policyClaim = decodedAccessToken.getClaim(PolicyCapable.POLICY_KEY) val policies = if (policyClaim.isMissing) emptySet() else policyClaim.asList(String::class.java).toSet() diff --git a/cosec-jwt/src/test/kotlin/me/ahoo/cosec/jwt/JwtTokenConverterTest.kt b/cosec-jwt/src/test/kotlin/me/ahoo/cosec/jwt/JwtTokenConverterTest.kt index fe3a17ff..c38087e0 100644 --- a/cosec-jwt/src/test/kotlin/me/ahoo/cosec/jwt/JwtTokenConverterTest.kt +++ b/cosec-jwt/src/test/kotlin/me/ahoo/cosec/jwt/JwtTokenConverterTest.kt @@ -13,10 +13,13 @@ package me.ahoo.cosec.jwt import me.ahoo.cosec.api.token.CompositeToken +import me.ahoo.cosec.api.token.TokenPrincipal +import me.ahoo.cosec.principal.ObjectAttributeValue.Companion.asAttributeValue import me.ahoo.cosec.principal.SimplePrincipal import me.ahoo.cosec.principal.SimpleTenantPrincipal import me.ahoo.cosid.test.MockIdGenerator import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.notNullValue import org.junit.jupiter.api.Test @@ -25,6 +28,7 @@ import org.junit.jupiter.api.Test */ internal class JwtTokenConverterTest { var jwtTokenConverter = JwtTokenConverter(MockIdGenerator.INSTANCE, JwtFixture.ALGORITHM) + private val jwtTokenVerifier = JwtTokenVerifier(JwtFixture.ALGORITHM) @Test fun anonymousAsToken() { @@ -35,8 +39,20 @@ internal class JwtTokenConverterTest { @Test fun asToken() { val principal = - SimplePrincipal("id", setOf("policyId"), setOf("roleId"), mapOf("attr_key" to "attr_value")) + SimplePrincipal( + "id", + setOf("policyId"), + setOf("roleId"), + mapOf( + "attr_string" to "attr_string_value".asAttributeValue(), + "attr_list" to listOf("attr_list_value").asAttributeValue() + ) + ) val token: CompositeToken = jwtTokenConverter.asToken(principal) assertThat(token, notNullValue()) + val verified = jwtTokenVerifier.verify(token) + assertThat(verified.id, equalTo(principal.id)) + assertThat(verified.attributes["attr_string"]!!.asString(), equalTo("attr_string_value")) + assertThat(verified.attributes["attr_list"]!!.asObject(List::class.java), equalTo(listOf("attr_list_value"))) } } diff --git a/cosec-oauth/src/main/kotlin/me/ahoo/cosec/oauth/client/DirectOAuthClientPrincipalConverter.kt b/cosec-oauth/src/main/kotlin/me/ahoo/cosec/oauth/client/DirectOAuthClientPrincipalConverter.kt index 00f9ef50..56d7da33 100644 --- a/cosec-oauth/src/main/kotlin/me/ahoo/cosec/oauth/client/DirectOAuthClientPrincipalConverter.kt +++ b/cosec-oauth/src/main/kotlin/me/ahoo/cosec/oauth/client/DirectOAuthClientPrincipalConverter.kt @@ -14,6 +14,7 @@ package me.ahoo.cosec.oauth.client import me.ahoo.cosec.api.principal.CoSecPrincipal import me.ahoo.cosec.oauth.OAuthUser +import me.ahoo.cosec.principal.ObjectAttributeValue.Companion.asAttributeValue import me.ahoo.cosec.principal.SimplePrincipal import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono @@ -31,7 +32,7 @@ object DirectOAuthClientPrincipalConverter : OAuthClientPrincipalConverter { id = asClientUserId(client, authUser), policies = emptySet(), roles = emptySet(), - attributes = authUser.rawInfo.mapValues { it.value.toString() }, + attributes = authUser.rawInfo.mapValues { it.value.asAttributeValue() }, ).toMono() } diff --git a/cosec-webflux/src/main/kotlin/me/ahoo/cosec/webflux/ReactiveSecurityFilter.kt b/cosec-webflux/src/main/kotlin/me/ahoo/cosec/webflux/ReactiveSecurityFilter.kt index 987b320b..b1791739 100644 --- a/cosec-webflux/src/main/kotlin/me/ahoo/cosec/webflux/ReactiveSecurityFilter.kt +++ b/cosec-webflux/src/main/kotlin/me/ahoo/cosec/webflux/ReactiveSecurityFilter.kt @@ -15,6 +15,7 @@ package me.ahoo.cosec.webflux import me.ahoo.cosec.api.authorization.Authorization import me.ahoo.cosec.api.authorization.AuthorizeResult +import me.ahoo.cosec.context.RequestSecurityContexts.setRequest import me.ahoo.cosec.context.SecurityContextParser import me.ahoo.cosec.context.SimpleSecurityContext import me.ahoo.cosec.context.request.RequestParser @@ -50,6 +51,7 @@ abstract class ReactiveSecurityFilter( } exchange.setSecurityContext(securityContext) val request = requestParser.parse(exchange) + securityContext.setRequest(request) return authorization.authorize(request, securityContext) .flatMap { authorizeResult -> if (authorizeResult.authorized) { diff --git a/cosec-webmvc/src/main/kotlin/me/ahoo/cosec/servlet/AbstractAuthorizationInterceptor.kt b/cosec-webmvc/src/main/kotlin/me/ahoo/cosec/servlet/AbstractAuthorizationInterceptor.kt index 9ce236b5..5a75baba 100644 --- a/cosec-webmvc/src/main/kotlin/me/ahoo/cosec/servlet/AbstractAuthorizationInterceptor.kt +++ b/cosec-webmvc/src/main/kotlin/me/ahoo/cosec/servlet/AbstractAuthorizationInterceptor.kt @@ -14,6 +14,7 @@ package me.ahoo.cosec.servlet import me.ahoo.cosec.api.authorization.Authorization import me.ahoo.cosec.api.authorization.AuthorizeResult +import me.ahoo.cosec.context.RequestSecurityContexts.setRequest import me.ahoo.cosec.context.SecurityContextHolder import me.ahoo.cosec.context.SecurityContextParser import me.ahoo.cosec.context.SimpleSecurityContext @@ -39,29 +40,31 @@ abstract class AbstractAuthorizationInterceptor( ) { protected fun authorize( - request: HttpServletRequest, - response: HttpServletResponse, + servletRequest: HttpServletRequest, + servletResponse: HttpServletResponse, ): Boolean { var tokenVerificationException: TokenVerificationException? = null val securityContext = try { - securityContextParser.parse(request) + securityContextParser.parse(servletRequest) } catch (verificationException: TokenVerificationException) { tokenVerificationException = verificationException SimpleSecurityContext.anonymous() } SecurityContextHolder.setContext(securityContext) - request.setSecurityContext(securityContext) - return authorization.authorize(requestParser.parse(request), securityContext) + servletRequest.setSecurityContext(securityContext) + val request = requestParser.parse(servletRequest) + securityContext.setRequest(request) + return authorization.authorize(request, securityContext) .map { if (!it.authorized) { if (!securityContext.principal.authenticated()) { - response.status = HttpStatus.UNAUTHORIZED.value() + servletResponse.status = HttpStatus.UNAUTHORIZED.value() } else { - response.status = HttpStatus.FORBIDDEN.value() + servletResponse.status = HttpStatus.FORBIDDEN.value() } - response.writeWithAuthorizeResult( + servletResponse.writeWithAuthorizeResult( tokenVerificationException?.asAuthorizeResult() ?: it ) return@map false diff --git a/gradle.properties b/gradle.properties index ce891bdd..aa6b2a04 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ # limitations under the License. # group=me.ahoo.cosec -version=1.16.3 +version=1.16.4 description=RBAC-based And Policy-based Multi-Tenant Reactive Security Framework website=https://github.com/Ahoo-Wang/CoSec issues=https://github.com/Ahoo-Wang/CoSec/issues