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

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. }