Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
11 KiB
397 lines
11 KiB
// 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 ( |
|
"context" |
|
"fmt" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/models/auth" |
|
"code.gitea.io/gitea/services/auth/source/ldap" |
|
|
|
"github.com/urfave/cli" |
|
) |
|
|
|
type ( |
|
authService struct { |
|
initDB func(ctx context.Context) error |
|
createAuthSource func(*auth.Source) error |
|
updateAuthSource func(*auth.Source) error |
|
getAuthSourceByID func(id int64) (*auth.Source, 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: "restricted-filter", |
|
Usage: "An LDAP filter specifying if a user should be given restricted status.", |
|
}, |
|
cli.BoolFlag{ |
|
Name: "allow-deactivate-all", |
|
Usage: "Allow empty search results to deactivate all users.", |
|
}, |
|
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.", |
|
}, |
|
cli.BoolFlag{ |
|
Name: "skip-local-2fa", |
|
Usage: "Set to true to skip local 2fa for users authenticated by this source", |
|
}, |
|
cli.StringFlag{ |
|
Name: "avatar-attribute", |
|
Usage: "The attribute of the user’s LDAP record containing the user’s avatar.", |
|
}, |
|
} |
|
|
|
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, |
|
createAuthSource: auth.CreateSource, |
|
updateAuthSource: auth.UpdateSource, |
|
getAuthSourceByID: auth.GetSourceByID, |
|
} |
|
} |
|
|
|
// parseAuthSource assigns values on authSource according to command line flags. |
|
func parseAuthSource(c *cli.Context, authSource *auth.Source) { |
|
if c.IsSet("name") { |
|
authSource.Name = c.String("name") |
|
} |
|
if c.IsSet("not-active") { |
|
authSource.IsActive = !c.Bool("not-active") |
|
} |
|
if c.IsSet("synchronize-users") { |
|
authSource.IsSyncEnabled = c.Bool("synchronize-users") |
|
} |
|
} |
|
|
|
// parseLdapConfig assigns values on config according to command line flags. |
|
func parseLdapConfig(c *cli.Context, config *ldap.Source) error { |
|
if c.IsSet("name") { |
|
config.Name = c.String("name") |
|
} |
|
if c.IsSet("host") { |
|
config.Host = c.String("host") |
|
} |
|
if c.IsSet("port") { |
|
config.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.SecurityProtocol = p |
|
} |
|
if c.IsSet("skip-tls-verify") { |
|
config.SkipVerify = c.Bool("skip-tls-verify") |
|
} |
|
if c.IsSet("bind-dn") { |
|
config.BindDN = c.String("bind-dn") |
|
} |
|
if c.IsSet("user-dn") { |
|
config.UserDN = c.String("user-dn") |
|
} |
|
if c.IsSet("bind-password") { |
|
config.BindPassword = c.String("bind-password") |
|
} |
|
if c.IsSet("user-search-base") { |
|
config.UserBase = c.String("user-search-base") |
|
} |
|
if c.IsSet("username-attribute") { |
|
config.AttributeUsername = c.String("username-attribute") |
|
} |
|
if c.IsSet("firstname-attribute") { |
|
config.AttributeName = c.String("firstname-attribute") |
|
} |
|
if c.IsSet("surname-attribute") { |
|
config.AttributeSurname = c.String("surname-attribute") |
|
} |
|
if c.IsSet("email-attribute") { |
|
config.AttributeMail = c.String("email-attribute") |
|
} |
|
if c.IsSet("attributes-in-bind") { |
|
config.AttributesInBind = c.Bool("attributes-in-bind") |
|
} |
|
if c.IsSet("public-ssh-key-attribute") { |
|
config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") |
|
} |
|
if c.IsSet("avatar-attribute") { |
|
config.AttributeAvatar = c.String("avatar-attribute") |
|
} |
|
if c.IsSet("page-size") { |
|
config.SearchPageSize = uint32(c.Uint("page-size")) |
|
} |
|
if c.IsSet("user-filter") { |
|
config.Filter = c.String("user-filter") |
|
} |
|
if c.IsSet("admin-filter") { |
|
config.AdminFilter = c.String("admin-filter") |
|
} |
|
if c.IsSet("restricted-filter") { |
|
config.RestrictedFilter = c.String("restricted-filter") |
|
} |
|
if c.IsSet("allow-deactivate-all") { |
|
config.AllowDeactivateAll = c.Bool("allow-deactivate-all") |
|
} |
|
if c.IsSet("skip-local-2fa") { |
|
config.SkipLocalTwoFA = c.Bool("skip-local-2fa") |
|
} |
|
|
|
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 ldap.SecurityProtocolNames { |
|
if strings.EqualFold(name, n) { |
|
return i, true |
|
} |
|
} |
|
return 0, false |
|
} |
|
|
|
// getAuthSource 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) getAuthSource(c *cli.Context, authType auth.Type) (*auth.Source, error) { |
|
if err := argsSet(c, "id"); err != nil { |
|
return nil, err |
|
} |
|
|
|
authSource, err := a.getAuthSourceByID(c.Int64("id")) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if authSource.Type != authType { |
|
return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String()) |
|
} |
|
|
|
return authSource, 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 |
|
} |
|
|
|
ctx, cancel := installSignals() |
|
defer cancel() |
|
|
|
if err := a.initDB(ctx); err != nil { |
|
return err |
|
} |
|
|
|
authSource := &auth.Source{ |
|
Type: auth.LDAP, |
|
IsActive: true, // active by default |
|
Cfg: &ldap.Source{ |
|
Enabled: true, // always true |
|
}, |
|
} |
|
|
|
parseAuthSource(c, authSource) |
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { |
|
return err |
|
} |
|
|
|
return a.createAuthSource(authSource) |
|
} |
|
|
|
// updateLdapBindDn updates a new LDAP via Bind DN authentication source. |
|
func (a *authService) updateLdapBindDn(c *cli.Context) error { |
|
ctx, cancel := installSignals() |
|
defer cancel() |
|
|
|
if err := a.initDB(ctx); err != nil { |
|
return err |
|
} |
|
|
|
authSource, err := a.getAuthSource(c, auth.LDAP) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
parseAuthSource(c, authSource) |
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { |
|
return err |
|
} |
|
|
|
return a.updateAuthSource(authSource) |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
ctx, cancel := installSignals() |
|
defer cancel() |
|
|
|
if err := a.initDB(ctx); err != nil { |
|
return err |
|
} |
|
|
|
authSource := &auth.Source{ |
|
Type: auth.DLDAP, |
|
IsActive: true, // active by default |
|
Cfg: &ldap.Source{ |
|
Enabled: true, // always true |
|
}, |
|
} |
|
|
|
parseAuthSource(c, authSource) |
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { |
|
return err |
|
} |
|
|
|
return a.createAuthSource(authSource) |
|
} |
|
|
|
// updateLdapBindDn updates a new LDAP (simple auth) authentication source. |
|
func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { |
|
ctx, cancel := installSignals() |
|
defer cancel() |
|
|
|
if err := a.initDB(ctx); err != nil { |
|
return err |
|
} |
|
|
|
authSource, err := a.getAuthSource(c, auth.DLDAP) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
parseAuthSource(c, authSource) |
|
if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil { |
|
return err |
|
} |
|
|
|
return a.updateAuthSource(authSource) |
|
}
|
|
|