Skip to content

Commit

Permalink
Feature: support PathConditionMatcher (#48)
Browse files Browse the repository at this point in the history
* Feature: support `PathConditionMatcher`
  • Loading branch information
Ahoo-Wang authored Jan 2, 2023
1 parent 514981e commit a6270e1
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.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<String, String>().asConfiguration().asPathPatternParser(),
equalTo(PathPatternParser.defaultInstance)
)
}

@Test
fun asPathPatternParser() {
val pathPatternParser = mapOf(
PathConditionMatcherFactory.TYPE to mapOf<String, Any>(
"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<String, Any>()
).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())
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.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))
}
}
10 changes: 10 additions & 0 deletions cosec-core/src/test/resources/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@
"in": [
"userId"
]
},
{
"type": "path",
"part": "request.remoteIp",
"path": {
"caseSensitive": false,
"separator": ".",
"decodeAndParseSegments": false
},
"pattern": "192.168.0.*"
}
]
},
Expand Down
23 changes: 23 additions & 0 deletions document/cosec-policy.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"eq",
"in",
"reg",
"path",
"spel",
"ognl"
]
Expand All @@ -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": {
Expand Down Expand Up @@ -136,6 +153,9 @@
},
"pattern": {
"type": "string"
},
"path": {
"$ref": "#/definitions/pathOptions"
}
},
"required": [
Expand Down Expand Up @@ -167,6 +187,9 @@
"eq": {
"type": "string"
},
"path": {
"$ref": "#/definitions/pathOptions"
},
"in": {
"type": "array"
}
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.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
Expand Down

0 comments on commit a6270e1

Please sign in to comment.