mirror of
https://codeberg.org/davrot/forgejo.git
synced 2025-05-20 14:00:04 +02:00
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:
parent
f135a818f5
commit
5d2e11eedb
77 changed files with 3803 additions and 2951 deletions
341
models/user.go
341
models/user.go
|
@ -34,7 +34,6 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
|
@ -1484,6 +1483,13 @@ func GetUserIDsByNames(names []string, ignoreNonExistent bool) ([]int64, error)
|
|||
return ids, nil
|
||||
}
|
||||
|
||||
// GetUsersBySource returns a list of Users for a login source
|
||||
func GetUsersBySource(s *LoginSource) ([]*User, error) {
|
||||
var users []*User
|
||||
err := x.Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// UserCommit represents a commit with validation of user.
|
||||
type UserCommit struct {
|
||||
User *User
|
||||
|
@ -1724,339 +1730,6 @@ func GetWatchedRepos(userID int64, private bool, listOptions ListOptions) ([]*Re
|
|||
return repos, sess.Find(&repos)
|
||||
}
|
||||
|
||||
// deleteKeysMarkedForDeletion returns true if ssh keys needs update
|
||||
func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
|
||||
// Start session
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Delete keys marked for deletion
|
||||
var sshKeysNeedUpdate bool
|
||||
for _, KeyToDelete := range keys {
|
||||
key, err := searchPublicKeyByContentWithEngine(sess, KeyToDelete)
|
||||
if err != nil {
|
||||
log.Error("SearchPublicKeyByContent: %v", err)
|
||||
continue
|
||||
}
|
||||
if err = deletePublicKeys(sess, key.ID); err != nil {
|
||||
log.Error("deletePublicKeys: %v", err)
|
||||
continue
|
||||
}
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
if err := sess.Commit(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return sshKeysNeedUpdate, nil
|
||||
}
|
||||
|
||||
// addLdapSSHPublicKeys add a users public keys. Returns true if there are changes.
|
||||
func addLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
|
||||
var sshKeysNeedUpdate bool
|
||||
for _, sshKey := range sshPublicKeys {
|
||||
var err error
|
||||
found := false
|
||||
keys := []byte(sshKey)
|
||||
loop:
|
||||
for len(keys) > 0 && err == nil {
|
||||
var out ssh.PublicKey
|
||||
// We ignore options as they are not relevant to Gitea
|
||||
out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
found = true
|
||||
marshalled := string(ssh.MarshalAuthorizedKey(out))
|
||||
marshalled = marshalled[:len(marshalled)-1]
|
||||
sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
|
||||
|
||||
if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
|
||||
if IsErrKeyAlreadyExist(err) {
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user", sshKeyName, usr.Name)
|
||||
} else {
|
||||
log.Error("addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
|
||||
}
|
||||
} else {
|
||||
log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", sshKeyName, usr.Name)
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
}
|
||||
if !found && err != nil {
|
||||
log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
|
||||
}
|
||||
}
|
||||
return sshKeysNeedUpdate
|
||||
}
|
||||
|
||||
// synchronizeLdapSSHPublicKeys updates a users public keys. Returns true if there are changes.
|
||||
func synchronizeLdapSSHPublicKeys(usr *User, s *LoginSource, sshPublicKeys []string) bool {
|
||||
var sshKeysNeedUpdate bool
|
||||
|
||||
log.Trace("synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s", s.Name, usr.Name)
|
||||
|
||||
// Get Public Keys from DB with current LDAP source
|
||||
var giteaKeys []string
|
||||
keys, err := ListPublicLdapSSHKeys(usr.ID, s.ID)
|
||||
if err != nil {
|
||||
log.Error("synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v", s.Name, usr.Name, err)
|
||||
}
|
||||
|
||||
for _, v := range keys {
|
||||
giteaKeys = append(giteaKeys, v.OmitEmail())
|
||||
}
|
||||
|
||||
// Get Public Keys from LDAP and skip duplicate keys
|
||||
var ldapKeys []string
|
||||
for _, v := range sshPublicKeys {
|
||||
sshKeySplit := strings.Split(v, " ")
|
||||
if len(sshKeySplit) > 1 {
|
||||
ldapKey := strings.Join(sshKeySplit[:2], " ")
|
||||
if !util.ExistsInSlice(ldapKey, ldapKeys) {
|
||||
ldapKeys = append(ldapKeys, ldapKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Public Key sync is needed
|
||||
if util.IsEqualSlice(giteaKeys, ldapKeys) {
|
||||
log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys))
|
||||
return false
|
||||
}
|
||||
log.Trace("synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)", s.Name, usr.Name, len(ldapKeys), len(giteaKeys))
|
||||
|
||||
// Add LDAP Public SSH Keys that doesn't already exist in DB
|
||||
var newLdapSSHKeys []string
|
||||
for _, LDAPPublicSSHKey := range ldapKeys {
|
||||
if !util.ExistsInSlice(LDAPPublicSSHKey, giteaKeys) {
|
||||
newLdapSSHKeys = append(newLdapSSHKeys, LDAPPublicSSHKey)
|
||||
}
|
||||
}
|
||||
if addLdapSSHPublicKeys(usr, s, newLdapSSHKeys) {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
// Mark LDAP keys from DB that doesn't exist in LDAP for deletion
|
||||
var giteaKeysToDelete []string
|
||||
for _, giteaKey := range giteaKeys {
|
||||
if !util.ExistsInSlice(giteaKey, ldapKeys) {
|
||||
log.Trace("synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey)
|
||||
giteaKeysToDelete = append(giteaKeysToDelete, giteaKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete LDAP keys from DB that doesn't exist in LDAP
|
||||
needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete)
|
||||
if err != nil {
|
||||
log.Error("synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v", s.Name, usr.Name, err)
|
||||
}
|
||||
if needUpd {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
return sshKeysNeedUpdate
|
||||
}
|
||||
|
||||
// SyncExternalUsers is used to synchronize users with external authorization source
|
||||
func SyncExternalUsers(ctx context.Context, updateExisting bool) error {
|
||||
log.Trace("Doing: SyncExternalUsers")
|
||||
|
||||
ls, err := LoginSources()
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range ls {
|
||||
if !s.IsActived || !s.IsSyncEnabled {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
|
||||
return ErrCancelledf("Before update of %s", s.Name)
|
||||
default:
|
||||
}
|
||||
|
||||
if s.IsLDAP() {
|
||||
log.Trace("Doing: SyncExternalUsers[%s]", s.Name)
|
||||
|
||||
var existingUsers []int64
|
||||
isAttributeSSHPublicKeySet := len(strings.TrimSpace(s.LDAP().AttributeSSHPublicKey)) > 0
|
||||
var sshKeysNeedUpdate bool
|
||||
|
||||
// Find all users with this login type
|
||||
var users []*User
|
||||
err = x.Where("login_type = ?", LoginLDAP).
|
||||
And("login_source = ?", s.ID).
|
||||
Find(&users)
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers: %v", err)
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Warn("SyncExternalUsers: Cancelled before update of %s", s.Name)
|
||||
return ErrCancelledf("Before update of %s", s.Name)
|
||||
default:
|
||||
}
|
||||
|
||||
sr, err := s.LDAP().SearchEntries()
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers LDAP source failure [%s], skipped", s.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(sr) == 0 {
|
||||
if !s.LDAP().AllowDeactivateAll {
|
||||
log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
|
||||
continue
|
||||
} else {
|
||||
log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
|
||||
}
|
||||
}
|
||||
|
||||
for _, su := range sr {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Warn("SyncExternalUsers: Cancelled at update of %s before completed update of users", s.Name)
|
||||
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
|
||||
if sshKeysNeedUpdate {
|
||||
err = RewriteAllPublicKeys()
|
||||
if err != nil {
|
||||
log.Error("RewriteAllPublicKeys: %v", err)
|
||||
}
|
||||
}
|
||||
return ErrCancelledf("During update of %s before completed update of users", s.Name)
|
||||
default:
|
||||
}
|
||||
if len(su.Username) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(su.Mail) == 0 {
|
||||
su.Mail = fmt.Sprintf("%s@localhost", su.Username)
|
||||
}
|
||||
|
||||
var usr *User
|
||||
// Search for existing user
|
||||
for _, du := range users {
|
||||
if du.LowerName == strings.ToLower(su.Username) {
|
||||
usr = du
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fullName := composeFullName(su.Name, su.Surname, su.Username)
|
||||
// If no existing user found, create one
|
||||
if usr == nil {
|
||||
log.Trace("SyncExternalUsers[%s]: Creating user %s", s.Name, su.Username)
|
||||
|
||||
usr = &User{
|
||||
LowerName: strings.ToLower(su.Username),
|
||||
Name: su.Username,
|
||||
FullName: fullName,
|
||||
LoginType: s.Type,
|
||||
LoginSource: s.ID,
|
||||
LoginName: su.Username,
|
||||
Email: su.Mail,
|
||||
IsAdmin: su.IsAdmin,
|
||||
IsRestricted: su.IsRestricted,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
err = CreateUser(usr)
|
||||
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers[%s]: Error creating user %s: %v", s.Name, su.Username, err)
|
||||
} else if isAttributeSSHPublicKeySet {
|
||||
log.Trace("SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s", s.Name, usr.Name)
|
||||
if addLdapSSHPublicKeys(usr, s, su.SSHPublicKey) {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
}
|
||||
} else if updateExisting {
|
||||
existingUsers = append(existingUsers, usr.ID)
|
||||
|
||||
// Synchronize SSH Public Key if that attribute is set
|
||||
if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(usr, s, su.SSHPublicKey) {
|
||||
sshKeysNeedUpdate = true
|
||||
}
|
||||
|
||||
// Check if user data has changed
|
||||
if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) ||
|
||||
(len(s.LDAP().RestrictedFilter) > 0 && usr.IsRestricted != su.IsRestricted) ||
|
||||
!strings.EqualFold(usr.Email, su.Mail) ||
|
||||
usr.FullName != fullName ||
|
||||
!usr.IsActive {
|
||||
|
||||
log.Trace("SyncExternalUsers[%s]: Updating user %s", s.Name, usr.Name)
|
||||
|
||||
usr.FullName = fullName
|
||||
usr.Email = su.Mail
|
||||
// Change existing admin flag only if AdminFilter option is set
|
||||
if len(s.LDAP().AdminFilter) > 0 {
|
||||
usr.IsAdmin = su.IsAdmin
|
||||
}
|
||||
// Change existing restricted flag only if RestrictedFilter option is set
|
||||
if !usr.IsAdmin && len(s.LDAP().RestrictedFilter) > 0 {
|
||||
usr.IsRestricted = su.IsRestricted
|
||||
}
|
||||
usr.IsActive = true
|
||||
|
||||
err = UpdateUserCols(usr, "full_name", "email", "is_admin", "is_restricted", "is_active")
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers[%s]: Error updating user %s: %v", s.Name, usr.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
|
||||
if sshKeysNeedUpdate {
|
||||
err = RewriteAllPublicKeys()
|
||||
if err != nil {
|
||||
log.Error("RewriteAllPublicKeys: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Warn("SyncExternalUsers: Cancelled during update of %s before delete users", s.Name)
|
||||
return ErrCancelledf("During update of %s before delete users", s.Name)
|
||||
default:
|
||||
}
|
||||
|
||||
// Deactivate users not present in LDAP
|
||||
if updateExisting {
|
||||
for _, usr := range users {
|
||||
found := false
|
||||
for _, uid := range existingUsers {
|
||||
if usr.ID == uid {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
log.Trace("SyncExternalUsers[%s]: Deactivating user %s", s.Name, usr.Name)
|
||||
|
||||
usr.IsActive = false
|
||||
err = UpdateUserCols(usr, "is_active")
|
||||
if err != nil {
|
||||
log.Error("SyncExternalUsers[%s]: Error deactivating user %s: %v", s.Name, usr.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IterateUser iterate users
|
||||
func IterateUser(f func(user *User) error) error {
|
||||
var start int
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue