mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-07 05:07:02 +02:00
Call OpenID connect logout endpoint when signed-out on GitBucket (#3219)
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user