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.

admin_auth_ldap.go 11KB

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