Skip to content

Commit

Permalink
support method for PathActionMatcher (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahoo-Wang authored Mar 23, 2023
1 parent 145ecea commit 306daae
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.policy.action

import me.ahoo.cosec.api.configuration.Configuration
import me.ahoo.cosec.api.context.SecurityContext
import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ActionMatcher

const val ACTION_MATCHER_METHOD_KEY = "method"

abstract class AbstractActionMatcher(
override val type: String,
final override val configuration: Configuration,
) : ActionMatcher {
companion object {
private fun Configuration.asMethod(): Set<String> {
val methodConfiguration = get(ACTION_MATCHER_METHOD_KEY) ?: return setOf()
if (methodConfiguration.isString) {
return setOf(methodConfiguration.asString().uppercase())
}
return methodConfiguration.asStringList().toSet()
}
}

val method: Set<String> = configuration.asMethod()

override fun match(request: Request, securityContext: SecurityContext): Boolean {
if (method.isNotEmpty() && !method.contains(request.method.uppercase())) {
return false
}
return internalMatch(request, securityContext)
}

abstract fun internalMatch(request: Request, securityContext: SecurityContext): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,27 @@ import me.ahoo.cosec.api.context.request.Request
import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.configuration.JsonConfiguration.Companion.asConfiguration

object AllActionMatcher : ActionMatcher {
const val TYPE = "all"
const val ALL = "*"
override val type: String
get() = TYPE
override val configuration: Configuration by lazy {
ALL.asConfiguration()
class AllActionMatcher(configuration: Configuration) :
AbstractActionMatcher(AllActionMatcherFactory.TYPE, configuration) {
companion object {
val INSTANCE = AllActionMatcher(AllActionMatcherFactory.ALL.asConfiguration())
}

override fun match(request: Request, securityContext: SecurityContext): Boolean {
override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
return true
}
}

class AllActionMatcherFactory : ActionMatcherFactory {
companion object {
const val TYPE = "all"
const val ALL = "*"
}

override val type: String
get() = TYPE

override fun create(configuration: Configuration): ActionMatcher {
return AllActionMatcher(configuration)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@ import org.springframework.web.util.pattern.PathPatternParser
class PathActionMatcher(
private val patternParser: PathPatternParser,
private val pathPattern: PathPattern,
override val configuration: Configuration
) :
ActionMatcher {
override val type: String
get() = PathActionMatcherFactory.TYPE
configuration: Configuration
) : AbstractActionMatcher(PathActionMatcherFactory.TYPE, configuration) {

override fun match(request: Request, securityContext: SecurityContext): Boolean {
override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
PathContainer.parsePath(request.path, patternParser.pathOptions)
.let { pathContainer ->
return pathPattern.matches(pathContainer)
Expand All @@ -43,12 +40,10 @@ class PathActionMatcher(
class ReplaceablePathActionMatcher(
private val patternParser: PathPatternParser,
private val pattern: String,
override val configuration: Configuration
) : ActionMatcher {
override val type: String
get() = PathActionMatcherFactory.TYPE
configuration: Configuration
) : AbstractActionMatcher(PathActionMatcherFactory.TYPE, configuration) {

override fun match(request: Request, securityContext: SecurityContext): Boolean {
override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
val pathPattern = ActionPatternReplacer.replace(pattern, securityContext)
val pathContainer = PathContainer.parsePath(request.path)
patternParser.parse(pathPattern).let {
Expand Down Expand Up @@ -140,8 +135,8 @@ class PathActionMatcherFactory : ActionMatcherFactory {
}

if (configuration.isArray) {
if (configuration.asStringList().contains(AllActionMatcher.ALL)) {
return AllActionMatcher
if (configuration.asStringList().contains(AllActionMatcherFactory.ALL)) {
return AllActionMatcher.INSTANCE
}
return configuration.arrayAsActionMatcher()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import me.ahoo.cosec.api.policy.ActionMatcher
import me.ahoo.cosec.configuration.JsonConfiguration
import me.ahoo.cosec.policy.action.ActionMatcherFactoryProvider
import me.ahoo.cosec.policy.action.AllActionMatcher
import me.ahoo.cosec.policy.action.AllActionMatcherFactory
import me.ahoo.cosec.policy.action.PathActionMatcherFactory

object JsonActionMatcherSerializer : StdSerializer<ActionMatcher>(ActionMatcher::class.java) {
Expand All @@ -39,8 +40,8 @@ object JsonActionMatcherDeserializer : StdDeserializer<ActionMatcher>(ActionMatc
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ActionMatcher {
val actionConfiguration = p.codec.readValue(p, JsonConfiguration::class.java)

if (actionConfiguration.isString && actionConfiguration.asString() == AllActionMatcher.ALL) {
return AllActionMatcher
if (actionConfiguration.isString && actionConfiguration.asString() == AllActionMatcherFactory.ALL) {
return AllActionMatcher.INSTANCE
}

if (actionConfiguration.isString || actionConfiguration.isArray) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
me.ahoo.cosec.policy.action.AllActionMatcherFactory
me.ahoo.cosec.policy.action.PathActionMatcherFactory
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ internal class SimpleAuthorizationTest {
every { statements } returns listOf(
StatementData(
effect = Effect.ALLOW,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
),
)
}
Expand Down Expand Up @@ -108,7 +108,7 @@ internal class SimpleAuthorizationTest {
every { statements } returns listOf(
StatementData(
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
),
)
}
Expand Down Expand Up @@ -136,7 +136,7 @@ internal class SimpleAuthorizationTest {
every { statements } returns listOf(
StatementData(
effect = Effect.ALLOW,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
),
)
}
Expand Down Expand Up @@ -171,7 +171,7 @@ internal class SimpleAuthorizationTest {
every { statements } returns listOf(
StatementData(
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
),
)
}
Expand Down Expand Up @@ -210,7 +210,7 @@ internal class SimpleAuthorizationTest {
id = permissionId,
name = "",
effect = Effect.ALLOW,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
)
)
)
Expand Down Expand Up @@ -261,7 +261,7 @@ internal class SimpleAuthorizationTest {
id = permissionId,
name = "",
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class DefaultAppPermissionEvaluatorTest {
id = "permissionId",
name = "",
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ internal class DefaultPolicyEvaluatorTest {
get() = "name"
override val effect: Effect
get() = Effect.ALLOW
override val action = AllActionMatcher
override val action = AllActionMatcher.INSTANCE
override val condition: ConditionMatcher
get() = AllConditionMatcher.INSTANCE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ internal class StatementDataTest {

@Test
fun verifyDefault() {
val statementData = StatementData(action = AllActionMatcher)
val statementData = StatementData(action = AllActionMatcher.INSTANCE)
assertThat(statementData.name, equalTo(""))
assertThat(statementData.effect, equalTo(Effect.ALLOW))
assertThat(statementData.action, equalTo(AllActionMatcher))
assertThat(statementData.action, equalTo(AllActionMatcher.INSTANCE))
assertThat(statementData.condition, instanceOf(AllConditionMatcher::class.java))
assertThat(statementData.verify(mockk(), mockk()), `is`(VerifyResult.ALLOW))
}
Expand Down Expand Up @@ -95,7 +95,7 @@ internal class StatementDataTest {
fun verifyDeny() {
val statementData = StatementData(
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
)
assertThat(statementData.verify(mockk(), mockk()), `is`(VerifyResult.EXPLICIT_DENY))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@
package me.ahoo.cosec.policy.action

import io.mockk.mockk
import me.ahoo.cosec.configuration.JsonConfiguration
import me.ahoo.cosec.configuration.JsonConfiguration.Companion.asConfiguration
import org.checkerframework.checker.units.qual.A
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test

internal class AllActionMatcherTest {
@Test
fun match() {
assertThat(AllActionMatcher.type, `is`(AllActionMatcher.TYPE))
assertThat(AllActionMatcher.configuration.asString(), `is`("*"))
assertThat(AllActionMatcher.match(mockk(), mockk()), `is`(true))
val allActionMatcher = AllActionMatcherFactory().create(AllActionMatcherFactory.ALL.asConfiguration())
assertThat(allActionMatcher.type, `is`(AllActionMatcherFactory.TYPE))
assertThat(allActionMatcher.configuration.asString(), `is`(AllActionMatcherFactory.ALL))
assertThat(allActionMatcher.match(mockk(), mockk()), `is`(true))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ internal class CoSecJsonSerializerTest {
@JvmStatic
fun serializeActionMatcherProvider(): Stream<ActionMatcher> {
return Stream.of(
AllActionMatcher,
AllActionMatcher.INSTANCE,
PathActionMatcherFactory().create(
".*".asConfiguration(),
),
Expand Down Expand Up @@ -251,10 +251,10 @@ internal class CoSecJsonSerializerTest {
@JvmStatic
fun serializeStatementProvider(): Stream<Statement> {
return Stream.of(
StatementData(action = AllActionMatcher),
StatementData(action = AllActionMatcher.INSTANCE),
StatementData(
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
condition = serializeConditionMatcherProvider(),
),
)
Expand Down
11 changes: 10 additions & 1 deletion cosec-core/src/test/resources/test-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,11 @@
{
"name": "TestEndsWith",
"effect": "allow",
"action": ["*"],
"action": {
"all": {
"method": "GET"
}
},
"condition": {
"endsWith": {
"part": "request.attributes.remoteIp",
Expand All @@ -170,6 +174,7 @@
"effect": "allow",
"action": {
"path": {
"method": "GET",
"pattern": [
"/user/#{principal.id}/*"
],
Expand All @@ -186,6 +191,10 @@
"effect": "allow",
"action": {
"path": {
"method": [
"GET",
"POST"
],
"pattern": [
"/user/#{principal.id}/*",
"/user/#{principal.id}/order/*"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal class AppPermissionCodecExecutorTest {
groups = listOf(
PermissionGroupData(
"groupName",
permissions = listOf(PermissionData("id", "name", action = AllActionMatcher))
permissions = listOf(PermissionData("id", "name", action = AllActionMatcher.INSTANCE))
)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal class PolicyCodecExecutorTest {
description = "",
type = PolicyType.SYSTEM,
tenantId = "1",
statements = listOf(StatementData(action = AllActionMatcher)),
statements = listOf(StatementData(action = AllActionMatcher.INSTANCE)),
)
val key = "policy:" + MockIdGenerator.INSTANCE.generateAsString()
val cacheValue: CacheValue<Policy> = CacheValue.forever(policy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class RedisAppRolePermissionRepositoryTest {
id = UUID.randomUUID().toString(),
name = "",
effect = Effect.DENY,
action = AllActionMatcher,
action = AllActionMatcher.INSTANCE,
)
val appPermission = AppPermissionData(
"appId",
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# limitations under the License.
#
group=me.ahoo.cosec
version=1.16.1
version=1.16.2
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
Expand Down
Loading

0 comments on commit 306daae

Please sign in to comment.