Refactor: Move login out of models (#16199)

`models` does far too much. In particular it handles all `UserSignin`.

It shouldn't be responsible for calling LDAP, SMTP or PAM for signing in.

Therefore we should move this code out of `models`.

This code has to depend on `models` - therefore it belongs in `services`.

There is a package in `services` called `auth` and clearly this functionality belongs in there.

Plan:

- [x] Change `auth.Auth` to `auth.Method` - as they represent methods of authentication.
- [x] Move `models.UserSignIn` into `auth`
- [x] Move `models.ExternalUserLogin`
- [x] Move most of the `LoginVia*` methods to `auth` or subpackages
- [x] Move Resynchronize functionality to `auth`
  - Involved some restructuring of `models/ssh_key.go` to reduce the size of this massive file and simplify its files.
- [x] Move the rest of the LDAP functionality in to the ldap subpackage
- [x] Re-factor the login sources to express an interfaces `auth.Source`?
  - I've done this through some smaller interfaces Authenticator and Synchronizable - which would allow us to extend things in future
- [x] Now LDAP is out of models - need to think about modules/auth/ldap and I think all of that functionality might just be moveable
- [x] Similarly a lot Oauth2 functionality need not be in models too and should be moved to services/auth/source/oauth2
  - [x] modules/auth/oauth2/oauth2.go uses xorm... This is naughty - probably need to move this into models.
  - [x] models/oauth2.go - mostly should be in modules/auth/oauth2 or services/auth/source/oauth2 
- [x] More simplifications of login_source.go may need to be done
- Allow wiring in of notify registration -  *this can now easily be done - but I think we should do it in another PR*  - see #16178 
- More refactors...?
  - OpenID should probably become an auth Method but I think that can be left for another PR
  - Methods should also probably be cleaned up  - again another PR I think.
  - SSPI still needs more refactors.* Rename auth.Auth auth.Method
* Restructure ssh_key.go

- move functions from models/user.go that relate to ssh_key to ssh_key
- split ssh_key.go to try create clearer function domains for allow for
future refactors here.

Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
zeripath
2021-07-24 11:16:34 +01:00
committed by GitHub
parent f135a818f5
commit 5d2e11eedb
77 changed files with 3803 additions and 2951 deletions

View File

@ -14,7 +14,6 @@ import (
"text/tabwriter"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
@ -22,6 +21,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/urfave/cli"
)
@ -597,7 +597,7 @@ func runRegenerateKeys(_ *cli.Context) error {
return models.RewriteAllPublicKeys()
}
func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
@ -609,7 +609,7 @@ func parseOAuth2Config(c *cli.Context) *models.OAuth2Config {
} else {
customURLMapping = nil
}
return &models.OAuth2Config{
return &oauth2.Source{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
@ -627,7 +627,7 @@ func runAddOauth(c *cli.Context) error {
return models.CreateLoginSource(&models.LoginSource{
Type: models.LoginOAuth2,
Name: c.String("name"),
IsActived: true,
IsActive: true,
Cfg: parseOAuth2Config(c),
})
}
@ -646,7 +646,7 @@ func runUpdateOauth(c *cli.Context) error {
return err
}
oAuth2Config := source.OAuth2()
oAuth2Config := source.Cfg.(*oauth2.Source)
if c.IsSet("name") {
source.Name = c.String("name")
@ -728,7 +728,7 @@ func runListAuth(c *cli.Context) error {
w := tabwriter.NewWriter(os.Stdout, c.Int("min-width"), c.Int("tab-width"), c.Int("padding"), padChar, flags)
fmt.Fprintf(w, "ID\tName\tType\tEnabled\n")
for _, source := range loginSources {
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActived)
fmt.Fprintf(w, "%d\t%s\t%s\t%t\n", source.ID, source.Name, models.LoginNames[source.Type], source.IsActive)
}
w.Flush()

View File

@ -9,7 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/services/auth/source/ldap"
"github.com/urfave/cli"
)
@ -172,7 +172,7 @@ func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
loginSource.Name = c.String("name")
}
if c.IsSet("not-active") {
loginSource.IsActived = !c.Bool("not-active")
loginSource.IsActive = !c.Bool("not-active")
}
if c.IsSet("synchronize-users") {
loginSource.IsSyncEnabled = c.Bool("synchronize-users")
@ -180,70 +180,70 @@ func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) {
}
// parseLdapConfig assigns values on config according to command line flags.
func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
if c.IsSet("name") {
config.Source.Name = c.String("name")
config.Name = c.String("name")
}
if c.IsSet("host") {
config.Source.Host = c.String("host")
config.Host = c.String("host")
}
if c.IsSet("port") {
config.Source.Port = c.Int("port")
config.Port = c.Int("port")
}
if c.IsSet("security-protocol") {
p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
if !ok {
return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
}
config.Source.SecurityProtocol = p
config.SecurityProtocol = p
}
if c.IsSet("skip-tls-verify") {
config.Source.SkipVerify = c.Bool("skip-tls-verify")
config.SkipVerify = c.Bool("skip-tls-verify")
}
if c.IsSet("bind-dn") {
config.Source.BindDN = c.String("bind-dn")
config.BindDN = c.String("bind-dn")
}
if c.IsSet("user-dn") {
config.Source.UserDN = c.String("user-dn")
config.UserDN = c.String("user-dn")
}
if c.IsSet("bind-password") {
config.Source.BindPassword = c.String("bind-password")
config.BindPassword = c.String("bind-password")
}
if c.IsSet("user-search-base") {
config.Source.UserBase = c.String("user-search-base")
config.UserBase = c.String("user-search-base")
}
if c.IsSet("username-attribute") {
config.Source.AttributeUsername = c.String("username-attribute")
config.AttributeUsername = c.String("username-attribute")
}
if c.IsSet("firstname-attribute") {
config.Source.AttributeName = c.String("firstname-attribute")
config.AttributeName = c.String("firstname-attribute")
}
if c.IsSet("surname-attribute") {
config.Source.AttributeSurname = c.String("surname-attribute")
config.AttributeSurname = c.String("surname-attribute")
}
if c.IsSet("email-attribute") {
config.Source.AttributeMail = c.String("email-attribute")
config.AttributeMail = c.String("email-attribute")
}
if c.IsSet("attributes-in-bind") {
config.Source.AttributesInBind = c.Bool("attributes-in-bind")
config.AttributesInBind = c.Bool("attributes-in-bind")
}
if c.IsSet("public-ssh-key-attribute") {
config.Source.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
}
if c.IsSet("page-size") {
config.Source.SearchPageSize = uint32(c.Uint("page-size"))
config.SearchPageSize = uint32(c.Uint("page-size"))
}
if c.IsSet("user-filter") {
config.Source.Filter = c.String("user-filter")
config.Filter = c.String("user-filter")
}
if c.IsSet("admin-filter") {
config.Source.AdminFilter = c.String("admin-filter")
config.AdminFilter = c.String("admin-filter")
}
if c.IsSet("restricted-filter") {
config.Source.RestrictedFilter = c.String("restricted-filter")
config.RestrictedFilter = c.String("restricted-filter")
}
if c.IsSet("allow-deactivate-all") {
config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
return nil
}
@ -251,7 +251,7 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
// findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
// It returns the value of the security protocol and if it was found.
func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
for i, n := range models.SecurityProtocolNames {
for i, n := range ldap.SecurityProtocolNames {
if strings.EqualFold(name, n) {
return i, true
}
@ -290,16 +290,14 @@ func (a *authService) addLdapBindDn(c *cli.Context) error {
loginSource := &models.LoginSource{
Type: models.LoginLDAP,
IsActived: true, // active by default
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
},
},
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@ -318,7 +316,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error {
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@ -337,16 +335,14 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
loginSource := &models.LoginSource{
Type: models.LoginDLDAP,
IsActived: true, // active by default
Cfg: &models.LDAPConfig{
Source: &ldap.Source{
IsActive: true, // active by default
Cfg: &ldap.Source{
Enabled: true, // always true
},
},
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
return err
}
@ -365,7 +361,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
}
parseLoginSource(c, loginSource)
if err := parseLdapConfig(c, loginSource.LDAP()); err != nil {
if err := parseLdapConfig(c, loginSource.Cfg.(*ldap.Source)); err != nil {
return err
}

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import (
"strings"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/auth"
"github.com/stretchr/testify/assert"
"github.com/unknwon/i18n"
@ -205,7 +205,7 @@ func TestLDAPUserSync(t *testing.T) {
}
defer prepareTestEnv(t)()
addAuthSourceLDAP(t, "")
models.SyncExternalUsers(context.Background(), true)
auth.SyncExternalUsers(context.Background(), true)
session := loginUser(t, "user1")
// Check if users exists
@ -270,7 +270,7 @@ func TestLDAPUserSSHKeySync(t *testing.T) {
defer prepareTestEnv(t)()
addAuthSourceLDAP(t, "sshPublicKey")
models.SyncExternalUsers(context.Background(), true)
auth.SyncExternalUsers(context.Background(), true)
// Check if users has SSH keys synced
for _, u := range gitLDAPUsers {

View File

@ -4,6 +4,12 @@
package models
import (
"encoding/binary"
jsoniter "github.com/json-iterator/go"
)
func keysInt64(m map[int64]struct{}) []int64 {
keys := make([]int64, 0, len(m))
for k := range m {
@ -27,3 +33,33 @@ func valuesUser(m map[int64]*User) []*User {
}
return values
}
// JSONUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
// possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
func JSONUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
json := jsoniter.ConfigCompatibleWithStandardLibrary
err := json.Unmarshal(bs, v)
if err != nil {
ok := true
rs := []byte{}
temp := make([]byte, 2)
for _, rn := range string(bs) {
if rn > 0xffff {
ok = false
break
}
binary.LittleEndian.PutUint16(temp, uint16(rn))
rs = append(rs, temp...)
}
if ok {
if rs[0] == 0xff && rs[1] == 0xfe {
rs = rs[2:]
}
err = json.Unmarshal(rs, v)
}
}
if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
err = json.Unmarshal(bs[2:], v)
}
return err
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
# type LoginSource struct {
# ID int64 `xorm:"pk autoincr"`
# Type int
# Cfg []byte `xorm:"TEXT"`
# Expected []byte `xorm:"TEXT"`
# }
-
id: 1
type: 1
is_actived: false
cfg: "{\"Source\":{\"A\":\"string\",\"B\":1}}"
expected: "{\"Source\":{\"A\":\"string\",\"B\":1}}"
-
id: 2
type: 2
is_actived: true
cfg: "{\"Source\":{\"A\":\"string2\",\"B\":2}}"
expected: "{\"A\":\"string2\",\"B\":2}"
-
id: 3
type: 3
is_actived: false
cfg: "{\"Source\":{\"A\":\"string3\",\"B\":3}}"
expected: "{\"Source\":{\"A\":\"string3\",\"B\":3}}"
-
id: 4
type: 4
is_actived: true
cfg: "{\"Source\":{\"A\":\"string4\",\"B\":4}}"
expected: "{\"Source\":{\"A\":\"string4\",\"B\":4}}"
-
id: 5
type: 5
is_actived: false
cfg: "{\"Source\":{\"A\":\"string5\",\"B\":5}}"
expected: "{\"A\":\"string5\",\"B\":5}"
-
id: 6
type: 2
is_actived: true
cfg: "{\"A\":\"string6\",\"B\":6}"
expected: "{\"A\":\"string6\",\"B\":6}"
-
id: 7
type: 5
is_actived: false
cfg: "{\"A\":\"string7\",\"B\":7}"
expected: "{\"A\":\"string7\",\"B\":7}"

View File

@ -327,6 +327,8 @@ var migrations = []Migration{
NewMigration("Drop unneeded webhook related columns", dropWebhookColumns),
// v188 -> v189
NewMigration("Add key is verified to gpg key", addKeyIsVerified),
// v189 -> v190
NewMigration("Unwrap ldap.Sources", unwrapLDAPSourceCfg),
}
// GetCurrentDBVersion returns the current db version

View File

@ -220,6 +220,9 @@ func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.En
if err := x.Close(); err != nil {
t.Errorf("error during close: %v", err)
}
if err := deleteDB(); err != nil {
t.Errorf("unable to reset database: %v", err)
}
}
}
if err != nil {

111
models/migrations/v189.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"encoding/binary"
"fmt"
jsoniter "github.com/json-iterator/go"
"xorm.io/xorm"
)
func unwrapLDAPSourceCfg(x *xorm.Engine) error {
jsonUnmarshalHandleDoubleEncode := func(bs []byte, v interface{}) error {
json := jsoniter.ConfigCompatibleWithStandardLibrary
err := json.Unmarshal(bs, v)
if err != nil {
ok := true
rs := []byte{}
temp := make([]byte, 2)
for _, rn := range string(bs) {
if rn > 0xffff {
ok = false
break
}
binary.LittleEndian.PutUint16(temp, uint16(rn))
rs = append(rs, temp...)
}
if ok {
if rs[0] == 0xff && rs[1] == 0xfe {
rs = rs[2:]
}
err = json.Unmarshal(rs, v)
}
}
if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
err = json.Unmarshal(bs[2:], v)
}
return err
}
// LoginSource represents an external way for authorizing users.
type LoginSource struct {
ID int64 `xorm:"pk autoincr"`
Type int
IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
Cfg string `xorm:"TEXT"`
}
const ldapType = 2
const dldapType = 5
type WrappedSource struct {
Source map[string]interface{}
}
// change lower_email as unique
if err := x.Sync2(new(LoginSource)); err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
const batchSize = 100
for start := 0; ; start += batchSize {
sources := make([]*LoginSource, 0, batchSize)
if err := sess.Limit(batchSize, start).Where("`type` = ? OR `type` = ?", ldapType, dldapType).Find(&sources); err != nil {
return err
}
if len(sources) == 0 {
break
}
for _, source := range sources {
wrapped := &WrappedSource{
Source: map[string]interface{}{},
}
err := jsonUnmarshalHandleDoubleEncode([]byte(source.Cfg), &wrapped)
if err != nil {
return fmt.Errorf("failed to unmarshal %s: %w", string(source.Cfg), err)
}
if wrapped.Source != nil && len(wrapped.Source) > 0 {
bs, err := jsoniter.Marshal(wrapped.Source)
if err != nil {
return err
}
source.Cfg = string(bs)
if _, err := sess.ID(source.ID).Cols("cfg").Update(source); err != nil {
return err
}
}
}
}
if _, err := x.SetExpr("is_active", "is_actived").Update(&LoginSource{}); err != nil {
return fmt.Errorf("SetExpr Update failed: %w", err)
}
if err := sess.Begin(); err != nil {
return err
}
if err := dropTableColumns(sess, "login_source", "is_actived"); err != nil {
return err
}
return sess.Commit()
}

View File

@ -0,0 +1,83 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"testing"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
)
// LoginSource represents an external way for authorizing users.
type LoginSourceOriginalV189 struct {
ID int64 `xorm:"pk autoincr"`
Type int
IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
Cfg string `xorm:"TEXT"`
Expected string `xorm:"TEXT"`
}
func (ls *LoginSourceOriginalV189) TableName() string {
return "login_source"
}
func Test_unwrapLDAPSourceCfg(t *testing.T) {
// Prepare and load the testing database
x, deferable := prepareTestEnv(t, 0, new(LoginSourceOriginalV189))
if x == nil || t.Failed() {
defer deferable()
return
}
defer deferable()
// LoginSource represents an external way for authorizing users.
type LoginSource struct {
ID int64 `xorm:"pk autoincr"`
Type int
IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"`
Cfg string `xorm:"TEXT"`
Expected string `xorm:"TEXT"`
}
// Run the migration
if err := unwrapLDAPSourceCfg(x); err != nil {
assert.NoError(t, err)
return
}
const batchSize = 100
for start := 0; ; start += batchSize {
sources := make([]*LoginSource, 0, batchSize)
if err := x.Table("login_source").Limit(batchSize, start).Find(&sources); err != nil {
assert.NoError(t, err)
return
}
if len(sources) == 0 {
break
}
for _, source := range sources {
converted := map[string]interface{}{}
expected := map[string]interface{}{}
if err := jsoniter.Unmarshal([]byte(source.Cfg), &converted); err != nil {
assert.NoError(t, err)
return
}
if err := jsoniter.Unmarshal([]byte(source.Expected), &expected); err != nil {
assert.NoError(t, err)
return
}
assert.EqualValues(t, expected, converted, "unwrapLDAPSourceCfg failed for %d", source.ID)
assert.EqualValues(t, source.ID%2 == 0, source.IsActive, "unwrapLDAPSourceCfg failed for %d", source.ID)
}
}
}

View File

@ -4,89 +4,10 @@
package models
import (
"sort"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/log"
)
// OAuth2Provider describes the display values of a single OAuth2 provider
type OAuth2Provider struct {
Name string
DisplayName string
Image string
CustomURLMapping *oauth2.CustomURLMapping
}
// OAuth2Providers contains the map of registered OAuth2 providers in Gitea (based on goth)
// key is used to map the OAuth2Provider with the goth provider type (also in LoginSource.OAuth2Config.Provider)
// value is used to store display data
var OAuth2Providers = map[string]OAuth2Provider{
"bitbucket": {Name: "bitbucket", DisplayName: "Bitbucket", Image: "/assets/img/auth/bitbucket.png"},
"dropbox": {Name: "dropbox", DisplayName: "Dropbox", Image: "/assets/img/auth/dropbox.png"},
"facebook": {Name: "facebook", DisplayName: "Facebook", Image: "/assets/img/auth/facebook.png"},
"github": {
Name: "github", DisplayName: "GitHub", Image: "/assets/img/auth/github.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("github"),
AuthURL: oauth2.GetDefaultAuthURL("github"),
ProfileURL: oauth2.GetDefaultProfileURL("github"),
EmailURL: oauth2.GetDefaultEmailURL("github"),
},
},
"gitlab": {
Name: "gitlab", DisplayName: "GitLab", Image: "/assets/img/auth/gitlab.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("gitlab"),
AuthURL: oauth2.GetDefaultAuthURL("gitlab"),
ProfileURL: oauth2.GetDefaultProfileURL("gitlab"),
},
},
"gplus": {Name: "gplus", DisplayName: "Google", Image: "/assets/img/auth/google.png"},
"openidConnect": {Name: "openidConnect", DisplayName: "OpenID Connect", Image: "/assets/img/auth/openid_connect.svg"},
"twitter": {Name: "twitter", DisplayName: "Twitter", Image: "/assets/img/auth/twitter.png"},
"discord": {Name: "discord", DisplayName: "Discord", Image: "/assets/img/auth/discord.png"},
"gitea": {
Name: "gitea", DisplayName: "Gitea", Image: "/assets/img/auth/gitea.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("gitea"),
AuthURL: oauth2.GetDefaultAuthURL("gitea"),
ProfileURL: oauth2.GetDefaultProfileURL("gitea"),
},
},
"nextcloud": {
Name: "nextcloud", DisplayName: "Nextcloud", Image: "/assets/img/auth/nextcloud.png",
CustomURLMapping: &oauth2.CustomURLMapping{
TokenURL: oauth2.GetDefaultTokenURL("nextcloud"),
AuthURL: oauth2.GetDefaultAuthURL("nextcloud"),
ProfileURL: oauth2.GetDefaultProfileURL("nextcloud"),
},
},
"yandex": {Name: "yandex", DisplayName: "Yandex", Image: "/assets/img/auth/yandex.png"},
"mastodon": {
Name: "mastodon", DisplayName: "Mastodon", Image: "/assets/img/auth/mastodon.png",
CustomURLMapping: &oauth2.CustomURLMapping{
AuthURL: oauth2.GetDefaultAuthURL("mastodon"),
},
},
}
// OAuth2DefaultCustomURLMappings contains the map of default URL's for OAuth2 providers that are allowed to have custom urls
// key is used to map the OAuth2Provider
// value is the mapping as defined for the OAuth2Provider
var OAuth2DefaultCustomURLMappings = map[string]*oauth2.CustomURLMapping{
"github": OAuth2Providers["github"].CustomURLMapping,
"gitlab": OAuth2Providers["gitlab"].CustomURLMapping,
"gitea": OAuth2Providers["gitea"].CustomURLMapping,
"nextcloud": OAuth2Providers["nextcloud"].CustomURLMapping,
"mastodon": OAuth2Providers["mastodon"].CustomURLMapping,
}
// GetActiveOAuth2ProviderLoginSources returns all actived LoginOAuth2 sources
func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
sources := make([]*LoginSource, 0, 1)
if err := x.Where("is_actived = ? and type = ?", true, LoginOAuth2).Find(&sources); err != nil {
if err := x.Where("is_active = ? and type = ?", true, LoginOAuth2).Find(&sources); err != nil {
return nil, err
}
return sources, nil
@ -95,81 +16,10 @@ func GetActiveOAuth2ProviderLoginSources() ([]*LoginSource, error) {
// GetActiveOAuth2LoginSourceByName returns a OAuth2 LoginSource based on the given name
func GetActiveOAuth2LoginSourceByName(name string) (*LoginSource, error) {
loginSource := new(LoginSource)
has, err := x.Where("name = ? and type = ? and is_actived = ?", name, LoginOAuth2, true).Get(loginSource)
has, err := x.Where("name = ? and type = ? and is_active = ?", name, LoginOAuth2, true).Get(loginSource)
if !has || err != nil {
return nil, err
}
return loginSource, nil
}
// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers
// key is used as technical name (like in the callbackURL)
// values to display
func GetActiveOAuth2Providers() ([]string, map[string]OAuth2Provider, error) {
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type
loginSources, err := GetActiveOAuth2ProviderLoginSources()
if err != nil {
return nil, nil, err
}
var orderedKeys []string
providers := make(map[string]OAuth2Provider)
for _, source := range loginSources {
prov := OAuth2Providers[source.OAuth2().Provider]
if source.OAuth2().IconURL != "" {
prov.Image = source.OAuth2().IconURL
}
providers[source.Name] = prov
orderedKeys = append(orderedKeys, source.Name)
}
sort.Strings(orderedKeys)
return orderedKeys, providers, nil
}
// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
func InitOAuth2() error {
if err := oauth2.InitSigningKey(); err != nil {
return err
}
if err := oauth2.Init(x); err != nil {
return err
}
return initOAuth2LoginSources()
}
// ResetOAuth2 clears existing OAuth2 providers and loads them from DB
func ResetOAuth2() error {
oauth2.ClearProviders()
return initOAuth2LoginSources()
}
// initOAuth2LoginSources is used to load and register all active OAuth2 providers
func initOAuth2LoginSources() error {
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
for _, source := range loginSources {
oAuth2Config := source.OAuth2()
err := oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
if err != nil {
log.Critical("Unable to register source: %s due to Error: %v. This source will be disabled.", source.Name, err)
source.IsActived = false
if err = UpdateSource(source); err != nil {
log.Critical("Unable to update source %s to disable it. Error: %v", err)
return err
}
}
}
return nil
}
// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
// inside oauth2: import cycle not allowed models -> modules/auth/oauth2 -> models
func wrapOpenIDConnectInitializeError(err error, providerName string, oAuth2Config *OAuth2Config) error {
if err != nil && "openidConnect" == oAuth2Config.Provider {
err = ErrOpenIDConnectInitialize{ProviderName: providerName, OpenIDConnectAutoDiscoveryURL: oAuth2Config.OpenIDConnectAutoDiscoveryURL, Cause: err}
}
return err
}

View File

@ -10,14 +10,11 @@ import (
"fmt"
"net/url"
"strings"
"time"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/dgrijalva/jwt-go"
uuid "github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"xorm.io/xorm"
@ -516,77 +513,3 @@ func revokeOAuth2Grant(e Engine, grantID, userID int64) error {
_, err := e.Delete(&OAuth2Grant{ID: grantID, UserID: userID})
return err
}
//////////////////////////////////////////////////////////////
// OAuth2TokenType represents the type of token for an oauth application
type OAuth2TokenType int
const (
// TypeAccessToken is a token with short lifetime to access the api
TypeAccessToken OAuth2TokenType = 0
// TypeRefreshToken is token with long lifetime to refresh access tokens obtained by the client
TypeRefreshToken = iota
)
// OAuth2Token represents a JWT token used to authenticate a client
type OAuth2Token struct {
GrantID int64 `json:"gnt"`
Type OAuth2TokenType `json:"tt"`
Counter int64 `json:"cnt,omitempty"`
jwt.StandardClaims
}
// ParseOAuth2Token parses a signed jwt string
func ParseOAuth2Token(jwtToken string) (*OAuth2Token, error) {
parsedToken, err := jwt.ParseWithClaims(jwtToken, &OAuth2Token{}, func(token *jwt.Token) (interface{}, error) {
if token.Method == nil || token.Method.Alg() != oauth2.DefaultSigningKey.SigningMethod().Alg() {
return nil, fmt.Errorf("unexpected signing algo: %v", token.Header["alg"])
}
return oauth2.DefaultSigningKey.VerifyKey(), nil
})
if err != nil {
return nil, err
}
var token *OAuth2Token
var ok bool
if token, ok = parsedToken.Claims.(*OAuth2Token); !ok || !parsedToken.Valid {
return nil, fmt.Errorf("invalid token")
}
return token, nil
}
// SignToken signs the token with the JWT secret
func (token *OAuth2Token) SignToken() (string, error) {
token.IssuedAt = time.Now().Unix()
jwtToken := jwt.NewWithClaims(oauth2.DefaultSigningKey.SigningMethod(), token)
oauth2.DefaultSigningKey.PreProcessToken(jwtToken)
return jwtToken.SignedString(oauth2.DefaultSigningKey.SignKey())
}
// OIDCToken represents an OpenID Connect id_token
type OIDCToken struct {
jwt.StandardClaims
Nonce string `json:"nonce,omitempty"`
// Scope profile
Name string `json:"name,omitempty"`
PreferredUsername string `json:"preferred_username,omitempty"`
Profile string `json:"profile,omitempty"`
Picture string `json:"picture,omitempty"`
Website string `json:"website,omitempty"`
Locale string `json:"locale,omitempty"`
UpdatedAt timeutil.TimeStamp `json:"updated_at,omitempty"`
// Scope email
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`
}
// SignToken signs an id_token with the (symmetric) client secret key
func (token *OIDCToken) SignToken(signingKey oauth2.JWTSigningKey) (string, error) {
token.IssuedAt = time.Now().Unix()
jwtToken := jwt.NewWithClaims(signingKey.SigningMethod(), token)
signingKey.PreProcessToken(jwtToken)
return jwtToken.SignedString(signingKey.SignKey())
}

View File

@ -28,7 +28,7 @@ type UnitConfig struct{}
// FromDB fills up a UnitConfig from serialized format.
func (cfg *UnitConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a UnitConfig to a serialized format.
@ -44,7 +44,7 @@ type ExternalWikiConfig struct {
// FromDB fills up a ExternalWikiConfig from serialized format.
func (cfg *ExternalWikiConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a ExternalWikiConfig to a serialized format.
@ -62,7 +62,7 @@ type ExternalTrackerConfig struct {
// FromDB fills up a ExternalTrackerConfig from serialized format.
func (cfg *ExternalTrackerConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a ExternalTrackerConfig to a serialized format.
@ -80,7 +80,7 @@ type IssuesConfig struct {
// FromDB fills up a IssuesConfig from serialized format.
func (cfg *IssuesConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a IssuesConfig to a serialized format.
@ -104,7 +104,7 @@ type PullRequestsConfig struct {
// FromDB fills up a PullRequestsConfig from serialized format.
func (cfg *PullRequestsConfig) FromDB(bs []byte) error {
return jsonUnmarshalHandleDoubleEncode(bs, &cfg)
return JSONUnmarshalHandleDoubleEncode(bs, &cfg)
}
// ToDB exports a PullRequestsConfig to a serialized format.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// ____ __.
// | |/ _|____ ___.__. ______
// | <_/ __ < | |/ ___/
// | | \ ___/\___ |\___ \
// |____|__ \___ > ____/____ >
// \/ \/\/ \/
//
// This file contains functions for creating authorized_keys files
//
// There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module
const (
tplCommentPrefix = `# gitea public key`
tplPublicKey = tplCommentPrefix + "\n" + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
)
var sshOpLocker sync.Mutex
// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key
func AuthorizedStringForKey(key *PublicKey) string {
sb := &strings.Builder{}
_ = setting.SSH.AuthorizedKeysCommandTemplateTemplate.Execute(sb, map[string]interface{}{
"AppPath": util.ShellEscape(setting.AppPath),
"AppWorkPath": util.ShellEscape(setting.AppWorkPath),
"CustomConf": util.ShellEscape(setting.CustomConf),
"CustomPath": util.ShellEscape(setting.CustomPath),
"Key": key,
})
return fmt.Sprintf(tplPublicKey, util.ShellEscape(sb.String()), key.Content)
}
// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file.
func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
// Don't need to rewrite this file if builtin SSH server is enabled.
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
return nil
}
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
if err != nil {
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
return err
}
}
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
f, err := os.OpenFile(fPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
if err != nil {
return err
}
defer f.Close()
// Note: chmod command does not support in Windows.
if !setting.IsWindows {
fi, err := f.Stat()
if err != nil {
return err
}
// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
if fi.Mode().Perm() > 0o600 {
log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
if err = f.Chmod(0o600); err != nil {
return err
}
}
}
for _, key := range keys {
if key.Type == KeyTypePrincipal {
continue
}
if _, err = f.WriteString(key.AuthorizedString()); err != nil {
return err
}
}
return nil
}
// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
// outside any session scope independently.
func RewriteAllPublicKeys() error {
return rewriteAllPublicKeys(x)
}
func rewriteAllPublicKeys(e Engine) error {
// Don't rewrite key if internal server
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedKeysFile {
return nil
}
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
if err != nil {
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
return err
}
}
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
tmpPath := fPath + ".tmp"
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
defer func() {
t.Close()
if err := util.Remove(tmpPath); err != nil {
log.Warn("Unable to remove temporary authorized keys file: %s: Error: %v", tmpPath, err)
}
}()
if setting.SSH.AuthorizedKeysBackup {
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
if err = util.CopyFile(fPath, bakPath); err != nil {
return err
}
}
}
if err := regeneratePublicKeys(e, t); err != nil {
return err
}
t.Close()
return util.Rename(tmpPath, fPath)
}
// RegeneratePublicKeys regenerates the authorized_keys file
func RegeneratePublicKeys(t io.StringWriter) error {
return regeneratePublicKeys(x, t)
}
func regeneratePublicKeys(e Engine, t io.StringWriter) error {
if err := e.Where("type != ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
return err
}); err != nil {
return err
}
fPath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
f, err := os.Open(fPath)
if err != nil {
return err
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, tplCommentPrefix) {
scanner.Scan()
continue
}
_, err = t.WriteString(line + "\n")
if err != nil {
f.Close()
return err
}
}
f.Close()
}
return nil
}

