Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OIDC RP-initiated logout #30072

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d414a7c
Add ExternalAuthToken model
jlehtoranta Mar 25, 2024
dc03aec
Fix Exist function in virtual session provider
jlehtoranta Mar 25, 2024
1a648a3
Support accessing virtual session provider for managing existing sess…
jlehtoranta Mar 25, 2024
658b9d6
Add ExternalAuthToken service
jlehtoranta Mar 25, 2024
b3fa45d
Remove external session related data from ExternalLoginUser
jlehtoranta Mar 25, 2024
cf86822
Migrate DB ExternalLoginUser by removing ExternalAuthToken related co…
jlehtoranta Mar 25, 2024
741193a
Register auth source Type to gob
jlehtoranta Mar 25, 2024
266b4d9
Use login type identifiers in a session
jlehtoranta Mar 25, 2024
207dc75
Add login type identifiers to AuthToken
jlehtoranta Mar 25, 2024
d6050ea
Support checking if an auth token exists
jlehtoranta Mar 25, 2024
4073ff7
Sync login type identifiers between an auth token and session
jlehtoranta Mar 25, 2024
e9a50a6
Manage external sessions by using ExternalAuthTokens
jlehtoranta Mar 25, 2024
991e682
Update from local sign in to OAuth2/OIDC sign in after linking an acc…
jlehtoranta Mar 25, 2024
b12cea1
Delete ExternalAuthTokens when removing an account link
jlehtoranta Mar 25, 2024
d12acc1
Delete ExternalAuthTokens when removing a user
jlehtoranta Mar 25, 2024
5296f7c
Generate OIDC RP-initiated logout URLs
jlehtoranta Mar 25, 2024
ff17f0e
Add a handler for OAuth2 or OIDC RP-initiated logout
jlehtoranta Mar 25, 2024
dc74954
Add a callback handler for OIDC RP-initiated logout
jlehtoranta Mar 25, 2024
041bb96
Show any sign out errors occurring before SignOutOAuth
jlehtoranta Mar 25, 2024
514fa96
Redirect OAuth2/OIDC sessions to OAuth2/OIDC logout handler
jlehtoranta Mar 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions models/auth/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import (
var ErrAuthTokenNotExist = util.NewNotExistErrorf("auth token does not exist")

type AuthToken struct { //nolint:revive
ID string `xorm:"pk"`
TokenHash string
UserID int64 `xorm:"INDEX"`
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
ID string `xorm:"pk"`
TokenHash string
UserID int64 `xorm:"INDEX"`
ExternalID string
LoginSourceID int64
LoginType Type
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
}

func init() {
Expand All @@ -31,6 +34,14 @@ func InsertAuthToken(ctx context.Context, t *AuthToken) error {
return err
}

func ExistAuthToken(ctx context.Context, id string) bool {
exist, err := db.Exist[AuthToken](ctx, builder.Eq{"`id`": id})
if err != nil {
return false
}
return exist
}

func GetAuthTokenByID(ctx context.Context, id string) (*AuthToken, error) {
at := &AuthToken{}

Expand Down
138 changes: 138 additions & 0 deletions models/auth/external_auth_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package auth

import (
"context"
"fmt"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util"

"xorm.io/builder"
)

type ErrExternalAuthTokenNotExist struct {
SessionID string
AuthTokenID string
}

func IsErrExternalAuthTokenNotExist(err error) bool {
_, ok := err.(ErrExternalAuthTokenNotExist)
return ok
}

func (err ErrExternalAuthTokenNotExist) Error() string {
return fmt.Sprintf("external auth token does not exist [sessionID: %s, authTokenID: %s]", err.SessionID, err.AuthTokenID)
}

func (err ErrExternalAuthTokenNotExist) Unwrap() error {
return util.ErrNotExist
}

type ExternalAuthToken struct {
SessionID string `xorm:"pk"`
AuthTokenID string `xorm:"INDEX"`
UserID int64 `xorm:"INDEX NOT NULL"`
ExternalID string `xorm:"NOT NULL"`
LoginSourceID int64 `xorm:"INDEX NOT NULL"`
RawData map[string]any `xorm:"TEXT JSON"`
AccessToken string `xorm:"TEXT"`
AccessTokenSecret string `xorm:"TEXT"`
RefreshToken string `xorm:"TEXT"`
ExpiresAt time.Time
IDToken string `xorm:"TEXT"`
}

func init() {
db.RegisterModel(new(ExternalAuthToken))
}

func InsertExternalAuthToken(ctx context.Context, t *ExternalAuthToken) error {
_, err := db.GetEngine(ctx).Insert(t)
return err
}

func GetExternalAuthTokenBySessionID(ctx context.Context, sessionID string) (*ExternalAuthToken, error) {
t := &ExternalAuthToken{}
has, err := db.GetEngine(ctx).ID(sessionID).Get(t)
if err != nil {
return nil, err
}
if !has {
return nil, ErrExternalAuthTokenNotExist{SessionID: sessionID}
}
return t, nil
}

func GetExternalAuthTokenByAuthTokenID(ctx context.Context, authTokenID string) (*ExternalAuthToken, error) {
t := &ExternalAuthToken{}
has, err := db.GetEngine(ctx).Where(builder.Eq{"auth_token_id": authTokenID}).Get(t)
if err != nil {
return nil, err
}
if !has {
return nil, ErrExternalAuthTokenNotExist{AuthTokenID: authTokenID}
}
return t, nil
}

func GetExternalAuthTokenSessionIDsAndAuthTokenIDs(ctx context.Context, userID, loginSourceID int64) ([]*ExternalAuthToken, error) {
tlist := []*ExternalAuthToken{}
cond := builder.NewCond().And(builder.Eq{"user_id": userID})
if loginSourceID > 0 {
cond = cond.And(builder.Eq{"login_source_id": loginSourceID})
}
if err := db.GetEngine(ctx).Cols("session_id", "auth_token_id").Where(cond).Find(&tlist); err != nil {
return nil, err
}
return tlist, nil
}

func UpdateExternalAuthTokenBySessionID(ctx context.Context, sessionID string, t *ExternalAuthToken) error {
_, err := db.GetEngine(ctx).ID(sessionID).AllCols().Update(t)
return err
}

func DeleteExternalAuthTokenBySessionID(ctx context.Context, sessionID string) error {
_, err := db.GetEngine(ctx).ID(sessionID).Delete(&ExternalAuthToken{})
return err
}

func DeleteExternalAuthTokensByUserLoginSourceID(ctx context.Context, userID, loginSourceID int64) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{"user_id": userID, "login_source_id": loginSourceID}).Delete(&ExternalAuthToken{})
return err
}

func DeleteExternalAuthTokensByUserID(ctx context.Context, userID int64) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{"user_id": userID}).Delete(&ExternalAuthToken{})
return err
}

