diff --git a/cmd/admin.go b/cmd/admin.go index 6234ab828db8d..4c4d6f9b66e37 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -131,6 +131,10 @@ var ( Subcommands: []cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, + cmdAuthAddLdapBindDn, + cmdAuthUpdateLdapBindDn, + cmdAuthAddLdapSimpleAuth, + cmdAuthUpdateLdapSimpleAuth, microcmdAuthList, microcmdAuthDelete, }, @@ -144,7 +148,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..cce3aa894f697 --- /dev/null +++ b/cmd/admin_auth_ldap.go @@ -0,0 +1,359 @@ +// 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" + + "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: "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([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), + } + + 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([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), + } +) + +// 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) { + 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") + } +} + +// 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 +} + +// 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 { + 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.initDB(); 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 + }, + }, + } + + parseLoginSource(c, loginSource) + 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.initDB(); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginLDAP) + if err != nil { + return err + } + + parseLoginSource(c, loginSource) + 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.initDB(); 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 + }, + }, + } + + parseLoginSource(c, loginSource) + 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.initDB(); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginDLDAP) + if err != nil { + return err + } + + parseLoginSource(c, loginSource) + 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 516e46ff0bf20..0955680af2209 100644 --- a/docs/content/doc/usage/command-line.en-us.md +++ b/docs/content/doc/usage/command-line.en-us.md @@ -123,6 +123,94 @@ 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: + - `--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: + - `--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: + - `--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: + - `--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