View File

@ -0,0 +1,142 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// _____ __ .__ .__ .___
// / _ \ __ ___/ |_| |__ ___________|__|_______ ____ __| _/
// / /_\ \| | \ __\ | \ / _ \_ __ \ \___ // __ \ / __ |
// / | \ | /| | | Y ( <_> ) | \/ |/ /\ ___// /_/ |
// \____|__ /____/ |__| |___| /\____/|__| |__/_____ \\___ >____ |
// \/ \/ \/ \/ \/
// __________ .__ .__ .__
// \______ _______|__| ____ ____ |_____________ | | ______
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
// \/ \/ |__| \/ \/
//
// This file contains functions for creating authorized_principals files
//
// There is a dependence on the database within RewriteAllPrincipalKeys & RegeneratePrincipalKeys
// The sshOpLocker is used from ssh_key_authorized_keys.go
const authorizedPrincipalsFile = "authorized_principals"
// RewriteAllPrincipalKeys removes any authorized principal and rewrite all keys from database again.
// Note: x.Iterate does not get latest data after insert/delete, so we have to call this function
// outside any session scope independently.
func RewriteAllPrincipalKeys() error {
return rewriteAllPrincipalKeys(x)
}
func rewriteAllPrincipalKeys(e Engine) error {
// Don't rewrite key if internal server
if setting.SSH.StartBuiltinServer || !setting.SSH.CreateAuthorizedPrincipalsFile {
return nil
}
sshOpLocker.Lock()
defer sshOpLocker.Unlock()
if setting.SSH.RootPath != "" {
// First of ensure that the RootPath is present, and if not make it with 0700 permissions
// This of course doesn't guarantee that this is the right directory for authorized_keys
// but at least if it's supposed to be this directory and it doesn't exist and we're the
// right user it will at least be created properly.
err := os.MkdirAll(setting.SSH.RootPath, 0o700)
if err != nil {
log.Error("Unable to MkdirAll(%s): %v", setting.SSH.RootPath, err)
return err
}
}
fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile)
tmpPath := fPath + ".tmp"
t, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
return err
}
defer func() {
t.Close()
os.Remove(tmpPath)
}()
if setting.SSH.AuthorizedPrincipalsBackup {
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
bakPath := fmt.Sprintf("%s_%d.gitea_bak", fPath, time.Now().Unix())
if err = util.CopyFile(fPath, bakPath); err != nil {
return err
}
}
}
if err := regeneratePrincipalKeys(e, t); err != nil {
return err
}
t.Close()
return util.Rename(tmpPath, fPath)
}
// RegeneratePrincipalKeys regenerates the authorized_principals file
func RegeneratePrincipalKeys(t io.StringWriter) error {
return regeneratePrincipalKeys(x, t)
}
func regeneratePrincipalKeys(e Engine, t io.StringWriter) error {
if err := e.Where("type = ?", KeyTypePrincipal).Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
_, err = t.WriteString((bean.(*PublicKey)).AuthorizedString())
return err
}); err != nil {
return err
}
fPath := filepath.Join(setting.SSH.RootPath, authorizedPrincipalsFile)
isExist, err := util.IsExist(fPath)
if err != nil {
log.Error("Unable to check if %s exists. Error: %v", fPath, err)
return err
}
if isExist {
f, err := os.Open(fPath)
if err != nil {
return err
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, tplCommentPrefix) {
scanner.Scan()
continue
}
_, err = t.WriteString(line + "\n")
if err != nil {
f.Close()
return err
}
}
f.Close()
}
return nil
}

