code.gitea.io/gitea@v1.21.7/cmd/admin_auth_ldap.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"code.gitea.io/gitea/models/auth"
    12  	"code.gitea.io/gitea/services/auth/source/ldap"
    13  
    14  	"github.com/urfave/cli/v2"
    15  )
    16  
    17  type (
    18  	authService struct {
    19  		initDB            func(ctx context.Context) error
    20  		createAuthSource  func(*auth.Source) error
    21  		updateAuthSource  func(*auth.Source) error
    22  		getAuthSourceByID func(id int64) (*auth.Source, error)
    23  	}
    24  )
    25  
    26  var (
    27  	commonLdapCLIFlags = []cli.Flag{
    28  		&cli.StringFlag{
    29  			Name:  "name",
    30  			Usage: "Authentication name.",
    31  		},
    32  		&cli.BoolFlag{
    33  			Name:  "not-active",
    34  			Usage: "Deactivate the authentication source.",
    35  		},
    36  		&cli.BoolFlag{
    37  			Name:  "active",
    38  			Usage: "Activate the authentication source.",
    39  		},
    40  		&cli.StringFlag{
    41  			Name:  "security-protocol",
    42  			Usage: "Security protocol name.",
    43  		},
    44  		&cli.BoolFlag{
    45  			Name:  "skip-tls-verify",
    46  			Usage: "Disable TLS verification.",
    47  		},
    48  		&cli.StringFlag{
    49  			Name:  "host",
    50  			Usage: "The address where the LDAP server can be reached.",
    51  		},
    52  		&cli.IntFlag{
    53  			Name:  "port",
    54  			Usage: "The port to use when connecting to the LDAP server.",
    55  		},
    56  		&cli.StringFlag{
    57  			Name:  "user-search-base",
    58  			Usage: "The LDAP base at which user accounts will be searched for.",
    59  		},
    60  		&cli.StringFlag{
    61  			Name:  "user-filter",
    62  			Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.",
    63  		},
    64  		&cli.StringFlag{
    65  			Name:  "admin-filter",
    66  			Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
    67  		},
    68  		&cli.StringFlag{
    69  			Name:  "restricted-filter",
    70  			Usage: "An LDAP filter specifying if a user should be given restricted status.",
    71  		},
    72  		&cli.BoolFlag{
    73  			Name:  "allow-deactivate-all",
    74  			Usage: "Allow empty search results to deactivate all users.",
    75  		},
    76  		&cli.StringFlag{
    77  			Name:  "username-attribute",
    78  			Usage: "The attribute of the user’s LDAP record containing the user name.",
    79  		},
    80  		&cli.StringFlag{
    81  			Name:  "firstname-attribute",
    82  			Usage: "The attribute of the user’s LDAP record containing the user’s first name.",
    83  		},
    84  		&cli.StringFlag{
    85  			Name:  "surname-attribute",
    86  			Usage: "The attribute of the user’s LDAP record containing the user’s surname.",
    87  		},
    88  		&cli.StringFlag{
    89  			Name:  "email-attribute",
    90  			Usage: "The attribute of the user’s LDAP record containing the user’s email address.",
    91  		},
    92  		&cli.StringFlag{
    93  			Name:  "public-ssh-key-attribute",
    94  			Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.",
    95  		},
    96  		&cli.BoolFlag{
    97  			Name:  "skip-local-2fa",
    98  			Usage: "Set to true to skip local 2fa for users authenticated by this source",
    99  		},
   100  		&cli.StringFlag{
   101  			Name:  "avatar-attribute",
   102  			Usage: "The attribute of the user’s LDAP record containing the user’s avatar.",
   103  		},
   104  	}
   105  
   106  	ldapBindDnCLIFlags = append(commonLdapCLIFlags,
   107  		&cli.StringFlag{
   108  			Name:  "bind-dn",
   109  			Usage: "The DN to bind to the LDAP server with when searching for the user.",
   110  		},
   111  		&cli.StringFlag{
   112  			Name:  "bind-password",
   113  			Usage: "The password for the Bind DN, if any.",
   114  		},
   115  		&cli.BoolFlag{
   116  			Name:  "attributes-in-bind",
   117  			Usage: "Fetch attributes in bind DN context.",
   118  		},
   119  		&cli.BoolFlag{
   120  			Name:  "synchronize-users",
   121  			Usage: "Enable user synchronization.",
   122  		},
   123  		&cli.BoolFlag{
   124  			Name:  "disable-synchronize-users",
   125  			Usage: "Disable user synchronization.",
   126  		},
   127  		&cli.UintFlag{
   128  			Name:  "page-size",
   129  			Usage: "Search page size.",
   130  		})
   131  
   132  	ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags,
   133  		&cli.StringFlag{
   134  			Name:  "user-dn",
   135  			Usage: "The user’s DN.",
   136  		})
   137  
   138  	cmdAuthAddLdapBindDn = &cli.Command{
   139  		Name:  "add-ldap",
   140  		Usage: "Add new LDAP (via Bind DN) authentication source",
   141  		Action: func(c *cli.Context) error {
   142  			return newAuthService().addLdapBindDn(c)
   143  		},
   144  		Flags: ldapBindDnCLIFlags,
   145  	}
   146  
   147  	cmdAuthUpdateLdapBindDn = &cli.Command{
   148  		Name:  "update-ldap",
   149  		Usage: "Update existing LDAP (via Bind DN) authentication source",
   150  		Action: func(c *cli.Context) error {
   151  			return newAuthService().updateLdapBindDn(c)
   152  		},
   153  		Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...),
   154  	}
   155  
   156  	cmdAuthAddLdapSimpleAuth = &cli.Command{
   157  		Name:  "add-ldap-simple",
   158  		Usage: "Add new LDAP (simple auth) authentication source",
   159  		Action: func(c *cli.Context) error {
   160  			return newAuthService().addLdapSimpleAuth(c)
   161  		},
   162  		Flags: ldapSimpleAuthCLIFlags,
   163  	}
   164  
   165  	cmdAuthUpdateLdapSimpleAuth = &cli.Command{
   166  		Name:  "update-ldap-simple",
   167  		Usage: "Update existing LDAP (simple auth) authentication source",
   168  		Action: func(c *cli.Context) error {
   169  			return newAuthService().updateLdapSimpleAuth(c)
   170  		},
   171  		Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...),
   172  	}
   173  )
   174  
   175  // newAuthService creates a service with default functions.
   176  func newAuthService() *authService {
   177  	return &authService{
   178  		initDB:            initDB,
   179  		createAuthSource:  auth.CreateSource,
   180  		updateAuthSource:  auth.UpdateSource,
   181  		getAuthSourceByID: auth.GetSourceByID,
   182  	}
   183  }
   184  
   185  // parseAuthSource assigns values on authSource according to command line flags.
   186  func parseAuthSource(c *cli.Context, authSource *auth.Source) {
   187  	if c.IsSet("name") {
   188  		authSource.Name = c.String("name")
   189  	}
   190  	if c.IsSet("not-active") {
   191  		authSource.IsActive = !c.Bool("not-active")
   192  	}
   193  	if c.IsSet("active") {
   194  		authSource.IsActive = c.Bool("active")
   195  	}
   196  	if c.IsSet("synchronize-users") {
   197  		authSource.IsSyncEnabled = c.Bool("synchronize-users")
   198  	}
   199  	if c.IsSet("disable-synchronize-users") {
   200  		authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
   201  	}
   202  }
   203  
   204  // parseLdapConfig assigns values on config according to command line flags.
   205  func parseLdapConfig(c *cli.Context, config *ldap.Source) error {
   206  	if c.IsSet("name") {
   207  		config.Name = c.String("name")
   208  	}
   209  	if c.IsSet("host") {
   210  		config.Host = c.String("host")
   211  	}
   212  	if c.IsSet("port") {
   213  		config.Port = c.Int("port")
   214  	}
   215  	if c.IsSet("security-protocol") {
   216  		p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
   217  		if !ok {
   218  			return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol"))
   219  		}
   220  		config.SecurityProtocol = p
   221  	}
   222  	if c.IsSet("skip-tls-verify") {
   223  		config.SkipVerify = c.Bool("skip-tls-verify")
   224  	}
   225  	if c.IsSet("bind-dn") {
   226  		config.BindDN = c.String("bind-dn")
   227  	}
   228  	if c.IsSet("user-dn") {
   229  		config.UserDN = c.String("user-dn")
   230  	}
   231  	if c.IsSet("bind-password") {
   232  		config.BindPassword = c.String("bind-password")
   233  	}
   234  	if c.IsSet("user-search-base") {
   235  		config.UserBase = c.String("user-search-base")
   236  	}
   237  	if c.IsSet("username-attribute") {
   238  		config.AttributeUsername = c.String("username-attribute")
   239  	}
   240  	if c.IsSet("firstname-attribute") {
   241  		config.AttributeName = c.String("firstname-attribute")
   242  	}
   243  	if c.IsSet("surname-attribute") {
   244  		config.AttributeSurname = c.String("surname-attribute")
   245  	}
   246  	if c.IsSet("email-attribute") {
   247  		config.AttributeMail = c.String("email-attribute")
   248  	}
   249  	if c.IsSet("attributes-in-bind") {
   250  		config.AttributesInBind = c.Bool("attributes-in-bind")
   251  	}
   252  	if c.IsSet("public-ssh-key-attribute") {
   253  		config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
   254  	}
   255  	if c.IsSet("avatar-attribute") {
   256  		config.AttributeAvatar = c.String("avatar-attribute")
   257  	}
   258  	if c.IsSet("page-size") {
   259  		config.SearchPageSize = uint32(c.Uint("page-size"))
   260  	}
   261  	if c.IsSet("user-filter") {
   262  		config.Filter = c.String("user-filter")
   263  	}
   264  	if c.IsSet("admin-filter") {
   265  		config.AdminFilter = c.String("admin-filter")
   266  	}
   267  	if c.IsSet("restricted-filter") {
   268  		config.RestrictedFilter = c.String("restricted-filter")
   269  	}
   270  	if c.IsSet("allow-deactivate-all") {
   271  		config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
   272  	}
   273  	if c.IsSet("skip-local-2fa") {
   274  		config.SkipLocalTwoFA = c.Bool("skip-local-2fa")
   275  	}
   276  	return nil
   277  }
   278  
   279  // findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
   280  // It returns the value of the security protocol and if it was found.
   281  func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
   282  	for i, n := range ldap.SecurityProtocolNames {
   283  		if strings.EqualFold(name, n) {
   284  			return i, true
   285  		}
   286  	}
   287  	return 0, false
   288  }
   289  
   290  // getAuthSource gets the login source by its id defined in the command line flags.
   291  // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
   292  func (a *authService) getAuthSource(c *cli.Context, authType auth.Type) (*auth.Source, error) {
   293  	if err := argsSet(c, "id"); err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	authSource, err := a.getAuthSourceByID(c.Int64("id"))
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	if authSource.Type != authType {
   303  		return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
   304  	}
   305  
   306  	return authSource, nil
   307  }
   308  
   309  // addLdapBindDn adds a new LDAP via Bind DN authentication source.
   310  func (a *authService) addLdapBindDn(c *cli.Context) error {
   311  	if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
   312  		return err
   313  	}
   314  
   315  	ctx, cancel := installSignals()
   316  	defer cancel()
   317  
   318  	if err := a.initDB(ctx); err != nil {
   319  		return err
   320  	}
   321  
   322  	authSource := &auth.Source{
   323  		Type:     auth.LDAP,
   324  		IsActive: true, // active by default
   325  		Cfg: &ldap.Source{
   326  			Enabled: true, // always true
   327  		},
   328  	}
   329  
   330  	parseAuthSource(c, authSource)
   331  	if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
   332  		return err
   333  	}
   334  
   335  	return a.createAuthSource(authSource)
   336  }
   337  
   338  // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
   339  func (a *authService) updateLdapBindDn(c *cli.Context) error {
   340  	ctx, cancel := installSignals()
   341  	defer cancel()
   342  
   343  	if err := a.initDB(ctx); err != nil {
   344  		return err
   345  	}
   346  
   347  	authSource, err := a.getAuthSource(c, auth.LDAP)
   348  	if err != nil {
   349  		return err
   350  	}
   351  
   352  	parseAuthSource(c, authSource)
   353  	if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
   354  		return err
   355  	}
   356  
   357  	return a.updateAuthSource(authSource)
   358  }
   359  
   360  // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
   361  func (a *authService) addLdapSimpleAuth(c *cli.Context) error {
   362  	if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
   363  		return err
   364  	}
   365  
   366  	ctx, cancel := installSignals()
   367  	defer cancel()
   368  
   369  	if err := a.initDB(ctx); err != nil {
   370  		return err
   371  	}
   372  
   373  	authSource := &auth.Source{
   374  		Type:     auth.DLDAP,
   375  		IsActive: true, // active by default
   376  		Cfg: &ldap.Source{
   377  			Enabled: true, // always true
   378  		},
   379  	}
   380  
   381  	parseAuthSource(c, authSource)
   382  	if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
   383  		return err
   384  	}
   385  
   386  	return a.createAuthSource(authSource)
   387  }
   388  
   389  // updateLdapBindDn updates a new LDAP (simple auth) authentication source.
   390  func (a *authService) updateLdapSimpleAuth(c *cli.Context) error {
   391  	ctx, cancel := installSignals()
   392  	defer cancel()
   393  
   394  	if err := a.initDB(ctx); err != nil {
   395  		return err
   396  	}
   397  
   398  	authSource, err := a.getAuthSource(c, auth.DLDAP)
   399  	if err != nil {
   400  		return err
   401  	}
   402  
   403  	parseAuthSource(c, authSource)
   404  	if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
   405  		return err
   406  	}
   407  
   408  	return a.updateAuthSource(authSource)
   409  }