code.gitea.io/gitea@v1.21.7/models/auth/oauth2.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package auth
     5  
     6  import (
     7  	"context"
     8  	"encoding/base32"
     9  	"encoding/base64"
    10  	"fmt"
    11  	"net"
    12  	"net/url"
    13  	"strings"
    14  
    15  	"code.gitea.io/gitea/models/db"
    16  	"code.gitea.io/gitea/modules/container"
    17  	"code.gitea.io/gitea/modules/setting"
    18  	"code.gitea.io/gitea/modules/timeutil"
    19  	"code.gitea.io/gitea/modules/util"
    20  
    21  	uuid "github.com/google/uuid"
    22  	"github.com/minio/sha256-simd"
    23  	"golang.org/x/crypto/bcrypt"
    24  	"xorm.io/builder"
    25  	"xorm.io/xorm"
    26  )
    27  
    28  // OAuth2Application represents an OAuth2 client (RFC 6749)
    29  type OAuth2Application struct {
    30  	ID           int64 `xorm:"pk autoincr"`
    31  	UID          int64 `xorm:"INDEX"`
    32  	Name         string
    33  	ClientID     string `xorm:"unique"`
    34  	ClientSecret string
    35  	// OAuth defines both Confidential and Public client types
    36  	// https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
    37  	// "Authorization servers MUST record the client type in the client registration details"
    38  	// https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
    39  	ConfidentialClient bool               `xorm:"NOT NULL DEFAULT TRUE"`
    40  	RedirectURIs       []string           `xorm:"redirect_uris JSON TEXT"`
    41  	CreatedUnix        timeutil.TimeStamp `xorm:"INDEX created"`
    42  	UpdatedUnix        timeutil.TimeStamp `xorm:"INDEX updated"`
    43  }
    44  
    45  func init() {
    46  	db.RegisterModel(new(OAuth2Application))
    47  	db.RegisterModel(new(OAuth2AuthorizationCode))
    48  	db.RegisterModel(new(OAuth2Grant))
    49  }
    50  
    51  type BuiltinOAuth2Application struct {
    52  	ConfigName   string
    53  	DisplayName  string
    54  	RedirectURIs []string
    55  }
    56  
    57  func BuiltinApplications() map[string]*BuiltinOAuth2Application {
    58  	m := make(map[string]*BuiltinOAuth2Application)
    59  	m["a4792ccc-144e-407e-86c9-5e7d8d9c3269"] = &BuiltinOAuth2Application{
    60  		ConfigName:   "git-credential-oauth",
    61  		DisplayName:  "git-credential-oauth",
    62  		RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
    63  	}
    64  	m["e90ee53c-94e2-48ac-9358-a874fb9e0662"] = &BuiltinOAuth2Application{
    65  		ConfigName:   "git-credential-manager",
    66  		DisplayName:  "Git Credential Manager",
    67  		RedirectURIs: []string{"http://127.0.0.1", "https://127.0.0.1"},
    68  	}
    69  	return m
    70  }
    71  
    72  func Init(ctx context.Context) error {
    73  	builtinApps := BuiltinApplications()
    74  	var builtinAllClientIDs []string
    75  	for clientID := range builtinApps {
    76  		builtinAllClientIDs = append(builtinAllClientIDs, clientID)
    77  	}
    78  
    79  	var registeredApps []*OAuth2Application
    80  	if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(&registeredApps); err != nil {
    81  		return err
    82  	}
    83  
    84  	clientIDsToAdd := container.Set[string]{}
    85  	for _, configName := range setting.OAuth2.DefaultApplications {
    86  		found := false
    87  		for clientID, builtinApp := range builtinApps {
    88  			if builtinApp.ConfigName == configName {
    89  				clientIDsToAdd.Add(clientID) // add all user-configured apps to the "add" list
    90  				found = true
    91  			}
    92  		}
    93  		if !found {
    94  			return fmt.Errorf("unknown oauth2 application: %q", configName)
    95  		}
    96  	}
    97  	clientIDsToDelete := container.Set[string]{}
    98  	for _, app := range registeredApps {
    99  		if !clientIDsToAdd.Contains(app.ClientID) {
   100  			clientIDsToDelete.Add(app.ClientID) // if a registered app is not in the "add" list, it should be deleted
   101  		}
   102  	}
   103  	for _, app := range registeredApps {
   104  		clientIDsToAdd.Remove(app.ClientID) // no need to re-add existing (registered) apps, so remove them from the set
   105  	}
   106  
   107  	for _, app := range registeredApps {
   108  		if clientIDsToDelete.Contains(app.ClientID) {
   109  			if err := deleteOAuth2Application(ctx, app.ID, 0); err != nil {
   110  				return err
   111  			}
   112  		}
   113  	}
   114  	for clientID := range clientIDsToAdd {
   115  		builtinApp := builtinApps[clientID]
   116  		if err := db.Insert(ctx, &OAuth2Application{
   117  			Name:         builtinApp.DisplayName,
   118  			ClientID:     clientID,
   119  			RedirectURIs: builtinApp.RedirectURIs,
   120  		}); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // TableName sets the table name to `oauth2_application`
   129  func (app *OAuth2Application) TableName() string {
   130  	return "oauth2_application"
   131  }
   132  
   133  // ContainsRedirectURI checks if redirectURI is allowed for app
   134  func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
   135  	contains := func(s string) bool {
   136  		s = strings.TrimSuffix(strings.ToLower(s), "/")
   137  		for _, u := range app.RedirectURIs {
   138  			if strings.TrimSuffix(strings.ToLower(u), "/") == s {
   139  				return true
   140  			}
   141  		}
   142  		return false
   143  	}
   144  	if !app.ConfidentialClient {
   145  		uri, err := url.Parse(redirectURI)
   146  		// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
   147  		if err == nil && uri.Scheme == "http" && uri.Port() != "" {
   148  			ip := net.ParseIP(uri.Hostname())
   149  			if ip != nil && ip.IsLoopback() {
   150  				// strip port
   151  				uri.Host = uri.Hostname()
   152  				if contains(uri.String()) {
   153  					return true
   154  				}
   155  			}
   156  		}
   157  	}
   158  	return contains(redirectURI)
   159  }
   160  
   161  // Base32 characters, but lowercased.
   162  const lowerBase32Chars = "abcdefghijklmnopqrstuvwxyz234567"
   163  
   164  // base32 encoder that uses lowered characters without padding.
   165  var base32Lower = base32.NewEncoding(lowerBase32Chars).WithPadding(base32.NoPadding)
   166  
   167  // GenerateClientSecret will generate the client secret and returns the plaintext and saves the hash at the database
   168  func (app *OAuth2Application) GenerateClientSecret() (string, error) {
   169  	rBytes, err := util.CryptoRandomBytes(32)
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  	// Add a prefix to the base32, this is in order to make it easier
   174  	// for code scanners to grab sensitive tokens.
   175  	clientSecret := "gto_" + base32Lower.EncodeToString(rBytes)
   176  
   177  	hashedSecret, err := bcrypt.GenerateFromPassword([]byte(clientSecret), bcrypt.DefaultCost)
   178  	if err != nil {
   179  		return "", err
   180  	}
   181  	app.ClientSecret = string(hashedSecret)
   182  	if _, err := db.GetEngine(db.DefaultContext).ID(app.ID).Cols("client_secret").Update(app); err != nil {
   183  		return "", err
   184  	}
   185  	return clientSecret, nil
   186  }
   187  
   188  // ValidateClientSecret validates the given secret by the hash saved in database
   189  func (app *OAuth2Application) ValidateClientSecret(secret []byte) bool {
   190  	return bcrypt.CompareHashAndPassword([]byte(app.ClientSecret), secret) == nil
   191  }
   192  
   193  // GetGrantByUserID returns a OAuth2Grant by its user and application ID
   194  func (app *OAuth2Application) GetGrantByUserID(ctx context.Context, userID int64) (grant *OAuth2Grant, err error) {
   195  	grant = new(OAuth2Grant)
   196  	if has, err := db.GetEngine(ctx).Where("user_id = ? AND application_id = ?", userID, app.ID).Get(grant); err != nil {
   197  		return nil, err
   198  	} else if !has {
   199  		return nil, nil
   200  	}
   201  	return grant, nil
   202  }
   203  
   204  // CreateGrant generates a grant for an user
   205  func (app *OAuth2Application) CreateGrant(ctx context.Context, userID int64, scope string) (*OAuth2Grant, error) {
   206  	grant := &OAuth2Grant{
   207  		ApplicationID: app.ID,
   208  		UserID:        userID,
   209  		Scope:         scope,
   210  	}
   211  	err := db.Insert(ctx, grant)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return grant, nil
   216  }
   217  
   218  // GetOAuth2ApplicationByClientID returns the oauth2 application with the given client_id. Returns an error if not found.
   219  func GetOAuth2ApplicationByClientID(ctx context.Context, clientID string) (app *OAuth2Application, err error) {
   220  	app = new(OAuth2Application)
   221  	has, err := db.GetEngine(ctx).Where("client_id = ?", clientID).Get(app)
   222  	if !has {
   223  		return nil, ErrOAuthClientIDInvalid{ClientID: clientID}
   224  	}
   225  	return app, err
   226  }
   227  
   228  // GetOAuth2ApplicationByID returns the oauth2 application with the given id. Returns an error if not found.
   229  func GetOAuth2ApplicationByID(ctx context.Context, id int64) (app *OAuth2Application, err error) {
   230  	app = new(OAuth2Application)
   231  	has, err := db.GetEngine(ctx).ID(id).Get(app)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	if !has {
   236  		return nil, ErrOAuthApplicationNotFound{ID: id}
   237  	}
   238  	return app, nil
   239  }
   240  
   241  // GetOAuth2ApplicationsByUserID returns all oauth2 applications owned by the user
   242  func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*OAuth2Application, err error) {
   243  	apps = make([]*OAuth2Application, 0)
   244  	err = db.GetEngine(ctx).Where("uid = ?", userID).Find(&apps)
   245  	return apps, err
   246  }
   247  
   248  // CreateOAuth2ApplicationOptions holds options to create an oauth2 application
   249  type CreateOAuth2ApplicationOptions struct {
   250  	Name               string
   251  	UserID             int64
   252  	ConfidentialClient bool
   253  	RedirectURIs       []string
   254  }
   255  
   256  // CreateOAuth2Application inserts a new oauth2 application
   257  func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) {
   258  	clientID := uuid.New().String()
   259  	app := &OAuth2Application{
   260  		UID:                opts.UserID,
   261  		Name:               opts.Name,
   262  		ClientID:           clientID,
   263  		RedirectURIs:       opts.RedirectURIs,
   264  		ConfidentialClient: opts.ConfidentialClient,
   265  	}
   266  	if err := db.Insert(ctx, app); err != nil {
   267  		return nil, err
   268  	}
   269  	return app, nil
   270  }
   271  
   272  // UpdateOAuth2ApplicationOptions holds options to update an oauth2 application
   273  type UpdateOAuth2ApplicationOptions struct {
   274  	ID                 int64
   275  	Name               string
   276  	UserID             int64
   277  	ConfidentialClient bool
   278  	RedirectURIs       []string
   279  }
   280  
   281  // UpdateOAuth2Application updates an oauth2 application
   282  func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) {
   283  	ctx, committer, err := db.TxContext(db.DefaultContext)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	defer committer.Close()
   288  
   289  	app, err := GetOAuth2ApplicationByID(ctx, opts.ID)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  	if app.UID != opts.UserID {
   294  		return nil, fmt.Errorf("UID mismatch")
   295  	}
   296  	builtinApps := BuiltinApplications()
   297  	if _, builtin := builtinApps[app.ClientID]; builtin {
   298  		return nil, fmt.Errorf("failed to edit OAuth2 application: application is locked: %s", app.ClientID)
   299  	}
   300  
   301  	app.Name = opts.Name
   302  	app.RedirectURIs = opts.RedirectURIs
   303  	app.ConfidentialClient = opts.ConfidentialClient
   304  
   305  	if err = updateOAuth2Application(ctx, app); err != nil {
   306  		return nil, err
   307  	}
   308  	app.ClientSecret = ""
   309  
   310  	return app, committer.Commit()
   311  }
   312  
   313  func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error {
   314  	if _, err := db.GetEngine(ctx).ID(app.ID).UseBool("confidential_client").Update(app); err != nil {
   315  		return err
   316  	}
   317  	return nil
   318  }
   319  
   320  func deleteOAuth2Application(ctx context.Context, id, userid int64) error {
   321  	sess := db.GetEngine(ctx)
   322  	// the userid could be 0 if the app is instance-wide
   323  	if deleted, err := sess.Where(builder.Eq{"id": id, "uid": userid}).Delete(&OAuth2Application{}); err != nil {
   324  		return err
   325  	} else if deleted == 0 {
   326  		return ErrOAuthApplicationNotFound{ID: id}
   327  	}
   328  	codes := make([]*OAuth2AuthorizationCode, 0)
   329  	// delete correlating auth codes
   330  	if err := sess.Join("INNER", "oauth2_grant",
   331  		"oauth2_authorization_code.grant_id = oauth2_grant.id AND oauth2_grant.application_id = ?", id).Find(&codes); err != nil {
   332  		return err
   333  	}
   334  	codeIDs := make([]int64, 0, len(codes))
   335  	for _, grant := range codes {
   336  		codeIDs = append(codeIDs, grant.ID)
   337  	}
   338  
   339  	if _, err := sess.In("id", codeIDs).Delete(new(OAuth2AuthorizationCode)); err != nil {
   340  		return err
   341  	}
   342  
   343  	if _, err := sess.Where("application_id = ?", id).Delete(new(OAuth2Grant)); err != nil {
   344  		return err
   345  	}
   346  	return nil
   347  }
   348  
   349  // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app.
   350  func DeleteOAuth2Application(id, userid int64) error {
   351  	ctx, committer, err := db.TxContext(db.DefaultContext)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	defer committer.Close()
   356  	app, err := GetOAuth2ApplicationByID(ctx, id)
   357  	if err != nil {
   358  		return err
   359  	}
   360  	builtinApps := BuiltinApplications()
   361  	if _, builtin := builtinApps[app.ClientID]; builtin {
   362  		return fmt.Errorf("failed to delete OAuth2 application: application is locked: %s", app.ClientID)
   363  	}
   364  	if err := deleteOAuth2Application(ctx, id, userid); err != nil {
   365  		return err
   366  	}
   367  	return committer.Commit()
   368  }
   369  
   370  // ListOAuth2Applications returns a list of oauth2 applications belongs to given user.
   371  func ListOAuth2Applications(uid int64, listOptions db.ListOptions) ([]*OAuth2Application, int64, error) {
   372  	sess := db.GetEngine(db.DefaultContext).
   373  		Where("uid=?", uid).
   374  		Desc("id")
   375  
   376  	if listOptions.Page != 0 {
   377  		sess = db.SetSessionPagination(sess, &listOptions)
   378  
   379  		apps := make([]*OAuth2Application, 0, listOptions.PageSize)
   380  		total, err := sess.FindAndCount(&apps)
   381  		return apps, total, err
   382  	}
   383  
   384  	apps := make([]*OAuth2Application, 0, 5)
   385  	total, err := sess.FindAndCount(&apps)
   386  	return apps, total, err
   387  }
   388  
   389  //////////////////////////////////////////////////////
   390  
   391  // OAuth2AuthorizationCode is a code to obtain an access token in combination with the client secret once. It has a limited lifetime.
   392  type OAuth2AuthorizationCode struct {
   393  	ID                  int64        `xorm:"pk autoincr"`
   394  	Grant               *OAuth2Grant `xorm:"-"`
   395  	GrantID             int64
   396  	Code                string `xorm:"INDEX unique"`
   397  	CodeChallenge       string
   398  	CodeChallengeMethod string
   399  	RedirectURI         string
   400  	ValidUntil          timeutil.TimeStamp `xorm:"index"`
   401  }
   402  
   403  // TableName sets the table name to `oauth2_authorization_code`
   404  func (code *OAuth2AuthorizationCode) TableName() string {
   405  	return "oauth2_authorization_code"
   406  }
   407  
   408  // GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty.
   409  func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL, error) {
   410  	redirect, err := url.Parse(code.RedirectURI)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	q := redirect.Query()
   415  	if state != "" {
   416  		q.Set("state", state)
   417  	}
   418  	q.Set("code", code.Code)
   419  	redirect.RawQuery = q.Encode()
   420  	return redirect, err
   421  }
   422  
   423  // Invalidate deletes the auth code from the database to invalidate this code
   424  func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error {
   425  	_, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
   426  	return err
   427  }
   428  
   429  // ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
   430  func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
   431  	switch code.CodeChallengeMethod {
   432  	case "S256":
   433  		// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
   434  		h := sha256.Sum256([]byte(verifier))
   435  		hashedVerifier := base64.RawURLEncoding.EncodeToString(h[:])
   436  		return hashedVerifier == code.CodeChallenge
   437  	case "plain":
   438  		return verifier == code.CodeChallenge
   439  	case "":
   440  		return true
   441  	default:
   442  		// unsupported method -> return false
   443  		return false
   444  	}
   445  }
   446  
   447  // GetOAuth2AuthorizationByCode returns an authorization by its code
   448  func GetOAuth2AuthorizationByCode(ctx context.Context, code string) (auth *OAuth2AuthorizationCode, err error) {
   449  	auth = new(OAuth2AuthorizationCode)
   450  	if has, err := db.GetEngine(ctx).Where("code = ?", code).Get(auth); err != nil {
   451  		return nil, err
   452  	} else if !has {
   453  		return nil, nil
   454  	}
   455  	auth.Grant = new(OAuth2Grant)
   456  	if has, err := db.GetEngine(ctx).ID(auth.GrantID).Get(auth.Grant); err != nil {
   457  		return nil, err
   458  	} else if !has {
   459  		return nil, nil
   460  	}
   461  	return auth, nil
   462  }
   463  
   464  //////////////////////////////////////////////////////
   465  
   466  // OAuth2Grant represents the permission of an user for a specific application to access resources
   467  type OAuth2Grant struct {
   468  	ID            int64              `xorm:"pk autoincr"`
   469  	UserID        int64              `xorm:"INDEX unique(user_application)"`
   470  	Application   *OAuth2Application `xorm:"-"`
   471  	ApplicationID int64              `xorm:"INDEX unique(user_application)"`
   472  	Counter       int64              `xorm:"NOT NULL DEFAULT 1"`
   473  	Scope         string             `xorm:"TEXT"`
   474  	Nonce         string             `xorm:"TEXT"`
   475  	CreatedUnix   timeutil.TimeStamp `xorm:"created"`
   476  	UpdatedUnix   timeutil.TimeStamp `xorm:"updated"`
   477  }
   478  
   479  // TableName sets the table name to `oauth2_grant`
   480  func (grant *OAuth2Grant) TableName() string {
   481  	return "oauth2_grant"
   482  }
   483  
   484  // GenerateNewAuthorizationCode generates a new authorization code for a grant and saves it to the database
   485  func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redirectURI, codeChallenge, codeChallengeMethod string) (code *OAuth2AuthorizationCode, err error) {
   486  	rBytes, err := util.CryptoRandomBytes(32)
   487  	if err != nil {
   488  		return &OAuth2AuthorizationCode{}, err
   489  	}
   490  	// Add a prefix to the base32, this is in order to make it easier
   491  	// for code scanners to grab sensitive tokens.
   492  	codeSecret := "gta_" + base32Lower.EncodeToString(rBytes)
   493  
   494  	code = &OAuth2AuthorizationCode{
   495  		Grant:               grant,
   496  		GrantID:             grant.ID,
   497  		RedirectURI:         redirectURI,
   498  		Code:                codeSecret,
   499  		CodeChallenge:       codeChallenge,
   500  		CodeChallengeMethod: codeChallengeMethod,
   501  	}
   502  	if err := db.Insert(ctx, code); err != nil {
   503  		return nil, err
   504  	}
   505  	return code, nil
   506  }
   507  
   508  // IncreaseCounter increases the counter and updates the grant
   509  func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
   510  	_, err := db.GetEngine(ctx).ID(grant.ID).Incr("counter").Update(new(OAuth2Grant))
   511  	if err != nil {
   512  		return err
   513  	}
   514  	updatedGrant, err := GetOAuth2GrantByID(ctx, grant.ID)
   515  	if err != nil {
   516  		return err
   517  	}
   518  	grant.Counter = updatedGrant.Counter
   519  	return nil
   520  }
   521  
   522  // ScopeContains returns true if the grant scope contains the specified scope
   523  func (grant *OAuth2Grant) ScopeContains(scope string) bool {
   524  	for _, currentScope := range strings.Split(grant.Scope, " ") {
   525  		if scope == currentScope {
   526  			return true
   527  		}
   528  	}
   529  	return false
   530  }
   531  
   532  // SetNonce updates the current nonce value of a grant
   533  func (grant *OAuth2Grant) SetNonce(ctx context.Context, nonce string) error {
   534  	grant.Nonce = nonce
   535  	_, err := db.GetEngine(ctx).ID(grant.ID).Cols("nonce").Update(grant)
   536  	if err != nil {
   537  		return err
   538  	}
   539  	return nil
   540  }
   541  
   542  // GetOAuth2GrantByID returns the grant with the given ID
   543  func GetOAuth2GrantByID(ctx context.Context, id int64) (grant *OAuth2Grant, err error) {
   544  	grant = new(OAuth2Grant)
   545  	if has, err := db.GetEngine(ctx).ID(id).Get(grant); err != nil {
   546  		return nil, err
   547  	} else if !has {
   548  		return nil, nil
   549  	}
   550  	return grant, err
   551  }
   552  
   553  // GetOAuth2GrantsByUserID lists all grants of a certain user
   554  func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, error) {
   555  	type joinedOAuth2Grant struct {
   556  		Grant       *OAuth2Grant       `xorm:"extends"`
   557  		Application *OAuth2Application `xorm:"extends"`
   558  	}
   559  	var results *xorm.Rows
   560  	var err error
   561  	if results, err = db.GetEngine(ctx).
   562  		Table("oauth2_grant").
   563  		Where("user_id = ?", uid).
   564  		Join("INNER", "oauth2_application", "application_id = oauth2_application.id").
   565  		Rows(new(joinedOAuth2Grant)); err != nil {
   566  		return nil, err
   567  	}
   568  	defer results.Close()
   569  	grants := make([]*OAuth2Grant, 0)
   570  	for results.Next() {
   571  		joinedGrant := new(joinedOAuth2Grant)
   572  		if err := results.Scan(joinedGrant); err != nil {
   573  			return nil, err
   574  		}
   575  		joinedGrant.Grant.Application = joinedGrant.Application
   576  		grants = append(grants, joinedGrant.Grant)
   577  	}
   578  	return grants, nil
   579  }
   580  
   581  // RevokeOAuth2Grant deletes the grant with grantID and userID
   582  func RevokeOAuth2Grant(ctx context.Context, grantID, userID int64) error {
   583  	_, err := db.GetEngine(ctx).Where(builder.Eq{"id": grantID, "user_id": userID}).Delete(&OAuth2Grant{})
   584  	return err
   585  }
   586  
   587  // ErrOAuthClientIDInvalid will be thrown if client id cannot be found
   588  type ErrOAuthClientIDInvalid struct {
   589  	ClientID string
   590  }
   591  
   592  // IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
   593  func IsErrOauthClientIDInvalid(err error) bool {
   594  	_, ok := err.(ErrOAuthClientIDInvalid)
   595  	return ok
   596  }
   597  
   598  // Error returns the error message
   599  func (err ErrOAuthClientIDInvalid) Error() string {
   600  	return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID)
   601  }
   602  
   603  // Unwrap unwraps this as a ErrNotExist err
   604  func (err ErrOAuthClientIDInvalid) Unwrap() error {
   605  	return util.ErrNotExist
   606  }
   607  
   608  // ErrOAuthApplicationNotFound will be thrown if id cannot be found
   609  type ErrOAuthApplicationNotFound struct {
   610  	ID int64
   611  }
   612  
   613  // IsErrOAuthApplicationNotFound checks if an error is a ErrReviewNotExist.
   614  func IsErrOAuthApplicationNotFound(err error) bool {
   615  	_, ok := err.(ErrOAuthApplicationNotFound)
   616  	return ok
   617  }
   618  
   619  // Error returns the error message
   620  func (err ErrOAuthApplicationNotFound) Error() string {
   621  	return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID)
   622  }
   623  
   624  // Unwrap unwraps this as a ErrNotExist err
   625  func (err ErrOAuthApplicationNotFound) Unwrap() error {
   626  	return util.ErrNotExist
   627  }
   628  
   629  // GetOAuth2ProviderSources returns all actived LoginOAuth2 sources
   630  func GetOAuth2ProviderSources(onlyActive bool) ([]*Source, error) {
   631  	sources := make([]*Source, 0, 1)
   632  	sess := db.GetEngine(db.DefaultContext)
   633  	if onlyActive {
   634  		sess = sess.Where("is_active = ?", true)
   635  	}
   636  	if err := sess.Where("type = ?", OAuth2).Find(&sources); err != nil {
   637  		return nil, err
   638  	}
   639  	return sources, nil
   640  }
   641  
   642  // GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
   643  func GetActiveOAuth2SourceByName(name string) (*Source, error) {
   644  	authSource := new(Source)
   645  	has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
   646  	if err != nil {
   647  		return nil, err
   648  	}
   649  
   650  	if !has {
   651  		return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
   652  	}
   653  
   654  	return authSource, nil
   655  }
   656  
   657  func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error {
   658  	deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID})
   659  
   660  	if _, err := db.GetEngine(ctx).In("grant_id", deleteCond).
   661  		Delete(&OAuth2AuthorizationCode{}); err != nil {
   662  		return err
   663  	}
   664  
   665  	if err := db.DeleteBeans(ctx,
   666  		&OAuth2Application{UID: userID},
   667  		&OAuth2Grant{UserID: userID},
   668  	); err != nil {
   669  		return fmt.Errorf("DeleteBeans: %w", err)
   670  	}
   671  
   672  	return nil
   673  }