Skip to content

Commit

Permalink
LDAP Public SSH Keys synchronization (#1844)
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
dnmgns authored and lafriks committed May 24, 2018
1 parent b908ac9 commit cdb9478
Show file tree
Hide file tree
Showing 25 changed files with 620 additions and 436 deletions.
6 changes: 4 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 58 additions & 21 deletions integrations/auth_ldap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ var gitLDAPUsers = []ldapUser{
Password: "hermes",
FullName: "Conrad Hermes",
Email: "hermes@planetexpress.com",
IsAdmin: true,
SSHKeys: []string{
"SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8",
"SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ",
},
IsAdmin: true,
},
{
UserName: "fry",
Expand Down Expand Up @@ -89,26 +93,27 @@ func getLDAPServerHost() string {
return host
}

func addAuthSourceLDAP(t *testing.T) {
func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string) {
session := loginUser(t, "user1")
csrf := GetCSRF(t, session, "/admin/auths/new")
req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{
"_csrf": csrf,
"type": "2",
"name": "ldap",
"host": getLDAPServerHost(),
"port": "389",
"bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com",
"bind_password": "password",
"user_base": "ou=people,dc=planetexpress,dc=com",
"filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
"admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
"attribute_username": "uid",
"attribute_name": "givenName",
"attribute_surname": "sn",
"attribute_mail": "mail",
"is_sync_enabled": "on",
"is_active": "on",
"_csrf": csrf,
"type": "2",
"name": "ldap",
"host": getLDAPServerHost(),
"port": "389",
"bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com",
"bind_password": "password",
"user_base": "ou=people,dc=planetexpress,dc=com",
"filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
"admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
"attribute_username": "uid",
"attribute_name": "givenName",
"attribute_surname": "sn",
"attribute_mail": "mail",
"attribute_ssh_public_key": sshKeyAttribute,
"is_sync_enabled": "on",
"is_active": "on",
})
session.MakeRequest(t, req, http.StatusFound)
}
Expand All @@ -119,7 +124,7 @@ func TestLDAPUserSignin(t *testing.T) {
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t)
addAuthSourceLDAP(t, "")

u := gitLDAPUsers[0]

Expand All @@ -140,7 +145,7 @@ func TestLDAPUserSync(t *testing.T) {
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t)
addAuthSourceLDAP(t, "")
models.SyncExternalUsers()

session := loginUser(t, "user1")
Expand Down Expand Up @@ -186,9 +191,41 @@ func TestLDAPUserSigninFailed(t *testing.T) {
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t)
addAuthSourceLDAP(t, "")

u := otherLDAPUsers[0]

testLoginFailed(t, u.UserName, u.Password, i18n.Tr("en", "form.username_password_incorrect"))
}

func TestLDAPUserSSHKeySync(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
prepareTestEnv(t)
addAuthSourceLDAP(t, "sshPublicKey")
models.SyncExternalUsers()

// Check if users has SSH keys synced
for _, u := range gitLDAPUsers {
if len(u.SSHKeys) == 0 {
continue
}
session := loginUserWithPassword(t, u.UserName, u.Password)

req := NewRequest(t, "GET", "/user/settings/keys")
resp := session.MakeRequest(t, req, http.StatusOK)

htmlDoc := NewHTMLParser(t, resp.Body)

divs := htmlDoc.doc.Find(".key.list .print.meta")

syncedKeys := make([]string, divs.Length())
for i := 0; i < divs.Length(); i++ {
syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text())
}

assert.ElementsMatch(t, u.SSHKeys, syncedKeys)
}
}
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions models/migrations/v66.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 24 additions & 14 deletions models/ssh_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit cdb9478

Please sign in to comment.