2021-04-14 12:02:12 +00:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 18:20:29 +00:00
// SPDX-License-Identifier: MIT
2021-04-14 12:02:12 +00:00
package setting
import (
2023-02-19 16:12:01 +00:00
"math"
"path/filepath"
2024-02-22 17:07:41 +00:00
"sync/atomic"
2023-02-19 16:12:01 +00:00
2023-04-25 15:06:39 +00:00
"code.gitea.io/gitea/modules/generate"
2021-04-14 12:02:12 +00: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 (
// OAuth2UsernameUserid oauth2 userid field will be used as gitea name
OAuth2UsernameUserid OAuth2UsernameType = "userid"
// OAuth2UsernameNickname oauth2 nickname field will be used as gitea name
OAuth2UsernameNickname OAuth2UsernameType = "nickname"
// OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name
OAuth2UsernameEmail OAuth2UsernameType = "email"
)
func ( username OAuth2UsernameType ) isValid ( ) bool {
switch username {
case OAuth2UsernameUserid , OAuth2UsernameNickname , OAuth2UsernameEmail :
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-19 16:12:01 +00:00
func loadOAuth2ClientFrom ( rootCfg ConfigProvider ) {
sec := rootCfg . Section ( "oauth2_client" )
2021-04-14 12:02:12 +00: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 ( ) {
log . Warn ( "Username setting is not valid: '%s', will fallback to '%s'" , OAuth2Client . Username , OAuth2UsernameNickname )
OAuth2Client . Username = OAuth2UsernameNickname
}
OAuth2Client . UpdateAvatar = sec . Key ( "UPDATE_AVATAR" ) . MustBool ( )
2021-05-07 14:15:16 +00:00
OAuth2Client . AccountLinking = OAuth2AccountLinkingType ( sec . Key ( "ACCOUNT_LINKING" ) . MustString ( string ( OAuth2AccountLinkingLogin ) ) )
2021-04-14 12:02:12 +00:00
if ! OAuth2Client . AccountLinking . isValid ( ) {
2021-05-07 14:15:16 +00:00
log . Warn ( "Account linking setting is not valid: '%s', will fallback to '%s'" , OAuth2Client . AccountLinking , OAuth2AccountLinkingLogin )
OAuth2Client . AccountLinking = OAuth2AccountLinkingLogin
2021-04-14 12:02:12 +00:00
}
}
2023-04-25 15:06:39 +00:00
func parseScopes ( sec ConfigSection , name string ) [ ] string {
2021-04-14 12:02:12 +00: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-19 16:12:01 +00:00
var OAuth2 = struct {
Enable bool
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 12:24:07 +00:00
DefaultApplications [ ] string
2023-02-19 16:12:01 +00:00
} {
Enable : true ,
AccessTokenExpirationTime : 3600 ,
RefreshTokenExpirationTime : 730 ,
InvalidateRefreshTokens : false ,
JWTSigningAlgorithm : "RS256" ,
JWTSigningPrivateKeyFile : "jwt/private.pem" ,
MaxTokenLength : math . MaxInt16 ,
2023-08-09 12:24:07 +00:00
DefaultApplications : [ ] string { "git-credential-oauth" , "git-credential-manager" } ,
2023-02-19 16:12:01 +00:00
}
func loadOAuth2From ( rootCfg ConfigProvider ) {
if err := rootCfg . Section ( "oauth2" ) . MapTo ( & OAuth2 ) ; err != nil {
log . Fatal ( "Failed to OAuth2 settings: %v" , err )
return
}
2023-06-28 21:30:06 +00:00
if ! OAuth2 . Enable {
return
}
2024-02-22 17:07:41 +00:00
jwtSecretBase64 := loadSecret ( rootCfg . Section ( "oauth2" ) , "JWT_SECRET_URI" , "JWT_SECRET" )
2023-06-23 00:16:12 +00:00
2023-02-19 16:12:01 +00:00
if ! filepath . IsAbs ( OAuth2 . JWTSigningPrivateKeyFile ) {
OAuth2 . JWTSigningPrivateKeyFile = filepath . Join ( AppDataPath , OAuth2 . JWTSigningPrivateKeyFile )
}
2023-04-25 15:06:39 +00:00
2023-06-18 16:10:44 +00:00
if InstallLock {
2024-02-22 17:07:41 +00:00
jwtSecretBytes , err := generate . DecodeJwtSecretBase64 ( jwtSecretBase64 )
if err != nil {
jwtSecretBytes , jwtSecretBase64 , err = generate . NewJwtSecretWithBase64 ( )
2023-06-18 16:10:44 +00:00
if err != nil {
log . Fatal ( "error generating JWT secret: %v" , err )
}
2023-06-21 02:31:40 +00:00
saveCfg , err := rootCfg . PrepareSaving ( )
if err != nil {
log . Fatal ( "save oauth2.JWT_SECRET failed: %v" , err )
}
2024-02-22 17:07:41 +00:00
rootCfg . Section ( "oauth2" ) . Key ( "JWT_SECRET" ) . SetValue ( jwtSecretBase64 )
saveCfg . Section ( "oauth2" ) . Key ( "JWT_SECRET" ) . SetValue ( jwtSecretBase64 )
2023-06-21 02:31:40 +00:00
if err := saveCfg . Save ( ) ; err != nil {
2023-06-18 16:10:44 +00:00
log . Fatal ( "save oauth2.JWT_SECRET failed: %v" , err )
}
2023-04-25 15:06:39 +00:00
}
2024-02-22 17:07:41 +00: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 ...)
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" )
return jwtSecret
}
return * generalSigningSecret . Load ( )
2023-04-25 15:06:39 +00:00
}
2024-02-22 17:07:41 +00:00
return * old
2023-02-19 16:12:01 +00:00
}