login_source.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. // Copyright 2014 The Gogs 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. // FIXME: Put this file into its own package and separate into different files based on login sources.
  5. package db
  6. import (
  7. "crypto/tls"
  8. "fmt"
  9. "net/smtp"
  10. "net/textproto"
  11. "strings"
  12. "github.com/go-macaron/binding"
  13. "github.com/unknwon/com"
  14. "gogs.io/gogs/internal/auth/github"
  15. "gogs.io/gogs/internal/auth/ldap"
  16. "gogs.io/gogs/internal/auth/pam"
  17. "gogs.io/gogs/internal/db/errors"
  18. )
  19. type LoginType int
  20. // Note: new type must append to the end of list to maintain compatibility.
  21. // TODO: Move to authutil.
  22. const (
  23. LoginNotype LoginType = iota
  24. LoginPlain // 1
  25. LoginLDAP // 2
  26. LoginSMTP // 3
  27. LoginPAM // 4
  28. LoginDLDAP // 5
  29. LoginGitHub // 6
  30. )
  31. var LoginNames = map[LoginType]string{
  32. LoginLDAP: "LDAP (via BindDN)",
  33. LoginDLDAP: "LDAP (simple auth)", // Via direct bind
  34. LoginSMTP: "SMTP",
  35. LoginPAM: "PAM",
  36. LoginGitHub: "GitHub",
  37. }
  38. // ***********************
  39. // ----- LDAP config -----
  40. // ***********************
  41. type LDAPConfig struct {
  42. ldap.Source `ini:"config"`
  43. }
  44. var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
  45. ldap.SecurityProtocolUnencrypted: "Unencrypted",
  46. ldap.SecurityProtocolLDAPS: "LDAPS",
  47. ldap.SecurityProtocolStartTLS: "StartTLS",
  48. }
  49. func (cfg *LDAPConfig) SecurityProtocolName() string {
  50. return SecurityProtocolNames[cfg.SecurityProtocol]
  51. }
  52. func composeFullName(firstname, surname, username string) string {
  53. switch {
  54. case len(firstname) == 0 && len(surname) == 0:
  55. return username
  56. case len(firstname) == 0:
  57. return surname
  58. case len(surname) == 0:
  59. return firstname
  60. default:
  61. return firstname + " " + surname
  62. }
  63. }
  64. // LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
  65. // and create a local user if success when enabled.
  66. func LoginViaLDAP(login, password string, source *LoginSource, autoRegister bool) (*User, error) {
  67. username, fn, sn, mail, isAdmin, succeed := source.Config.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
  68. if !succeed {
  69. // User not in LDAP, do nothing
  70. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  71. }
  72. if !autoRegister {
  73. return nil, nil
  74. }
  75. // Fallback.
  76. if len(username) == 0 {
  77. username = login
  78. }
  79. // Validate username make sure it satisfies requirement.
  80. if binding.AlphaDashDotPattern.MatchString(username) {
  81. return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
  82. }
  83. if len(mail) == 0 {
  84. mail = fmt.Sprintf("%s@localhost", username)
  85. }
  86. user := &User{
  87. LowerName: strings.ToLower(username),
  88. Name: username,
  89. FullName: composeFullName(fn, sn, username),
  90. Email: mail,
  91. LoginSource: source.ID,
  92. LoginName: login,
  93. IsActive: true,
  94. IsAdmin: isAdmin,
  95. }
  96. ok, err := IsUserExist(0, user.Name)
  97. if err != nil {
  98. return user, err
  99. }
  100. if ok {
  101. return user, UpdateUser(user)
  102. }
  103. return user, CreateUser(user)
  104. }
  105. // ***********************
  106. // ----- SMTP config -----
  107. // ***********************
  108. type SMTPConfig struct {
  109. Auth string
  110. Host string
  111. Port int
  112. AllowedDomains string
  113. TLS bool `ini:"tls"`
  114. SkipVerify bool
  115. }
  116. type smtpLoginAuth struct {
  117. username, password string
  118. }
  119. func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  120. return "LOGIN", []byte(auth.username), nil
  121. }
  122. func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  123. if more {
  124. switch string(fromServer) {
  125. case "Username:":
  126. return []byte(auth.username), nil
  127. case "Password:":
  128. return []byte(auth.password), nil
  129. }
  130. }
  131. return nil, nil
  132. }
  133. const (
  134. SMTPPlain = "PLAIN"
  135. SMTPLogin = "LOGIN"
  136. )
  137. var SMTPAuths = []string{SMTPPlain, SMTPLogin}
  138. func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
  139. c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
  140. if err != nil {
  141. return err
  142. }
  143. defer c.Close()
  144. if err = c.Hello("gogs"); err != nil {
  145. return err
  146. }
  147. if cfg.TLS {
  148. if ok, _ := c.Extension("STARTTLS"); ok {
  149. if err = c.StartTLS(&tls.Config{
  150. InsecureSkipVerify: cfg.SkipVerify,
  151. ServerName: cfg.Host,
  152. }); err != nil {
  153. return err
  154. }
  155. } else {
  156. return errors.New("SMTP server unsupports TLS")
  157. }
  158. }
  159. if ok, _ := c.Extension("AUTH"); ok {
  160. if err = c.Auth(a); err != nil {
  161. return err
  162. }
  163. return nil
  164. }
  165. return errors.New("Unsupported SMTP authentication method")
  166. }
  167. // LoginViaSMTP queries if login/password is valid against the SMTP,
  168. // and create a local user if success when enabled.
  169. func LoginViaSMTP(login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
  170. // Verify allowed domains.
  171. if len(cfg.AllowedDomains) > 0 {
  172. idx := strings.Index(login, "@")
  173. if idx == -1 {
  174. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  175. } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) {
  176. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  177. }
  178. }
  179. var auth smtp.Auth
  180. if cfg.Auth == SMTPPlain {
  181. auth = smtp.PlainAuth("", login, password, cfg.Host)
  182. } else if cfg.Auth == SMTPLogin {
  183. auth = &smtpLoginAuth{login, password}
  184. } else {
  185. return nil, errors.New("Unsupported SMTP authentication type")
  186. }
  187. if err := SMTPAuth(auth, cfg); err != nil {
  188. // Check standard error format first,
  189. // then fallback to worse case.
  190. tperr, ok := err.(*textproto.Error)
  191. if (ok && tperr.Code == 535) ||
  192. strings.Contains(err.Error(), "Username and Password not accepted") {
  193. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  194. }
  195. return nil, err
  196. }
  197. if !autoRegister {
  198. return nil, nil
  199. }
  200. username := login
  201. idx := strings.Index(login, "@")
  202. if idx > -1 {
  203. username = login[:idx]
  204. }
  205. user := &User{
  206. LowerName: strings.ToLower(username),
  207. Name: strings.ToLower(username),
  208. Email: login,
  209. Passwd: password,
  210. LoginSource: sourceID,
  211. LoginName: login,
  212. IsActive: true,
  213. }
  214. return user, CreateUser(user)
  215. }
  216. // **********************
  217. // ----- PAM config -----
  218. // **********************
  219. type PAMConfig struct {
  220. // The name of the PAM service, e.g. system-auth.
  221. ServiceName string
  222. }
  223. // LoginViaPAM queries if login/password is valid against the PAM,
  224. // and create a local user if success when enabled.
  225. func LoginViaPAM(login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
  226. if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
  227. if strings.Contains(err.Error(), "Authentication failure") {
  228. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  229. }
  230. return nil, err
  231. }
  232. if !autoRegister {
  233. return nil, nil
  234. }
  235. user := &User{
  236. LowerName: strings.ToLower(login),
  237. Name: login,
  238. Email: login,
  239. Passwd: password,
  240. LoginSource: sourceID,
  241. LoginName: login,
  242. IsActive: true,
  243. }
  244. return user, CreateUser(user)
  245. }
  246. // *************************
  247. // ----- GitHub config -----
  248. // *************************
  249. type GitHubConfig struct {
  250. // the GitHub service endpoint, e.g. https://api.github.com/.
  251. APIEndpoint string
  252. }
  253. func LoginViaGitHub(login, password string, sourceID int64, cfg *GitHubConfig, autoRegister bool) (*User, error) {
  254. fullname, email, url, location, err := github.Authenticate(cfg.APIEndpoint, login, password)
  255. if err != nil {
  256. if strings.Contains(err.Error(), "401") {
  257. return nil, ErrUserNotExist{args: map[string]interface{}{"login": login}}
  258. }
  259. return nil, err
  260. }
  261. if !autoRegister {
  262. return nil, nil
  263. }
  264. user := &User{
  265. LowerName: strings.ToLower(login),
  266. Name: login,
  267. FullName: fullname,
  268. Email: email,
  269. Website: url,
  270. Passwd: password,
  271. LoginSource: sourceID,
  272. LoginName: login,
  273. IsActive: true,
  274. Location: location,
  275. }
  276. return user, CreateUser(user)
  277. }
  278. func authenticateViaLoginSource(source *LoginSource, login, password string, autoRegister bool) (*User, error) {
  279. if !source.IsActived {
  280. return nil, errors.LoginSourceNotActivated{SourceID: source.ID}
  281. }
  282. switch source.Type {
  283. case LoginLDAP, LoginDLDAP:
  284. return LoginViaLDAP(login, password, source, autoRegister)
  285. case LoginSMTP:
  286. return LoginViaSMTP(login, password, source.ID, source.Config.(*SMTPConfig), autoRegister)
  287. case LoginPAM:
  288. return LoginViaPAM(login, password, source.ID, source.Config.(*PAMConfig), autoRegister)
  289. case LoginGitHub:
  290. return LoginViaGitHub(login, password, source.ID, source.Config.(*GitHubConfig), autoRegister)
  291. }
  292. return nil, errors.InvalidLoginSourceType{Type: source.Type}
  293. }