From a6270e11f6c1781957da20572ba8c5b0f3dc53f9 Mon Sep 17 00:00:00 2001 From: Ahoo Wang Date: Mon, 2 Jan 2023 17:27:03 +0800 Subject: [PATCH] Feature: support `PathConditionMatcher` (#48) * Feature: support `PathConditionMatcher` --- .../cosec/policy/action/PathActionMatcher.kt | 10 +-- .../cosec/policy/action/PathPatternParsers.kt | 38 +++++++++++ .../condition/part/PathConditionMatcher.kt | 48 ++++++++++++++ ...c.policy.condition.ConditionMatcherFactory | 1 + .../policy/action/PathPatternParsersTest.kt | 63 +++++++++++++++++++ .../part/PathConditionMatcherTest.kt | 50 +++++++++++++++ cosec-core/src/test/resources/policy.json | 10 +++ document/cosec-policy.schema.json | 23 +++++++ gradle.properties | 2 +- 9 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt create mode 100644 cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcher.kt create mode 100644 cosec-core/src/test/kotlin/me/ahoo/cosec/policy/action/PathPatternParsersTest.kt create mode 100644 cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcherTest.kt diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt index 82c84917..0149860c 100644 --- a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt @@ -17,13 +17,13 @@ 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 +import me.ahoo.cosec.policy.action.PathPatternParsers.asPathPatternParser import me.ahoo.cosec.policy.getMatcherPattern import org.springframework.http.server.PathContainer -import org.springframework.web.util.pattern.PathPatternParser class PathActionMatcher(configuration: Configuration) : AbstractActionMatcher(PathActionMatcherFactory.TYPE, configuration) { - private val pathPattern = PathPatternParser.defaultInstance.parse(configuration.getMatcherPattern()) + private val pathPattern = configuration.asPathPatternParser().parse(configuration.getMatcherPattern()) override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean { PathContainer.parsePath(request.path).let { return pathPattern.matches(it) @@ -33,10 +33,12 @@ class PathActionMatcher(configuration: Configuration) : class ReplaceablePathActionMatcher(configuration: Configuration) : AbstractActionMatcher(PathActionMatcherFactory.TYPE, configuration) { + private val pathPatternParser = configuration.asPathPatternParser() + private val pattern = configuration.getMatcherPattern() override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean { - val pathPattern = ActionPatternReplacer.replace(configuration.getMatcherPattern(), securityContext) + val pathPattern = ActionPatternReplacer.replace(pattern, securityContext) val pathContainer = PathContainer.parsePath(request.path) - PathPatternParser.defaultInstance.parse(pathPattern).let { + pathPatternParser.parse(pathPattern).let { return it.matches(pathContainer) } } diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt new file mode 100644 index 00000000..a3bff699 --- /dev/null +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt @@ -0,0 +1,38 @@ +/* + * 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.policy.action + +import me.ahoo.cosec.api.configuration.Configuration +import me.ahoo.cosec.policy.condition.part.PathConditionMatcherFactory +import org.springframework.http.server.PathContainer +import org.springframework.web.util.pattern.PathPatternParser + +internal object PathPatternParsers { + fun Configuration.asPathPatternParser(): PathPatternParser { + val pathConfiguration = get(PathConditionMatcherFactory.TYPE) ?: return PathPatternParser.defaultInstance + val caseSensitive = + pathConfiguration.get("caseSensitive")?.asBoolean() ?: PathPatternParser.defaultInstance.isCaseSensitive + val separator = pathConfiguration.get("separator")?.asString()?.trim() + ?: PathPatternParser.defaultInstance.pathOptions.separator().toString() + require(separator.length == 1) { + "separator must be a single character." + } + val decodeAndParseSegments = pathConfiguration.get("decodeAndParseSegments")?.asBoolean() + ?: PathPatternParser.defaultInstance.pathOptions.shouldDecodeAndParseSegments() + val parser = PathPatternParser() + parser.isCaseSensitive = caseSensitive + parser.pathOptions = PathContainer.Options.create(separator[0], decodeAndParseSegments) + return parser + } +} diff --git a/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcher.kt b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcher.kt new file mode 100644 index 00000000..ff992426 --- /dev/null +++ b/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcher.kt @@ -0,0 +1,48 @@ +/* + * 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.policy.condition.part + +import me.ahoo.cosec.api.configuration.Configuration +import me.ahoo.cosec.api.policy.ConditionMatcher +import me.ahoo.cosec.policy.action.PathPatternParsers.asPathPatternParser +import me.ahoo.cosec.policy.condition.ConditionMatcherFactory +import me.ahoo.cosec.policy.getMatcherPattern +import org.springframework.http.server.PathContainer +import org.springframework.web.util.pattern.PathPattern + +class PathConditionMatcher(configuration: Configuration) : + PartConditionMatcher(PathConditionMatcherFactory.TYPE, configuration) { + override val type: String + get() = PathConditionMatcherFactory.TYPE + private val pathPattern: PathPattern = configuration.asPathPatternParser().parse(configuration.getMatcherPattern()) + + override fun matchPart(partValue: String): Boolean { + PathContainer.parsePath(partValue).let { + return pathPattern.matches(it) + } + } +} + +class PathConditionMatcherFactory : ConditionMatcherFactory { + companion object { + const val TYPE = "path" + } + + override val type: String + get() = TYPE + + override fun create(configuration: Configuration): ConditionMatcher { + return PathConditionMatcher(configuration) + } +} diff --git a/cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory b/cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory index 26363d3d..d3519524 100644 --- a/cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory +++ b/cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.condition.ConditionMatcherFactory @@ -20,6 +20,7 @@ me.ahoo.cosec.policy.condition.context.InUserTenantConditionMatcherFactory me.ahoo.cosec.policy.condition.part.InConditionMatcherFactory me.ahoo.cosec.policy.condition.part.EqConditionMatcherFactory me.ahoo.cosec.policy.condition.part.RegularConditionMatcherFactory +me.ahoo.cosec.policy.condition.part.PathConditionMatcherFactory me.ahoo.cosec.policy.condition.SpelConditionMatcherFactory me.ahoo.cosec.policy.condition.OgnlConditionMatcherFactory diff --git a/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/action/PathPatternParsersTest.kt b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/action/PathPatternParsersTest.kt new file mode 100644 index 00000000..03488983 --- /dev/null +++ b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/action/PathPatternParsersTest.kt @@ -0,0 +1,63 @@ +/* + * 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.policy.action + +import me.ahoo.cosec.configuration.JsonConfiguration.Companion.asConfiguration +import me.ahoo.cosec.policy.action.PathPatternParsers.asPathPatternParser +import me.ahoo.cosec.policy.condition.part.PathConditionMatcherFactory +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.Test +import org.springframework.web.util.pattern.PathPatternParser + +class PathPatternParsersTest { + + @Test + fun asPathPatternParserWhenPathIsNull() { + assertThat( + mapOf().asConfiguration().asPathPatternParser(), + equalTo(PathPatternParser.defaultInstance) + ) + } + + @Test + fun asPathPatternParser() { + val pathPatternParser = mapOf( + PathConditionMatcherFactory.TYPE to mapOf( + "caseSensitive" to false, + "separator" to ".", + "decodeAndParseSegments" to false + ) + ).asConfiguration().asPathPatternParser() + assertThat(pathPatternParser.isCaseSensitive, equalTo(false)) + assertThat(pathPatternParser.pathOptions.separator(), equalTo('.')) + assertThat(pathPatternParser.pathOptions.shouldDecodeAndParseSegments(), equalTo(false)) + } + + @Test + fun asPathPatternParserWhenDefault() { + val pathPatternParser = mapOf( + PathConditionMatcherFactory.TYPE to mapOf() + ).asConfiguration().asPathPatternParser() + assertThat(pathPatternParser.isCaseSensitive, equalTo(PathPatternParser.defaultInstance.isCaseSensitive)) + assertThat( + pathPatternParser.pathOptions.separator(), + equalTo(PathPatternParser.defaultInstance.pathOptions.separator()) + ) + assertThat( + pathPatternParser.pathOptions.shouldDecodeAndParseSegments(), + equalTo(PathPatternParser.defaultInstance.pathOptions.shouldDecodeAndParseSegments()) + ) + } +} diff --git a/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcherTest.kt b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcherTest.kt new file mode 100644 index 00000000..ee0153b3 --- /dev/null +++ b/cosec-core/src/test/kotlin/me/ahoo/cosec/policy/condition/part/PathConditionMatcherTest.kt @@ -0,0 +1,50 @@ +/* + * 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.policy.condition.part + +import io.mockk.every +import io.mockk.mockk +import me.ahoo.cosec.api.context.request.Request +import me.ahoo.cosec.configuration.JsonConfiguration.Companion.asConfiguration +import me.ahoo.cosec.policy.MATCHER_PATTERN_KEY +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.* +import org.junit.jupiter.api.Test + +class PathConditionMatcherTest { + private val conditionMatcher = + PathConditionMatcherFactory().create( + mapOf( + CONDITION_MATCHER_PART_KEY to RequestParts.REMOTE_IP, + MATCHER_PATTERN_KEY to "192.168.*.*" + ).asConfiguration() + ) + + @Test + fun match() { + val request: Request = mockk { + every { remoteIp } returns "192.168.1.1" + } + assertThat(conditionMatcher.type, `is`(PathConditionMatcherFactory.TYPE)) + assertThat(conditionMatcher.match(request, mockk()), `is`(true)) + } + + @Test + fun notMatch() { + val requestNotMatch: Request = mockk { + every { remoteIp } returns "remoteIp2" + } + assertThat(conditionMatcher.match(requestNotMatch, mockk()), `is`(false)) + } +} diff --git a/cosec-core/src/test/resources/policy.json b/cosec-core/src/test/resources/policy.json index 28f84e19..dc9612f1 100644 --- a/cosec-core/src/test/resources/policy.json +++ b/cosec-core/src/test/resources/policy.json @@ -36,6 +36,16 @@ "in": [ "userId" ] + }, + { + "type": "path", + "part": "request.remoteIp", + "path": { + "caseSensitive": false, + "separator": ".", + "decodeAndParseSegments": false + }, + "pattern": "192.168.0.*" } ] }, diff --git a/document/cosec-policy.schema.json b/document/cosec-policy.schema.json index 141df4db..3a930ad4 100644 --- a/document/cosec-policy.schema.json +++ b/document/cosec-policy.schema.json @@ -88,6 +88,7 @@ "eq", "in", "reg", + "path", "spel", "ognl" ] @@ -105,6 +106,22 @@ "context.principal.name" ] }, + "pathOptions": { + "type": "object", + "properties": { + "caseSensitive": { + "type": "boolean" + }, + "separator": { + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "decodeAndParseSegments": { + "type": "boolean" + } + } + }, "actionMatcher": { "type": "object", "additionalProperties": { @@ -136,6 +153,9 @@ }, "pattern": { "type": "string" + }, + "path": { + "$ref": "#/definitions/pathOptions" } }, "required": [ @@ -167,6 +187,9 @@ "eq": { "type": "string" }, + "path": { + "$ref": "#/definitions/pathOptions" + }, "in": { "type": "array" } diff --git a/gradle.properties b/gradle.properties index 4817a230..7ad39c6d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ # limitations under the License. # group=me.ahoo.cosec -version=1.8.0 +version=1.8.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