type FindExternalAuthTokenOptions struct {
db.ListOptions
UserID int64
ExternalID string
LoginSourceID int64
OrderBy string
}

func (opts FindExternalAuthTokenOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.UserID > 0 {
cond = cond.And(builder.Eq{"user_id": opts.UserID})
}
if len(opts.ExternalID) > 0 {
cond = cond.And(builder.Eq{"external_id": opts.ExternalID})
}
if opts.LoginSourceID > 0 {
cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID})
}
return cond
}

func (opts FindExternalAuthTokenOptions) ToOrders() string {
return opts.OrderBy
}
2 changes: 2 additions & 0 deletions models/auth/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package auth

import (
"context"
"encoding/gob"
"fmt"
"reflect"

Expand Down Expand Up @@ -129,6 +130,7 @@ func (Source) TableName() string {

func init() {
db.RegisterModel(new(Source))
gob.Register(Type(0))
}

// BeforeSet is invoked from XORM before setting the value of a field of this object.
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ var migrations = []Migration{
NewMigration("Add PayloadVersion to HookTask", v1_22.AddPayloadVersionToHookTaskTable),
// v291 -> v292
NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment),
// v292 -> v293
NewMigration("Drop raw_data, access_token, access_token_secret, refresh_token and expires_at columns from external_login_user table", v1_22.DropColumnsFromExternalLoginUserTable),
}

// GetCurrentDBVersion returns the current db version
Expand Down
37 changes: 37 additions & 0 deletions models/migrations/v1_22/v292.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"time"

"code.gitea.io/gitea/models/migrations/base"

"xorm.io/xorm"
)

