Skip to content

Commit

Permalink
Add client_secret to oidc requests (#7263)
Browse files Browse the repository at this point in the history
* WIP: add client_secret to oidc requests

* changelog
  • Loading branch information
fm3 authored Aug 14, 2023
1 parent 075e35c commit 84887cd
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Added a new button to the layer settings in view only dataset mode to save the current view configuration as the dataset's default. [#7205](https://github.com/scalableminds/webknossos/pull/7205)
- Added option to select multiple segments in the segment list in order to perform batch actions. [#7242](https://github.com/scalableminds/webknossos/pull/7242)
- If a dataset layer is transformed (using an affine matrix or a thin plate spline), it can be dynamically shown without that transform via the layers sidebar. All other layers will be transformed accordingly. [#7246](https://github.com/scalableminds/webknossos/pull/7246)
- OpenID Connect authorization can now use a client secret for added security. [#7263](https://github.com/scalableminds/webknossos/pull/7263)

### Changed
- Small messages during annotating (e.g. “finished undo”, “applying mapping…”) are now click-through so they do not block users from selecting tools. [7239](https://github.com/scalableminds/webknossos/pull/7239)
Expand Down
32 changes: 20 additions & 12 deletions app/oxalis/security/OpenIdConnectClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import scala.concurrent.ExecutionContext

class OpenIdConnectClient @Inject()(rpc: RPC, conf: WkConf)(implicit executionContext: ExecutionContext) {

lazy val oidcConfig: OpenIdConnectConfig =
OpenIdConnectConfig(conf.SingleSignOn.OpenIdConnect.providerUrl, conf.SingleSignOn.OpenIdConnect.clientId)
private lazy val oidcConfig: OpenIdConnectConfig =
OpenIdConnectConfig(
conf.SingleSignOn.OpenIdConnect.providerUrl,
conf.SingleSignOn.OpenIdConnect.clientId,
if (conf.SingleSignOn.OpenIdConnect.clientSecret.nonEmpty) Some(conf.SingleSignOn.OpenIdConnect.clientSecret)
else None
)

/*
Build redirect URL to redirect to OIDC provider for auth request (https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest)
Expand Down Expand Up @@ -49,20 +54,22 @@ class OpenIdConnectClient @Inject()(rpc: RPC, conf: WkConf)(implicit executionCo
_ <- bool2Fox(conf.Features.openIdConnectEnabled) ?~> "oidc.disabled"
_ <- bool2Fox(oidcConfig.isValid) ?~> "oidc.configuration.invalid"
serverInfos <- discover
tokenResponse <- rpc(serverInfos.token_endpoint).postFormParseJson[OpenIdConnectTokenResponse](
Map(
"grant_type" -> "authorization_code",
"client_id" -> oidcConfig.clientId,
"redirect_uri" -> redirectUrl,
"code" -> code
))
tokenResponse <- rpc(serverInfos.token_endpoint)
.withBasicAuthOpt(Some(oidcConfig.clientId), oidcConfig.clientSecret)
.postFormParseJson[OpenIdConnectTokenResponse](
Map(
"grant_type" -> "authorization_code",
"client_id" -> oidcConfig.clientId,
"redirect_uri" -> redirectUrl,
"code" -> code
))
newToken <- validateOpenIdConnectTokenResponse(tokenResponse) ?~> "failed to parse JWT"
} yield newToken

/*
Discover endpoints of the provider (https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)
*/
def discover: Fox[OpenIdConnectProviderInfo] =
private def discover: Fox[OpenIdConnectProviderInfo] =
for {
response: WSResponse <- rpc(oidcConfig.discoveryUrl).get
serverInfo <- response.json.validate[OpenIdConnectProviderInfo](OpenIdConnectProviderInfo.format)
Expand All @@ -75,7 +82,7 @@ class OpenIdConnectClient @Inject()(rpc: RPC, conf: WkConf)(implicit executionCo
JwtJson.decodeJson(tr.access_token, JwtOptions.DEFAULT.copy(signature = false)).toFox
}

lazy val publicKey: Option[PublicKey] = {
private lazy val publicKey: Option[PublicKey] = {
if (conf.SingleSignOn.OpenIdConnect.publicKey.isEmpty || conf.SingleSignOn.OpenIdConnect.publicKeyAlgorithm.isEmpty) {
None
} else {
Expand Down Expand Up @@ -103,6 +110,7 @@ object OpenIdConnectProviderInfo {
case class OpenIdConnectConfig(
baseUrl: String,
clientId: String,
clientSecret: Option[String],
scope: String = "openid profile"
) {

Expand Down Expand Up @@ -135,5 +143,5 @@ case class OpenIdConnectClaimSet(iss: String,
}

object OpenIdConnectClaimSet {
implicit val format = Json.format[OpenIdConnectClaimSet]
implicit val format: OFormat[OpenIdConnectClaimSet] = Json.format[OpenIdConnectClaimSet]
}
1 change: 1 addition & 0 deletions app/utils/WkConf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class WkConf @Inject()(configuration: Configuration) extends ConfigReader with L
object OpenIdConnect {
val providerUrl: String = get[String]("singleSignOn.openIdConnect.providerUrl")
val clientId: String = get[String]("singleSignOn.openIdConnect.clientId")
val clientSecret: String = get[String]("singleSignOn.openIdConnect.clientSecret")
val publicKey: String = get[String]("singleSignOn.openIdConnect.publicKey")
val publicKeyAlgorithm: String = get[String]("singleSignOn.openIdConnect.publicKeyAlgorithm")
}
Expand Down
1 change: 1 addition & 0 deletions conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ singleSignOn {
openIdConnect {
providerUrl = "http://localhost:8080/auth/realms/master/"
clientId = "myclient"
clientSecret = "myClientSecret"
# Public Key to validate claim, for keycloak see Realm settings > keys
publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAscUZB3Y5fiOfIdLC/31N1GufZ26bmB21V8D9Crg2bAHPD3g8qofRMg5Uo1+WuKuT5CJrCu+x0hIbA50GYb6E1V78MkYOaCbCT+xE+ec+Jv6zUJAaNJugx71oXI+X5e9kW/O8JSwIicSUYDz7LKvCklwn9/QmgetqGsBrAEOG+4WlwPnrZiKRaQl9V0vBOcwzD946Cbrgg3iLnryJ0pGVKHvWePsXR7Pt8hdA0FeA9V9hVd6gVHR2pHqg46kyPItNMwWTXENqJ4lbhgaoZ9sZpoMXIy1kjh3GXSXGOG+GeOOtOinr1K24I8HG9wsnEefjVSPDB6EvflPrhLKXMfI/JQIDAQAB"
publicKeyAlgorithm = "RSA"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class RPCRequest(val id: Int, val url: String, wsClient: WSClient)(implicit ec:
this
}

def withBasicAuthOpt(usernameOpt: Option[String], passwordOpt: Option[String]): RPCRequest = {
(usernameOpt, passwordOpt) match {
case (Some(username), Some(password)) => request = request.withAuth(username, password, WSAuthScheme.BASIC)
case _ => ()
}
this
}

def withLongTimeout: RPCRequest = {
request = request.withRequestTimeout(2 hours)
this
Expand Down

0 comments on commit 84887cd

Please sign in to comment.