Skip to content

Commit

Permalink
feat: add AttributeValue for CoSecPrincipal.attributes (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahoo-Wang authored Mar 24, 2023
1 parent f92e0b6 commit 29d1af0
Show file tree
Hide file tree
Showing 23 changed files with 260 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface Configuration {
fun asInt(): Int
fun asLong(): Long
fun asDouble(): Double
fun <T> asPojo(pojoClass: Class<T>): T
fun <T> asObject(objectClass: Class<T>): T

fun has(key: String): Boolean = get(key) != null
fun asStringList(): List<String> = asList().map { it.asString() }
Expand All @@ -41,4 +41,4 @@ interface Configuration {
val isObject: Boolean
}

inline fun <reified T> Configuration.asPojo(): T = asPojo(T::class.java)
inline fun <reified T> Configuration.asObject(): T = asObject(T::class.java)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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<V> {
val value: V

fun asString(): String

fun <T> asObject(objectClass: Class<T>): T
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ interface CoSecPrincipal : Principal, PolicyCapable, RoleCapable {
return id
}

val attributes: Map<String, String>
val attributes: Map<String, AttributeValue<*>>
fun anonymous(): Boolean {
return ANONYMOUS_ID == id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CoSecPrincipalTest {
override val id: String
get() = ANONYMOUS_ID

override val attributes: Map<String, String>
override val attributes: Map<String, AttributeValue<*>>
get() = throw UnsupportedOperationException()
override val policies: Set<String>
get() = throw UnsupportedOperationException()
Expand All @@ -45,7 +45,7 @@ class CoSecPrincipalTest {
override val id: String
get() = "id"

override val attributes: Map<String, String>
override val attributes: Map<String, AttributeValue<*>>
get() = throw UnsupportedOperationException()
override val policies: Set<String>
get() = throw UnsupportedOperationException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ class JsonConfiguration(
return delegate.asDouble()
}

override fun <T> asPojo(pojoClass: Class<T>): T {
override fun <T> asObject(objectClass: Class<T>): T {
return delegate.traverse(objectCodec).use {
it.readValueAs(pojoClass)
it.readValueAs(objectClass)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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 <R : Request> SecurityContext.setRequest(request: R) {
setAttributeValue(KEY, request)
}

fun <R : Request> SecurityContext.getRequest(): R? {
return getAttributeValue<R>(KEY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,11 +29,11 @@ class BoolConditionMatcher(configuration: Configuration) : AbstractConditionMatc
val and: List<ConditionMatcher> =
configuration.get(BOOL_CONDITION_MATCHER_AND_KEY)
?.asList()
?.map { it.asPojo<ConditionMatcher>() }.orEmpty()
?.map { it.asObject<ConditionMatcher>() }.orEmpty()
val or: List<ConditionMatcher> =
configuration.get(BOOL_CONDITION_MATCHER_OR_KEY)
?.asList()
?.map { it.asPojo<ConditionMatcher>() }.orEmpty()
?.map { it.asObject<ConditionMatcher>() }.orEmpty()

override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
and.any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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<V>(override val value: V) : AttributeValue<V> {
companion object {
@Suppress("UNCHECKED_CAST")
fun <V> V.asAttributeValue(): AttributeValue<V> {
return when (this) {
is AttributeValue<*> -> this
is String -> TextAttributeValue(this)
else -> ObjectAttributeValue(this)
} as AttributeValue<V>
}
}

override fun asString(): String {
return CoSecJsonSerializer.writeValueAsString(value)
}

override fun <T> asObject(objectClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return value as T
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package me.ahoo.cosec.principal

import me.ahoo.cosec.api.principal.AttributeValue
import me.ahoo.cosec.api.principal.CoSecPrincipal

/**
Expand All @@ -23,7 +24,7 @@ data class SimplePrincipal(
override val id: String,
override val policies: Set<String> = emptySet(),
override val roles: Set<String> = emptySet(),
override val attributes: Map<String, String> = emptyMap(),
override val attributes: Map<String, AttributeValue<*>> = emptyMap(),
) : CoSecPrincipal {

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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<String> {

override fun asString(): String {
return value
}

override fun <T> asObject(objectClass: Class<T>): T {
return CoSecJsonSerializer.readValue(value, objectClass)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -38,7 +38,7 @@ class JsonConfigurationSerializerTest {
equalTo(mapOf("kind" to "request", "name" to "remoteIp")),
)
assertThat(
jsonConfiguration.getRequired("part").asPojo<Part>(),
jsonConfiguration.getRequired("part").asObject<Part>(),
equalTo(Part("request", "remoteIp")),
)
val jsonString = CoSecJsonSerializer.writeValueAsString(jsonConfiguration)
Expand Down
33 changes: 33 additions & 0 deletions cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/ClaimAttributeValue.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright [2021-present] [ahoo wang <[email protected]> (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<Claim> {
companion object {
fun Claim.asClaimAttributeValue(): ClaimAttributeValue {
return ClaimAttributeValue(this)
}
}

override fun asString(): String {
return value.asString()
}

override fun <T> asObject(objectClass: Class<T>): T {
return value.`as`(objectClass)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ class JwtTokenConverter(
val now = Date()
val accessTokenExp = Date(System.currentTimeMillis() + accessTokenValidity.toMillis())
val payloadClaims: Map<String, *> = principal.attributes
.asSequence()
.filter {
!Jwts.isRegisteredClaim(it.key)
}
.map {
it.key to it.value.value
}
.toMap()

val accessTokenBuilder = JWT.create()
Expand Down
7 changes: 4 additions & 3 deletions cosec-jwt/src/main/kotlin/me/ahoo/cosec/jwt/Jwts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down
Loading

0 comments on commit 29d1af0

Please sign in to comment.