From 53f4abc4cacb7b4bbbb7c12be2aba58421b686d5 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Fri, 19 Apr 2019 18:24:15 +0200 Subject: [PATCH 1/7] add CLI commands to manage LDAP authentication source --- cmd/admin.go | 6 +- cmd/admin_auth_ldap.go | 387 +++++ cmd/admin_auth_ldap_test.go | 1350 ++++++++++++++++++ docs/content/doc/usage/command-line.en-us.md | 92 ++ 4 files changed, 1834 insertions(+), 1 deletion(-) create mode 100644 cmd/admin_auth_ldap.go create mode 100644 cmd/admin_auth_ldap_test.go diff --git a/cmd/admin.go b/cmd/admin.go index b46eb2871e043..56ba9726a5b45 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -151,6 +151,10 @@ var ( Subcommands: []cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, + cmdAuthAddLdapBindDn, + cmdAuthUpdateLdapBindDn, + cmdAuthAddLdapSimpleAuth, + cmdAuthUpdateLdapSimpleAuth, microcmdAuthList, microcmdAuthDelete, }, @@ -171,7 +175,7 @@ var ( idFlag = cli.Int64Flag{ Name: "id", - Usage: "ID of OAuth authentication source", + Usage: "ID of authentication source", } microcmdAuthDelete = cli.Command{ diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go new file mode 100644 index 0000000000000..bcf8cc7320538 --- /dev/null +++ b/cmd/admin_auth_ldap.go @@ -0,0 +1,387 @@ +// Copyright 2016 The Gogs Authors. All rights reserved. +// Copyright 2019 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 cmd + +import ( + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/ldap" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +type ( + authService struct { + initDB func() error + createLoginSource func(loginSource *models.LoginSource) error + updateLoginSource func(loginSource *models.LoginSource) error + getLoginSourceByID func(id int64) (*models.LoginSource, error) + } +) + +var ( + commonLdapCLIFlags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Value: "custom/conf/app.ini", + Usage: "Custom configuration file path.", + }, + cli.StringFlag{ + Name: "name", + Usage: "Authentication name.", + }, + cli.BoolFlag{ + Name: "not-active", + Usage: "Deactivate the authentication source.", + }, + cli.StringFlag{ + Name: "security-protocol", + Usage: "Security protocol name.", + }, + cli.BoolFlag{ + Name: "skip-tls-verify", + Usage: "Disable TLS verification.", + }, + cli.StringFlag{ + Name: "host", + Usage: "The address where the LDAP server can be reached.", + }, + cli.IntFlag{ + Name: "port", + Usage: "The port to use when connecting to the LDAP server.", + }, + cli.StringFlag{ + Name: "user-search-base", + Usage: "The LDAP base at which user accounts will be searched for.", + }, + cli.StringFlag{ + Name: "user-filter", + Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", + }, + cli.StringFlag{ + Name: "admin-filter", + Usage: "An LDAP filter specifying if a user should be given administrator privileges.", + }, + cli.StringFlag{ + Name: "username-attribute", + Usage: "The attribute of the user’s LDAP record containing the user name.", + }, + cli.StringFlag{ + Name: "firstname-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s first name.", + }, + cli.StringFlag{ + Name: "surname-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s surname.", + }, + cli.StringFlag{ + Name: "email-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s email address.", + }, + cli.StringFlag{ + Name: "public-ssh-key-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", + }, + } + + ldapBindDnCLIFlags = append(commonLdapCLIFlags, + cli.StringFlag{ + Name: "bind-dn", + Usage: "The DN to bind to the LDAP server with when searching for the user.", + }, + cli.StringFlag{ + Name: "bind-password", + Usage: "The password for the Bind DN, if any.", + }, + cli.BoolFlag{ + Name: "attributes-in-bind", + Usage: "Fetch attributes in bind DN context.", + }, + cli.BoolFlag{ + Name: "synchronize-users", + Usage: "Enable user synchronization.", + }, + cli.UintFlag{ + Name: "page-size", + Usage: "Search page size.", + }) + + ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, + cli.StringFlag{ + Name: "user-dn", + Usage: "The user’s DN.", + }) + + cmdAuthAddLdapBindDn = cli.Command{ + Name: "add-ldap", + Usage: "Add new LDAP (via Bind DN) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().addLdapBindDn(c) + }, + Flags: ldapBindDnCLIFlags, + } + + cmdAuthUpdateLdapBindDn = cli.Command{ + Name: "update-ldap", + Usage: "Update existing LDAP (via Bind DN) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().updateLdapBindDn(c) + }, + Flags: append(append(make([]cli.Flag, 0, len(ldapBindDnCLIFlags)+1), ldapBindDnCLIFlags[0], idFlag), ldapBindDnCLIFlags[1:]...), + } + + cmdAuthAddLdapSimpleAuth = cli.Command{ + Name: "add-ldap-simple", + Usage: "Add new LDAP (simple auth) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().addLdapSimpleAuth(c) + }, + Flags: ldapSimpleAuthCLIFlags, + } + + cmdAuthUpdateLdapSimpleAuth = cli.Command{ + Name: "update-ldap-simple", + Usage: "Update existing LDAP (simple auth) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().updateLdapSimpleAuth(c) + }, + Flags: append(append(make([]cli.Flag, 0, len(ldapSimpleAuthCLIFlags)+1), ldapSimpleAuthCLIFlags[0], idFlag), ldapSimpleAuthCLIFlags[1:]...), + } +) + +// newAuthService creates a service with default functions. +func newAuthService() *authService { + return &authService{ + initDB: initDB, + createLoginSource: models.CreateLoginSource, + updateLoginSource: models.UpdateSource, + getLoginSourceByID: models.GetLoginSourceByID, + } +} + +// parseLoginSource assigns values on loginSource according to command line flags. +func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) error { + if c.IsSet("name") { + loginSource.Name = c.String("name") + } + if c.IsSet("not-active") { + loginSource.IsActived = !c.Bool("not-active") + } + if c.IsSet("synchronize-users") { + loginSource.IsSyncEnabled = c.Bool("synchronize-users") + } + return nil +} + +// parseLdapConfig assigns values on config according to command line flags. +func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error { + if c.IsSet("name") { + config.Source.Name = c.String("name") + } + if c.IsSet("host") { + config.Source.Host = c.String("host") + } + if c.IsSet("port") { + config.Source.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 + } + if c.IsSet("skip-tls-verify") { + config.Source.SkipVerify = c.Bool("skip-tls-verify") + } + if c.IsSet("bind-dn") { + config.Source.BindDN = c.String("bind-dn") + } + if c.IsSet("user-dn") { + config.Source.UserDN = c.String("user-dn") + } + if c.IsSet("bind-password") { + config.Source.BindPassword = c.String("bind-password") + } + if c.IsSet("user-search-base") { + config.Source.UserBase = c.String("user-search-base") + } + if c.IsSet("username-attribute") { + config.Source.AttributeUsername = c.String("username-attribute") + } + if c.IsSet("firstname-attribute") { + config.Source.AttributeName = c.String("firstname-attribute") + } + if c.IsSet("surname-attribute") { + config.Source.AttributeSurname = c.String("surname-attribute") + } + if c.IsSet("email-attribute") { + config.Source.AttributeMail = c.String("email-attribute") + } + if c.IsSet("attributes-in-bind") { + config.Source.AttributesInBind = c.Bool("attributes-in-bind") + } + if c.IsSet("public-ssh-key-attribute") { + config.Source.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") + } + if c.IsSet("page-size") { + config.Source.SearchPageSize = uint32(c.Uint("page-size")) + } + if c.IsSet("user-filter") { + config.Source.Filter = c.String("user-filter") + } + if c.IsSet("admin-filter") { + config.Source.AdminFilter = c.String("admin-filter") + } + return nil +} + +// 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 { + if strings.EqualFold(name, n) { + return i, true + } + } + return 0, false +} + +func (a *authService) initLdapCommand(c *cli.Context) error { + if c.IsSet("config") { + setting.CustomConf = c.String("config") + } + + return a.initDB() +} + +// getLoginSource gets the login source by its id define in command line flags. +// It returns an error if the id is not set, does not match any source or if the source is not of expected type. +func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) { + if err := argsSet(c, "id"); err != nil { + return nil, err + } + + loginSource, err := a.getLoginSourceByID(c.Int64("id")) + if err != nil { + return nil, err + } + + if loginSource.Type != loginType { + return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", models.LoginNames[loginType], models.LoginNames[loginSource.Type]) + } + + return loginSource, nil +} + +// addLdapBindDn adds a new LDAP via Bind DN authentication source. +func (a *authService) addLdapBindDn(c *cli.Context) error { + if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { + return err + } + + if err := a.initLdapCommand(c); err != nil { + return err + } + + loginSource := &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, // active by default + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, // always true + }, + }, + } + + if err := parseLoginSource(c, loginSource); err != nil { + return err + } + + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.createLoginSource(loginSource) +} + +// updateLdapBindDn updates a new LDAP via Bind DN authentication source. +func (a *authService) updateLdapBindDn(c *cli.Context) error { + if err := a.initLdapCommand(c); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginLDAP) + if err != nil { + return err + } + + if err := parseLoginSource(c, loginSource); err != nil { + return err + } + + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.updateLoginSource(loginSource) +} + +// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. +func (a *authService) addLdapSimpleAuth(c *cli.Context) error { + if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { + return err + } + + if err := a.initLdapCommand(c); err != nil { + return err + } + + loginSource := &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: true, // active by default + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, // always true + }, + }, + } + + if err := parseLoginSource(c, loginSource); err != nil { + return err + } + + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.createLoginSource(loginSource) +} + +// updateLdapBindDn updates a new LDAP (simple auth) authentication source. +func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { + if err := a.initLdapCommand(c); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginDLDAP) + if err != nil { + return err + } + + if err := parseLoginSource(c, loginSource); err != nil { + return err + } + + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.updateLoginSource(loginSource) +} diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go new file mode 100644 index 0000000000000..4af9f167c3799 --- /dev/null +++ b/cmd/admin_auth_ldap_test.go @@ -0,0 +1,1350 @@ +// Copyright 2019 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 cmd + +import ( + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/ldap" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestAddLdapBindDn(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source full", + "--not-active", + "--security-protocol", "ldaps", + "--skip-tls-verify", + "--host", "ldap-bind-server full", + "--port", "9876", + "--user-search-base", "ou=Users,dc=full-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + "--username-attribute", "uid-bind full", + "--firstname-attribute", "givenName-bind full", + "--surname-attribute", "sn-bind full", + "--email-attribute", "mail-bind full", + "--public-ssh-key-attribute", "publickey-bind full", + "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", + "--bind-password", "secret-bind-full", + "--attributes-in-bind", + "--synchronize-users", + "--page-size", "99", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source full", + IsActived: false, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source full", + Host: "ldap-bind-server full", + Port: 9876, + SecurityProtocol: ldap.SecurityProtocol(1), + SkipVerify: true, + BindDN: "cn=readonly,dc=full-domain-bind,dc=org", + BindPassword: "secret-bind-full", + UserBase: "ou=Users,dc=full-domain-bind,dc=org", + AttributeUsername: "uid-bind full", + AttributeName: "givenName-bind full", + AttributeSurname: "sn-bind full", + AttributeMail: "mail-bind full", + AttributesInBind: true, + AttributeSSHPublicKey: "publickey-bind full", + SearchPageSize: 99, + Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source min", + "--security-protocol", "unencrypted", + "--host", "ldap-bind-server min", + "--port", "1234", + "--user-search-base", "ou=Users,dc=min-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)", + "--email-attribute", "mail-bind min", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source min", + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source min", + Host: "ldap-bind-server min", + Port: 1234, + SecurityProtocol: ldap.SecurityProtocol(0), + UserBase: "ou=Users,dc=min-domain-bind,dc=org", + AttributeMail: "mail-bind min", + Filter: "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "zzzzz", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "Unknown security protocol name: zzzzz", + }, + // case 3 + { + args: []string{ + "ldap-test", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "name is not set", + }, + // case 4 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "security-protocol is not set", + }, + // case 5 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "host is not set", + }, + // case 6 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "port is not set", + }, + // case 7 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--email-attribute", "mail", + }, + errMsg: "user-filter is not set", + }, + // case 8 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + errMsg: "email-attribute is not set", + }, + } + + for n, c := range cases { + // Mock functions. + var createdLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + createdLoginSource = loginSource + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call updateLoginSource", n) + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) + return nil, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthAddLdapBindDn.Flags + app.Action = service.addLdapBindDn + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, createdLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestAddLdapSimpleAuth(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source full", + "--not-active", + "--security-protocol", "starttls", + "--skip-tls-verify", + "--host", "ldap-simple-server full", + "--port", "987", + "--user-search-base", "ou=Users,dc=full-domain-simple,dc=org", + "--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + "--username-attribute", "uid-simple full", + "--firstname-attribute", "givenName-simple full", + "--surname-attribute", "sn-simple full", + "--email-attribute", "mail-simple full", + "--public-ssh-key-attribute", "publickey-simple full", + "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source full", + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source full", + Host: "ldap-simple-server full", + Port: 987, + SecurityProtocol: ldap.SecurityProtocol(2), + SkipVerify: true, + UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + UserBase: "ou=Users,dc=full-domain-simple,dc=org", + AttributeUsername: "uid-simple full", + AttributeName: "givenName-simple full", + AttributeSurname: "sn-simple full", + AttributeMail: "mail-simple full", + AttributeSSHPublicKey: "publickey-simple full", + Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source min", + "--security-protocol", "unencrypted", + "--host", "ldap-simple-server min", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(min-simple-cn=%s))", + "--email-attribute", "mail-simple min", + "--user-dn", "cn=%s,ou=Users,dc=min-domain-simple,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source min", + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source min", + Host: "ldap-simple-server min", + Port: 123, + SecurityProtocol: ldap.SecurityProtocol(0), + UserDN: "cn=%s,ou=Users,dc=min-domain-simple,dc=org", + AttributeMail: "mail-simple min", + Filter: "(&(objectClass=posixAccount)(min-simple-cn=%s))", + Enabled: true, + }, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "zzzzz", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "Unknown security protocol name: zzzzz", + }, + // case 3 + { + args: []string{ + "ldap-test", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "name is not set", + }, + // case 4 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "security-protocol is not set", + }, + // case 5 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "host is not set", + }, + // case 6 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "port is not set", + }, + // case 7 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "user-filter is not set", + }, + // case 8 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "email-attribute is not set", + }, + // case 9 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + }, + errMsg: "user-dn is not set", + }, + } + + for n, c := range cases { + // Mock functions. + var createdLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + createdLoginSource = loginSource + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call updateLoginSource", n) + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) + return nil, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthAddLdapSimpleAuth.Flags + app.Action = service.addLdapSimpleAuth + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, createdLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestUpdateLdapBindDn(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + id int64 + existingLoginSource *models.LoginSource + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--id", "23", + "--name", "ldap (via Bind DN) source full", + "--not-active", + "--security-protocol", "LDAPS", + "--skip-tls-verify", + "--host", "ldap-bind-server full", + "--port", "9876", + "--user-search-base", "ou=Users,dc=full-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + "--username-attribute", "uid-bind full", + "--firstname-attribute", "givenName-bind full", + "--surname-attribute", "sn-bind full", + "--email-attribute", "mail-bind full", + "--public-ssh-key-attribute", "publickey-bind full", + "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", + "--bind-password", "secret-bind-full", + "--synchronize-users", + "--page-size", "99", + }, + id: 23, + existingLoginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, + }, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source full", + IsActived: false, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source full", + Host: "ldap-bind-server full", + Port: 9876, + SecurityProtocol: ldap.SecurityProtocol(1), + SkipVerify: true, + BindDN: "cn=readonly,dc=full-domain-bind,dc=org", + BindPassword: "secret-bind-full", + UserBase: "ou=Users,dc=full-domain-bind,dc=org", + AttributeUsername: "uid-bind full", + AttributeName: "givenName-bind full", + AttributeSurname: "sn-bind full", + AttributeMail: "mail-bind full", + AttributesInBind: false, + AttributeSSHPublicKey: "publickey-bind full", + SearchPageSize: 99, + Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--id", "1", + "--name", "ldap (via Bind DN) source", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source", + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source", + }, + }, + }, + }, + // case 3 + { + args: []string{ + "ldap-test", + "--id", "1", + "--not-active", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 4 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "LDAPS", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SecurityProtocol: ldap.SecurityProtocol(1), + }, + }, + }, + }, + // case 5 + { + args: []string{ + "ldap-test", + "--id", "1", + "--skip-tls-verify", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SkipVerify: true, + }, + }, + }, + }, + // case 6 + { + args: []string{ + "ldap-test", + "--id", "1", + "--host", "ldap-server", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Host: "ldap-server", + }, + }, + }, + }, + // case 7 + { + args: []string{ + "ldap-test", + "--id", "1", + "--port", "389", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Port: 389, + }, + }, + }, + }, + // case 8 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-search-base", "ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserBase: "ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 9 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 10 + { + args: []string{ + "ldap-test", + "--id", "1", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 11 + { + args: []string{ + "ldap-test", + "--id", "1", + "--username-attribute", "uid", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeUsername: "uid", + }, + }, + }, + }, + // case 12 + { + args: []string{ + "ldap-test", + "--id", "1", + "--firstname-attribute", "givenName", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeName: "givenName", + }, + }, + }, + }, + // case 13 + { + args: []string{ + "ldap-test", + "--id", "1", + "--surname-attribute", "sn", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSurname: "sn", + }, + }, + }, + }, + // case 14 + { + args: []string{ + "ldap-test", + "--id", "1", + "--email-attribute", "mail", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeMail: "mail", + }, + }, + }, + }, + // case 15 + { + args: []string{ + "ldap-test", + "--id", "1", + "--attributes-in-bind", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributesInBind: true, + }, + }, + }, + }, + // case 16 + { + args: []string{ + "ldap-test", + "--id", "1", + "--public-ssh-key-attribute", "publickey", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSSHPublicKey: "publickey", + }, + }, + }, + }, + // case 17 + { + args: []string{ + "ldap-test", + "--id", "1", + "--bind-dn", "cn=readonly,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + BindDN: "cn=readonly,dc=domain,dc=org", + }, + }, + }, + }, + // case 18 + { + args: []string{ + "ldap-test", + "--id", "1", + "--bind-password", "secret", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + BindPassword: "secret", + }, + }, + }, + }, + // case 19 + { + args: []string{ + "ldap-test", + "--id", "1", + "--synchronize-users", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 20 + { + args: []string{ + "ldap-test", + "--id", "1", + "--page-size", "12", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SearchPageSize: 12, + }, + }, + }, + }, + // case 21 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "xxxxx", + }, + errMsg: "Unknown security protocol name: xxxxx", + }, + // case 22 + { + args: []string{ + "ldap-test", + }, + errMsg: "id is not set", + }, + // case 23 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginOAuth2, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", + }, + } + + for n, c := range cases { + // Mock functions. + var updatedLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call createLoginSource", n) + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + updatedLoginSource = loginSource + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + if c.id != 0 { + assert.Equal(t, c.id, id, "case %d: wrong id", n) + } + if c.existingLoginSource != nil { + return c.existingLoginSource, nil + } + return &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthUpdateLdapBindDn.Flags + app.Action = service.updateLdapBindDn + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, updatedLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestUpdateLdapSimpleAuth(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + id int64 + existingLoginSource *models.LoginSource + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--id", "7", + "--name", "ldap (simple auth) source full", + "--not-active", + "--security-protocol", "starttls", + "--skip-tls-verify", + "--host", "ldap-simple-server full", + "--port", "987", + "--user-search-base", "ou=Users,dc=full-domain-simple,dc=org", + "--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + "--username-attribute", "uid-simple full", + "--firstname-attribute", "givenName-simple full", + "--surname-attribute", "sn-simple full", + "--email-attribute", "mail-simple full", + "--public-ssh-key-attribute", "publickey-simple full", + "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + }, + id: 7, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source full", + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source full", + Host: "ldap-simple-server full", + Port: 987, + SecurityProtocol: ldap.SecurityProtocol(2), + SkipVerify: true, + UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + UserBase: "ou=Users,dc=full-domain-simple,dc=org", + AttributeUsername: "uid-simple full", + AttributeName: "givenName-simple full", + AttributeSurname: "sn-simple full", + AttributeMail: "mail-simple full", + AttributeSSHPublicKey: "publickey-simple full", + Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--id", "1", + "--name", "ldap (simple auth) source", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source", + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source", + }, + }, + }, + }, + // case 3 + { + args: []string{ + "ldap-test", + "--id", "1", + "--not-active", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 4 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "starttls", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SecurityProtocol: ldap.SecurityProtocol(2), + }, + }, + }, + }, + // case 5 + { + args: []string{ + "ldap-test", + "--id", "1", + "--skip-tls-verify", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SkipVerify: true, + }, + }, + }, + }, + // case 6 + { + args: []string{ + "ldap-test", + "--id", "1", + "--host", "ldap-server", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Host: "ldap-server", + }, + }, + }, + }, + // case 7 + { + args: []string{ + "ldap-test", + "--id", "1", + "--port", "987", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Port: 987, + }, + }, + }, + }, + // case 8 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-search-base", "ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserBase: "ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 9 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Filter: "(&(objectClass=posixAccount)(cn=%s))", + }, + }, + }, + }, + // case 10 + { + args: []string{ + "ldap-test", + "--id", "1", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 11 + { + args: []string{ + "ldap-test", + "--id", "1", + "--username-attribute", "uid", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeUsername: "uid", + }, + }, + }, + }, + // case 12 + { + args: []string{ + "ldap-test", + "--id", "1", + "--firstname-attribute", "givenName", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeName: "givenName", + }, + }, + }, + }, + // case 13 + { + args: []string{ + "ldap-test", + "--id", "1", + "--surname-attribute", "sn", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSurname: "sn", + }, + }, + }, + }, + // case 14 + { + args: []string{ + "ldap-test", + "--id", "1", + "--email-attribute", "mail", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeMail: "mail", + }, + }, + }, + }, + // case 15 + { + args: []string{ + "ldap-test", + "--id", "1", + "--public-ssh-key-attribute", "publickey", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSSHPublicKey: "publickey", + }, + }, + }, + }, + // case 16 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserDN: "cn=%s,ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 17 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "xxxxx", + }, + errMsg: "Unknown security protocol name: xxxxx", + }, + // case 18 + { + args: []string{ + "ldap-test", + }, + errMsg: "id is not set", + }, + // case 19 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginPAM, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", + }, + } + + for n, c := range cases { + // Mock functions. + var updatedLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call createLoginSource", n) + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + updatedLoginSource = loginSource + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + if c.id != 0 { + assert.Equal(t, c.id, id, "case %d: wrong id", n) + } + if c.existingLoginSource != nil { + return c.existingLoginSource, nil + } + return &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthUpdateLdapSimpleAuth.Flags + app.Action = service.updateLdapSimpleAuth + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, updatedLoginSource, "case %d: wrong loginSource", n) + } + } +} diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index df749ebe04cf2..e9382d5561702 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -123,6 +123,98 @@ Admin operations: - `--custom-email-url`: Use a custom Email URL (option for GitHub). - Examples: - `gitea admin auth update-oauth --id 1 --name external-github-updated` + - `add-ldap`: Add new LDAP (via Bind DN) authentication source + - Options: + - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") + - `--name value`: Authentication name. Required. + - `--not-active`: Deactivate the authentication source. + - `--security-protocol value`: Security protocol name. Required. + - `--skip-tls-verify`: Disable TLS verification. + - `--host value`: The address where the LDAP server can be reached. Required. + - `--port value`: The port to use when connecting to the LDAP server. Required. + - `--user-search-base value`: The LDAP base at which user accounts will be searched for. Required. + - `--user-filter value`: An LDAP filter declaring how to find the user record that is attempting to authenticate. Required. + - `--admin-filter value`: An LDAP filter specifying if a user should be given administrator privileges. + - `--username-attribute value`: The attribute of the user’s LDAP record containing the user name. + - `--firstname-attribute value`: The attribute of the user’s LDAP record containing the user’s first name. + - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. + - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. + - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. + - `--bind-password value`: The password for the Bind DN, if any. + - `--attributes-in-bind`: Fetch attributes in bind DN context. + - `--synchronize-users`: Enable user synchronization. + - `--page-size value`: Search page size. + - Examples: + - `gitea admin auth add-ldap --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-search-base "ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(uid=%s))" --email-attribute mail` + - `update-ldap`: Update existing LDAP (via Bind DN) authentication source + - Options: + - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") + - `--id value`: ID of authentication source. Required. + - `--name value`: Authentication name. + - `--not-active`: Deactivate the authentication source. + - `--security-protocol value`: Security protocol name. + - `--skip-tls-verify`: Disable TLS verification. + - `--host value`: The address where the LDAP server can be reached. + - `--port value`: The port to use when connecting to the LDAP server. + - `--user-search-base value`: The LDAP base at which user accounts will be searched for. + - `--user-filter value`: An LDAP filter declaring how to find the user record that is attempting to authenticate. + - `--admin-filter value`: An LDAP filter specifying if a user should be given administrator privileges. + - `--username-attribute value`: The attribute of the user’s LDAP record containing the user name. + - `--firstname-attribute value`: The attribute of the user’s LDAP record containing the user’s first name. + - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. + - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. + - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--bind-dn value`: The DN to bind to the LDAP server with when searching for the user. + - `--bind-password value`: The password for the Bind DN, if any. + - `--attributes-in-bind`: Fetch attributes in bind DN context. + - `--synchronize-users`: Enable user synchronization. + - `--page-size value`: Search page size. + - Examples: + - `gitea admin auth update-ldap --id 1 --name "my ldap auth source"` + - `gitea admin auth update-ldap --id 1 --username-attribute uid --firstname-attribute givenName --surname-attribute sn` + - `add-ldap-simple`: Add new LDAP (simple auth) authentication source + - Options: + - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") + - `--name value`: Authentication name. Required. + - `--not-active`: Deactivate the authentication source. + - `--security-protocol value`: Security protocol name. Required. + - `--skip-tls-verify`: Disable TLS verification. + - `--host value`: The address where the LDAP server can be reached. Required. + - `--port value`: The port to use when connecting to the LDAP server. Required. + - `--user-search-base value`: The LDAP base at which user accounts will be searched for. + - `--user-filter value`: An LDAP filter declaring how to find the user record that is attempting to authenticate. Required. + - `--admin-filter value`: An LDAP filter specifying if a user should be given administrator privileges. + - `--username-attribute value`: The attribute of the user’s LDAP record containing the user name. + - `--firstname-attribute value`: The attribute of the user’s LDAP record containing the user’s first name. + - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. + - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. Required. + - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--user-dn value`: The user’s DN. Required. + - Examples: + - `gitea admin auth add-ldap-simple --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-dn "cn=%s,ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(cn=%s))" --email-attribute mail` + - `update-ldap-simple`: Update existing LDAP (simple auth) authentication source + - Options: + - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") + - `--id value`: ID of authentication source. Required. + - `--name value`: Authentication name. + - `--not-active`: Deactivate the authentication source. + - `--security-protocol value`: Security protocol name. + - `--skip-tls-verify`: Disable TLS verification. + - `--host value`: The address where the LDAP server can be reached. + - `--port value`: The port to use when connecting to the LDAP server. + - `--user-search-base value`: The LDAP base at which user accounts will be searched for. + - `--user-filter value`: An LDAP filter declaring how to find the user record that is attempting to authenticate. + - `--admin-filter value`: An LDAP filter specifying if a user should be given administrator privileges. + - `--username-attribute value`: The attribute of the user’s LDAP record containing the user name. + - `--firstname-attribute value`: The attribute of the user’s LDAP record containing the user’s first name. + - `--surname-attribute value`: The attribute of the user’s LDAP record containing the user’s surname. + - `--email-attribute value`: The attribute of the user’s LDAP record containing the user’s email address. + - `--public-ssh-key-attribute value`: The attribute of the user’s LDAP record containing the user’s public ssh key. + - `--user-dn value`: The user’s DN. + - Examples: + - `gitea admin auth update-ldap-simple --id 1 --name "my ldap auth source"` + - `gitea admin auth update-ldap-simple --id 1 --username-attribute uid --firstname-attribute givenName --surname-attribute sn` #### cert From 9af33abf459bada97f3d0fb42df9cc78d21dfbec Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Fri, 19 Apr 2019 22:29:15 +0200 Subject: [PATCH 2/7] delete Gogs copyright --- cmd/admin_auth_ldap.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index bcf8cc7320538..5bd0d3ff41c08 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -1,4 +1,3 @@ -// Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2019 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. From 41ce41b583264f941f0f33d917954ae7742af503 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Fri, 19 Apr 2019 22:53:19 +0200 Subject: [PATCH 3/7] remove unused return value of func parseLoginSource --- cmd/admin_auth_ldap.go | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 5bd0d3ff41c08..ab187a11569ab 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -165,7 +165,7 @@ func newAuthService() *authService { } // parseLoginSource assigns values on loginSource according to command line flags. -func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) error { +func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) { if c.IsSet("name") { loginSource.Name = c.String("name") } @@ -175,7 +175,6 @@ func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) error { if c.IsSet("synchronize-users") { loginSource.IsSyncEnabled = c.Bool("synchronize-users") } - return nil } // parseLdapConfig assigns values on config according to command line flags. @@ -299,10 +298,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { }, } - if err := parseLoginSource(c, loginSource); err != nil { - return err - } - + parseLoginSource(c, loginSource) if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { return err } @@ -321,10 +317,7 @@ func (a *authService) updateLdapBindDn(c *cli.Context) error { return err } - if err := parseLoginSource(c, loginSource); err != nil { - return err - } - + parseLoginSource(c, loginSource) if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { return err } @@ -352,10 +345,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { }, } - if err := parseLoginSource(c, loginSource); err != nil { - return err - } - + parseLoginSource(c, loginSource) if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { return err } @@ -374,10 +364,7 @@ func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { return err } - if err := parseLoginSource(c, loginSource); err != nil { - return err - } - + parseLoginSource(c, loginSource) if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { return err } From 617cababa50e58aaa3a523b574c0ba99705808e2 Mon Sep 17 00:00:00 2001 From: Lanre Adelowo Date: Sun, 21 Apr 2019 20:30:20 +0200 Subject: [PATCH 4/7] fix comment Co-Authored-By: ngourdon <31291059+ngourdon@users.noreply.github.com> --- cmd/admin_auth_ldap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index ab187a11569ab..a093c4bd1ed40 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -259,7 +259,7 @@ func (a *authService) initLdapCommand(c *cli.Context) error { return a.initDB() } -// getLoginSource gets the login source by its id define in command line flags. +// getLoginSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) { if err := argsSet(c, "id"); err != nil { From 280c2282fd5962e74eefdfb22f68b92a1e38e184 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Thu, 2 May 2019 13:23:49 +0200 Subject: [PATCH 5/7] remove config flag already present in global flags --- cmd/admin_auth_ldap.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index a093c4bd1ed40..4a6606c068aae 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -26,11 +26,6 @@ type ( var ( commonLdapCLIFlags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Value: "custom/conf/app.ini", - Usage: "Custom configuration file path.", - }, cli.StringFlag{ Name: "name", Usage: "Authentication name.", @@ -132,7 +127,7 @@ var ( Action: func(c *cli.Context) error { return newAuthService().updateLdapBindDn(c) }, - Flags: append(append(make([]cli.Flag, 0, len(ldapBindDnCLIFlags)+1), ldapBindDnCLIFlags[0], idFlag), ldapBindDnCLIFlags[1:]...), + Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), } cmdAuthAddLdapSimpleAuth = cli.Command{ @@ -150,7 +145,7 @@ var ( Action: func(c *cli.Context) error { return newAuthService().updateLdapSimpleAuth(c) }, - Flags: append(append(make([]cli.Flag, 0, len(ldapSimpleAuthCLIFlags)+1), ldapSimpleAuthCLIFlags[0], idFlag), ldapSimpleAuthCLIFlags[1:]...), + Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), } ) From 0c492f64924380a0b9b6cda411fd998c3531cb56 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Thu, 2 May 2019 13:52:52 +0200 Subject: [PATCH 6/7] remove config flag from ldap commands in docs --- docs/content/doc/usage/command-line.en-us.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/content/doc/usage/command-line.en-us.md b/docs/content/doc/usage/command-line.en-us.md index 36399ea892023..1393bb3a2f297 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -120,7 +120,6 @@ Admin operations: - `gitea admin auth update-oauth --id 1 --name external-github-updated` - `add-ldap`: Add new LDAP (via Bind DN) authentication source - Options: - - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") - `--name value`: Authentication name. Required. - `--not-active`: Deactivate the authentication source. - `--security-protocol value`: Security protocol name. Required. @@ -144,7 +143,6 @@ Admin operations: - `gitea admin auth add-ldap --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-search-base "ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(uid=%s))" --email-attribute mail` - `update-ldap`: Update existing LDAP (via Bind DN) authentication source - Options: - - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") - `--id value`: ID of authentication source. Required. - `--name value`: Authentication name. - `--not-active`: Deactivate the authentication source. @@ -170,7 +168,6 @@ Admin operations: - `gitea admin auth update-ldap --id 1 --username-attribute uid --firstname-attribute givenName --surname-attribute sn` - `add-ldap-simple`: Add new LDAP (simple auth) authentication source - Options: - - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") - `--name value`: Authentication name. Required. - `--not-active`: Deactivate the authentication source. - `--security-protocol value`: Security protocol name. Required. @@ -190,7 +187,6 @@ Admin operations: - `gitea admin auth add-ldap-simple --name ldap --security-protocol unencrypted --host mydomain.org --port 389 --user-dn "cn=%s,ou=Users,dc=mydomain,dc=org" --user-filter "(&(objectClass=posixAccount)(cn=%s))" --email-attribute mail` - `update-ldap-simple`: Update existing LDAP (simple auth) authentication source - Options: - - `--config value`, `-c value`: Custom configuration file path (default: "custom/conf/app.ini") - `--id value`: ID of authentication source. Required. - `--name value`: Authentication name. - `--not-active`: Deactivate the authentication source. From 8be2418907368d63b1adf52a27b84268669210a5 Mon Sep 17 00:00:00 2001 From: Nicolas Gourdon Date: Thu, 2 May 2019 20:16:06 +0200 Subject: [PATCH 7/7] remove config flag handling --- cmd/admin_auth_ldap.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go index 4a6606c068aae..cce3aa894f697 100644 --- a/cmd/admin_auth_ldap.go +++ b/cmd/admin_auth_ldap.go @@ -10,7 +10,6 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth/ldap" - "code.gitea.io/gitea/modules/setting" "github.com/urfave/cli" ) @@ -246,14 +245,6 @@ func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { return 0, false } -func (a *authService) initLdapCommand(c *cli.Context) error { - if c.IsSet("config") { - setting.CustomConf = c.String("config") - } - - return a.initDB() -} - // getLoginSource gets the login source by its id defined in the command line flags. // It returns an error if the id is not set, does not match any source or if the source is not of expected type. func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) { @@ -279,7 +270,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { return err } - if err := a.initLdapCommand(c); err != nil { + if err := a.initDB(); err != nil { return err } @@ -303,7 +294,7 @@ func (a *authService) addLdapBindDn(c *cli.Context) error { // updateLdapBindDn updates a new LDAP via Bind DN authentication source. func (a *authService) updateLdapBindDn(c *cli.Context) error { - if err := a.initLdapCommand(c); err != nil { + if err := a.initDB(); err != nil { return err } @@ -326,7 +317,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { return err } - if err := a.initLdapCommand(c); err != nil { + if err := a.initDB(); err != nil { return err } @@ -350,7 +341,7 @@ func (a *authService) addLdapSimpleAuth(c *cli.Context) error { // updateLdapBindDn updates a new LDAP (simple auth) authentication source. func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { - if err := a.initLdapCommand(c); err != nil { + if err := a.initDB(); err != nil { return err }