2021-04-14 20:02:12 +08:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-28 02:20:29 +08:00
// SPDX-License-Identifier: MIT
2021-04-14 20:02:12 +08:00
package setting
import (
2023-02-20 00:12:01 +08:00
"math"
"path/filepath"
2024-02-19 01:39:04 +08:00
"sync/atomic"
2023-02-20 00:12:01 +08:00
2023-04-25 23:06:39 +08:00
"code.gitea.io/gitea/modules/generate"
2021-04-14 20:02:12 +08:00
"code.gitea.io/gitea/modules/log"
)
// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data
type OAuth2UsernameType string
const (
2024-04-25 19:22:32 +08:00
OAuth2UsernameUserid OAuth2UsernameType = "userid" // use user id (sub) field as gitea's username
OAuth2UsernameNickname OAuth2UsernameType = "nickname" // use nickname field
OAuth2UsernameEmail OAuth2UsernameType = "email" // use email field
OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" // use preferred_username field
2021-04-14 20:02:12 +08:00
)
func ( username OAuth2UsernameType ) isValid ( ) bool {
switch username {
2024-04-16 13:41:39 +08:00
case OAuth2UsernameUserid , OAuth2UsernameNickname , OAuth2UsernameEmail , OAuth2UsernamePreferredUsername :
2021-04-14 20:02:12 +08:00
return true
}
return false
}
// OAuth2AccountLinkingType is enum describing behaviour of linking with existing account
type OAuth2AccountLinkingType string
const (
// OAuth2AccountLinkingDisabled error will be displayed if account exist
OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled"
// OAuth2AccountLinkingLogin account linking login will be displayed if account exist
OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login"
// OAuth2AccountLinkingAuto account will be automatically linked if account exist
OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto"
)
func ( accountLinking OAuth2AccountLinkingType ) isValid ( ) bool {
switch accountLinking {
case OAuth2AccountLinkingDisabled , OAuth2AccountLinkingLogin , OAuth2AccountLinkingAuto :
return true
}
return false
}
// OAuth2Client settings
var OAuth2Client struct {
RegisterEmailConfirm bool
OpenIDConnectScopes [ ] string
EnableAutoRegistration bool
Username OAuth2UsernameType
UpdateAvatar bool
AccountLinking OAuth2AccountLinkingType
}
2023-02-20 00:12:01 +08:00
func loadOAuth2ClientFrom ( rootCfg ConfigProvider ) {
sec := rootCfg . Section ( "oauth2_client" )
2021-04-14 20:02:12 +08:00
OAuth2Client . RegisterEmailConfirm = sec . Key ( "REGISTER_EMAIL_CONFIRM" ) . MustBool ( Service . RegisterEmailConfirm )
OAuth2Client . OpenIDConnectScopes = parseScopes ( sec , "OPENID_CONNECT_SCOPES" )
OAuth2Client . EnableAutoRegistration = sec . Key ( "ENABLE_AUTO_REGISTRATION" ) . MustBool ( )
OAuth2Client . Username = OAuth2UsernameType ( sec . Key ( "USERNAME" ) . MustString ( string ( OAuth2UsernameNickname ) ) )
if ! OAuth2Client . Username . isValid ( ) {
OAuth2Client . Username = OAuth2UsernameNickname
2024-04-25 19:22:32 +08:00
log . Warn ( "[oauth2_client].USERNAME setting is invalid, falls back to %q" , OAuth2Client . Username )
2021-04-14 20:02:12 +08:00
}
OAuth2Client . UpdateAvatar = sec . Key ( "UPDATE_AVATAR" ) . MustBool ( )
2021-05-07 22:15:16 +08:00
OAuth2Client . AccountLinking = OAuth2AccountLinkingType ( sec . Key ( "ACCOUNT_LINKING" ) . MustString ( string ( OAuth2AccountLinkingLogin ) ) )
2021-04-14 20:02:12 +08:00
if ! OAuth2Client . AccountLinking . isValid ( ) {
2021-05-07 22:15:16 +08:00
log . Warn ( "Account linking setting is not valid: '%s', will fallback to '%s'" , OAuth2Client . AccountLinking , OAuth2AccountLinkingLogin )
OAuth2Client . AccountLinking = OAuth2AccountLinkingLogin
2021-04-14 20:02:12 +08:00
}
}
2023-04-25 23:06:39 +08:00
func parseScopes ( sec ConfigSection , name string ) [ ] string {
2021-04-14 20:02:12 +08:00
parts := sec . Key ( name ) . Strings ( " " )
scopes := make ( [ ] string , 0 , len ( parts ) )
for _ , scope := range parts {
if scope != "" {
scopes = append ( scopes , scope )
}
}
return scopes
}
2023-02-20 00:12:01 +08:00
var OAuth2 = struct {
2024-01-28 20:36:44 +08:00
Enabled bool
2023-02-20 00:12:01 +08:00
AccessTokenExpirationTime int64
RefreshTokenExpirationTime int64
InvalidateRefreshTokens bool
JWTSigningAlgorithm string ` ini:"JWT_SIGNING_ALGORITHM" `
JWTSigningPrivateKeyFile string ` ini:"JWT_SIGNING_PRIVATE_KEY_FILE" `
MaxTokenLength int
2023-08-09 20:24:07 +08:00
DefaultApplications [ ] string
2023-02-20 00:12:01 +08:00
} {
2024-01-28 20:36:44 +08:00
Enabled : true ,
2023-02-20 00:12:01 +08:00
AccessTokenExpirationTime : 3600 ,
RefreshTokenExpirationTime : 730 ,
InvalidateRefreshTokens : false ,
JWTSigningAlgorithm : "RS256" ,
JWTSigningPrivateKeyFile : "jwt/private.pem" ,
MaxTokenLength : math . MaxInt16 ,
2023-10-08 11:51:08 +08:00
DefaultApplications : [ ] string { "git-credential-oauth" , "git-credential-manager" , "tea" } ,
2023-02-20 00:12:01 +08:00
}
func loadOAuth2From ( rootCfg ConfigProvider ) {
2024-01-28 20:36:44 +08:00
sec := rootCfg . Section ( "oauth2" )
if err := sec . MapTo ( & OAuth2 ) ; err != nil {
log . Fatal ( "Failed to map OAuth2 settings: %v" , err )
2023-02-20 00:12:01 +08:00
return
}
2024-04-08 12:13:34 +08:00
if sec . HasKey ( "DEFAULT_APPLICATIONS" ) && sec . Key ( "DEFAULT_APPLICATIONS" ) . String ( ) == "" {
OAuth2 . DefaultApplications = nil
}
2024-01-28 20:36:44 +08:00
// Handle the rename of ENABLE to ENABLED
deprecatedSetting ( rootCfg , "oauth2" , "ENABLE" , "oauth2" , "ENABLED" , "v1.23.0" )
if sec . HasKey ( "ENABLE" ) && ! sec . HasKey ( "ENABLED" ) {
OAuth2 . Enabled = sec . Key ( "ENABLE" ) . MustBool ( OAuth2 . Enabled )
}
if ! OAuth2 . Enabled {
2023-06-29 05:30:06 +08:00
return
}
2024-02-19 01:39:04 +08:00
jwtSecretBase64 := loadSecret ( sec , "JWT_SECRET_URI" , "JWT_SECRET" )
2023-06-23 08:16:12 +08:00
2023-02-20 00:12:01 +08:00
if ! filepath . IsAbs ( OAuth2 . JWTSigningPrivateKeyFile ) {
OAuth2 . JWTSigningPrivateKeyFile = filepath . Join ( AppDataPath , OAuth2 . JWTSigningPrivateKeyFile )
}
2023-04-25 23:06:39 +08:00
2023-06-19 00:10:44 +08:00
if InstallLock {
2024-02-19 01:39:04 +08:00
jwtSecretBytes , err := generate . DecodeJwtSecretBase64 ( jwtSecretBase64 )
if err != nil {
jwtSecretBytes , jwtSecretBase64 , err = generate . NewJwtSecretWithBase64 ( )
2023-06-19 00:10:44 +08:00
if err != nil {
log . Fatal ( "error generating JWT secret: %v" , err )
}
2023-06-21 10:31:40 +08:00
saveCfg , err := rootCfg . PrepareSaving ( )
if err != nil {
log . Fatal ( "save oauth2.JWT_SECRET failed: %v" , err )
}
2024-02-19 01:39:04 +08:00
rootCfg . Section ( "oauth2" ) . Key ( "JWT_SECRET" ) . SetValue ( jwtSecretBase64 )
saveCfg . Section ( "oauth2" ) . Key ( "JWT_SECRET" ) . SetValue ( jwtSecretBase64 )
2023-06-21 10:31:40 +08:00
if err := saveCfg . Save ( ) ; err != nil {
2023-06-19 00:10:44 +08:00
log . Fatal ( "save oauth2.JWT_SECRET failed: %v" , err )
}
2023-04-25 23:06:39 +08:00
}
2024-02-19 01:39:04 +08:00
generalSigningSecret . Store ( & jwtSecretBytes )
}
}
// generalSigningSecret is used as container for a []byte value
// instead of an additional mutex, we use CompareAndSwap func to change the value thread save
var generalSigningSecret atomic . Pointer [ [ ] byte ]
func GetGeneralTokenSigningSecret ( ) [ ] byte {
old := generalSigningSecret . Load ( )
if old == nil || len ( * old ) == 0 {
jwtSecret , _ , err := generate . NewJwtSecretWithBase64 ( )
if err != nil {
log . Fatal ( "Unable to generate general JWT secret: %s" , err . Error ( ) )
}
if generalSigningSecret . CompareAndSwap ( old , & jwtSecret ) {
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...)
2024-04-24 00:18:41 +08:00
LogStartupProblem ( 1 , log . WARN , "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes" )
2024-02-19 01:39:04 +08:00
return jwtSecret
}
return * generalSigningSecret . Load ( )
2023-04-25 23:06:39 +08:00
}
2024-02-19 01:39:04 +08:00
return * old
2023-02-20 00:12:01 +08:00
}