299
models/ssh_key_deploy.go Normal file
View File

@ -0,0 +1,299 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
"xorm.io/xorm"
)
// ________ .__ ____ __.
// \______ \ ____ ______ | | ____ ___.__.| |/ _|____ ___.__.
// | | \_/ __ \\____ \| | / _ < | || <_/ __ < | |
// | ` \ ___/| |_> > |_( <_> )___ || | \ ___/\___ |
// /_______ /\___ > __/|____/\____// ____||____|__ \___ > ____|
// \/ \/|__| \/ \/ \/\/
//
// This file contains functions specific to DeployKeys
// DeployKey represents deploy key information and its relation with repository.
type DeployKey struct {
ID int64 `xorm:"pk autoincr"`
KeyID int64 `xorm:"UNIQUE(s) INDEX"`
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
Name string
Fingerprint string
Content string `xorm:"-"`
Mode AccessMode `xorm:"NOT NULL DEFAULT 1"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
HasRecentActivity bool `xorm:"-"`
HasUsed bool `xorm:"-"`
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (key *DeployKey) AfterLoad() {
key.HasUsed = key.UpdatedUnix > key.CreatedUnix
key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
}
// GetContent gets associated public key content.
func (key *DeployKey) GetContent() error {
pkey, err := GetPublicKeyByID(key.KeyID)
if err != nil {
return err
}
key.Content = pkey.Content
return nil
}
// IsReadOnly checks if the key can only be used for read operations
func (key *DeployKey) IsReadOnly() bool {
return key.Mode == AccessModeRead
}
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
// Note: We want error detail, not just true or false here.
has, err := e.
Where("key_id = ? AND repo_id = ?", keyID, repoID).
Get(new(DeployKey))
if err != nil {
return err
} else if has {
return ErrDeployKeyAlreadyExist{keyID, repoID}
}
has, err = e.
Where("repo_id = ? AND name = ?", repoID, name).
Get(new(DeployKey))
if err != nil {
return err
} else if has {
return ErrDeployKeyNameAlreadyUsed{repoID, name}
}
return nil
}
// addDeployKey adds new key-repo relation.
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string, mode AccessMode) (*DeployKey, error) {
if err := checkDeployKey(e, keyID, repoID, name); err != nil {
return nil, err
}
key := &DeployKey{
KeyID: keyID,
RepoID: repoID,
Name: name,
Fingerprint: fingerprint,
Mode: mode,
}
_, err := e.Insert(key)
return key, err
}
// HasDeployKey returns true if public key is a deploy key of given repository.
func HasDeployKey(keyID, repoID int64) bool {
has, _ := x.
Where("key_id = ? AND repo_id = ?", keyID, repoID).
Get(new(DeployKey))
return has
}
// AddDeployKey add new deploy key to database and authorized_keys file.
func AddDeployKey(repoID int64, name, content string, readOnly bool) (*DeployKey, error) {
fingerprint, err := calcFingerprint(content)
if err != nil {
return nil, err
}
accessMode := AccessModeRead
if !readOnly {
accessMode = AccessModeWrite
}
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return nil, err
}
pkey := &PublicKey{
Fingerprint: fingerprint,
}
has, err := sess.Get(pkey)
if err != nil {
return nil, err
}
if has {
if pkey.Type != KeyTypeDeploy {
return nil, ErrKeyAlreadyExist{0, fingerprint, ""}
}
} else {
// First time use this deploy key.
pkey.Mode = accessMode
pkey.Type = KeyTypeDeploy
pkey.Content = content
pkey.Name = name
if err = addKey(sess, pkey); err != nil {
return nil, fmt.Errorf("addKey: %v", err)
}
}
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint, accessMode)
if err != nil {
return nil, err
}
return key, sess.Commit()
}
// GetDeployKeyByID returns deploy key by given ID.
func GetDeployKeyByID(id int64) (*DeployKey, error) {
return getDeployKeyByID(x, id)
}
func getDeployKeyByID(e Engine, id int64) (*DeployKey, error) {
key := new(DeployKey)
has, err := e.ID(id).Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{id, 0, 0}
}
return key, nil
}
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
return getDeployKeyByRepo(x, keyID, repoID)
}
func getDeployKeyByRepo(e Engine, keyID, repoID int64) (*DeployKey, error) {
key := &DeployKey{
KeyID: keyID,
RepoID: repoID,
}
has, err := e.Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrDeployKeyNotExist{0, keyID, repoID}
}
return key, nil
}
// UpdateDeployKeyCols updates deploy key information in the specified columns.
func UpdateDeployKeyCols(key *DeployKey, cols ...string) error {
_, err := x.ID(key.ID).Cols(cols...).Update(key)
return err
}
// UpdateDeployKey updates deploy key information.
func UpdateDeployKey(key *DeployKey) error {
_, err := x.ID(key.ID).AllCols().Update(key)
return err
}
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
func DeleteDeployKey(doer *User, id int64) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := deleteDeployKey(sess, doer, id); err != nil {
return err
}
return sess.Commit()
}
func deleteDeployKey(sess Engine, doer *User, id int64) error {
key, err := getDeployKeyByID(sess, id)
if err != nil {
if IsErrDeployKeyNotExist(err) {
return nil
}
return fmt.Errorf("GetDeployKeyByID: %v", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin {
repo, err := getRepositoryByID(sess, key.RepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
has, err := isUserRepoAdmin(sess, repo, doer)
if err != nil {
return fmt.Errorf("GetUserRepoPermission: %v", err)
} else if !has {
return ErrKeyAccessDenied{doer.ID, key.ID, "deploy"}
}
}
if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil {
return fmt.Errorf("delete deploy key [%d]: %v", key.ID, err)
}
// Check if this is the last reference to same key content.
has, err := sess.
Where("key_id = ?", key.KeyID).
Get(new(DeployKey))
if err != nil {
return err
} else if !has {
if err = deletePublicKeys(sess, key.KeyID); err != nil {
return err
}
// after deleted the public keys, should rewrite the public keys file
if err = rewriteAllPublicKeys(sess); err != nil {
return err
}
}
return nil
}
// ListDeployKeys returns all deploy keys by given repository ID.
func ListDeployKeys(repoID int64, listOptions ListOptions) ([]*DeployKey, error) {
return listDeployKeys(x, repoID, listOptions)
}
func listDeployKeys(e Engine, repoID int64, listOptions ListOptions) ([]*DeployKey, error) {
sess := e.Where("repo_id = ?", repoID)
if listOptions.Page != 0 {
sess = listOptions.setSessionPagination(sess)
keys := make([]*DeployKey, 0, listOptions.PageSize)
return keys, sess.Find(&keys)
}
keys := make([]*DeployKey, 0, 5)
return keys, sess.Find(&keys)
}
// SearchDeployKeys returns a list of deploy keys matching the provided arguments.
func SearchDeployKeys(repoID, keyID int64, fingerprint string) ([]*DeployKey, error) {
keys := make([]*DeployKey, 0, 5)
cond := builder.NewCond()
if repoID != 0 {
cond = cond.And(builder.Eq{"repo_id": repoID})
}
if keyID != 0 {
cond = cond.And(builder.Eq{"key_id": keyID})
}
if fingerprint != "" {
cond = cond.And(builder.Eq{"fingerprint": fingerprint})
}
return keys, x.Where(cond).Find(&keys)
}

View File

@ -0,0 +1,97 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/ssh"
)
// ___________.__ .__ __
// \_ _____/|__| ____ ____ ________________________|__| _____/ |_
// | __) | |/ \ / ___\_/ __ \_ __ \____ \_ __ \ |/ \ __\
// | \ | | | \/ /_/ > ___/| | \/ |_> > | \/ | | \ |
// \___ / |__|___| /\___ / \___ >__| | __/|__| |__|___| /__|
// \/ \//_____/ \/ |__| \/
//
// This file contains functions for fingerprinting SSH keys
//
// The database is used in checkKeyFingerprint however most of these functions probably belong in a module
// checkKeyFingerprint only checks if key fingerprint has been used as public key,
// it is OK to use same key as deploy key for multiple repositories/users.
func checkKeyFingerprint(e Engine, fingerprint string) error {
has, err := e.Get(&PublicKey{
Fingerprint: fingerprint,
})
if err != nil {
return err
} else if has {
return ErrKeyAlreadyExist{0, fingerprint, ""}
}
return nil
}
func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) {
// Calculate fingerprint.
tmpPath, err := writeTmpKeyFile(publicKeyContent)
if err != nil {
return "", err
}
defer func() {
if err := util.Remove(tmpPath); err != nil {
log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpPath, err)
}
}()
stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
if err != nil {
if strings.Contains(stderr, "is not a public key file") {
return "", ErrKeyUnableVerify{stderr}
}
return "", fmt.Errorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr)
} else if len(stdout) < 2 {
return "", errors.New("not enough output for calculating fingerprint: " + stdout)
}
return strings.Split(stdout, " ")[1], nil
}
func calcFingerprintNative(publicKeyContent string) (string, error) {
// Calculate fingerprint.
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent))
if err != nil {
return "", err
}
return ssh.FingerprintSHA256(pk), nil
}
func calcFingerprint(publicKeyContent string) (string, error) {
// Call the method based on configuration
var (
fnName, fp string
err error
)
if setting.SSH.StartBuiltinServer {
fnName = "calcFingerprintNative"
fp, err = calcFingerprintNative(publicKeyContent)
} else {
fnName = "calcFingerprintSSHKeygen"
fp, err = calcFingerprintSSHKeygen(publicKeyContent)
}
if err != nil {
if IsErrKeyUnableVerify(err) {
log.Info("%s", publicKeyContent)
return "", err
}
return "", fmt.Errorf("%s: %v", fnName, err)
}
return fp, nil
}

309
models/ssh_key_parse.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"errors"
"fmt"
"strings"
"code.gitea.io/gitea/modules/setting"
)
// __________ .__ .__ .__
// \______ _______|__| ____ ____ |_____________ | | ______
// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/
// | | | | \| | | \ \___| | |_> / __ \| |__\___ \
// |____| |__| |__|___| /\___ |__| __(____ |____/____ >
// \/ \/ |__| \/ \/
//
// This file contains functions related to principals
// AddPrincipalKey adds new principal to database and authorized_principals file.
func AddPrincipalKey(ownerID int64, content string, loginSourceID int64) (*PublicKey, error) {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return nil, err
}
// Principals cannot be duplicated.
has, err := sess.
Where("content = ? AND type = ?", content, KeyTypePrincipal).
Get(new(PublicKey))
if err != nil {
return nil, err
} else if has {
return nil, ErrKeyAlreadyExist{0, "", content}
}
key := &PublicKey{
OwnerID: ownerID,
Name: content,
Content: content,
Mode: AccessModeWrite,
Type: KeyTypePrincipal,
LoginSourceID: loginSourceID,
}
if err = addPrincipalKey(sess, key); err != nil {
return nil, fmt.Errorf("addKey: %v", err)
}
if err = sess.Commit(); err != nil {
return nil, err
}
sess.Close()
return key, RewriteAllPrincipalKeys()
}
func addPrincipalKey(e Engine, key *PublicKey) (err error) {
// Save Key representing a principal.
if _, err = e.Insert(key); err != nil {
return err
}
return nil
}
// CheckPrincipalKeyString strips spaces and returns an error if the given principal contains newlines
func CheckPrincipalKeyString(user *User, content string) (_ string, err error) {
if setting.SSH.Disabled {
return "", ErrSSHDisabled{}
}
content = strings.TrimSpace(content)
if strings.ContainsAny(content, "\r\n") {
return "", errors.New("only a single line with a single principal please")
}
// check all the allowed principals, email, username or anything
// if any matches, return ok
for _, v := range setting.SSH.AuthorizedPrincipalsAllow {
switch v {
case "anything":
return content, nil
case "email":
emails, err := GetEmailAddresses(user.ID)
if err != nil {
return "", err
}
for _, email := range emails {
if !email.IsActivated {
continue
}
if content == email.Email {
return content, nil
}
}
case "username":
if content == user.Name {
return content, nil
}
}
}
return "", fmt.Errorf("didn't match allowed principals: %s", setting.SSH.AuthorizedPrincipalsAllow)
}
// ListPrincipalKeys returns a list of principals belongs to given user.
func ListPrincipalKeys(uid int64, listOptions ListOptions) ([]*PublicKey, error) {
sess := x.Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal)
if listOptions.Page != 0 {
sess = listOptions.setSessionPagination(sess)
keys := make([]*PublicKey, 0, listOptions.PageSize)
return keys, sess.Find(&keys)
}
keys := make([]*PublicKey, 0, 5)
return keys, sess.Find(&keys)
}

16
models/store.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import "github.com/lafriks/xormstore"
// CreateStore creates a xormstore for the provided table and key
func CreateStore(table, key string) (*xormstore.Store, error) {
store, err := xormstore.NewOptions(x, xormstore.Options{
TableName: table,
}, []byte(key))
return store, err
}

File diff suppressed because it is too large Load Diff

View File

@ -453,8 +453,8 @@ ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ib
for i, kase := range testCases {
s.ID = int64(i) + 20
addLdapSSHPublicKeys(user, s, []string{kase.keyString})
keys, err := ListPublicLdapSSHKeys(user.ID, s.ID)
AddPublicKeysBySource(user, s, []string{kase.keyString})
keys, err := ListPublicKeysBySource(user.ID, s.ID)
assert.NoError(t, err)
if err != nil {
continue

View File

@ -218,7 +218,7 @@ func (ctx *APIContext) CheckForOTP() {
}
// APIAuth converts auth.Auth as a middleware
func APIAuth(authMethod auth.Auth) func(*APIContext) {
func APIAuth(authMethod auth.Method) func(*APIContext) {
return func(ctx *APIContext) {
// Get user from session if logged in.
ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)

View File

@ -627,7 +627,7 @@ func getCsrfOpts() CsrfOptions {
}
// Auth converts auth.Auth as a middleware
func Auth(authMethod auth.Auth) func(*Context) {
func Auth(authMethod auth.Method) func(*Context) {
return func(ctx *Context) {
ctx.User = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
if ctx.User != nil {

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/migrations"
repository_service "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/auth"
mirror_service "code.gitea.io/gitea/services/mirror"
)
@ -80,7 +81,7 @@ func registerSyncExternalUsers() {
UpdateExisting: true,
}, func(ctx context.Context, _ *models.User, config Config) error {
realConfig := config.(*UpdateExistingConfig)
return models.SyncExternalUsers(ctx, realConfig.UpdateExisting)
return auth.SyncExternalUsers(ctx, realConfig.UpdateExisting)
})
}

View File

@ -35,6 +35,7 @@ import (
web_routers "code.gitea.io/gitea/routers/web"
"code.gitea.io/gitea/services/archiver"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/mailer"
mirror_service "code.gitea.io/gitea/services/mirror"
pull_service "code.gitea.io/gitea/services/pull"
@ -100,7 +101,7 @@ func GlobalInit(ctx context.Context) {
log.Fatal("ORM engine initialization failed: %v", err)
}
if err := models.InitOAuth2(); err != nil {
if err := oauth2.Init(); err != nil {
log.Fatal("Failed to initialize OAuth2 support: %v", err)
}

View File

@ -11,8 +11,6 @@ import (
"regexp"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/ldap"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/auth/pam"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@ -20,6 +18,11 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/services/auth/source/oauth2"
pamService "code.gitea.io/gitea/services/auth/source/pam"
"code.gitea.io/gitea/services/auth/source/smtp"
"code.gitea.io/gitea/services/auth/source/sspi"
"code.gitea.io/gitea/services/forms"
"xorm.io/xorm/convert"
@ -74,9 +77,9 @@ var (
}()
securityProtocols = []dropdownItem{
{models.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted], ldap.SecurityProtocolUnencrypted},
{models.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{models.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted], ldap.SecurityProtocolUnencrypted},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS},
}
)
@ -88,15 +91,15 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["type"] = models.LoginLDAP
ctx.Data["CurrentTypeName"] = models.LoginNames[models.LoginLDAP]
ctx.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
ctx.Data["smtp_auth"] = "PLAIN"
ctx.Data["is_active"] = true
ctx.Data["is_sync_enabled"] = true
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = models.SMTPAuths
ctx.Data["OAuth2Providers"] = models.OAuth2Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
ctx.Data["SMTPAuths"] = smtp.Authenticators
ctx.Data["OAuth2Providers"] = oauth2.Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings
ctx.Data["SSPIAutoCreateUsers"] = true
ctx.Data["SSPIAutoActivateUsers"] = true
@ -105,7 +108,7 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["SSPIDefaultLanguage"] = ""
// only the first as default
for key := range models.OAuth2Providers {
for key := range oauth2.Providers {
ctx.Data["oauth2_provider"] = key
break
}
@ -113,13 +116,12 @@ func NewAuthSource(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplAuthNew)
}
func parseLDAPConfig(form forms.AuthenticationForm) *models.LDAPConfig {
func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
var pageSize uint32
if form.UsePagedSearch {
pageSize = uint32(form.SearchPageSize)
}
return &models.LDAPConfig{
Source: &ldap.Source{
return &ldap.Source{
Name: form.Name,
Host: form.Host,
Port: form.Port,
@ -146,12 +148,11 @@ func parseLDAPConfig(form forms.AuthenticationForm) *models.LDAPConfig {
RestrictedFilter: form.RestrictedFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
},
}
}
func parseSMTPConfig(form forms.AuthenticationForm) *models.SMTPConfig {
return &models.SMTPConfig{
func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
return &smtp.Source{
Auth: form.SMTPAuth,
Host: form.SMTPHost,
Port: form.SMTPPort,
@ -161,7 +162,7 @@ func parseSMTPConfig(form forms.AuthenticationForm) *models.SMTPConfig {
}
}
func parseOAuth2Config(form forms.AuthenticationForm) *models.OAuth2Config {
func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if form.Oauth2UseCustomURL {
customURLMapping = &oauth2.CustomURLMapping{
@ -173,7 +174,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *models.OAuth2Config {
} else {
customURLMapping = nil
}
return &models.OAuth2Config{
return &oauth2.Source{
Provider: form.Oauth2Provider,
ClientID: form.Oauth2Key,
ClientSecret: form.Oauth2Secret,
@ -183,7 +184,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *models.OAuth2Config {
}
}
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*models.SSPIConfig, error) {
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
if util.IsEmptyString(form.SSPISeparatorReplacement) {
ctx.Data["Err_SSPISeparatorReplacement"] = true
return nil, errors.New(ctx.Tr("form.SSPISeparatorReplacement") + ctx.Tr("form.require_error"))
@ -198,7 +199,7 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*mode
return nil, errors.New(ctx.Tr("form.lang_select_error"))
}
return &models.SSPIConfig{
return &sspi.Source{
AutoCreateUsers: form.SSPIAutoCreateUsers,
AutoActivateUsers: form.SSPIAutoActivateUsers,
StripDomainNames: form.SSPIStripDomainNames,
@ -215,12 +216,12 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["CurrentTypeName"] = models.LoginNames[models.LoginType(form.Type)]
ctx.Data["CurrentSecurityProtocol"] = models.SecurityProtocolNames[ldap.SecurityProtocol(form.SecurityProtocol)]
ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocol(form.SecurityProtocol)]
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = models.SMTPAuths
ctx.Data["OAuth2Providers"] = models.OAuth2Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
ctx.Data["SMTPAuths"] = smtp.Authenticators
ctx.Data["OAuth2Providers"] = oauth2.Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings
ctx.Data["SSPIAutoCreateUsers"] = true
ctx.Data["SSPIAutoActivateUsers"] = true
@ -238,7 +239,7 @@ func NewAuthSourcePost(ctx *context.Context) {
config = parseSMTPConfig(form)
hasTLS = true
case models.LoginPAM:
config = &models.PAMConfig{
config = &pamService.Source{
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
}
@ -271,7 +272,7 @@ func NewAuthSourcePost(ctx *context.Context) {
if err := models.CreateLoginSource(&models.LoginSource{
Type: models.LoginType(form.Type),
Name: form.Name,
IsActived: form.IsActive,
IsActive: form.IsActive,
IsSyncEnabled: form.IsSyncEnabled,
Cfg: config,
}); err != nil {
@ -297,9 +298,9 @@ func EditAuthSource(ctx *context.Context) {
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = models.SMTPAuths
ctx.Data["OAuth2Providers"] = models.OAuth2Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
ctx.Data["SMTPAuths"] = smtp.Authenticators
ctx.Data["OAuth2Providers"] = oauth2.Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings
source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid"))
if err != nil {
@ -310,7 +311,7 @@ func EditAuthSource(ctx *context.Context) {
ctx.Data["HasTLS"] = source.HasTLS()
if source.IsOAuth2() {
ctx.Data["CurrentOAuth2Provider"] = models.OAuth2Providers[source.OAuth2().Provider]
ctx.Data["CurrentOAuth2Provider"] = oauth2.Providers[source.Cfg.(*oauth2.Source).Provider]
}
ctx.HTML(http.StatusOK, tplAuthEdit)
}
@ -322,9 +323,9 @@ func EditAuthSourcePost(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["SMTPAuths"] = models.SMTPAuths
ctx.Data["OAuth2Providers"] = models.OAuth2Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = models.OAuth2DefaultCustomURLMappings
ctx.Data["SMTPAuths"] = smtp.Authenticators
ctx.Data["OAuth2Providers"] = oauth2.Providers
ctx.Data["OAuth2DefaultCustomURLMappings"] = oauth2.DefaultCustomURLMappings
source, err := models.GetLoginSourceByID(ctx.ParamsInt64(":authid"))
if err != nil {
@ -346,7 +347,7 @@ func EditAuthSourcePost(ctx *context.Context) {
case models.LoginSMTP:
config = parseSMTPConfig(form)
case models.LoginPAM:
config = &models.PAMConfig{
config = &pamService.Source{
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
}
@ -364,7 +365,7 @@ func EditAuthSourcePost(ctx *context.Context) {
}
source.Name = form.Name
source.IsActived = form.IsActive
source.IsActive = form.IsActive
source.IsSyncEnabled = form.IsSyncEnabled
source.Cfg = config
if err := models.UpdateSource(source); err != nil {

View File

@ -14,7 +14,6 @@ import (
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/oauth2"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/eventsource"
@ -27,6 +26,8 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/routers/utils"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
@ -135,7 +136,7 @@ func SignIn(ctx *context.Context) {
return
}
orderedOAuth2Names, oauth2Providers, err := models.GetActiveOAuth2Providers()
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@ -155,7 +156,7 @@ func SignIn(ctx *context.Context) {
func SignInPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("sign_in")
orderedOAuth2Names, oauth2Providers, err := models.GetActiveOAuth2Providers()
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
@ -174,7 +175,7 @@ func SignInPost(ctx *context.Context) {
}
form := web.GetForm(ctx).(*forms.SignInForm)
u, err := models.UserSignIn(form.UserName, form.Password)
u, err := auth.UserSignIn(form.UserName, form.Password)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form)
@ -577,13 +578,13 @@ func SignInOAuth(ctx *context.Context) {
return
}
if err = oauth2.Auth(loginSource.Name, ctx.Req, ctx.Resp); err != nil {
if err = loginSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
if strings.Contains(err.Error(), "no provider for ") {
if err = models.ResetOAuth2(); err != nil {
if err = oauth2.ResetOAuth2(); err != nil {
ctx.ServerError("SignIn", err)
return
}
if err = oauth2.Auth(loginSource.Name, ctx.Req, ctx.Resp); err != nil {
if err = loginSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
ctx.ServerError("SignIn", err)
}
return
@ -631,7 +632,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
if len(missingFields) > 0 {
log.Error("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields)
if loginSource.IsOAuth2() && loginSource.OAuth2().Provider == "openidConnect" {
if loginSource.IsOAuth2() && loginSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
}
err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields)
@ -772,8 +773,7 @@ func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
// login the user
func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Request, response http.ResponseWriter) (*models.User, goth.User, error) {
gothUser, err := oauth2.ProviderCallback(loginSource.Name, request, response)
gothUser, err := loginSource.Cfg.(*oauth2.Source).Callback(request, response)
if err != nil {
if err.Error() == "securecookie: the value is too long" {
log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", loginSource.Name, setting.OAuth2.MaxTokenLength)
@ -901,7 +901,7 @@ func LinkAccountPostSignIn(ctx *context.Context) {
return
}
u, err := models.UserSignIn(signInForm.UserName, signInForm.Password)
u, err := auth.UserSignIn(signInForm.UserName, signInForm.Password)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Data["user_exists"] = true

Some files were not shown because too many files have changed in this diff Show More