Call OpenID connect logout endpoint when signed-out on GitBucket (#3219)

This commit is contained in:
Naoki Takezoe
2022-12-26 09:41:29 +09:00
committed by GitHub
parent 963bc4d672
commit 10d611c0eb
3 changed files with 55 additions and 29 deletions

View File

@@ -1,7 +1,8 @@
package gitbucket.core.controller
import java.net.URI
import com.nimbusds.jwt.JWT
import java.net.URI
import com.nimbusds.oauth2.sdk.id.State
import com.nimbusds.openid.connect.sdk.Nonce
import gitbucket.core.helper.xml
@@ -57,7 +58,8 @@ trait IndexControllerBase extends ControllerBase {
//
// case class SearchForm(query: String, owner: String, repository: String)
case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String)
case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
case class OidcSessionContext(token: JWT)
get("/") {
context.loginAccount
@@ -120,8 +122,8 @@ trait IndexControllerBase extends ControllerBase {
case _ => "/"
}
session.setAttribute(
Keys.Session.OidcContext,
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
Keys.Session.OidcAuthContext,
OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
)
redirect(authenticationRequest.toURI.toString)
} getOrElse {
@@ -135,10 +137,12 @@ trait IndexControllerBase extends ControllerBase {
get("/signin/oidc") {
context.settings.oidc.map { oidc =>
val redirectURI = new URI(s"$baseUrl/signin/oidc")
session.get(Keys.Session.OidcContext) match {
case Some(context: OidcContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account =>
signin(account, context.redirectBackURI)
session.get(Keys.Session.OidcAuthContext) match {
case Some(context: OidcAuthContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map {
case (jwt, account) =>
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
signin(account, context.redirectBackURI)
} orElse {
flash.update("error", "Sorry, authentication failed. Please try again.")
session.invalidate()
@@ -155,6 +159,15 @@ trait IndexControllerBase extends ControllerBase {
}
get("/signout") {
context.settings.oidc.map { oidc =>
session.get(Keys.Session.OidcSessionContext).foreach {
case context: OidcSessionContext =>
val redirectURI = new URI(baseUrl)
val authenticationRequest = createOIDLogoutRequest(oidc.issuer, oidc.clientID, redirectURI, context.token)
session.invalidate
redirect(authenticationRequest.toURI.toString)
}
}
session.invalidate
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
deleteLoginAccountFromLocalFile()

View File

@@ -1,11 +1,11 @@
package gitbucket.core.service
import java.net.URI
import com.nimbusds.jose.JWSAlgorithm.Family
import com.nimbusds.jose.proc.BadJOSEException
import com.nimbusds.jose.util.DefaultResourceRetriever
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
import com.nimbusds.jwt.JWT
import com.nimbusds.oauth2.sdk._
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer, State}
@@ -52,6 +52,11 @@ trait OpenIDConnectService {
)
}
def createOIDLogoutRequest(issuer: Issuer, clientID: ClientID, redirectURI: URI, token: JWT): LogoutRequest = {
val metadata = OIDCProviderMetadata.resolve(issuer)
new LogoutRequest(metadata.getEndSessionEndpointURI, token, null, clientID, redirectURI, null, null)
}
/**
* Proceed the OpenID Connect authentication.
*
@@ -60,7 +65,7 @@ trait OpenIDConnectService {
* @param state State saved in the session
* @param nonce Nonce saved in the session
* @param oidc OIDC settings
* @return ID token
* @return (ID token, GitBucket account)
*/
def authenticate(
params: Map[String, String],
@@ -68,22 +73,25 @@ trait OpenIDConnectService {
state: State,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
)(implicit s: Session): Option[Account] =
)(implicit s: Session): Option[(JWT, Account)] =
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
)
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap {
case (jwt, claims) =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
).map { account =>
(jwt, account)
}
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
}
}
@@ -136,7 +144,7 @@ trait OpenIDConnectService {
nonce: Nonce,
redirectURI: URI,
oidc: SystemSettingsService.OIDC
): Option[IDTokenClaimsSet] = {
): Option[(JWT, IDTokenClaimsSet)] = {
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
val tokenRequest = new TokenRequest(
metadata.getTokenEndpointURI,
@@ -173,7 +181,7 @@ trait OpenIDConnectService {
metadata: OIDCProviderMetadata,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
): Option[IDTokenClaimsSet] =
): Option[(JWT, IDTokenClaimsSet)] =
Option(response.getOIDCTokens.getIDToken) match {
case Some(jwt) =>
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
@@ -188,7 +196,7 @@ trait OpenIDConnectService {
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
}
try {
Some(validator.validate(jwt, nonce))
Some((jwt, validator.validate(jwt, nonce)))
} catch {
case e @ (_: BadJOSEException | _: JOSEException) =>
logger.info(s"OIDC ID token has error: $e")

View File

@@ -28,7 +28,12 @@ object Keys {
/**
* Session key for the OpenID Connect authentication.
*/
val OidcContext = "oidcContext"
val OidcAuthContext = "oidcAuthContext"
/**
* Session key for the OpenID Connect token.
*/
val OidcSessionContext = "oidcSessionContext"
/**
* Generate session key for the issue search condition.