-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #133 from SwissDataScienceCenter/improve-jwt-verify
Improves jwt verify by adding an issuer url whitelist and scoping jwks data by their issuer
- Loading branch information
Showing
17 changed files
with
815 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
modules/commons/src/main/scala/io/renku/search/common/UrlPattern.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* | ||
* Copyright 2024 Swiss Data Science Center (SDSC) | ||
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and | ||
* Eidgenössische Technische Hochschule Zürich (ETHZ). | ||
* | ||
* 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 io.renku.search.common | ||
|
||
import io.renku.search.common.UrlPattern.Segment | ||
|
||
final case class UrlPattern( | ||
scheme: Option[Segment], | ||
host: List[Segment], | ||
port: Option[Segment], | ||
path: List[Segment] | ||
): | ||
def matches(url: String): Boolean = | ||
val parts = UrlPattern.splitUrl(url) | ||
scheme.forall(s => parts.scheme.exists(s.matches)) && | ||
(host.isEmpty || host.length == parts.host.length) && | ||
host.zip(parts.host).forall { case (s, h) => s.matches(h) } && | ||
port.forall(p => parts.port.exists(p.matches)) && | ||
(path.isEmpty || path.length == parts.path.length) && | ||
path.zip(parts.path).forall { case (s, p) => s.matches(p) } | ||
|
||
def render: String = | ||
scheme.map(s => s"${s.render}://").getOrElse("") + | ||
host.map(_.render).mkString(".") + | ||
port.map(p => s":${p.render}").getOrElse("") + | ||
(if (path.isEmpty) "" else path.map(_.render).mkString("/", "/", "")) | ||
|
||
object UrlPattern: | ||
val all: UrlPattern = UrlPattern(None, Nil, None, Nil) | ||
|
||
final private[common] case class UrlParts( | ||
scheme: Option[String], | ||
host: List[String], | ||
port: Option[String], | ||
path: List[String] | ||
) | ||
private[common] def splitUrl(url: String): UrlParts = { | ||
def readScheme(s: String): (Option[String], String) = | ||
s.split("://").filter(_.nonEmpty).toList match | ||
case s :: rest :: Nil => (Some(s), rest) | ||
case rest => (None, rest.mkString) | ||
|
||
def readHostPort(s: String): (List[String], Option[String]) = | ||
s.split(':').toList match | ||
case h :: p :: _ => | ||
(h.split('.').filter(_.nonEmpty).toList, Option(p).filter(_.nonEmpty)) | ||
case rest => | ||
(s.split('.').filter(_.nonEmpty).toList, None) | ||
|
||
val (scheme, rest0) = readScheme(url) | ||
rest0.split('/').toList match | ||
case hp :: rest => | ||
val (host, port) = readHostPort(hp) | ||
UrlParts(scheme, host, port, rest) | ||
case _ => | ||
val (host, port) = readHostPort(rest0) | ||
UrlParts(scheme, host, port, Nil) | ||
} | ||
|
||
def fromString(str: String): UrlPattern = | ||
if (str == "*" || str.isEmpty) UrlPattern.all | ||
else { | ||
val parts = splitUrl(str) | ||
UrlPattern( | ||
parts.scheme.map(Segment.fromString), | ||
parts.host.map(Segment.fromString), | ||
parts.port.map(Segment.fromString), | ||
parts.path.map(Segment.fromString) | ||
) | ||
} | ||
|
||
enum Segment: | ||
case Literal(value: String) | ||
case Prefix(value: String) | ||
case Suffix(value: String) | ||
case MatchAll | ||
|
||
def matches(value: String): Boolean = this match | ||
case Literal(v) => v.equalsIgnoreCase(value) | ||
case Prefix(v) => value.startsWith(v) | ||
case Suffix(v) => value.endsWith(v) | ||
case MatchAll => true | ||
|
||
def render: String = this match | ||
case Literal(v) => v | ||
case Prefix(v) => s"${v}*" | ||
case Suffix(v) => s"*${v}" | ||
case MatchAll => "*" | ||
|
||
object Segment: | ||
def fromString(s: String): Segment = s match | ||
case "*" => MatchAll | ||
case x if x.startsWith("*") => Suffix(x.drop(1)) | ||
case x if x.endsWith("*") => Prefix(x.dropRight(1)) | ||
case _ => Literal(s) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
modules/commons/src/test/scala/io/renku/search/common/UrlPatternSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/* | ||
* Copyright 2024 Swiss Data Science Center (SDSC) | ||
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and | ||
* Eidgenössische Technische Hochschule Zürich (ETHZ). | ||
* | ||
* 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 io.renku.search.common | ||
|
||
import io.renku.search.common.UrlPattern.{Segment, UrlParts} | ||
import munit.FunSuite | ||
import munit.ScalaCheckSuite | ||
import org.scalacheck.Prop | ||
|
||
class UrlPatternSpec extends ScalaCheckSuite: | ||
|
||
test("read parts"): | ||
assertEquals(UrlPattern.splitUrl(""), UrlParts(None, Nil, None, Nil)) | ||
assertEquals( | ||
UrlPattern.splitUrl("test.com"), | ||
UrlParts(None, List("test", "com"), None, Nil) | ||
) | ||
assertEquals( | ||
UrlPattern.splitUrl("*.test.com"), | ||
UrlParts(None, List("*", "test", "com"), None, Nil) | ||
) | ||
assertEquals( | ||
UrlPattern.splitUrl("test.com:123"), | ||
UrlParts(None, List("test", "com"), Some("123"), Nil) | ||
) | ||
assertEquals( | ||
UrlPattern.splitUrl("test.com:123/auth"), | ||
UrlParts(None, List("test", "com"), Some("123"), List("auth")) | ||
) | ||
assertEquals( | ||
UrlPattern.splitUrl("https://test.com:123/auth"), | ||
UrlParts(Some("https"), List("test", "com"), Some("123"), List("auth")) | ||
) | ||
assertEquals( | ||
UrlPattern.splitUrl("/auth/exec"), | ||
UrlParts(None, Nil, None, List("auth", "exec")) | ||
) | ||
|
||
test("fromString"): | ||
assertEquals(UrlPattern.fromString("*"), UrlPattern.all) | ||
assertEquals(UrlPattern.fromString(""), UrlPattern.all) | ||
assertEquals( | ||
UrlPattern.fromString("*.*"), | ||
UrlPattern( | ||
None, | ||
List(Segment.MatchAll, Segment.MatchAll), | ||
None, | ||
Nil | ||
) | ||
) | ||
assertEquals( | ||
UrlPattern.fromString("*.test.com"), | ||
UrlPattern( | ||
None, | ||
List(Segment.MatchAll, Segment.Literal("test"), Segment.Literal("com")), | ||
None, | ||
Nil | ||
) | ||
) | ||
assertEquals( | ||
UrlPattern.fromString("*test.com"), | ||
UrlPattern( | ||
None, | ||
List(Segment.Suffix("test"), Segment.Literal("com")), | ||
None, | ||
Nil | ||
) | ||
) | ||
assertEquals( | ||
UrlPattern.fromString("*test.com/auth*"), | ||
UrlPattern( | ||
None, | ||
List(Segment.Suffix("test"), Segment.Literal("com")), | ||
None, | ||
List(Segment.Prefix("auth")) | ||
) | ||
) | ||
assertEquals( | ||
UrlPattern.fromString("https://test.com:15*/auth/sign"), | ||
UrlPattern( | ||
Some(Segment.Literal("https")), | ||
List(Segment.Literal("test"), Segment.Literal("com")), | ||
Some(Segment.Prefix("15")), | ||
List(Segment.Literal("auth"), Segment.Literal("sign")) | ||
) | ||
) | ||
|
||
property("read valid url pattern") { | ||
Prop.forAll(CommonGenerators.urlPatternGen) { pattern => | ||
val parsed = UrlPattern.fromString(pattern.render) | ||
val result = parsed == pattern | ||
if (!result) { | ||
println(s"Given: $pattern Parsed: ${parsed} Rendered: ${pattern.render}") | ||
} | ||
result | ||
} | ||
} | ||
|
||
property("match all for all patterns") { | ||
Prop.forAll(CommonGenerators.urlPatternGen) { pattern => | ||
val result = UrlPattern.all.matches(pattern.render) | ||
if (!result) { | ||
println(s"Failed pattern: ${pattern.render}") | ||
} | ||
result | ||
} | ||
} | ||
|
||
test("matches successful"): | ||
List( | ||
UrlPattern.fromString("*.test.com") -> List( | ||
"dev.test.com", | ||
"http://sub.test.com/ab/cd" | ||
), | ||
UrlPattern.fromString("/auth/renku") -> List( | ||
"dev.test.com/auth/renku", | ||
"http://sub.test.com/auth/renku" | ||
), | ||
UrlPattern.fromString("*.test.com/auth/renku") -> List( | ||
"http://dev.test.com/auth/renku", | ||
"sub1.test.com/auth/renku" | ||
) | ||
).foreach { case (pattern, values) => | ||
values.foreach(v => | ||
assert( | ||
pattern.matches(v), | ||
s"Pattern ${pattern.render} did not match $v, but it should" | ||
) | ||
) | ||
} | ||
|
||
test("matches not successful"): | ||
List( | ||
UrlPattern.fromString("*.test.com") -> List( | ||
"fest.com", | ||
"http://sub.fest.com/ab/cd" | ||
), | ||
UrlPattern.fromString("/auth/renku") -> List( | ||
"fest.com/tauth/renku", | ||
"http://sub.test.com/auth/renkuu" | ||
), | ||
UrlPattern.fromString("*.test.com/auth/renku") -> List( | ||
"http://dev.test.com/auth", | ||
"sub1.sub2.test.com/auth/renku" | ||
) | ||
).foreach { case (pattern, values) => | ||
values.foreach(v => | ||
assert( | ||
!pattern.matches(v), | ||
s"Pattern ${pattern.render} matched $v, but it should not" | ||
) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.