gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. "code.gitea.io/gitea/models/auth"
  9. "code.gitea.io/gitea/modules/util"
  10. "code.gitea.io/gitea/services/auth/source/ldap"
  11. "github.com/urfave/cli/v3"
  12. )
  13. type (
  14. authService struct {
  15. initDB func(ctx context.Context) error
  16. createAuthSource func(context.Context, *auth.Source) error
  17. updateAuthSource func(context.Context, *auth.Source) error
  18. getAuthSourceByID func(ctx context.Context, id int64) (*auth.Source, error)
  19. }
  20. )
  21. func commonLdapCLIFlags() []cli.Flag {
  22. return []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. }
  101. func ldapBindDnCLIFlags() []cli.Flag {
  102. return append(commonLdapCLIFlags(),
  103. &cli.StringFlag{
  104. Name: "bind-dn",
  105. Usage: "The DN to bind to the LDAP server with when searching for the user.",
  106. },
  107. &cli.StringFlag{
  108. Name: "bind-password",
  109. Usage: "The password for the Bind DN, if any.",
  110. },
  111. &cli.BoolFlag{
  112. Name: "attributes-in-bind",
  113. Usage: "Fetch attributes in bind DN context.",
  114. },
  115. &cli.BoolFlag{
  116. Name: "synchronize-users",
  117. Usage: "Enable user synchronization.",
  118. },
  119. &cli.BoolFlag{
  120. Name: "disable-synchronize-users",
  121. Usage: "Disable user synchronization.",
  122. },
  123. &cli.UintFlag{
  124. Name: "page-size",
  125. Usage: "Search page size.",
  126. },
  127. &cli.BoolFlag{
  128. Name: "enable-groups",
  129. Usage: "Enable LDAP groups",
  130. },
  131. &cli.StringFlag{
  132. Name: "group-search-base-dn",
  133. Usage: "The LDAP base DN at which group accounts will be searched for",
  134. },
  135. &cli.StringFlag{
  136. Name: "group-member-attribute",
  137. Usage: "Group attribute containing list of users",
  138. },
  139. &cli.StringFlag{
  140. Name: "group-user-attribute",
  141. Usage: "User attribute listed in group",
  142. },
  143. &cli.StringFlag{
  144. Name: "group-filter",
  145. Usage: "Verify group membership in LDAP",
  146. },
  147. &cli.StringFlag{
  148. Name: "group-team-map",
  149. Usage: "Map LDAP groups to Organization teams",
  150. },
  151. &cli.BoolFlag{
  152. Name: "group-team-map-removal",
  153. Usage: "Remove users from synchronized teams if user does not belong to corresponding LDAP group",
  154. })
  155. }
  156. func ldapSimpleAuthCLIFlags() []cli.Flag {
  157. return append(commonLdapCLIFlags(),
  158. &cli.StringFlag{
  159. Name: "user-dn",
  160. Usage: "The user's DN.",
  161. })
  162. }
  163. func microcmdAuthAddLdapBindDn() *cli.Command {
  164. return &cli.Command{
  165. Name: "add-ldap",
  166. Usage: "Add new LDAP (via Bind DN) authentication source",
  167. Action: func(ctx context.Context, cmd *cli.Command) error {
  168. return newAuthService().addLdapBindDn(ctx, cmd)
  169. },
  170. Flags: ldapBindDnCLIFlags(),
  171. }
  172. }
  173. func microcmdAuthUpdateLdapBindDn() *cli.Command {
  174. return &cli.Command{
  175. Name: "update-ldap",
  176. Usage: "Update existing LDAP (via Bind DN) authentication source",
  177. Action: func(ctx context.Context, cmd *cli.Command) error {
  178. return newAuthService().updateLdapBindDn(ctx, cmd)
  179. },
  180. Flags: append([]cli.Flag{idFlag()}, ldapBindDnCLIFlags()...),
  181. }
  182. }
  183. func microcmdAuthAddLdapSimpleAuth() *cli.Command {
  184. return &cli.Command{
  185. Name: "add-ldap-simple",
  186. Usage: "Add new LDAP (simple auth) authentication source",
  187. Action: func(ctx context.Context, cmd *cli.Command) error {
  188. return newAuthService().addLdapSimpleAuth(ctx, cmd)
  189. },
  190. Flags: ldapSimpleAuthCLIFlags(),
  191. }
  192. }
  193. func microcmdAuthUpdateLdapSimpleAuth() *cli.Command {
  194. return &cli.Command{
  195. Name: "update-ldap-simple",
  196. Usage: "Update existing LDAP (simple auth) authentication source",
  197. Action: func(ctx context.Context, cmd *cli.Command) error {
  198. return newAuthService().updateLdapSimpleAuth(ctx, cmd)
  199. },
  200. Flags: append([]cli.Flag{idFlag()}, ldapSimpleAuthCLIFlags()...),
  201. }
  202. }
  203. // newAuthService creates a service with default functions.
  204. func newAuthService() *authService {
  205. return &authService{
  206. initDB: initDB,
  207. createAuthSource: auth.CreateSource,
  208. updateAuthSource: auth.UpdateSource,
  209. getAuthSourceByID: auth.GetSourceByID,
  210. }
  211. }
  212. // parseAuthSourceLdap assigns values on authSource according to command line flags.
  213. func parseAuthSourceLdap(c *cli.Command, authSource *auth.Source) {
  214. if c.IsSet("name") {
  215. authSource.Name = c.String("name")
  216. }
  217. if c.IsSet("not-active") {
  218. authSource.IsActive = !c.Bool("not-active")
  219. }
  220. if c.IsSet("active") {
  221. authSource.IsActive = c.Bool("active")
  222. }
  223. if c.IsSet("synchronize-users") {
  224. authSource.IsSyncEnabled = c.Bool("synchronize-users")
  225. }
  226. if c.IsSet("disable-synchronize-users") {
  227. authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
  228. }
  229. authSource.TwoFactorPolicy = util.Iif(c.Bool("skip-local-2fa"), "skip", "")
  230. }
  231. // parseLdapConfig assigns values on config according to command line flags.
  232. func parseLdapConfig(c *cli.Command, config *ldap.Source) error {
  233. if c.IsSet("name") {
  234. config.Name = c.String("name")
  235. }
  236. if c.IsSet("host") {
  237. config.Host = c.String("host")
  238. }
  239. if c.IsSet("port") {
  240. config.Port = c.Int("port")
  241. }
  242. if c.IsSet("security-protocol") {
  243. p, ok := findLdapSecurityProtocolByName(c.String("security-protocol"))
  244. if !ok {
  245. return fmt.Errorf("unknown security protocol name: %s", c.String("security-protocol"))
  246. }
  247. config.SecurityProtocol = p
  248. }
  249. if c.IsSet("skip-tls-verify") {
  250. config.SkipVerify = c.Bool("skip-tls-verify")
  251. }
  252. if c.IsSet("bind-dn") {
  253. config.BindDN = c.String("bind-dn")
  254. }
  255. if c.IsSet("user-dn") {
  256. config.UserDN = c.String("user-dn")
  257. }
  258. if c.IsSet("bind-password") {
  259. config.BindPassword = c.String("bind-password")
  260. }
  261. if c.IsSet("user-search-base") {
  262. config.UserBase = c.String("user-search-base")
  263. }
  264. if c.IsSet("username-attribute") {
  265. config.AttributeUsername = c.String("username-attribute")
  266. }
  267. if c.IsSet("firstname-attribute") {
  268. config.AttributeName = c.String("firstname-attribute")
  269. }
  270. if c.IsSet("surname-attribute") {
  271. config.AttributeSurname = c.String("surname-attribute")
  272. }
  273. if c.IsSet("email-attribute") {
  274. config.AttributeMail = c.String("email-attribute")
  275. }
  276. if c.IsSet("attributes-in-bind") {
  277. config.AttributesInBind = c.Bool("attributes-in-bind")
  278. }
  279. if c.IsSet("public-ssh-key-attribute") {
  280. config.AttributeSSHPublicKey = c.String("public-ssh-key-attribute")
  281. }
  282. if c.IsSet("avatar-attribute") {
  283. config.AttributeAvatar = c.String("avatar-attribute")
  284. }
  285. if c.IsSet("page-size") {
  286. config.SearchPageSize = uint32(c.Uint("page-size"))
  287. }
  288. if c.IsSet("user-filter") {
  289. config.Filter = c.String("user-filter")
  290. }
  291. if c.IsSet("admin-filter") {
  292. config.AdminFilter = c.String("admin-filter")
  293. }
  294. if c.IsSet("restricted-filter") {
  295. config.RestrictedFilter = c.String("restricted-filter")
  296. }
  297. if c.IsSet("allow-deactivate-all") {
  298. config.AllowDeactivateAll = c.Bool("allow-deactivate-all")
  299. }
  300. if c.IsSet("enable-groups") {
  301. config.GroupsEnabled = c.Bool("enable-groups")
  302. }
  303. if c.IsSet("group-search-base-dn") {
  304. config.GroupDN = c.String("group-search-base-dn")
  305. }
  306. if c.IsSet("group-member-attribute") {
  307. config.GroupMemberUID = c.String("group-member-attribute")
  308. }
  309. if c.IsSet("group-user-attribute") {
  310. config.UserUID = c.String("group-user-attribute")
  311. }
  312. if c.IsSet("group-filter") {
  313. config.GroupFilter = c.String("group-filter")
  314. }
  315. if c.IsSet("group-team-map") {
  316. config.GroupTeamMap = c.String("group-team-map")
  317. }
  318. if c.IsSet("group-team-map-removal") {
  319. config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
  320. }
  321. return nil
  322. }
  323. // findLdapSecurityProtocolByName finds security protocol by its name ignoring case.
  324. // It returns the value of the security protocol and if it was found.
  325. func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) {
  326. for i, n := range ldap.SecurityProtocolNames {
  327. if strings.EqualFold(name, n) {
  328. return i, true
  329. }
  330. }
  331. return 0, false
  332. }
  333. // getAuthSource gets the login source by its id defined in the command line flags.
  334. // It returns an error if the id is not set, does not match any source or if the source is not of expected type.
  335. func (a *authService) getAuthSource(ctx context.Context, c *cli.Command, authType auth.Type) (*auth.Source, error) {
  336. if err := argsSet(c, "id"); err != nil {
  337. return nil, err
  338. }
  339. authSource, err := a.getAuthSourceByID(ctx, c.Int64("id"))
  340. if err != nil {
  341. return nil, err
  342. }
  343. if authSource.Type != authType {
  344. return nil, fmt.Errorf("invalid authentication type. expected: %s, actual: %s", authType.String(), authSource.Type.String())
  345. }
  346. return authSource, nil
  347. }
  348. // addLdapBindDn adds a new LDAP via Bind DN authentication source.
  349. func (a *authService) addLdapBindDn(ctx context.Context, c *cli.Command) error {
  350. if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil {
  351. return err
  352. }
  353. if err := a.initDB(ctx); err != nil {
  354. return err
  355. }
  356. authSource := &auth.Source{
  357. Type: auth.LDAP,
  358. IsActive: true, // active by default
  359. Cfg: &ldap.Source{
  360. Enabled: true, // always true
  361. },
  362. }
  363. parseAuthSourceLdap(c, authSource)
  364. if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
  365. return err
  366. }
  367. return a.createAuthSource(ctx, authSource)
  368. }
  369. // updateLdapBindDn updates a new LDAP via Bind DN authentication source.
  370. func (a *authService) updateLdapBindDn(ctx context.Context, c *cli.Command) error {
  371. if err := a.initDB(ctx); err != nil {
  372. return err
  373. }
  374. authSource, err := a.getAuthSource(ctx, c, auth.LDAP)
  375. if err != nil {
  376. return err
  377. }
  378. parseAuthSourceLdap(c, authSource)
  379. if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
  380. return err
  381. }
  382. return a.updateAuthSource(ctx, authSource)
  383. }
  384. // addLdapSimpleAuth adds a new LDAP (simple auth) authentication source.
  385. func (a *authService) addLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
  386. if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil {
  387. return err
  388. }
  389. if err := a.initDB(ctx); err != nil {
  390. return err
  391. }
  392. authSource := &auth.Source{
  393. Type: auth.DLDAP,
  394. IsActive: true, // active by default
  395. Cfg: &ldap.Source{
  396. Enabled: true, // always true
  397. },
  398. }
  399. parseAuthSourceLdap(c, authSource)
  400. if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
  401. return err
  402. }
  403. return a.createAuthSource(ctx, authSource)
  404. }
  405. // updateLdapSimpleAuth updates a new LDAP (simple auth) authentication source.
  406. func (a *authService) updateLdapSimpleAuth(ctx context.Context, c *cli.Command) error {
  407. if err := a.initDB(ctx); err != nil {
  408. return err
  409. }
  410. authSource, err := a.getAuthSource(ctx, c, auth.DLDAP)
  411. if err != nil {
  412. return err
  413. }
  414. parseAuthSourceLdap(c, authSource)
  415. if err := parseLdapConfig(c, authSource.Cfg.(*ldap.Source)); err != nil {
  416. return err
  417. }
  418. return a.updateAuthSource(ctx, authSource)
  419. }