code.gitea.io/gitea@v1.22.3/modules/setting/oauth2.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package setting 5 6 import ( 7 "math" 8 "path/filepath" 9 "sync/atomic" 10 11 "code.gitea.io/gitea/modules/generate" 12 "code.gitea.io/gitea/modules/log" 13 ) 14 15 // OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data 16 type OAuth2UsernameType string 17 18 const ( 19 OAuth2UsernameUserid OAuth2UsernameType = "userid" // use user id (sub) field as gitea's username 20 OAuth2UsernameNickname OAuth2UsernameType = "nickname" // use nickname field 21 OAuth2UsernameEmail OAuth2UsernameType = "email" // use email field 22 OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" // use preferred_username field 23 ) 24 25 func (username OAuth2UsernameType) isValid() bool { 26 switch username { 27 case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail, OAuth2UsernamePreferredUsername: 28 return true 29 } 30 return false 31 } 32 33 // OAuth2AccountLinkingType is enum describing behaviour of linking with existing account 34 type OAuth2AccountLinkingType string 35 36 const ( 37 // OAuth2AccountLinkingDisabled error will be displayed if account exist 38 OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled" 39 // OAuth2AccountLinkingLogin account linking login will be displayed if account exist 40 OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login" 41 // OAuth2AccountLinkingAuto account will be automatically linked if account exist 42 OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto" 43 ) 44 45 func (accountLinking OAuth2AccountLinkingType) isValid() bool { 46 switch accountLinking { 47 case OAuth2AccountLinkingDisabled, OAuth2AccountLinkingLogin, OAuth2AccountLinkingAuto: 48 return true 49 } 50 return false 51 } 52 53 // OAuth2Client settings 54 var OAuth2Client struct { 55 RegisterEmailConfirm bool 56 OpenIDConnectScopes []string 57 EnableAutoRegistration bool 58 Username OAuth2UsernameType 59 UpdateAvatar bool 60 AccountLinking OAuth2AccountLinkingType 61 } 62 63 func loadOAuth2ClientFrom(rootCfg ConfigProvider) { 64 sec := rootCfg.Section("oauth2_client") 65 OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) 66 OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") 67 OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() 68 OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) 69 if !OAuth2Client.Username.isValid() { 70 OAuth2Client.Username = OAuth2UsernameNickname 71 log.Warn("[oauth2_client].USERNAME setting is invalid, falls back to %q", OAuth2Client.Username) 72 } 73 OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() 74 OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin))) 75 if !OAuth2Client.AccountLinking.isValid() { 76 log.Warn("Account linking setting is not valid: '%s', will fallback to '%s'", OAuth2Client.AccountLinking, OAuth2AccountLinkingLogin) 77 OAuth2Client.AccountLinking = OAuth2AccountLinkingLogin 78 } 79 } 80 81 func parseScopes(sec ConfigSection, name string) []string { 82 parts := sec.Key(name).Strings(" ") 83 scopes := make([]string, 0, len(parts)) 84 for _, scope := range parts { 85 if scope != "" { 86 scopes = append(scopes, scope) 87 } 88 } 89 return scopes 90 } 91 92 var OAuth2 = struct { 93 Enabled bool 94 AccessTokenExpirationTime int64 95 RefreshTokenExpirationTime int64 96 InvalidateRefreshTokens bool 97 JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"` 98 JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"` 99 MaxTokenLength int 100 DefaultApplications []string 101 }{ 102 Enabled: true, 103 AccessTokenExpirationTime: 3600, 104 RefreshTokenExpirationTime: 730, 105 InvalidateRefreshTokens: false, 106 JWTSigningAlgorithm: "RS256", 107 JWTSigningPrivateKeyFile: "jwt/private.pem", 108 MaxTokenLength: math.MaxInt16, 109 DefaultApplications: []string{"git-credential-oauth", "git-credential-manager", "tea"}, 110 } 111 112 func loadOAuth2From(rootCfg ConfigProvider) { 113 sec := rootCfg.Section("oauth2") 114 if err := sec.MapTo(&OAuth2); err != nil { 115 log.Fatal("Failed to map OAuth2 settings: %v", err) 116 return 117 } 118 119 if sec.HasKey("DEFAULT_APPLICATIONS") && sec.Key("DEFAULT_APPLICATIONS").String() == "" { 120 OAuth2.DefaultApplications = nil 121 } 122 123 // Handle the rename of ENABLE to ENABLED 124 deprecatedSetting(rootCfg, "oauth2", "ENABLE", "oauth2", "ENABLED", "v1.23.0") 125 if sec.HasKey("ENABLE") && !sec.HasKey("ENABLED") { 126 OAuth2.Enabled = sec.Key("ENABLE").MustBool(OAuth2.Enabled) 127 } 128 129 if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) { 130 OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile) 131 } 132 133 // FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET" 134 // Because this secret is also used as GeneralTokenSigningSecret (as a quick not-that-breaking fix for some legacy problems). 135 // Including: CSRF token, account validation token, etc ... 136 // In main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) 137 jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET") 138 if InstallLock { 139 jwtSecretBytes, err := generate.DecodeJwtSecretBase64(jwtSecretBase64) 140 if err != nil { 141 jwtSecretBytes, jwtSecretBase64, err = generate.NewJwtSecretWithBase64() 142 if err != nil { 143 log.Fatal("error generating JWT secret: %v", err) 144 } 145 saveCfg, err := rootCfg.PrepareSaving() 146 if err != nil { 147 log.Fatal("save oauth2.JWT_SECRET failed: %v", err) 148 } 149 rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64) 150 saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64) 151 if err := saveCfg.Save(); err != nil { 152 log.Fatal("save oauth2.JWT_SECRET failed: %v", err) 153 } 154 } 155 generalSigningSecret.Store(&jwtSecretBytes) 156 } 157 } 158 159 var generalSigningSecret atomic.Pointer[[]byte] 160 161 func GetGeneralTokenSigningSecret() []byte { 162 old := generalSigningSecret.Load() 163 if old == nil || len(*old) == 0 { 164 jwtSecret, _, err := generate.NewJwtSecretWithBase64() 165 if err != nil { 166 log.Fatal("Unable to generate general JWT secret: %v", err) 167 } 168 if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { 169 return jwtSecret 170 } 171 return *generalSigningSecret.Load() 172 } 173 return *old 174 }