func DropColumnsFromExternalLoginUserTable(x *xorm.Engine) error {
type ExternalLoginUser struct {
RawData map[string]any `xorm:"TEXT JSON"`
AccessToken string `xorm:"TEXT"`
AccessTokenSecret string `xorm:"TEXT"`
RefreshToken string `xorm:"TEXT"`
ExpiresAt time.Time
}
if err := x.Sync(new(ExternalLoginUser)); err != nil {
return err
}

sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

if err := base.DropTableColumns(sess, "external_login_user", "raw_data", "access_token", "access_token_secret", "refresh_token", "expires_at"); err != nil {
return err
}

return sess.Commit()
}
30 changes: 12 additions & 18 deletions models/user/external_login_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package user
import (
"context"
"fmt"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/util"
Expand Down Expand Up @@ -57,23 +56,18 @@ func (err ErrExternalLoginUserNotExist) Unwrap() error {

// ExternalLoginUser makes the connecting between some existing user and additional external login sources
type ExternalLoginUser struct {
ExternalID string `xorm:"pk NOT NULL"`
UserID int64 `xorm:"INDEX NOT NULL"`
LoginSourceID int64 `xorm:"pk NOT NULL"`
RawData map[string]any `xorm:"TEXT JSON"`
Provider string `xorm:"index VARCHAR(25)"`
Email string
Name string
FirstName string
LastName string
NickName string
Description string
AvatarURL string `xorm:"TEXT"`
Location string
AccessToken string `xorm:"TEXT"`
AccessTokenSecret string `xorm:"TEXT"`
RefreshToken string `xorm:"TEXT"`
ExpiresAt time.Time
ExternalID string `xorm:"pk NOT NULL"`
UserID int64 `xorm:"INDEX NOT NULL"`
LoginSourceID int64 `xorm:"pk NOT NULL"`
Provider string `xorm:"index VARCHAR(25)"`
Email string
Name string
FirstName string
LastName string
NickName string
Description string
AvatarURL string `xorm:"TEXT"`
Location string
}

type ExternalUserMigrated interface {
Expand Down
16 changes: 16 additions & 0 deletions modules/session/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package session

import (
"code.gitea.io/gitea/modules/setting"
)

func GetSessionProvider() (*VirtualSessionProvider, error) {
sessionProvider := &VirtualSessionProvider{}
if err := sessionProvider.Init(setting.SessionConfig.Gclifetime, setting.SessionConfig.ProviderConfig); err != nil {
return nil, err
}
return sessionProvider, nil
}
4 changes: 3 additions & 1 deletion modules/session/virtual.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {

// Exist returns true if session with given ID exists.
func (o *VirtualSessionProvider) Exist(sid string) bool {
return true
o.lock.RLock()
defer o.lock.RUnlock()
return o.provider.Exist(sid)
}

// Destroy deletes a session by session ID.
Expand Down
2 changes: 1 addition & 1 deletion routers/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ func SubmitInstall(ctx *context.Context) {
u, _ = user_model.GetUserByName(ctx, u.Name)
}

nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID)
nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID, 0, 0)
if err != nil {
ctx.ServerError("CreateAuthTokenForUserID", err)
return
Expand Down
Loading