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)
This commit is contained in:
Generated
+4
-2
@@ -167,7 +167,8 @@
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "ecdeabc65495df2dec95d7c4a4c3e021903035e5"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/denisenkom/go-mssqldb"
|
||||
@@ -669,7 +670,8 @@
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "2aa2c176b9dab406a6970f6a55f513e8a8c8b18f"
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
|
||||
@@ -40,6 +40,10 @@ var gitLDAPUsers = []ldapUser{
|
||||
Password: "hermes",
|
||||
FullName: "Conrad Hermes",
|
||||
Email: "hermes@planetexpress.com",
|
||||
SSHKeys: []string{
|
||||
"SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8",
|
||||
"SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ",
|
||||
},
|
||||
IsAdmin: true,
|
||||
},
|
||||
{
|
||||
@@ -89,7 +93,7 @@ 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{
|
||||
@@ -107,6 +111,7 @@ func addAuthSourceLDAP(t *testing.T) {
|
||||
"attribute_name": "givenName",
|
||||
"attribute_surname": "sn",
|
||||
"attribute_mail": "mail",
|
||||
"attribute_ssh_public_key": sshKeyAttribute,
|
||||
"is_sync_enabled": "on",
|
||||
"is_active": "on",
|
||||
})
|
||||
@@ -119,7 +124,7 @@ func TestLDAPUserSignin(t *testing.T) {
|
||||
return
|
||||
}
|
||||
prepareTestEnv(t)
|
||||
addAuthSourceLDAP(t)
|
||||
addAuthSourceLDAP(t, "")
|
||||
|
||||
u := gitLDAPUsers[0]
|
||||
|
||||
@@ -140,7 +145,7 @@ func TestLDAPUserSync(t *testing.T) {
|
||||
return
|
||||
}
|
||||
prepareTestEnv(t)
|
||||
addAuthSourceLDAP(t)
|
||||
addAuthSourceLDAP(t, "")
|
||||
models.SyncExternalUsers()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+11
-1
@@ -54,6 +54,7 @@ type PublicKey struct {
|
||||
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)
|
||||
@@ -426,6 +427,7 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
|
||||
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
|
||||
|
||||
+133
-1
@@ -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 {
|
||||
|
||||
@@ -24,6 +24,7 @@ type AuthenticationForm struct {
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
AttributeSSHPublicKey string
|
||||
AttributesInBind bool
|
||||
UsePagedSearch bool
|
||||
SearchPageSize int
|
||||
|
||||
@@ -42,6 +42,7 @@ type Source struct {
|
||||
AttributeSurname string // Surname attribute
|
||||
AttributeMail string // E-mail attribute
|
||||
AttributesInBind bool // fetch attributes in bind context (not user)
|
||||
AttributeSSHPublicKey string // LDAP SSH Public Key attribute
|
||||
SearchPageSize uint32 // Search with paging page size
|
||||
Filter string // Query filter to validate entry
|
||||
AdminFilter string // Query filter to check if user is admin
|
||||
@@ -54,6 +55,7 @@ type SearchResult struct {
|
||||
Name string // Name
|
||||
Surname string // Surname
|
||||
Mail string // E-mail address
|
||||
SSHPublicKey []string // SSH Public Key
|
||||
IsAdmin bool // if user is administrator
|
||||
}
|
||||
|
||||
@@ -298,10 +300,10 @@ func (ls *Source) SearchEntries() []*SearchResult {
|
||||
|
||||
userFilter := fmt.Sprintf(ls.Filter, "*")
|
||||
|
||||
log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, ls.UserBase)
|
||||
log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, ls.UserBase)
|
||||
search := ldap.NewSearchRequest(
|
||||
ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter,
|
||||
[]string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
|
||||
[]string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey},
|
||||
nil)
|
||||
|
||||
var sr *ldap.SearchResult
|
||||
@@ -323,6 +325,7 @@ func (ls *Source) SearchEntries() []*SearchResult {
|
||||
Name: v.GetAttributeValue(ls.AttributeName),
|
||||
Surname: v.GetAttributeValue(ls.AttributeSurname),
|
||||
Mail: v.GetAttributeValue(ls.AttributeMail),
|
||||
SSHPublicKey: v.GetAttributeValues(ls.AttributeSSHPublicKey),
|
||||
IsAdmin: checkAdmin(l, ls, v.DN),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,3 +27,32 @@ func IsSliceInt64Eq(a, b []int64) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ExistsInSlice returns true if string exists in slice.
|
||||
func ExistsInSlice(target string, slice []string) bool {
|
||||
i := sort.Search(len(slice),
|
||||
func(i int) bool { return slice[i] == target })
|
||||
return i < len(slice)
|
||||
}
|
||||
|
||||
// IsEqualSlice returns true if slices are equal.
|
||||
func IsEqualSlice(target []string, source []string) bool {
|
||||
if len(target) != len(source) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (target == nil) != (source == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
sort.Strings(target)
|
||||
sort.Strings(source)
|
||||
|
||||
for i, v := range target {
|
||||
if v != source[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1390,6 +1390,7 @@ auths.attribute_username_placeholder = Leave empty to use the username entered i
|
||||
auths.attribute_name = First Name Attribute
|
||||
auths.attribute_surname = Surname Attribute
|
||||
auths.attribute_mail = Email Attribute
|
||||
auths.attribute_ssh_public_key = Public SSH Key Attribute
|
||||
auths.attributes_in_bind = Fetch Attributes in Bind DN Context
|
||||
auths.use_paged_search = Use Paged Search
|
||||
auths.search_page_size = Page Size
|
||||
|
||||
@@ -111,6 +111,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig {
|
||||
AttributeSurname: form.AttributeSurname,
|
||||
AttributeMail: form.AttributeMail,
|
||||
AttributesInBind: form.AttributesInBind,
|
||||
AttributeSSHPublicKey: form.AttributeSSHPublicKey,
|
||||
SearchPageSize: pageSize,
|
||||
Filter: form.Filter,
|
||||
AdminFilter: form.AdminFilter,
|
||||
|
||||
@@ -129,7 +129,7 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
|
||||
return
|
||||
}
|
||||
|
||||
key, err := models.AddPublicKey(uid, form.Title, content)
|
||||
key, err := models.AddPublicKey(uid, form.Title, content, 0)
|
||||
if err != nil {
|
||||
repo.HandleAddKeyError(ctx, err)
|
||||
return
|
||||
|
||||
@@ -99,7 +99,7 @@ func KeysPost(ctx *context.Context, form auth.AddKeyForm) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content); err != nil {
|
||||
if _, err = models.AddPublicKey(ctx.User.ID, form.Title, content, 0); err != nil {
|
||||
ctx.Data["HasSSHError"] = true
|
||||
switch {
|
||||
case models.IsErrKeyAlreadyExist(err):
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label>
|
||||
<input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="e.g. mail" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="attribute_ssh_public_key">{{.i18n.Tr "admin.auths.attribute_ssh_public_key"}}</label>
|
||||
<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{$cfg.AttributeSSHPublicKey}}" placeholder="e.g. SshPublicKey">
|
||||
</div>
|
||||
{{if .Source.IsLDAP}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label>
|
||||
<input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="e.g. mail">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="attribute_ssh_public_key">{{.i18n.Tr "admin.auths.attribute_ssh_public_key"}}</label>
|
||||
<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{.attribute_ssh_public_key}}" placeholder="e.g. SshPublicKey">
|
||||
</div>
|
||||
<div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}">
|
||||
<div class="ui checkbox">
|
||||
<label for="use_paged_search"><strong>{{.i18n.Tr "admin.auths.use_paged_search"}}</strong></label>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
|
||||
+3
-3
@@ -41,9 +41,9 @@ var (
|
||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
||||
// the original format. Code in the init function updates these offsets
|
||||
// as necessary.
|
||||
offsetPtr = ptrSize
|
||||
offsetPtr = uintptr(ptrSize)
|
||||
offsetScalar = uintptr(0)
|
||||
offsetFlag = ptrSize * 2
|
||||
offsetFlag = uintptr(ptrSize * 2)
|
||||
|
||||
// flagKindWidth and flagKindShift indicate various bits that the
|
||||
// reflect package uses internally to track kind information.
|
||||
@@ -58,7 +58,7 @@ var (
|
||||
// changed their positions. Code in the init function updates these
|
||||
// flags as necessary.
|
||||
flagKindWidth = uintptr(5)
|
||||
flagKindShift = flagKindWidth - 1
|
||||
flagKindShift = uintptr(flagKindWidth - 1)
|
||||
flagRO = uintptr(1 << 0)
|
||||
flagIndir = uintptr(1 << 1)
|
||||
)
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
|
||||
+5
-5
@@ -35,16 +35,16 @@ var (
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
@@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
case nilFound == true:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
case cycleFound == true:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
|
||||
+2
-2
@@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) {
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
case nilFound == true:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
case cycleFound == true:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
|
||||
|
||||
Please consider promoting this project if you find it useful.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
|
||||
OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+37
-67
File diff suppressed because it is too large
Load Diff
+74
-134
File diff suppressed because it is too large
Load Diff
+153
-105
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -25,7 +25,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
@@ -45,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
@@ -65,7 +65,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool {
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
@@ -98,7 +98,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
|
||||
// assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool {
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
@@ -115,7 +115,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool {
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
|
||||
Reference in New Issue
Block a user