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.

login_source.go 16KB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
7 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
7 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
7 years ago
7 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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. package models
  5. import (
  6. "crypto/tls"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "net/smtp"
  11. "net/textproto"
  12. "strings"
  13. "time"
  14. "github.com/Unknwon/com"
  15. "github.com/go-macaron/binding"
  16. "github.com/go-xorm/core"
  17. "github.com/go-xorm/xorm"
  18. "code.gitea.io/gitea/modules/auth/ldap"
  19. "code.gitea.io/gitea/modules/auth/pam"
  20. "code.gitea.io/gitea/modules/log"
  21. )
  22. // LoginType represents an login type.
  23. type LoginType int
  24. // Note: new type must append to the end of list to maintain compatibility.
  25. const (
  26. LoginNoType LoginType = iota
  27. LoginPlain // 1
  28. LoginLDAP // 2
  29. LoginSMTP // 3
  30. LoginPAM // 4
  31. LoginDLDAP // 5
  32. )
  33. // LoginNames contains the name of LoginType values.
  34. var LoginNames = map[LoginType]string{
  35. LoginLDAP: "LDAP (via BindDN)",
  36. LoginDLDAP: "LDAP (simple auth)", // Via direct bind
  37. LoginSMTP: "SMTP",
  38. LoginPAM: "PAM",
  39. }
  40. // SecurityProtocolNames contains the name of SecurityProtocol values.
  41. var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
  42. ldap.SecurityProtocolUnencrypted: "Unencrypted",
  43. ldap.SecurityProtocolLDAPS: "LDAPS",
  44. ldap.SecurityProtocolStartTLS: "StartTLS",
  45. }
  46. // Ensure structs implemented interface.
  47. var (
  48. _ core.Conversion = &LDAPConfig{}
  49. _ core.Conversion = &SMTPConfig{}
  50. _ core.Conversion = &PAMConfig{}
  51. )
  52. // LDAPConfig holds configuration for LDAP login source.
  53. type LDAPConfig struct {
  54. *ldap.Source
  55. }
  56. // FromDB fills up a LDAPConfig from serialized format.
  57. func (cfg *LDAPConfig) FromDB(bs []byte) error {
  58. return json.Unmarshal(bs, &cfg)
  59. }
  60. // ToDB exports a LDAPConfig to a serialized format.
  61. func (cfg *LDAPConfig) ToDB() ([]byte, error) {
  62. return json.Marshal(cfg)
  63. }
  64. // SecurityProtocolName returns the name of configured security
  65. // protocol.
  66. func (cfg *LDAPConfig) SecurityProtocolName() string {
  67. return SecurityProtocolNames[cfg.SecurityProtocol]
  68. }
  69. // SMTPConfig holds configuration for the SMTP login source.
  70. type SMTPConfig struct {
  71. Auth string
  72. Host string
  73. Port int
  74. AllowedDomains string `xorm:"TEXT"`
  75. TLS bool
  76. SkipVerify bool
  77. }
  78. // FromDB fills up an SMTPConfig from serialized format.
  79. func (cfg *SMTPConfig) FromDB(bs []byte) error {
  80. return json.Unmarshal(bs, cfg)
  81. }
  82. // ToDB exports an SMTPConfig to a serialized format.
  83. func (cfg *SMTPConfig) ToDB() ([]byte, error) {
  84. return json.Marshal(cfg)
  85. }
  86. // PAMConfig holds configuration for the PAM login source.
  87. type PAMConfig struct {
  88. ServiceName string // pam service (e.g. system-auth)
  89. }
  90. // FromDB fills up a PAMConfig from serialized format.
  91. func (cfg *PAMConfig) FromDB(bs []byte) error {
  92. return json.Unmarshal(bs, &cfg)
  93. }
  94. // ToDB exports a PAMConfig to a serialized format.
  95. func (cfg *PAMConfig) ToDB() ([]byte, error) {
  96. return json.Marshal(cfg)
  97. }
  98. // LoginSource represents an external way for authorizing users.
  99. type LoginSource struct {
  100. ID int64 `xorm:"pk autoincr"`
  101. Type LoginType
  102. Name string `xorm:"UNIQUE"`
  103. IsActived bool `xorm:"NOT NULL DEFAULT false"`
  104. Cfg core.Conversion `xorm:"TEXT"`
  105. Created time.Time `xorm:"-"`
  106. CreatedUnix int64
  107. Updated time.Time `xorm:"-"`
  108. UpdatedUnix int64
  109. }
  110. // BeforeInsert is invoked from XORM before inserting an object of this type.
  111. func (source *LoginSource) BeforeInsert() {
  112. source.CreatedUnix = time.Now().Unix()
  113. source.UpdatedUnix = source.CreatedUnix
  114. }
  115. // BeforeUpdate is invoked from XORM before updating this object.
  116. func (source *LoginSource) BeforeUpdate() {
  117. source.UpdatedUnix = time.Now().Unix()
  118. }
  119. // Cell2Int64 converts a xorm.Cell type to int64,
  120. // and handles possible irregular cases.
  121. func Cell2Int64(val xorm.Cell) int64 {
  122. switch (*val).(type) {
  123. case []uint8:
  124. log.Trace("Cell2Int64 ([]uint8): %v", *val)
  125. return com.StrTo(string((*val).([]uint8))).MustInt64()
  126. }
  127. return (*val).(int64)
  128. }
  129. // BeforeSet is invoked from XORM before setting the value of a field of this object.
  130. func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
  131. switch colName {
  132. case "type":
  133. switch LoginType(Cell2Int64(val)) {
  134. case LoginLDAP, LoginDLDAP:
  135. source.Cfg = new(LDAPConfig)
  136. case LoginSMTP:
  137. source.Cfg = new(SMTPConfig)
  138. case LoginPAM:
  139. source.Cfg = new(PAMConfig)
  140. default:
  141. panic("unrecognized login source type: " + com.ToStr(*val))
  142. }
  143. }
  144. }
  145. // AfterSet is invoked from XORM after setting the value of a field of this object.
  146. func (source *LoginSource) AfterSet(colName string, _ xorm.Cell) {
  147. switch colName {
  148. case "created_unix":
  149. source.Created = time.Unix(source.CreatedUnix, 0).Local()
  150. case "updated_unix":
  151. source.Updated = time.Unix(source.UpdatedUnix, 0).Local()
  152. }
  153. }
  154. // TypeName return name of this login source type.
  155. func (source *LoginSource) TypeName() string {
  156. return LoginNames[source.Type]
  157. }
  158. // IsLDAP returns true of this source is of the LDAP type.
  159. func (source *LoginSource) IsLDAP() bool {
  160. return source.Type == LoginLDAP
  161. }
  162. // IsDLDAP returns true of this source is of the DLDAP type.
  163. func (source *LoginSource) IsDLDAP() bool {
  164. return source.Type == LoginDLDAP
  165. }
  166. // IsSMTP returns true of this source is of the SMTP type.
  167. func (source *LoginSource) IsSMTP() bool {
  168. return source.Type == LoginSMTP
  169. }
  170. // IsPAM returns true of this source is of the PAM type.
  171. func (source *LoginSource) IsPAM() bool {
  172. return source.Type == LoginPAM
  173. }
  174. // HasTLS returns true of this source supports TLS.
  175. func (source *LoginSource) HasTLS() bool {
  176. return ((source.IsLDAP() || source.IsDLDAP()) &&
  177. source.LDAP().SecurityProtocol > ldap.SecurityProtocolUnencrypted) ||
  178. source.IsSMTP()
  179. }
  180. // UseTLS returns true of this source is configured to use TLS.
  181. func (source *LoginSource) UseTLS() bool {
  182. switch source.Type {
  183. case LoginLDAP, LoginDLDAP:
  184. return source.LDAP().SecurityProtocol != ldap.SecurityProtocolUnencrypted
  185. case LoginSMTP:
  186. return source.SMTP().TLS
  187. }
  188. return false
  189. }
  190. // SkipVerify returns true if this source is configured to skip SSL
  191. // verification.
  192. func (source *LoginSource) SkipVerify() bool {
  193. switch source.Type {
  194. case LoginLDAP, LoginDLDAP:
  195. return source.LDAP().SkipVerify
  196. case LoginSMTP:
  197. return source.SMTP().SkipVerify
  198. }
  199. return false
  200. }
  201. // LDAP returns LDAPConfig for this source, if of LDAP type.
  202. func (source *LoginSource) LDAP() *LDAPConfig {
  203. return source.Cfg.(*LDAPConfig)
  204. }
  205. // SMTP returns SMTPConfig for this source, if of SMTP type.
  206. func (source *LoginSource) SMTP() *SMTPConfig {
  207. return source.Cfg.(*SMTPConfig)
  208. }
  209. // PAM returns PAMConfig for this source, if of PAM type.
  210. func (source *LoginSource) PAM() *PAMConfig {
  211. return source.Cfg.(*PAMConfig)
  212. }
  213. // CreateLoginSource inserts a LoginSource in the DB if not already
  214. // existing with the given name.
  215. func CreateLoginSource(source *LoginSource) error {
  216. has, err := x.Get(&LoginSource{Name: source.Name})
  217. if err != nil {
  218. return err
  219. } else if has {
  220. return ErrLoginSourceAlreadyExist{source.Name}
  221. }
  222. _, err = x.Insert(source)
  223. return err
  224. }
  225. // LoginSources returns a slice of all login sources found in DB.
  226. func LoginSources() ([]*LoginSource, error) {
  227. auths := make([]*LoginSource, 0, 5)
  228. return auths, x.Find(&auths)
  229. }
  230. // GetLoginSourceByID returns login source by given ID.
  231. func GetLoginSourceByID(id int64) (*LoginSource, error) {
  232. source := new(LoginSource)
  233. has, err := x.Id(id).Get(source)
  234. if err != nil {
  235. return nil, err
  236. } else if !has {
  237. return nil, ErrLoginSourceNotExist{id}
  238. }
  239. return source, nil
  240. }
  241. // UpdateSource updates a LoginSource record in DB.
  242. func UpdateSource(source *LoginSource) error {
  243. _, err := x.Id(source.ID).AllCols().Update(source)
  244. return err
  245. }
  246. // DeleteSource deletes a LoginSource record in DB.
  247. func DeleteSource(source *LoginSource) error {
  248. count, err := x.Count(&User{LoginSource: source.ID})
  249. if err != nil {
  250. return err
  251. } else if count > 0 {
  252. return ErrLoginSourceInUse{source.ID}
  253. }
  254. _, err = x.Id(source.ID).Delete(new(LoginSource))
  255. return err
  256. }
  257. // CountLoginSources returns number of login sources.
  258. func CountLoginSources() int64 {
  259. count, _ := x.Count(new(LoginSource))
  260. return count
  261. }
  262. // .____ ________ _____ __________
  263. // | | \______ \ / _ \\______ \
  264. // | | | | \ / /_\ \| ___/
  265. // | |___ | ` \/ | \ |
  266. // |_______ \/_______ /\____|__ /____|
  267. // \/ \/ \/
  268. func composeFullName(firstname, surname, username string) string {
  269. switch {
  270. case len(firstname) == 0 && len(surname) == 0:
  271. return username
  272. case len(firstname) == 0:
  273. return surname
  274. case len(surname) == 0:
  275. return firstname
  276. default:
  277. return firstname + " " + surname
  278. }
  279. }
  280. // LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
  281. // and create a local user if success when enabled.
  282. func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
  283. username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
  284. if !succeed {
  285. // User not in LDAP, do nothing
  286. return nil, ErrUserNotExist{0, login, 0}
  287. }
  288. if !autoRegister {
  289. return user, nil
  290. }
  291. // Fallback.
  292. if len(username) == 0 {
  293. username = login
  294. }
  295. // Validate username make sure it satisfies requirement.
  296. if binding.AlphaDashDotPattern.MatchString(username) {
  297. return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
  298. }
  299. if len(mail) == 0 {
  300. mail = fmt.Sprintf("%s@localhost", username)
  301. }
  302. user = &User{
  303. LowerName: strings.ToLower(username),
  304. Name: username,
  305. FullName: composeFullName(fn, sn, username),
  306. Email: mail,
  307. LoginType: source.Type,
  308. LoginSource: source.ID,
  309. LoginName: login,
  310. IsActive: true,
  311. IsAdmin: isAdmin,
  312. }
  313. return user, CreateUser(user)
  314. }
  315. // _________ __________________________
  316. // / _____/ / \__ ___/\______ \
  317. // \_____ \ / \ / \| | | ___/
  318. // / \/ Y \ | | |
  319. // /_______ /\____|__ /____| |____|
  320. // \/ \/
  321. type smtpLoginAuth struct {
  322. username, password string
  323. }
  324. func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  325. return "LOGIN", []byte(auth.username), nil
  326. }
  327. func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  328. if more {
  329. switch string(fromServer) {
  330. case "Username:":
  331. return []byte(auth.username), nil
  332. case "Password:":
  333. return []byte(auth.password), nil
  334. }
  335. }
  336. return nil, nil
  337. }
  338. // SMTP authentication type names.
  339. const (
  340. SMTPPlain = "PLAIN"
  341. SMTPLogin = "LOGIN"
  342. )
  343. // SMTPAuths contains available SMTP authentication type names.
  344. var SMTPAuths = []string{SMTPPlain, SMTPLogin}
  345. // SMTPAuth performs an SMTP authentication.
  346. func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
  347. c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
  348. if err != nil {
  349. return err
  350. }
  351. defer c.Close()
  352. if err = c.Hello("gogs"); err != nil {
  353. return err
  354. }
  355. if cfg.TLS {
  356. if ok, _ := c.Extension("STARTTLS"); ok {
  357. if err = c.StartTLS(&tls.Config{
  358. InsecureSkipVerify: cfg.SkipVerify,
  359. ServerName: cfg.Host,
  360. }); err != nil {
  361. return err
  362. }
  363. } else {
  364. return errors.New("SMTP server unsupports TLS")
  365. }
  366. }
  367. if ok, _ := c.Extension("AUTH"); ok {
  368. if err = c.Auth(a); err != nil {
  369. return err
  370. }
  371. return nil
  372. }
  373. return ErrUnsupportedLoginType
  374. }
  375. // LoginViaSMTP queries if login/password is valid against the SMTP,
  376. // and create a local user if success when enabled.
  377. func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
  378. // Verify allowed domains.
  379. if len(cfg.AllowedDomains) > 0 {
  380. idx := strings.Index(login, "@")
  381. if idx == -1 {
  382. return nil, ErrUserNotExist{0, login, 0}
  383. } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) {
  384. return nil, ErrUserNotExist{0, login, 0}
  385. }
  386. }
  387. var auth smtp.Auth
  388. if cfg.Auth == SMTPPlain {
  389. auth = smtp.PlainAuth("", login, password, cfg.Host)
  390. } else if cfg.Auth == SMTPLogin {
  391. auth = &smtpLoginAuth{login, password}
  392. } else {
  393. return nil, errors.New("Unsupported SMTP auth type")
  394. }
  395. if err := SMTPAuth(auth, cfg); err != nil {
  396. // Check standard error format first,
  397. // then fallback to worse case.
  398. tperr, ok := err.(*textproto.Error)
  399. if (ok && tperr.Code == 535) ||
  400. strings.Contains(err.Error(), "Username and Password not accepted") {
  401. return nil, ErrUserNotExist{0, login, 0}
  402. }
  403. return nil, err
  404. }
  405. if !autoRegister {
  406. return user, nil
  407. }
  408. username := login
  409. idx := strings.Index(login, "@")
  410. if idx > -1 {
  411. username = login[:idx]
  412. }
  413. user = &User{
  414. LowerName: strings.ToLower(username),
  415. Name: strings.ToLower(username),
  416. Email: login,
  417. Passwd: password,
  418. LoginType: LoginSMTP,
  419. LoginSource: sourceID,
  420. LoginName: login,
  421. IsActive: true,
  422. }
  423. return user, CreateUser(user)
  424. }
  425. // __________ _____ _____
  426. // \______ \/ _ \ / \
  427. // | ___/ /_\ \ / \ / \
  428. // | | / | \/ Y \
  429. // |____| \____|__ /\____|__ /
  430. // \/ \/
  431. // LoginViaPAM queries if login/password is valid against the PAM,
  432. // and create a local user if success when enabled.
  433. func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
  434. if err := pam.Auth(cfg.ServiceName, login, password); err != nil {
  435. if strings.Contains(err.Error(), "Authentication failure") {
  436. return nil, ErrUserNotExist{0, login, 0}
  437. }
  438. return nil, err
  439. }
  440. if !autoRegister {
  441. return user, nil
  442. }
  443. user = &User{
  444. LowerName: strings.ToLower(login),
  445. Name: login,
  446. Email: login,
  447. Passwd: password,
  448. LoginType: LoginPAM,
  449. LoginSource: sourceID,
  450. LoginName: login,
  451. IsActive: true,
  452. }
  453. return user, CreateUser(user)
  454. }
  455. // ExternalUserLogin attempts a login using external source types.
  456. func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
  457. if !source.IsActived {
  458. return nil, ErrLoginSourceNotActived
  459. }
  460. switch source.Type {
  461. case LoginLDAP, LoginDLDAP:
  462. return LoginViaLDAP(user, login, password, source, autoRegister)
  463. case LoginSMTP:
  464. return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
  465. case LoginPAM:
  466. return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
  467. }
  468. return nil, ErrUnsupportedLoginType
  469. }
  470. // UserSignIn validates user name and password.
  471. func UserSignIn(username, password string) (*User, error) {
  472. var user *User
  473. if strings.Contains(username, "@") {
  474. user = &User{Email: strings.ToLower(strings.TrimSpace(username))}
  475. } else {
  476. user = &User{LowerName: strings.ToLower(strings.TrimSpace(username))}
  477. }
  478. hasUser, err := x.Get(user)
  479. if err != nil {
  480. return nil, err
  481. }
  482. if hasUser {
  483. switch user.LoginType {
  484. case LoginNoType, LoginPlain:
  485. if user.ValidatePassword(password) {
  486. return user, nil
  487. }
  488. return nil, ErrUserNotExist{user.ID, user.Name, 0}
  489. default:
  490. var source LoginSource
  491. hasSource, err := x.Id(user.LoginSource).Get(&source)
  492. if err != nil {
  493. return nil, err
  494. } else if !hasSource {
  495. return nil, ErrLoginSourceNotExist{user.LoginSource}
  496. }
  497. return ExternalUserLogin(user, user.LoginName, password, &source, false)
  498. }
  499. }
  500. sources := make([]*LoginSource, 0, 3)
  501. if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
  502. return nil, err
  503. }
  504. for _, source := range sources {
  505. authUser, err := ExternalUserLogin(nil, username, password, source, true)
  506. if err == nil {
  507. return authUser, nil
  508. }
  509. log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
  510. }
  511. return nil, ErrUserNotExist{user.ID, user.Name, 0}
  512. }