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 }