402 lines · 10.9 KB · Go
RawDownload
// Copyright 2019 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package cmd import (	"context"	"fmt"	"strings" 	"forgejo.org/models/auth"	"forgejo.org/services/auth/source/ldap" 	"github.com/urfave/cli/v3") func commonLdapCLIFlags() []cli.Flag {	return []cli.Flag{		&cli.StringFlag{			Name:  "name",			Usage: "Authentication name.",		},		&cli.BoolFlag{			Name:  "not-active",			Usage: "Deactivate the authentication source.",		},		&cli.BoolFlag{			Name:  "active",			Usage: "Activate 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.",		},	}} func ldapBindDnCLIFlags() []cli.Flag {	return 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.BoolFlag{			Name:  "disable-synchronize-users",			Usage: "Disable user synchronization.",		},		&cli.UintFlag{			Name:  "page-size",			Usage: "Search page size.",		})} func ldapSimpleAuthCLIFlags() []cli.Flag {	return append(commonLdapCLIFlags(),		&cli.StringFlag{			Name:  "user-dn",			Usage: "The user's DN.",		})} func microcmdAuthAddLdapBindDn() *cli.Command {	return &cli.Command{		Name:  "add-ldap",		Usage: "Add new LDAP (via Bind DN) authentication source",		Action: func(ctx context.Context, cli *cli.Command) error {			return newAuthService().addLdapBindDn(ctx, cli)		},		Flags: ldapBindDnCLIFlags(),	}} func microcmdAuthUpdateLdapBindDn() *cli.Command {	return &cli.Command{		Name:  "update-ldap",		Usage: "Update existing LDAP (via Bind DN) authentication source",		Action: func(ctx context.Context, cli *cli.Command) error {			return newAuthService().updateLdapBindDn(ctx, cli)		},		Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),	}} func microcmdAuthAddLdapSimpleAuth() *cli.Command {	return &cli.Command{		Name:  "add-ldap-simple",		Usage: "Add new LDAP (simple auth) authentication source",		Action: func(ctx context.Context, cli *cli.Command) error {			return newAuthService().addLdapSimpleAuth(ctx, cli)		},		Flags: ldapSimpleAuthCLIFlags(),	}} func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {	return &cli.Command{		Name:  "update-ldap-simple",		Usage: "Update existing LDAP (simple auth) authentication source",		Action: func(ctx context.Context, cli *cli.Command) error {			return newAuthService().updateLdapSimpleAuth(ctx, cli)		},		Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),	}} // parseAuthSource assigns values on authSource according to command line flags.func parseAuthSource(c *cli.Command, 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("active") {		authSource.IsActive = c.Bool("active")	}	if c.IsSet("synchronize-users") {		authSource.IsSyncEnabled = c.Bool("synchronize-users")	}	if c.IsSet("disable-synchronize-users") {		authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")	}} // parseLdapConfig assigns values on config according to command line flags.func parseLdapConfig(c *cli.Command, 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(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {	if err := argsSet(c, "id"); err != nil {		return nil, err	} 	authSource, err := a.getAuthSourceByID(ctx, 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(ctx context.Context, c *cli.Command) error {	if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {		return err	} 	ctx, cancel := installSignals(ctx)	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(ctx, authSource)} // updateLdapBindDn updates a new LDAP via Bind DN authentication source.func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {	ctx, cancel := installSignals(ctx)	defer cancel() 	if err := a.initDB(ctx); err != nil {		return err	} 	authSource, err := a.getAuthSource(ctx, 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(ctx, authSource)} // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {	if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {		return err	} 	ctx, cancel := installSignals(ctx)	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(ctx, authSource)} // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {	ctx, cancel := installSignals(ctx)	defer cancel() 	if err := a.initDB(ctx); err != nil {		return err	} 	authSource, err := a.getAuthSource(ctx, 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(ctx, authSource)}