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.

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