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

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