summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorMagnus Lindvall <magnus@dnmgns.com>2018-05-24 06:59:02 +0200
committerLauris BH <lauris@nix.lv>2018-05-24 07:59:02 +0300
commitcdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb (patch)
treea3f8a487c45d43b15a9aaf7518e0b342880b3361 /models
parentb908ac9fab141b72f38db3d40a9f6054bb701982 (diff)
downloadgitea-cdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb.tar.gz
gitea-cdb9478774e6c5cebf5a75ff35bfa6d8a37bdbdb.zip
LDAP Public SSH Keys synchronization (#1844)
* Add LDAP Key Synchronization feature Signed-off-by: Magnus Lindvall <magnus@dnmgns.com> * Add migration: add login source id column for public_key table * Only update keys if needed * Add function to only list pubkey synchronized from ldap * Only list pub ssh keys synchronized from ldap. Do not sort strings as ExistsInSlice does it. * Only get keys belonging to current login source id * Set default login source id to 0 * Some minor cleanup. Add integration tests (updete dep testify)
Diffstat (limited to 'models')
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v66.go22
-rw-r--r--models/ssh_key.go38
-rw-r--r--models/user.go134
4 files changed, 181 insertions, 15 deletions
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 7c90f1eb1f..1300065ab4 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -184,6 +184,8 @@ var migrations = []Migration{
NewMigration("add multiple assignees", addMultipleAssignees),
// v65 -> v66
NewMigration("add u2f", addU2FReg),
+ // v66 -> v67
+ NewMigration("add login source id column for public_key table", addLoginSourceIDToPublicKeyTable),
}
// Migrate database to current version
diff --git a/models/migrations/v66.go b/models/migrations/v66.go
new file mode 100644
index 0000000000..43acfb4ea5
--- /dev/null
+++ b/models/migrations/v66.go
@@ -0,0 +1,22 @@
+// Copyright 2017 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 (
+ "fmt"
+
+ "github.com/go-xorm/xorm"
+)
+
+func addLoginSourceIDToPublicKeyTable(x *xorm.Engine) error {
+ type PublicKey struct {
+ LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`
+ }
+
+ if err := x.Sync2(new(PublicKey)); err != nil {
+ return fmt.Errorf("Sync2: %v", err)
+ }
+ return nil
+}
diff --git a/models/ssh_key.go b/models/ssh_key.go
index 97a2d2dee4..997e8ee997 100644
--- a/models/ssh_key.go
+++ b/models/ssh_key.go
@@ -47,13 +47,14 @@ const (
// PublicKey represents a user or deploy SSH public key.
type PublicKey struct {
- ID int64 `xorm:"pk autoincr"`
- OwnerID int64 `xorm:"INDEX NOT NULL"`
- Name string `xorm:"NOT NULL"`
- Fingerprint string `xorm:"NOT NULL"`
- Content string `xorm:"TEXT NOT NULL"`
- Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
- Type KeyType `xorm:"NOT NULL DEFAULT 1"`
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64 `xorm:"INDEX NOT NULL"`
+ Name string `xorm:"NOT NULL"`
+ Fingerprint string `xorm:"NOT NULL"`
+ Content string `xorm:"TEXT NOT NULL"`
+ Mode AccessMode `xorm:"NOT NULL DEFAULT 2"`
+ Type KeyType `xorm:"NOT NULL DEFAULT 1"`
+ LoginSourceID int64 `xorm:"NOT NULL DEFAULT 0"`
CreatedUnix util.TimeStamp `xorm:"created"`
UpdatedUnix util.TimeStamp `xorm:"updated"`
@@ -391,7 +392,7 @@ func addKey(e Engine, key *PublicKey) (err error) {
}
// AddPublicKey adds new public key to database and authorized_keys file.
-func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
+func AddPublicKey(ownerID int64, name, content string, LoginSourceID int64) (*PublicKey, error) {
log.Trace(content)
fingerprint, err := calcFingerprint(content)
@@ -420,12 +421,13 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
}
key := &PublicKey{
- OwnerID: ownerID,
- Name: name,
- Fingerprint: fingerprint,
- Content: content,
- Mode: AccessModeWrite,
- Type: KeyTypeUser,
+ OwnerID: ownerID,
+ Name: name,
+ Fingerprint: fingerprint,
+ Content: content,
+ Mode: AccessModeWrite,
+ Type: KeyTypeUser,
+ LoginSourceID: LoginSourceID,
}
if err = addKey(sess, key); err != nil {
return nil, fmt.Errorf("addKey: %v", err)
@@ -471,6 +473,14 @@ func ListPublicKeys(uid int64) ([]*PublicKey, error) {
Find(&keys)
}
+// ListPublicLdapSSHKeys returns a list of synchronized public ldap ssh keys belongs to given user and login source.
+func ListPublicLdapSSHKeys(uid int64, LoginSourceID int64) ([]*PublicKey, error) {
+ keys := make([]*PublicKey, 0, 5)
+ return keys, x.
+ Where("owner_id = ? AND login_source_id = ?", uid, LoginSourceID).
+ Find(&keys)
+}
+
// UpdatePublicKeyUpdated updates public key use time.
func UpdatePublicKeyUpdated(id int64) error {
// Check if key exists before update as affected rows count is unreliable
diff --git a/models/user.go b/models/user.go
index d642054979..1497eef44d 100644
--- a/models/user.go
+++ b/models/user.go
@@ -1356,6 +1356,119 @@ func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) {
return repos, nil
}
+// 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 := SearchPublicKeyByContent(KeyToDelete)
+ if err != nil {
+ log.Error(4, "SearchPublicKeyByContent: %v", err)
+ continue
+ }
+ if err = deletePublicKeys(sess, key.ID); err != nil {
+ log.Error(4, "deletePublicKeys: %v", err)
+ continue
+ }
+ sshKeysNeedUpdate = true
+ }
+
+ if err := sess.Commit(); err != nil {
+ return false, err
+ }
+
+ return sshKeysNeedUpdate, nil
+}
+
+func addLdapSSHPublicKeys(s *LoginSource, usr *User, SSHPublicKeys []string) bool {
+ var sshKeysNeedUpdate bool
+ for _, sshKey := range SSHPublicKeys {
+ if strings.HasPrefix(strings.ToLower(sshKey), "ssh") {
+ sshKeyName := fmt.Sprintf("%s-%s", s.Name, sshKey[0:40])
+ if _, err := AddPublicKey(usr.ID, sshKeyName, sshKey, s.ID); err != nil {
+ log.Error(4, "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, err)
+ } else {
+ log.Trace("addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s", s.Name, usr.Name)
+ sshKeysNeedUpdate = true
+ }
+ } else {
+ log.Warn("addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
+ }
+ }
+ return sshKeysNeedUpdate
+}
+
+func synchronizeLdapSSHPublicKeys(s *LoginSource, SSHPublicKeys []string, usr *User) 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(4, "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 {
+ ldapKey := strings.Join(strings.Split(v, " ")[: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(s, usr, 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(4, "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() {
if !taskStatusTable.StartIfNotRunning(syncExternalUsers) {
@@ -1377,10 +1490,13 @@ func SyncExternalUsers() {
if !s.IsActived || !s.IsSyncEnabled {
continue
}
+
if s.IsLDAP() {
log.Trace("Doing: SyncExternalUsers[%s]", s.Name)
var existingUsers []int64
+ var isAttributeSSHPublicKeySet = len(strings.TrimSpace(s.LDAP().AttributeSSHPublicKey)) > 0
+ var sshKeysNeedUpdate bool
// Find all users with this login type
var users []User
@@ -1389,7 +1505,6 @@ func SyncExternalUsers() {
Find(&users)
sr := s.LDAP().SearchEntries()
-
for _, su := range sr {
if len(su.Username) == 0 {
continue
@@ -1426,11 +1541,23 @@ func SyncExternalUsers() {
}
err = CreateUser(usr)
+
if err != nil {
log.Error(4, "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(s, usr, su.SSHPublicKey) {
+ sshKeysNeedUpdate = true
+ }
}
} else if updateExisting {
existingUsers = append(existingUsers, usr.ID)
+
+ // Synchronize SSH Public Key if that attribute is set
+ if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(s, su.SSHPublicKey, usr) {
+ sshKeysNeedUpdate = true
+ }
+
// Check if user data has changed
if (len(s.LDAP().AdminFilter) > 0 && usr.IsAdmin != su.IsAdmin) ||
strings.ToLower(usr.Email) != strings.ToLower(su.Mail) ||
@@ -1455,6 +1582,11 @@ func SyncExternalUsers() {
}
}
+ // Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
+ if sshKeysNeedUpdate {
+ RewriteAllPublicKeys()
+ }
+
// Deactivate users not present in LDAP
if updateExisting {
for _, usr := range users {