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 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "crypto/tls"
  8. "encoding/binary"
  9. "errors"
  10. "fmt"
  11. "net/smtp"
  12. "net/textproto"
  13. "strconv"
  14. "strings"
  15. "code.gitea.io/gitea/modules/auth/ldap"
  16. "code.gitea.io/gitea/modules/auth/oauth2"
  17. "code.gitea.io/gitea/modules/auth/pam"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/secret"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/timeutil"
  22. "code.gitea.io/gitea/modules/util"
  23. gouuid "github.com/google/uuid"
  24. jsoniter "github.com/json-iterator/go"
  25. "xorm.io/xorm"
  26. "xorm.io/xorm/convert"
  27. )
  28. // LoginType represents an login type.
  29. type LoginType int
  30. // Note: new type must append to the end of list to maintain compatibility.
  31. const (
  32. LoginNoType LoginType = iota
  33. LoginPlain // 1
  34. LoginLDAP // 2
  35. LoginSMTP // 3
  36. LoginPAM // 4
  37. LoginDLDAP // 5
  38. LoginOAuth2 // 6
  39. LoginSSPI // 7
  40. )
  41. // LoginNames contains the name of LoginType values.
  42. var LoginNames = map[LoginType]string{
  43. LoginLDAP: "LDAP (via BindDN)",
  44. LoginDLDAP: "LDAP (simple auth)", // Via direct bind
  45. LoginSMTP: "SMTP",
  46. LoginPAM: "PAM",
  47. LoginOAuth2: "OAuth2",
  48. LoginSSPI: "SPNEGO with SSPI",
  49. }
  50. // SecurityProtocolNames contains the name of SecurityProtocol values.
  51. var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
  52. ldap.SecurityProtocolUnencrypted: "Unencrypted",
  53. ldap.SecurityProtocolLDAPS: "LDAPS",
  54. ldap.SecurityProtocolStartTLS: "StartTLS",
  55. }
  56. // Ensure structs implemented interface.
  57. var (
  58. _ convert.Conversion = &LDAPConfig{}
  59. _ convert.Conversion = &SMTPConfig{}
  60. _ convert.Conversion = &PAMConfig{}
  61. _ convert.Conversion = &OAuth2Config{}
  62. _ convert.Conversion = &SSPIConfig{}
  63. )
  64. // jsonUnmarshalHandleDoubleEncode - due to a bug in xorm (see https://gitea.com/xorm/xorm/pulls/1957) - it's
  65. // possible that a Blob may be double encoded or gain an unwanted prefix of 0xff 0xfe.
  66. func jsonUnmarshalHandleDoubleEncode(bs []byte, v interface{}) error {
  67. json := jsoniter.ConfigCompatibleWithStandardLibrary
  68. err := json.Unmarshal(bs, v)
  69. if err != nil {
  70. ok := true
  71. rs := []byte{}
  72. temp := make([]byte, 2)
  73. for _, rn := range string(bs) {
  74. if rn > 0xffff {
  75. ok = false
  76. break
  77. }
  78. binary.LittleEndian.PutUint16(temp, uint16(rn))
  79. rs = append(rs, temp...)
  80. }
  81. if ok {
  82. if rs[0] == 0xff && rs[1] == 0xfe {
  83. rs = rs[2:]
  84. }
  85. err = json.Unmarshal(rs, v)
  86. }
  87. }
  88. if err != nil && len(bs) > 2 && bs[0] == 0xff && bs[1] == 0xfe {
  89. err = json.Unmarshal(bs[2:], v)
  90. }
  91. return err
  92. }
  93. // LDAPConfig holds configuration for LDAP login source.
  94. type LDAPConfig struct {
  95. *ldap.Source
  96. }
  97. // FromDB fills up a LDAPConfig from serialized format.
  98. func (cfg *LDAPConfig) FromDB(bs []byte) error {
  99. err := jsonUnmarshalHandleDoubleEncode(bs, &cfg)
  100. if err != nil {
  101. return err
  102. }
  103. if cfg.BindPasswordEncrypt != "" {
  104. cfg.BindPassword, err = secret.DecryptSecret(setting.SecretKey, cfg.BindPasswordEncrypt)
  105. cfg.BindPasswordEncrypt = ""
  106. }
  107. return err
  108. }
  109. // ToDB exports a LDAPConfig to a serialized format.
  110. func (cfg *LDAPConfig) ToDB() ([]byte, error) {
  111. var err error
  112. cfg.BindPasswordEncrypt, err = secret.EncryptSecret(setting.SecretKey, cfg.BindPassword)
  113. if err != nil {
  114. return nil, err
  115. }
  116. cfg.BindPassword = ""
  117. json := jsoniter.ConfigCompatibleWithStandardLibrary
  118. return json.Marshal(cfg)
  119. }
  120. // SecurityProtocolName returns the name of configured security
  121. // protocol.
  122. func (cfg *LDAPConfig) SecurityProtocolName() string {
  123. return SecurityProtocolNames[cfg.SecurityProtocol]
  124. }
  125. // SMTPConfig holds configuration for the SMTP login source.
  126. type SMTPConfig struct {
  127. Auth string
  128. Host string
  129. Port int
  130. AllowedDomains string `xorm:"TEXT"`
  131. TLS bool
  132. SkipVerify bool
  133. }
  134. // FromDB fills up an SMTPConfig from serialized format.
  135. func (cfg *SMTPConfig) FromDB(bs []byte) error {
  136. return jsonUnmarshalHandleDoubleEncode(bs, cfg)
  137. }
  138. // ToDB exports an SMTPConfig to a serialized format.
  139. func (cfg *SMTPConfig) ToDB() ([]byte, error) {
  140. json := jsoniter.ConfigCompatibleWithStandardLibrary
  141. return json.Marshal(cfg)
  142. }
  143. // PAMConfig holds configuration for the PAM login source.
  144. type PAMConfig struct {
  145. ServiceName string // pam service (e.g. system-auth)
  146. EmailDomain string
  147. }
  148. // FromDB fills up a PAMConfig from serialized format.
  149. func (cfg *PAMConfig) FromDB(bs []byte) error {
  150. return jsonUnmarshalHandleDoubleEncode(bs, cfg)
  151. }
  152. // ToDB exports a PAMConfig to a serialized format.
  153. func (cfg *PAMConfig) ToDB() ([]byte, error) {
  154. json := jsoniter.ConfigCompatibleWithStandardLibrary
  155. return json.Marshal(cfg)
  156. }
  157. // OAuth2Config holds configuration for the OAuth2 login source.
  158. type OAuth2Config struct {
  159. Provider string
  160. ClientID string
  161. ClientSecret string
  162. OpenIDConnectAutoDiscoveryURL string
  163. CustomURLMapping *oauth2.CustomURLMapping
  164. IconURL string
  165. }
  166. // FromDB fills up an OAuth2Config from serialized format.
  167. func (cfg *OAuth2Config) FromDB(bs []byte) error {
  168. return jsonUnmarshalHandleDoubleEncode(bs, cfg)
  169. }
  170. // ToDB exports an SMTPConfig to a serialized format.
  171. func (cfg *OAuth2Config) ToDB() ([]byte, error) {
  172. json := jsoniter.ConfigCompatibleWithStandardLibrary
  173. return json.Marshal(cfg)
  174. }
  175. // SSPIConfig holds configuration for SSPI single sign-on.
  176. type SSPIConfig struct {
  177. AutoCreateUsers bool
  178. AutoActivateUsers bool
  179. StripDomainNames bool
  180. SeparatorReplacement string
  181. DefaultLanguage string
  182. }
  183. // FromDB fills up an SSPIConfig from serialized format.
  184. func (cfg *SSPIConfig) FromDB(bs []byte) error {
  185. return jsonUnmarshalHandleDoubleEncode(bs, cfg)
  186. }
  187. // ToDB exports an SSPIConfig to a serialized format.
  188. func (cfg *SSPIConfig) ToDB() ([]byte, error) {
  189. json := jsoniter.ConfigCompatibleWithStandardLibrary
  190. return json.Marshal(cfg)
  191. }
  192. // LoginSource represents an external way for authorizing users.
  193. type LoginSource struct {
  194. ID int64 `xorm:"pk autoincr"`
  195. Type LoginType
  196. Name string `xorm:"UNIQUE"`
  197. IsActived bool `xorm:"INDEX NOT NULL DEFAULT false"`
  198. IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"`
  199. Cfg convert.Conversion `xorm:"TEXT"`
  200. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  201. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  202. }
  203. // Cell2Int64 converts a xorm.Cell type to int64,
  204. // and handles possible irregular cases.
  205. func Cell2Int64(val xorm.Cell) int64 {
  206. switch (*val).(type) {
  207. case []uint8:
  208. log.Trace("Cell2Int64 ([]uint8): %v", *val)
  209. v, _ := strconv.ParseInt(string((*val).([]uint8)), 10, 64)
  210. return v
  211. }
  212. return (*val).(int64)
  213. }
  214. // BeforeSet is invoked from XORM before setting the value of a field of this object.
  215. func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
  216. if colName == "type" {
  217. switch LoginType(Cell2Int64(val)) {
  218. case LoginLDAP, LoginDLDAP:
  219. source.Cfg = new(LDAPConfig)
  220. case LoginSMTP:
  221. source.Cfg = new(SMTPConfig)
  222. case LoginPAM:
  223. source.Cfg = new(PAMConfig)
  224. case LoginOAuth2:
  225. source.Cfg = new(OAuth2Config)
  226. case LoginSSPI:
  227. source.Cfg = new(SSPIConfig)
  228. default:
  229. panic(fmt.Sprintf("unrecognized login source type: %v", *val))
  230. }
  231. }
  232. }
  233. // TypeName return name of this login source type.
  234. func (source *LoginSource) TypeName() string {
  235. return LoginNames[source.Type]
  236. }
  237. // IsLDAP returns true of this source is of the LDAP type.
  238. func (source *LoginSource) IsLDAP() bool {
  239. return source.Type == LoginLDAP
  240. }
  241. // IsDLDAP returns true of this source is of the DLDAP type.
  242. func (source *LoginSource) IsDLDAP() bool {
  243. return source.Type == LoginDLDAP
  244. }
  245. // IsSMTP returns true of this source is of the SMTP type.
  246. func (source *LoginSource) IsSMTP() bool {
  247. return source.Type == LoginSMTP
  248. }
  249. // IsPAM returns true of this source is of the PAM type.
  250. func (source *LoginSource) IsPAM() bool {
  251. return source.Type == LoginPAM
  252. }
  253. // IsOAuth2 returns true of this source is of the OAuth2 type.
  254. func (source *LoginSource) IsOAuth2() bool {
  255. return source.Type == LoginOAuth2
  256. }
  257. // IsSSPI returns true of this source is of the SSPI type.
  258. func (source *LoginSource) IsSSPI() bool {
  259. return source.Type == LoginSSPI
  260. }
  261. // HasTLS returns true of this source supports TLS.
  262. func (source *LoginSource) HasTLS() bool {
  263. return ((source.IsLDAP() || source.IsDLDAP()) &&
  264. source.LDAP().SecurityProtocol > ldap.SecurityProtocolUnencrypted) ||
  265. source.IsSMTP()
  266. }
  267. // UseTLS returns true of this source is configured to use TLS.
  268. func (source *LoginSource) UseTLS() bool {
  269. switch source.Type {
  270. case LoginLDAP, LoginDLDAP:
  271. return source.LDAP().SecurityProtocol != ldap.SecurityProtocolUnencrypted
  272. case LoginSMTP:
  273. return source.SMTP().TLS
  274. }
  275. return false
  276. }
  277. // SkipVerify returns true if this source is configured to skip SSL
  278. // verification.
  279. func (source *LoginSource) SkipVerify() bool {
  280. switch source.Type {
  281. case LoginLDAP, LoginDLDAP:
  282. return source.LDAP().SkipVerify
  283. case LoginSMTP:
  284. return source.SMTP().SkipVerify
  285. }
  286. return false
  287. }
  288. // LDAP returns LDAPConfig for this source, if of LDAP type.
  289. func (source *LoginSource) LDAP() *LDAPConfig {
  290. return source.Cfg.(*LDAPConfig)
  291. }
  292. // SMTP returns SMTPConfig for this source, if of SMTP type.
  293. func (source *LoginSource) SMTP() *SMTPConfig {
  294. return source.Cfg.(*SMTPConfig)
  295. }
  296. // PAM returns PAMConfig for this source, if of PAM type.
  297. func (source *LoginSource) PAM() *PAMConfig {
  298. return source.Cfg.(*PAMConfig)
  299. }
  300. // OAuth2 returns OAuth2Config for this source, if of OAuth2 type.
  301. func (source *LoginSource) OAuth2() *OAuth2Config {
  302. return source.Cfg.(*OAuth2Config)
  303. }
  304. // SSPI returns SSPIConfig for this source, if of SSPI type.
  305. func (source *LoginSource) SSPI() *SSPIConfig {
  306. return source.Cfg.(*SSPIConfig)
  307. }
  308. // CreateLoginSource inserts a LoginSource in the DB if not already
  309. // existing with the given name.
  310. func CreateLoginSource(source *LoginSource) error {
  311. has, err := x.Where("name=?", source.Name).Exist(new(LoginSource))
  312. if err != nil {
  313. return err
  314. } else if has {
  315. return ErrLoginSourceAlreadyExist{source.Name}
  316. }
  317. // Synchronization is only available with LDAP for now
  318. if !source.IsLDAP() {
  319. source.IsSyncEnabled = false
  320. }
  321. _, err = x.Insert(source)
  322. if err == nil && source.IsOAuth2() && source.IsActived {
  323. oAuth2Config := source.OAuth2()
  324. err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
  325. err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
  326. if err != nil {
  327. // remove the LoginSource in case of errors while registering OAuth2 providers
  328. if _, err := x.Delete(source); err != nil {
  329. log.Error("CreateLoginSource: Error while wrapOpenIDConnectInitializeError: %v", err)
  330. }
  331. return err
  332. }
  333. }
  334. return err
  335. }
  336. // LoginSources returns a slice of all login sources found in DB.
  337. func LoginSources() ([]*LoginSource, error) {
  338. auths := make([]*LoginSource, 0, 6)
  339. return auths, x.Find(&auths)
  340. }
  341. // LoginSourcesByType returns all sources of the specified type
  342. func LoginSourcesByType(loginType LoginType) ([]*LoginSource, error) {
  343. sources := make([]*LoginSource, 0, 1)
  344. if err := x.Where("type = ?", loginType).Find(&sources); err != nil {
  345. return nil, err
  346. }
  347. return sources, nil
  348. }
  349. // ActiveLoginSources returns all active sources of the specified type
  350. func ActiveLoginSources(loginType LoginType) ([]*LoginSource, error) {
  351. sources := make([]*LoginSource, 0, 1)
  352. if err := x.Where("is_actived = ? and type = ?", true, loginType).Find(&sources); err != nil {
  353. return nil, err
  354. }
  355. return sources, nil
  356. }
  357. // IsSSPIEnabled returns true if there is at least one activated login
  358. // source of type LoginSSPI
  359. func IsSSPIEnabled() bool {
  360. if !HasEngine {
  361. return false
  362. }
  363. sources, err := ActiveLoginSources(LoginSSPI)
  364. if err != nil {
  365. log.Error("ActiveLoginSources: %v", err)
  366. return false
  367. }
  368. return len(sources) > 0
  369. }
  370. // GetLoginSourceByID returns login source by given ID.
  371. func GetLoginSourceByID(id int64) (*LoginSource, error) {
  372. source := new(LoginSource)
  373. has, err := x.ID(id).Get(source)
  374. if err != nil {
  375. return nil, err
  376. } else if !has {
  377. return nil, ErrLoginSourceNotExist{id}
  378. }
  379. return source, nil
  380. }
  381. // UpdateSource updates a LoginSource record in DB.
  382. func UpdateSource(source *LoginSource) error {
  383. var originalLoginSource *LoginSource
  384. if source.IsOAuth2() {
  385. // keep track of the original values so we can restore in case of errors while registering OAuth2 providers
  386. var err error
  387. if originalLoginSource, err = GetLoginSourceByID(source.ID); err != nil {
  388. return err
  389. }
  390. }
  391. _, err := x.ID(source.ID).AllCols().Update(source)
  392. if err == nil && source.IsOAuth2() && source.IsActived {
  393. oAuth2Config := source.OAuth2()
  394. err = oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
  395. err = wrapOpenIDConnectInitializeError(err, source.Name, oAuth2Config)
  396. if err != nil {
  397. // restore original values since we cannot update the provider it self
  398. if _, err := x.ID(source.ID).AllCols().Update(originalLoginSource); err != nil {
  399. log.Error("UpdateSource: Error while wrapOpenIDConnectInitializeError: %v", err)
  400. }
  401. return err
  402. }
  403. }
  404. return err
  405. }
  406. // DeleteSource deletes a LoginSource record in DB.
  407. func DeleteSource(source *LoginSource) error {
  408. count, err := x.Count(&User{LoginSource: source.ID})
  409. if err != nil {
  410. return err
  411. } else if count > 0 {
  412. return ErrLoginSourceInUse{source.ID}
  413. }
  414. count, err = x.Count(&ExternalLoginUser{LoginSourceID: source.ID})
  415. if err != nil {
  416. return err
  417. } else if count > 0 {
  418. return ErrLoginSourceInUse{source.ID}
  419. }
  420. if source.IsOAuth2() {
  421. oauth2.RemoveProvider(source.Name)
  422. }
  423. _, err = x.ID(source.ID).Delete(new(LoginSource))
  424. return err
  425. }
  426. // CountLoginSources returns number of login sources.
  427. func CountLoginSources() int64 {
  428. count, _ := x.Count(new(LoginSource))
  429. return count
  430. }
  431. // .____ ________ _____ __________
  432. // | | \______ \ / _ \\______ \
  433. // | | | | \ / /_\ \| ___/
  434. // | |___ | ` \/ | \ |
  435. // |_______ \/_______ /\____|__ /____|
  436. // \/ \/ \/
  437. func composeFullName(firstname, surname, username string) string {
  438. switch {
  439. case len(firstname) == 0 && len(surname) == 0:
  440. return username
  441. case len(firstname) == 0:
  442. return surname
  443. case len(surname) == 0:
  444. return firstname
  445. default:
  446. return firstname + " " + surname
  447. }
  448. }
  449. // LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
  450. // and create a local user if success when enabled.
  451. func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*User, error) {
  452. sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP)
  453. if sr == nil {
  454. // User not in LDAP, do nothing
  455. return nil, ErrUserNotExist{0, login, 0}
  456. }
  457. isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.LDAP().AttributeSSHPublicKey)) > 0
  458. // Update User admin flag if exist
  459. if isExist, err := IsUserExist(0, sr.Username); err != nil {
  460. return nil, err
  461. } else if isExist {
  462. if user == nil {
  463. user, err = GetUserByName(sr.Username)
  464. if err != nil {
  465. return nil, err
  466. }
  467. }
  468. if user != nil && !user.ProhibitLogin {
  469. cols := make([]string, 0)
  470. if len(source.LDAP().AdminFilter) > 0 && user.IsAdmin != sr.IsAdmin {
  471. // Change existing admin flag only if AdminFilter option is set
  472. user.IsAdmin = sr.IsAdmin
  473. cols = append(cols, "is_admin")
  474. }
  475. if !user.IsAdmin && len(source.LDAP().RestrictedFilter) > 0 && user.IsRestricted != sr.IsRestricted {
  476. // Change existing restricted flag only if RestrictedFilter option is set
  477. user.IsRestricted = sr.IsRestricted
  478. cols = append(cols, "is_restricted")
  479. }
  480. if len(cols) > 0 {
  481. err = UpdateUserCols(user, cols...)
  482. if err != nil {
  483. return nil, err
  484. }
  485. }
  486. }
  487. }
  488. if user != nil {
  489. if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys(user, source, sr.SSHPublicKey) {
  490. return user, RewriteAllPublicKeys()
  491. }
  492. return user, nil
  493. }
  494. // Fallback.
  495. if len(sr.Username) == 0 {
  496. sr.Username = login
  497. }
  498. if len(sr.Mail) == 0 {
  499. sr.Mail = fmt.Sprintf("%s@localhost", sr.Username)
  500. }
  501. user = &User{
  502. LowerName: strings.ToLower(sr.Username),
  503. Name: sr.Username,
  504. FullName: composeFullName(sr.Name, sr.Surname, sr.Username),
  505. Email: sr.Mail,
  506. LoginType: source.Type,
  507. LoginSource: source.ID,
  508. LoginName: login,
  509. IsActive: true,
  510. IsAdmin: sr.IsAdmin,
  511. IsRestricted: sr.IsRestricted,
  512. }
  513. err := CreateUser(user)
  514. if err == nil && isAttributeSSHPublicKeySet && addLdapSSHPublicKeys(user, source, sr.SSHPublicKey) {
  515. err = RewriteAllPublicKeys()
  516. }
  517. return user, err
  518. }
  519. // _________ __________________________
  520. // / _____/ / \__ ___/\______ \
  521. // \_____ \ / \ / \| | | ___/
  522. // / \/ Y \ | | |
  523. // /_______ /\____|__ /____| |____|
  524. // \/ \/
  525. type smtpLoginAuth struct {
  526. username, password string
  527. }
  528. func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  529. return "LOGIN", []byte(auth.username), nil
  530. }
  531. func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  532. if more {
  533. switch string(fromServer) {
  534. case "Username:":
  535. return []byte(auth.username), nil
  536. case "Password:":
  537. return []byte(auth.password), nil
  538. }
  539. }
  540. return nil, nil
  541. }
  542. // SMTP authentication type names.
  543. const (
  544. SMTPPlain = "PLAIN"
  545. SMTPLogin = "LOGIN"
  546. )
  547. // SMTPAuths contains available SMTP authentication type names.
  548. var SMTPAuths = []string{SMTPPlain, SMTPLogin}
  549. // SMTPAuth performs an SMTP authentication.
  550. func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
  551. c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
  552. if err != nil {
  553. return err
  554. }
  555. defer c.Close()
  556. if err = c.Hello("gogs"); err != nil {
  557. return err
  558. }
  559. if cfg.TLS {
  560. if ok, _ := c.Extension("STARTTLS"); ok {
  561. if err = c.StartTLS(&tls.Config{
  562. InsecureSkipVerify: cfg.SkipVerify,
  563. ServerName: cfg.Host,
  564. }); err != nil {
  565. return err
  566. }
  567. } else {
  568. return errors.New("SMTP server unsupports TLS")
  569. }
  570. }
  571. if ok, _ := c.Extension("AUTH"); ok {
  572. return c.Auth(a)
  573. }
  574. return ErrUnsupportedLoginType
  575. }
  576. // LoginViaSMTP queries if login/password is valid against the SMTP,
  577. // and create a local user if success when enabled.
  578. func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig) (*User, error) {
  579. // Verify allowed domains.
  580. if len(cfg.AllowedDomains) > 0 {
  581. idx := strings.Index(login, "@")
  582. if idx == -1 {
  583. return nil, ErrUserNotExist{0, login, 0}
  584. } else if !util.IsStringInSlice(login[idx+1:], strings.Split(cfg.AllowedDomains, ","), true) {
  585. return nil, ErrUserNotExist{0, login, 0}
  586. }
  587. }
  588. var auth smtp.Auth
  589. if cfg.Auth == SMTPPlain {
  590. auth = smtp.PlainAuth("", login, password, cfg.Host)
  591. } else if cfg.Auth == SMTPLogin {
  592. auth = &smtpLoginAuth{login, password}
  593. } else {
  594. return nil, errors.New("Unsupported SMTP auth type")
  595. }
  596. if err := SMTPAuth(auth, cfg); err != nil {
  597. // Check standard error format first,
  598. // then fallback to worse case.
  599. tperr, ok := err.(*textproto.Error)
  600. if (ok && tperr.Code == 535) ||
  601. strings.Contains(err.Error(), "Username and Password not accepted") {
  602. return nil, ErrUserNotExist{0, login, 0}
  603. }
  604. return nil, err
  605. }
  606. if user != nil {
  607. return user, nil
  608. }
  609. username := login
  610. idx := strings.Index(login, "@")
  611. if idx > -1 {
  612. username = login[:idx]
  613. }
  614. user = &User{
  615. LowerName: strings.ToLower(username),
  616. Name: strings.ToLower(username),
  617. Email: login,
  618. Passwd: password,
  619. LoginType: LoginSMTP,
  620. LoginSource: sourceID,
  621. LoginName: login,
  622. IsActive: true,
  623. }
  624. return user, CreateUser(user)
  625. }
  626. // __________ _____ _____
  627. // \______ \/ _ \ / \
  628. // | ___/ /_\ \ / \ / \
  629. // | | / | \/ Y \
  630. // |____| \____|__ /\____|__ /
  631. // \/ \/
  632. // LoginViaPAM queries if login/password is valid against the PAM,
  633. // and create a local user if success when enabled.
  634. func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig) (*User, error) {
  635. pamLogin, err := pam.Auth(cfg.ServiceName, login, password)
  636. if err != nil {
  637. if strings.Contains(err.Error(), "Authentication failure") {
  638. return nil, ErrUserNotExist{0, login, 0}
  639. }
  640. return nil, err
  641. }
  642. if user != nil {
  643. return user, nil
  644. }
  645. // Allow PAM sources with `@` in their name, like from Active Directory
  646. username := pamLogin
  647. email := pamLogin
  648. idx := strings.Index(pamLogin, "@")
  649. if idx > -1 {
  650. username = pamLogin[:idx]
  651. }
  652. if ValidateEmail(email) != nil {
  653. if cfg.EmailDomain != "" {
  654. email = fmt.Sprintf("%s@%s", username, cfg.EmailDomain)
  655. } else {
  656. email = fmt.Sprintf("%s@%s", username, setting.Service.NoReplyAddress)
  657. }
  658. if ValidateEmail(email) != nil {
  659. email = gouuid.New().String() + "@localhost"
  660. }
  661. }
  662. user = &User{
  663. LowerName: strings.ToLower(username),
  664. Name: username,
  665. Email: email,
  666. Passwd: password,
  667. LoginType: LoginPAM,
  668. LoginSource: sourceID,
  669. LoginName: login, // This is what the user typed in
  670. IsActive: true,
  671. }
  672. return user, CreateUser(user)
  673. }
  674. // ExternalUserLogin attempts a login using external source types.
  675. func ExternalUserLogin(user *User, login, password string, source *LoginSource) (*User, error) {
  676. if !source.IsActived {
  677. return nil, ErrLoginSourceNotActived
  678. }
  679. var err error
  680. switch source.Type {
  681. case LoginLDAP, LoginDLDAP:
  682. user, err = LoginViaLDAP(user, login, password, source)
  683. case LoginSMTP:
  684. user, err = LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig))
  685. case LoginPAM:
  686. user, err = LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig))
  687. default:
  688. return nil, ErrUnsupportedLoginType
  689. }
  690. if err != nil {
  691. return nil, err
  692. }
  693. // WARN: DON'T check user.IsActive, that will be checked on reqSign so that
  694. // user could be hint to resend confirm email.
  695. if user.ProhibitLogin {
  696. return nil, ErrUserProhibitLogin{user.ID, user.Name}
  697. }
  698. return user, nil
  699. }
  700. // UserSignIn validates user name and password.
  701. func UserSignIn(username, password string) (*User, error) {
  702. var user *User
  703. if strings.Contains(username, "@") {
  704. user = &User{Email: strings.ToLower(strings.TrimSpace(username))}
  705. // check same email
  706. cnt, err := x.Count(user)
  707. if err != nil {
  708. return nil, err
  709. }
  710. if cnt > 1 {
  711. return nil, ErrEmailAlreadyUsed{
  712. Email: user.Email,
  713. }
  714. }
  715. } else {
  716. trimmedUsername := strings.TrimSpace(username)
  717. if len(trimmedUsername) == 0 {
  718. return nil, ErrUserNotExist{0, username, 0}
  719. }
  720. user = &User{LowerName: strings.ToLower(trimmedUsername)}
  721. }
  722. hasUser, err := x.Get(user)
  723. if err != nil {
  724. return nil, err
  725. }
  726. if hasUser {
  727. switch user.LoginType {
  728. case LoginNoType, LoginPlain, LoginOAuth2:
  729. if user.IsPasswordSet() && user.ValidatePassword(password) {
  730. // Update password hash if server password hash algorithm have changed
  731. if user.PasswdHashAlgo != setting.PasswordHashAlgo {
  732. if err = user.SetPassword(password); err != nil {
  733. return nil, err
  734. }
  735. if err = UpdateUserCols(user, "passwd", "passwd_hash_algo", "salt"); err != nil {
  736. return nil, err
  737. }
  738. }
  739. // WARN: DON'T check user.IsActive, that will be checked on reqSign so that
  740. // user could be hint to resend confirm email.
  741. if user.ProhibitLogin {
  742. return nil, ErrUserProhibitLogin{user.ID, user.Name}
  743. }
  744. return user, nil
  745. }
  746. return nil, ErrUserNotExist{user.ID, user.Name, 0}
  747. default:
  748. var source LoginSource
  749. hasSource, err := x.ID(user.LoginSource).Get(&source)
  750. if err != nil {
  751. return nil, err
  752. } else if !hasSource {
  753. return nil, ErrLoginSourceNotExist{user.LoginSource}
  754. }
  755. return ExternalUserLogin(user, user.LoginName, password, &source)
  756. }
  757. }
  758. sources := make([]*LoginSource, 0, 5)
  759. if err = x.Where("is_actived = ?", true).Find(&sources); err != nil {
  760. return nil, err
  761. }
  762. for _, source := range sources {
  763. if source.IsOAuth2() || source.IsSSPI() {
  764. // don't try to authenticate against OAuth2 and SSPI sources here
  765. continue
  766. }
  767. authUser, err := ExternalUserLogin(nil, username, password, source)
  768. if err == nil {
  769. return authUser, nil
  770. }
  771. if IsErrUserNotExist(err) {
  772. log.Debug("Failed to login '%s' via '%s': %v", username, source.Name, err)
  773. } else {
  774. log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
  775. }
  776. }
  777. return nil, ErrUserNotExist{user.ID, user.Name, 0}
  778. }