2019-03-08 17:42:50 +01:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 19:20:29 +01:00
// SPDX-License-Identifier: MIT
2019-03-08 17:42:50 +01:00
2022-01-02 14:12:35 +01:00
package auth
2019-03-08 17:42:50 +01:00
import (
2022-05-11 13:16:35 +02:00
"context"
2023-11-24 04:49:41 +01:00
"crypto/sha256"
2022-02-04 18:03:15 +01:00
"encoding/base32"
2019-03-08 17:42:50 +01:00
"encoding/base64"
"fmt"
2022-09-29 00:19:55 +02:00
"net"
2019-03-08 17:42:50 +01:00
"net/url"
2021-01-01 17:33:27 +01:00
"strings"
2019-03-08 17:42:50 +01:00
2021-09-19 13:49:59 +02:00
"code.gitea.io/gitea/models/db"
2023-08-09 14:24:07 +02:00
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/setting"
2019-08-15 16:46:21 +02:00
"code.gitea.io/gitea/modules/timeutil"
2020-12-25 10:59:32 +01:00
"code.gitea.io/gitea/modules/util"
2019-03-08 17:42:50 +01:00
2020-06-18 11:18:44 +02:00
uuid "github.com/google/uuid"
2019-03-08 17:42:50 +01:00
"golang.org/x/crypto/bcrypt"
2022-05-11 13:16:35 +02:00
"xorm.io/builder"
2019-10-17 11:26:49 +02:00
"xorm.io/xorm"
2019-03-08 17:42:50 +01:00
)
// OAuth2Application represents an OAuth2 client (RFC 6749)
type OAuth2Application struct {
2021-09-24 13:32:56 +02:00
ID int64 ` xorm:"pk autoincr" `
UID int64 ` xorm:"INDEX" `
Name string
2019-03-08 17:42:50 +01:00
ClientID string ` xorm:"unique" `
ClientSecret string
2022-10-24 09:59:24 +02:00
// OAuth defines both Confidential and Public client types
// https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
// "Authorization servers MUST record the client type in the client registration details"
// https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
ConfidentialClient bool ` xorm:"NOT NULL DEFAULT TRUE" `
RedirectURIs [ ] string ` xorm:"redirect_uris JSON TEXT" `
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
2019-03-08 17:42:50 +01:00
}
2021-09-19 13:49:59 +02:00
func init ( ) {
db . RegisterModel ( new ( OAuth2Application ) )
db . RegisterModel ( new ( OAuth2AuthorizationCode ) )
db . RegisterModel ( new ( OAuth2Grant ) )
}
2023-08-09 14:24:07 +02:00
type BuiltinOAuth2Application struct {
ConfigName string
DisplayName string
RedirectURIs [ ] string
}
func BuiltinApplications ( ) map [ string ] * BuiltinOAuth2Application {
m := make ( map [ string ] * BuiltinOAuth2Application )
m [ "a4792ccc-144e-407e-86c9-5e7d8d9c3269" ] = & BuiltinOAuth2Application {
ConfigName : "git-credential-oauth" ,
DisplayName : "git-credential-oauth" ,
RedirectURIs : [ ] string { "http://127.0.0.1" , "https://127.0.0.1" } ,
}
m [ "e90ee53c-94e2-48ac-9358-a874fb9e0662" ] = & BuiltinOAuth2Application {
ConfigName : "git-credential-manager" ,
DisplayName : "Git Credential Manager" ,
RedirectURIs : [ ] string { "http://127.0.0.1" , "https://127.0.0.1" } ,
}
2023-10-08 05:51:08 +02:00
m [ "d57cb8c4-630c-4168-8324-ec79935e18d4" ] = & BuiltinOAuth2Application {
ConfigName : "tea" ,
DisplayName : "tea" ,
RedirectURIs : [ ] string { "http://127.0.0.1" , "https://127.0.0.1" } ,
}
2023-08-09 14:24:07 +02:00
return m
}
2024-04-06 00:52:39 +02:00
func BuiltinApplicationsClientIDs ( ) ( clientIDs [ ] string ) {
for clientID := range BuiltinApplications ( ) {
clientIDs = append ( clientIDs , clientID )
}
return clientIDs
}
2023-08-09 14:24:07 +02:00
func Init ( ctx context . Context ) error {
builtinApps := BuiltinApplications ( )
var builtinAllClientIDs [ ] string
for clientID := range builtinApps {
builtinAllClientIDs = append ( builtinAllClientIDs , clientID )
}
var registeredApps [ ] * OAuth2Application
if err := db . GetEngine ( ctx ) . In ( "client_id" , builtinAllClientIDs ) . Find ( & registeredApps ) ; err != nil {
return err
}
clientIDsToAdd := container . Set [ string ] { }
for _ , configName := range setting . OAuth2 . DefaultApplications {
found := false
for clientID , builtinApp := range builtinApps {
if builtinApp . ConfigName == configName {
clientIDsToAdd . Add ( clientID ) // add all user-configured apps to the "add" list
found = true
}
}
if ! found {
return fmt . Errorf ( "unknown oauth2 application: %q" , configName )
}
}
clientIDsToDelete := container . Set [ string ] { }
for _ , app := range registeredApps {
if ! clientIDsToAdd . Contains ( app . ClientID ) {
clientIDsToDelete . Add ( app . ClientID ) // if a registered app is not in the "add" list, it should be deleted
}
}
for _ , app := range registeredApps {
clientIDsToAdd . Remove ( app . ClientID ) // no need to re-add existing (registered) apps, so remove them from the set
}
for _ , app := range registeredApps {
if clientIDsToDelete . Contains ( app . ClientID ) {
if err := deleteOAuth2Application ( ctx , app . ID , 0 ) ; err != nil {
return err
}
}
}
for clientID := range clientIDsToAdd {
builtinApp := builtinApps [ clientID ]
if err := db . Insert ( ctx , & OAuth2Application {
Name : builtinApp . DisplayName ,
ClientID : clientID ,
RedirectURIs : builtinApp . RedirectURIs ,
} ) ; err != nil {
return err
}
}
return nil
}
2019-03-08 17:42:50 +01:00
// TableName sets the table name to `oauth2_application`
func ( app * OAuth2Application ) TableName ( ) string {
return "oauth2_application"
}
// ContainsRedirectURI checks if redirectURI is allowed for app
func ( app * OAuth2Application ) ContainsRedirectURI ( redirectURI string ) bool {
2024-04-13 11:31:40 +02:00
// OAuth2 requires the redirect URI to be an exact match, no dynamic parts are allowed.
// https://stackoverflow.com/questions/55524480/should-dynamic-query-parameters-be-present-in-the-redirection-uri-for-an-oauth2
// https://www.rfc-editor.org/rfc/rfc6819#section-5.2.3.3
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-12#section-3.1
2023-08-21 06:15:55 +02:00
contains := func ( s string ) bool {
s = strings . TrimSuffix ( strings . ToLower ( s ) , "/" )
for _ , u := range app . RedirectURIs {
if strings . TrimSuffix ( strings . ToLower ( u ) , "/" ) == s {
return true
}
}
return false
}
2022-10-24 09:59:24 +02:00
if ! app . ConfidentialClient {
uri , err := url . Parse ( redirectURI )
// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
if err == nil && uri . Scheme == "http" && uri . Port ( ) != "" {
ip := net . ParseIP ( uri . Hostname ( ) )
if ip != nil && ip . IsLoopback ( ) {
// strip port
uri . Host = uri . Hostname ( )
2023-08-21 06:15:55 +02:00
if contains ( uri . String ( ) ) {
2022-10-24 09:59:24 +02:00
return true
}
2022-09-29 00:19:55 +02:00
}
}
}
2023-08-21 06:15:55 +02:00
return contains ( redirectURI )
2019-03-08 17:42:50 +01:00
}
2022-02-04 18:03:15 +01:00
// Base32 characters, but lowercased.
const lowerBase32Chars = "abcdefghijklmnopqrstuvwxyz234567"
// base32 encoder that uses lowered characters without padding.
var base32Lower = base32 . NewEncoding ( lowerBase32Chars ) . WithPadding ( base32 . NoPadding )
2019-03-08 17:42:50 +01:00
// GenerateClientSecret will generate the client secret and returns the plaintext and saves the hash at the database
2023-10-14 10:37:24 +02:00
func ( app * OAuth2Application ) GenerateClientSecret ( ctx context . Context ) ( string , error ) {
2022-02-04 18:03:15 +01:00
rBytes , err := util . CryptoRandomBytes ( 32 )
2019-03-08 17:42:50 +01:00
if err != nil {
return "" , err
}
2022-02-04 18:03:15 +01:00
// Add a prefix to the base32, this is in order to make it easier
// for code scanners to grab sensitive tokens.
clientSecret := "gto_" + base32Lower . EncodeToString ( rBytes )
2019-03-08 17:42:50 +01:00
hashedSecret , err := bcrypt . GenerateFromPassword ( [ ] byte ( clientSecret ) , bcrypt . DefaultCost )
if err != nil {
return "" , err
}
app . ClientSecret = string ( hashedSecret )
2023-10-14 10:37:24 +02:00
if _ , err := db . GetEngine ( ctx ) . ID ( app . ID ) . Cols ( "client_secret" ) . Update ( app ) ; err != nil {
2019-03-08 17:42:50 +01:00
return "" , err
}
return clientSecret , nil
}
// ValidateClientSecret validates the given secret by the hash saved in database
func ( app * OAuth2Application ) ValidateClientSecret ( secret [ ] byte ) bool {
return bcrypt . CompareHashAndPassword ( [ ] byte ( app . ClientSecret ) , secret ) == nil
}
// GetGrantByUserID returns a OAuth2Grant by its user and application ID
2022-05-20 16:08:52 +02:00
func ( app * OAuth2Application ) GetGrantByUserID ( ctx context . Context , userID int64 ) ( grant * OAuth2Grant , err error ) {
2019-03-08 17:42:50 +01:00
grant = new ( OAuth2Grant )
2022-05-20 16:08:52 +02:00
if has , err := db . GetEngine ( ctx ) . Where ( "user_id = ? AND application_id = ?" , userID , app . ID ) . Get ( grant ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
} else if ! has {
return nil , nil
}
return grant , nil
}
// CreateGrant generates a grant for an user
2022-05-20 16:08:52 +02:00
func ( app * OAuth2Application ) CreateGrant ( ctx context . Context , userID int64 , scope string ) ( * OAuth2Grant , error ) {
2019-03-08 17:42:50 +01:00
grant := & OAuth2Grant {
ApplicationID : app . ID ,
UserID : userID ,
2021-01-01 17:33:27 +01:00
Scope : scope ,
2019-03-08 17:42:50 +01:00
}
2022-05-20 16:08:52 +02:00
err := db . Insert ( ctx , grant )
2019-03-08 17:42:50 +01:00
if err != nil {
return nil , err
}
return grant , nil
}
// GetOAuth2ApplicationByClientID returns the oauth2 application with the given client_id. Returns an error if not found.
2022-05-20 16:08:52 +02:00
func GetOAuth2ApplicationByClientID ( ctx context . Context , clientID string ) ( app * OAuth2Application , err error ) {
2019-03-08 17:42:50 +01:00
app = new ( OAuth2Application )
2022-05-20 16:08:52 +02:00
has , err := db . GetEngine ( ctx ) . Where ( "client_id = ?" , clientID ) . Get ( app )
2019-03-08 17:42:50 +01:00
if ! has {
return nil , ErrOAuthClientIDInvalid { ClientID : clientID }
}
2022-06-20 12:02:49 +02:00
return app , err
2019-03-08 17:42:50 +01:00
}
// GetOAuth2ApplicationByID returns the oauth2 application with the given id. Returns an error if not found.
2022-05-20 16:08:52 +02:00
func GetOAuth2ApplicationByID ( ctx context . Context , id int64 ) ( app * OAuth2Application , err error ) {
2019-03-08 17:42:50 +01:00
app = new ( OAuth2Application )
2022-05-20 16:08:52 +02:00
has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( app )
2019-06-12 21:41:28 +02:00
if err != nil {
return nil , err
}
2019-03-08 17:42:50 +01:00
if ! has {
return nil , ErrOAuthApplicationNotFound { ID : id }
}
return app , nil
}
// CreateOAuth2ApplicationOptions holds options to create an oauth2 application
type CreateOAuth2ApplicationOptions struct {
2022-10-24 09:59:24 +02:00
Name string
UserID int64
ConfidentialClient bool
RedirectURIs [ ] string
2019-03-08 17:42:50 +01:00
}
// CreateOAuth2Application inserts a new oauth2 application
2022-05-20 16:08:52 +02:00
func CreateOAuth2Application ( ctx c ontext . Context , opts CreateOAuth2ApplicationOptions ) ( * OAuth2Application , error ) {
2020-06-18 11:18:44 +02:00
clientID := uuid . New ( ) . String ( )
2019-03-08 17:42:50 +01:00
app := & OAuth2Application {
2022-10-24 09:59:24 +02:00
UID : opts . UserID ,
Name : opts . Name ,
ClientID : clientID ,
RedirectURIs : opts . RedirectURIs ,
ConfidentialClient : opts . ConfidentialClient ,
2019-03-08 17:42:50 +01:00
}
2022-05-20 16:08:52 +02:00
if err := db . Insert ( ctx , app ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
}
return app , nil
}
// UpdateOAuth2ApplicationOptions holds options to update an oauth2 application
type UpdateOAuth2ApplicationOptions struct {
2022-10-24 09:59:24 +02:00
ID int64
Name string
UserID int64
ConfidentialClient bool
RedirectURIs [ ] string
2019-03-08 17:42:50 +01:00
}
// UpdateOAuth2Application updates an oauth2 application
2023-10-14 10:37:24 +02:00
func UpdateOAuth2Application ( ctx context . Context , opts UpdateOAuth2ApplicationOptions ) ( * OAuth2Application , error ) {
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 16:41:00 +01:00
if err != nil {
2020-04-30 19:50:47 +02:00
return nil , err
}
2021-11-21 16:41:00 +01:00
defer committer . Close ( )
2019-03-08 17:42:50 +01:00
2022-05-20 16:08:52 +02:00
app , err := GetOAuth2ApplicationByID ( ctx , opts . ID )
2020-04-30 19:50:47 +02:00
if err != nil {
return nil , err
2019-03-08 17:42:50 +01:00
}
2020-04-30 19:50:47 +02:00
if app . UID != opts . UserID {
2021-07-08 13:38:13 +02:00
return nil , fmt . Errorf ( "UID mismatch" )
2020-04-30 19:50:47 +02:00
}
2023-08-09 14:24:07 +02:00
builtinApps := BuiltinApplications ( )
if _ , builtin := builtinApps [ app . ClientID ] ; builtin {
return nil , fmt . Errorf ( "failed to edit OAuth2 application: application is locked: %s" , app . ClientID )
}
2020-04-30 19:50:47 +02:00
app . Name = opts . Name
app . RedirectURIs = opts . RedirectURIs
2022-10-24 09:59:24 +02:00
app . ConfidentialClient = opts . ConfidentialClient
2020-04-30 19:50:47 +02:00
2022-05-20 16:08:52 +02:00
if err = updateOAuth2Application ( ctx , app ) ; err != nil {
2020-04-30 19:50:47 +02:00
return nil , err
}
app . ClientSecret = ""
2021-11-21 16:41:00 +01:00
return app , committer . Commit ( )
2020-04-30 19:50:47 +02:00
}
2022-05-20 16:08:52 +02:00
func updateOAuth2Application ( ctx context . Context , app * OAuth2Application ) error {
2022-10-24 09:59:24 +02:00
if _ , err := db . GetEngine ( ctx ) . ID ( app . ID ) . UseBool ( "confidential_client" ) . Update ( app ) ; err != nil {
2019-03-08 17:42:50 +01:00
return err
}
return nil
}
2022-05-20 16:08:52 +02:00
func deleteOAuth2Application ( ctx context . Context , id , userid int64 ) error {
sess := db . GetEngine ( ctx )
2022-10-09 14:07:41 +02:00
// the userid could be 0 if the app is instance-wide
if deleted , err := sess . Where ( builder . Eq { "id" : id , "uid" : userid } ) . Delete ( & OAuth2Application { } ) ; err != nil {
2019-03-08 17:42:50 +01:00
return err
} else if deleted == 0 {
2021-04-10 22:49:10 +02:00
return ErrOAuthApplicationNotFound { ID : id }
2019-03-08 17:42:50 +01:00
}
codes := make ( [ ] * OAuth2AuthorizationCode , 0 )
// delete correlating auth codes
if err := sess . Join ( "INNER" , "oauth2_grant" ,
"oauth2_authorization_code.grant_id = oauth2_grant.id AND oauth2_grant.application_id = ?" , id ) . Find ( & codes ) ; err != nil {
return err
}
2022-05-03 11:04:23 +02:00
codeIDs := make ( [ ] int64 , 0 , len ( codes ) )
2019-03-08 17:42:50 +01:00
for _ , grant := range codes {
codeIDs = append ( codeIDs , grant . ID )
}
if _ , err := sess . In ( "id" , codeIDs ) . Delete ( new ( OAuth2AuthorizationCode ) ) ; err != nil {
return err
}
if _ , err := sess . Where ( "application_id = ?" , id ) . Delete ( new ( OAuth2Grant ) ) ; err != nil {
return err
}
return nil
}
// 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.
2023-10-14 10:37:24 +02:00
func DeleteOAuth2Application ( ctx context . Context , id , userid int64 ) error {
ctx , committer , err := db . TxContext ( ctx )
2021-11-21 16:41:00 +01:00
if err != nil {
2019-03-08 17:42:50 +01:00
return err
}
2021-11-21 16:41:00 +01:00
defer committer . Close ( )
2023-08-09 14:24:07 +02:00
app , err := GetOAuth2ApplicationByID ( ctx , id )
if err != nil {
return err
}
builtinApps := BuiltinApplications ( )
if _ , builtin := builtinApps [ app . ClientID ] ; builtin {
return fmt . Errorf ( "failed to delete OAuth2 application: application is locked: %s" , app . ClientID )
}
2022-05-20 16:08:52 +02:00
if err := deleteOAuth2Application ( ctx , id , userid ) ; err != nil {
2019-03-08 17:42:50 +01:00
return err
}
2021-11-21 16:41:00 +01:00
return committer . Commit ( )
2019-03-08 17:42:50 +01:00
}
//////////////////////////////////////////////////////
// OAuth2AuthorizationCode is a code to obtain an access token in combination with the client secret once. It has a limited lifetime.
type OAuth2AuthorizationCode struct {
ID int64 ` xorm:"pk autoincr" `
Grant * OAuth2Grant ` xorm:"-" `
GrantID int64
Code string ` xorm:"INDEX unique" `
CodeChallenge string
CodeChallengeMethod string
RedirectURI string
2019-08-15 16:46:21 +02:00
ValidUntil timeutil . TimeStamp ` xorm:"index" `
2019-03-08 17:42:50 +01:00
}
// TableName sets the table name to `oauth2_authorization_code`
func ( code * OAuth2AuthorizationCode ) TableName ( ) string {
return "oauth2_authorization_code"
}
// GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty.
2023-07-07 07:31:56 +02:00
func ( code * OAuth2AuthorizationCode ) GenerateRedirectURI ( state string ) ( * url . URL , error ) {
redirect , err := url . Parse ( code . RedirectURI )
if err != nil {
return nil , err
2019-03-08 17:42:50 +01:00
}
q := redirect . Query ( )
if state != "" {
q . Set ( "state" , state )
}
q . Set ( "code" , code . Code )
redirect . RawQuery = q . Encode ( )
2022-06-20 12:02:49 +02:00
return redirect , err
2019-03-08 17:42:50 +01:00
}
// Invalidate deletes the auth code from the database to invalidate this code
2022-05-20 16:08:52 +02:00
func ( code * OAuth2AuthorizationCode ) Invalidate ( ctx context . Context ) error {
_ , err := db . GetEngine ( ctx ) . ID ( code . ID ) . NoAutoCondition ( ) . Delete ( code )
2019-03-08 17:42:50 +01:00
return err
}
// ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
func ( code * OAuth2AuthorizationCode ) ValidateCodeChallenge ( verifier string ) bool {
switch code . CodeChallengeMethod {
case "S256" :
// base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
h := sha256 . Sum256 ( [ ] byte ( verifier ) )
hashedVerifier := base64 . RawURLEncoding . EncodeToString ( h [ : ] )
return hashedVerifier == code . CodeChallenge
case "plain" :
return verifier == code . CodeChallenge
case "" :
return true
default :
// unsupported method -> return false
return false
}
}
// GetOAuth2AuthorizationByCode returns an authorization by its code
2022-05-20 16:08:52 +02:00
func GetOAuth2AuthorizationByCode ( ctx context . Context , code string ) ( auth * OAuth2AuthorizationCode , err error ) {
2019-03-08 17:42:50 +01:00
auth = new ( OAuth2AuthorizationCode )
2022-05-20 16:08:52 +02:00
if has , err := db . GetEngine ( ctx ) . Where ( "code = ?" , code ) . Get ( auth ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
} else if ! has {
return nil , nil
}
auth . Grant = new ( OAuth2Grant )
2022-05-20 16:08:52 +02:00
if has , err := db . GetEngine ( ctx ) . ID ( auth . GrantID ) . Get ( auth . Grant ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
} else if ! has {
return nil , nil
}
return auth , nil
}
//////////////////////////////////////////////////////
2021-07-08 13:38:13 +02:00
// OAuth2Grant represents the permission of an user for a specific application to access resources
2019-03-08 17:42:50 +01:00
type OAuth2Grant struct {
2019-04-17 10:18:16 +02:00
ID int64 ` xorm:"pk autoincr" `
UserID int64 ` xorm:"INDEX unique(user_application)" `
Application * OAuth2Application ` xorm:"-" `
ApplicationID int64 ` xorm:"INDEX unique(user_application)" `
Counter int64 ` xorm:"NOT NULL DEFAULT 1" `
2021-01-01 17:33:27 +01:00
Scope string ` xorm:"TEXT" `
Nonce string ` xorm:"TEXT" `
2019-08-15 16:46:21 +02:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"updated" `
2019-03-08 17:42:50 +01:00
}
// TableName sets the table name to `oauth2_grant`
func ( grant * OAuth2Grant ) TableName ( ) string {
return "oauth2_grant"
}
2021-06-14 12:33:16 +02:00
// GenerateNewAuthorizationCode generates a new authorization code for a grant and saves it to the database
2022-05-20 16:08:52 +02:00
func ( grant * OAuth2Grant ) GenerateNewAuthorizationCode ( ctx context . Context , redirectURI , codeChallenge , codeChallengeMethod string ) ( code * OAuth2AuthorizationCode , err error ) {
2022-02-04 18:03:15 +01:00
rBytes , err := util . CryptoRandomBytes ( 32 )
if err != nil {
2019-03-08 17:42:50 +01:00
return & OAuth2AuthorizationCode { } , err
}
2022-02-04 18:03:15 +01:00
// Add a prefix to the base32, this is in order to make it easier
// for code scanners to grab sensitive tokens.
codeSecret := "gta_" + base32Lower . EncodeToString ( rBytes )
2019-03-08 17:42:50 +01:00
code = & OAuth2AuthorizationCode {
Grant : grant ,
GrantID : grant . ID ,
RedirectURI : redirectURI ,
Code : codeSecret ,
CodeChallenge : codeChallenge ,
CodeChallengeMethod : codeChallengeMethod ,
}
2022-05-20 16:08:52 +02:00
if err := db . Insert ( ctx , code ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
}
return code , nil
}
// IncreaseCounter increases the counter and updates the grant
2022-05-20 16:08:52 +02:00
func ( grant * OAuth2Grant ) IncreaseCounter ( ctx context . Context ) error {
_ , err := db . GetEngine ( ctx ) . ID ( grant . ID ) . Incr ( "counter" ) . Update ( new ( OAuth2Grant ) )
2019-03-08 17:42:50 +01:00
if err != nil {
return err
}
2022-05-20 16:08:52 +02:00
updatedGrant , err := GetOAuth2GrantByID ( ctx , grant . ID )
2019-03-08 17:42:50 +01:00
if err != nil {
return err
}
grant . Counter = updatedGrant . Counter
return nil
}
2021-01-01 17:33:27 +01:00
// ScopeContains returns true if the grant scope contains the specified scope
func ( grant * OAuth2Grant ) ScopeContains ( scope string ) bool {
for _ , currentScope := range strings . Split ( grant . Scope , " " ) {
if scope == currentScope {
return true
}
}
return false
}
// SetNonce updates the current nonce value of a grant
2022-05-20 16:08:52 +02:00
func ( grant * OAuth2Grant ) SetNonce ( ctx context . Context , nonce string ) error {
2021-01-01 17:33:27 +01:00
grant . Nonce = nonce
2022-05-20 16:08:52 +02:00
_ , err := db . GetEngine ( ctx ) . ID ( grant . ID ) . Cols ( "nonce" ) . Update ( grant )
2021-01-01 17:33:27 +01:00
if err != nil {
return err
}
return nil
}
2019-03-08 17:42:50 +01:00
// GetOAuth2GrantByID returns the grant with the given ID
2022-05-20 16:08:52 +02:00
func GetOAuth2GrantByID ( ctx context . Context , id int64 ) ( grant * OAuth2Grant , err error ) {
2019-03-08 17:42:50 +01:00
grant = new ( OAuth2Grant )
2022-05-20 16:08:52 +02:00
if has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( grant ) ; err != nil {
2019-03-08 17:42:50 +01:00
return nil , err
} else if ! has {
return nil , nil
}
2022-06-20 12:02:49 +02:00
return grant , err
2019-03-08 17:42:50 +01:00
}
2019-04-17 10:18:16 +02:00
// GetOAuth2GrantsByUserID lists all grants of a certain user
2022-05-20 16:08:52 +02:00
func GetOAuth2GrantsByUserID ( ctx context . Context , uid int64 ) ( [ ] * OAuth2Grant , error ) {
2019-04-17 10:18:16 +02:00
type joinedOAuth2Grant struct {
Grant * OAuth2Grant ` xorm:"extends" `
Application * OAuth2Application ` xorm:"extends" `
}
var results * xorm . Rows
var err error
2022-05-20 16:08:52 +02:00
if results , err = db . GetEngine ( ctx ) .
2019-04-17 10:18:16 +02:00
Table ( "oauth2_grant" ) .
Where ( "user_id = ?" , uid ) .
Join ( "INNER" , "oauth2_application" , "application_id = oauth2_application.id" ) .
Rows ( new ( joinedOAuth2Grant ) ) ; err != nil {
return nil , err
}
defer results . Close ( )
grants := make ( [ ] * OAuth2Grant , 0 )
for results . Next ( ) {
joinedGrant := new ( joinedOAuth2Grant )
if err := results . Scan ( joinedGrant ) ; err != nil {
return nil , err
}
joinedGrant . Grant . Application = joinedGrant . Application
grants = append ( grants , joinedGrant . Grant )
}
return grants , nil
}
// RevokeOAuth2Grant deletes the grant with grantID and userID
2022-05-20 16:08:52 +02:00
func RevokeOAuth2Grant ( ctx context . Context , grantID , userID int64 ) error {
2022-10-09 14:07:41 +02:00
_ , err := db . GetEngine ( ctx ) . Where ( builder . Eq { "id" : grantID , "user_id" : userID } ) . Delete ( & OAuth2Grant { } )
2019-04-17 10:18:16 +02:00
return err
}
2022-01-02 14:12:35 +01:00
// ErrOAuthClientIDInvalid will be thrown if client id cannot be found
type ErrOAuthClientIDInvalid struct {
ClientID string
}
2022-10-18 07:50:37 +02:00
// IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
2022-01-02 14:12:35 +01:00
func IsErrOauthClientIDInvalid ( err error ) bool {
_ , ok := err . ( ErrOAuthClientIDInvalid )
return ok
}
// Error returns the error message
func ( err ErrOAuthClientIDInvalid ) Error ( ) string {
return fmt . Sprintf ( "Client ID invalid [Client ID: %s]" , err . ClientID )
}
2022-10-18 07:50:37 +02:00
// Unwrap unwraps this as a ErrNotExist err
func ( err ErrOAuthClientIDInvalid ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-01-02 14:12:35 +01:00
// ErrOAuthApplicationNotFound will be thrown if id cannot be found
type ErrOAuthApplicationNotFound struct {
ID int64
}
// IsErrOAuthApplicationNotFound checks if an error is a ErrReviewNotExist.
func IsErrOAuthApplicationNotFound ( err error ) bool {
_ , ok := err . ( ErrOAuthApplicationNotFound )
return ok
}
// Error returns the error message
func ( err ErrOAuthApplicationNotFound ) Error ( ) string {
return fmt . Sprintf ( "OAuth application not found [ID: %d]" , err . ID )
}
2022-10-18 07:50:37 +02:00
// Unwrap unwraps this as a ErrNotExist err
func ( err ErrOAuthApplicationNotFound ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-01-02 14:12:35 +01:00
// GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
2023-10-14 10:37:24 +02:00
func GetActiveOAuth2SourceByName ( ctx context . Context , name string ) ( * Source , error ) {
2022-01-02 14:12:35 +01:00
authSource := new ( Source )
2023-10-14 10:37:24 +02:00
has , err := db . GetEngine ( ctx ) . Where ( "name = ? and type = ? and is_active = ?" , name , OAuth2 , true ) . Get ( authSource )
2022-08-17 20:25:28 +02:00
if err != nil {
2022-01-02 14:12:35 +01:00
return nil , err
}
2022-08-17 20:25:28 +02:00
if ! has {
return nil , fmt . Errorf ( "oauth2 source not found, name: %q" , name )
}
2022-01-02 14:12:35 +01:00
return authSource , nil
}
2022-05-11 13:16:35 +02:00
func DeleteOAuth2RelictsByUserID ( ctx context . Context , userID int64 ) error {
deleteCond := builder . Select ( "id" ) . From ( "oauth2_grant" ) . Where ( builder . Eq { "oauth2_grant.user_id" : userID } )
if _ , err := db . GetEngine ( ctx ) . In ( "grant_id" , deleteCond ) .
Delete ( & OAuth2AuthorizationCode { } ) ; err != nil {
return err
}
if err := db . DeleteBeans ( ctx ,
& OAuth2Application { UID : userID } ,
& OAuth2Grant { UserID : userID } ,
) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "DeleteBeans: %w" , err )
2022-05-11 13:16:35 +02:00
}
return nil
}
2024-04-06 00:52:39 +02:00
// CountOrphanedOAuth2Applications returns the amount of orphaned OAuth2 applications.
func CountOrphanedOAuth2Applications ( ctx context . Context ) ( int64 , error ) {
return db . GetEngine ( ctx ) .
Table ( "`oauth2_application`" ) .
Join ( "LEFT" , "`user`" , "`oauth2_application`.`uid` = `user`.`id`" ) .
Where ( builder . IsNull { "`user`.id" } ) .
Where ( builder . NotIn ( "`oauth2_application`.`client_id`" , BuiltinApplicationsClientIDs ( ) ) ) .
Select ( "COUNT(`oauth2_application`.`id`)" ) .
Count ( )
}
// DeleteOrphanedOAuth2Applications deletes orphaned OAuth2 applications.
func DeleteOrphanedOAuth2Applications ( ctx context . Context ) ( int64 , error ) {
subQuery := builder . Select ( "`oauth2_application`.id" ) .
From ( "`oauth2_application`" ) .
Join ( "LEFT" , "`user`" , "`oauth2_application`.`uid` = `user`.`id`" ) .
Where ( builder . IsNull { "`user`.id" } ) .
Where ( builder . NotIn ( "`oauth2_application`.`client_id`" , BuiltinApplicationsClientIDs ( ) ) )
b := builder . Delete ( builder . In ( "id" , subQuery ) ) . From ( "`oauth2_application`" )
_ , err := db . GetEngine ( ctx ) . Exec ( b )
return - 1 , err
}