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.

373 lines
10 KiB

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