login.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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-xorm/core"
  16. "github.com/go-xorm/xorm"
  17. "github.com/gogits/gogs/modules/auth/ldap"
  18. "github.com/gogits/gogs/modules/auth/pam"
  19. "github.com/gogits/gogs/modules/log"
  20. )
  21. type LoginType int
  22. // Note: new type must be added at the end of list to maintain compatibility.
  23. const (
  24. LOGIN_NOTYPE LoginType = iota
  25. LOGIN_PLAIN
  26. LOGIN_LDAP
  27. LOGIN_SMTP
  28. LOGIN_PAM
  29. LOGIN_DLDAP
  30. )
  31. var (
  32. ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
  33. ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
  34. )
  35. var LoginNames = map[LoginType]string{
  36. LOGIN_LDAP: "LDAP (via BindDN)",
  37. LOGIN_DLDAP: "LDAP (simple auth)",
  38. LOGIN_SMTP: "SMTP",
  39. LOGIN_PAM: "PAM",
  40. }
  41. // Ensure structs implemented interface.
  42. var (
  43. _ core.Conversion = &LDAPConfig{}
  44. _ core.Conversion = &SMTPConfig{}
  45. _ core.Conversion = &PAMConfig{}
  46. )
  47. type LDAPConfig struct {
  48. *ldap.Source
  49. }
  50. func (cfg *LDAPConfig) FromDB(bs []byte) error {
  51. return json.Unmarshal(bs, &cfg)
  52. }
  53. func (cfg *LDAPConfig) ToDB() ([]byte, error) {
  54. return json.Marshal(cfg)
  55. }
  56. type SMTPConfig struct {
  57. Auth string
  58. Host string
  59. Port int
  60. AllowedDomains string `xorm:"TEXT"`
  61. TLS bool
  62. SkipVerify bool
  63. }
  64. func (cfg *SMTPConfig) FromDB(bs []byte) error {
  65. return json.Unmarshal(bs, cfg)
  66. }
  67. func (cfg *SMTPConfig) ToDB() ([]byte, error) {
  68. return json.Marshal(cfg)
  69. }
  70. type PAMConfig struct {
  71. ServiceName string // pam service (e.g. system-auth)
  72. }
  73. func (cfg *PAMConfig) FromDB(bs []byte) error {
  74. return json.Unmarshal(bs, &cfg)
  75. }
  76. func (cfg *PAMConfig) ToDB() ([]byte, error) {
  77. return json.Marshal(cfg)
  78. }
  79. type LoginSource struct {
  80. ID int64 `xorm:"pk autoincr"`
  81. Type LoginType
  82. Name string `xorm:"UNIQUE"`
  83. IsActived bool `xorm:"NOT NULL DEFAULT false"`
  84. Cfg core.Conversion `xorm:"TEXT"`
  85. Created time.Time `xorm:"CREATED"`
  86. Updated time.Time `xorm:"UPDATED"`
  87. }
  88. // Cell2Int64 converts a xorm.Cell type to int64,
  89. // and handles possible irregular cases.
  90. func Cell2Int64(val xorm.Cell) int64 {
  91. switch (*val).(type) {
  92. case []uint8:
  93. log.Trace("Cell2Int64 ([]uint8): %v", *val)
  94. return com.StrTo(string((*val).([]uint8))).MustInt64()
  95. }
  96. return (*val).(int64)
  97. }
  98. func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
  99. switch colName {
  100. case "type":
  101. switch LoginType(Cell2Int64(val)) {
  102. case LOGIN_LDAP, LOGIN_DLDAP:
  103. source.Cfg = new(LDAPConfig)
  104. case LOGIN_SMTP:
  105. source.Cfg = new(SMTPConfig)
  106. case LOGIN_PAM:
  107. source.Cfg = new(PAMConfig)
  108. default:
  109. panic("unrecognized login source type: " + com.ToStr(*val))
  110. }
  111. }
  112. }
  113. func (source *LoginSource) TypeName() string {
  114. return LoginNames[source.Type]
  115. }
  116. func (source *LoginSource) IsLDAP() bool {
  117. return source.Type == LOGIN_LDAP
  118. }
  119. func (source *LoginSource) IsDLDAP() bool {
  120. return source.Type == LOGIN_DLDAP
  121. }
  122. func (source *LoginSource) IsSMTP() bool {
  123. return source.Type == LOGIN_SMTP
  124. }
  125. func (source *LoginSource) IsPAM() bool {
  126. return source.Type == LOGIN_PAM
  127. }
  128. func (source *LoginSource) UseTLS() bool {
  129. switch source.Type {
  130. case LOGIN_LDAP, LOGIN_DLDAP:
  131. return source.LDAP().UseSSL
  132. case LOGIN_SMTP:
  133. return source.SMTP().TLS
  134. }
  135. return false
  136. }
  137. func (source *LoginSource) SkipVerify() bool {
  138. switch source.Type {
  139. case LOGIN_LDAP, LOGIN_DLDAP:
  140. return source.LDAP().SkipVerify
  141. case LOGIN_SMTP:
  142. return source.SMTP().SkipVerify
  143. }
  144. return false
  145. }
  146. func (source *LoginSource) LDAP() *LDAPConfig {
  147. return source.Cfg.(*LDAPConfig)
  148. }
  149. func (source *LoginSource) SMTP() *SMTPConfig {
  150. return source.Cfg.(*SMTPConfig)
  151. }
  152. func (source *LoginSource) PAM() *PAMConfig {
  153. return source.Cfg.(*PAMConfig)
  154. }
  155. // CountLoginSources returns number of login sources.
  156. func CountLoginSources() int64 {
  157. count, _ := x.Count(new(LoginSource))
  158. return count
  159. }
  160. func CreateSource(source *LoginSource) error {
  161. _, err := x.Insert(source)
  162. return err
  163. }
  164. func LoginSources() ([]*LoginSource, error) {
  165. auths := make([]*LoginSource, 0, 5)
  166. return auths, x.Find(&auths)
  167. }
  168. // GetLoginSourceByID returns login source by given ID.
  169. func GetLoginSourceByID(id int64) (*LoginSource, error) {
  170. source := new(LoginSource)
  171. has, err := x.Id(id).Get(source)
  172. if err != nil {
  173. return nil, err
  174. } else if !has {
  175. return nil, ErrAuthenticationNotExist{id}
  176. }
  177. return source, nil
  178. }
  179. func UpdateSource(source *LoginSource) error {
  180. _, err := x.Id(source.ID).AllCols().Update(source)
  181. return err
  182. }
  183. func DeleteSource(source *LoginSource) error {
  184. count, err := x.Count(&User{LoginSource: source.ID})
  185. if err != nil {
  186. return err
  187. } else if count > 0 {
  188. return ErrAuthenticationUserUsed
  189. }
  190. _, err = x.Id(source.ID).Delete(new(LoginSource))
  191. return err
  192. }
  193. // .____ ________ _____ __________
  194. // | | \______ \ / _ \\______ \
  195. // | | | | \ / /_\ \| ___/
  196. // | |___ | ` \/ | \ |
  197. // |_______ \/_______ /\____|__ /____|
  198. // \/ \/ \/
  199. // LoginUserLDAPSource queries if loginName/passwd can login against the LDAP directory pool,
  200. // and create a local user if success when enabled.
  201. // It returns the same LoginUserPlain semantic.
  202. func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
  203. cfg := source.Cfg.(*LDAPConfig)
  204. directBind := (source.Type == LOGIN_DLDAP)
  205. name, fn, sn, mail, admin, logged := cfg.SearchEntry(loginName, passwd, directBind)
  206. if !logged {
  207. // User not in LDAP, do nothing
  208. return nil, ErrUserNotExist{0, loginName}
  209. }
  210. if !autoRegister {
  211. return u, nil
  212. }
  213. // Fallback.
  214. if len(name) == 0 {
  215. name = loginName
  216. }
  217. if len(mail) == 0 {
  218. mail = fmt.Sprintf("%s@localhost", name)
  219. }
  220. u = &User{
  221. LowerName: strings.ToLower(name),
  222. Name: name,
  223. FullName: composeFullName(fn, sn, name),
  224. LoginType: source.Type,
  225. LoginSource: source.ID,
  226. LoginName: loginName,
  227. Email: mail,
  228. IsAdmin: admin,
  229. IsActive: true,
  230. }
  231. return u, CreateUser(u)
  232. }
  233. func composeFullName(firstName, surename, userName string) string {
  234. switch {
  235. case len(firstName) == 0 && len(surename) == 0:
  236. return userName
  237. case len(firstName) == 0:
  238. return surename
  239. case len(surename) == 0:
  240. return firstName
  241. default:
  242. return firstName + " " + surename
  243. }
  244. }
  245. // _________ __________________________
  246. // / _____/ / \__ ___/\______ \
  247. // \_____ \ / \ / \| | | ___/
  248. // / \/ Y \ | | |
  249. // /_______ /\____|__ /____| |____|
  250. // \/ \/
  251. type loginAuth struct {
  252. username, password string
  253. }
  254. func LoginAuth(username, password string) smtp.Auth {
  255. return &loginAuth{username, password}
  256. }
  257. func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
  258. return "LOGIN", []byte(a.username), nil
  259. }
  260. func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
  261. if more {
  262. switch string(fromServer) {
  263. case "Username:":
  264. return []byte(a.username), nil
  265. case "Password:":
  266. return []byte(a.password), nil
  267. }
  268. }
  269. return nil, nil
  270. }
  271. const (
  272. SMTP_PLAIN = "PLAIN"
  273. SMTP_LOGIN = "LOGIN"
  274. )
  275. var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
  276. func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
  277. c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
  278. if err != nil {
  279. return err
  280. }
  281. defer c.Close()
  282. if err = c.Hello("gogs"); err != nil {
  283. return err
  284. }
  285. if cfg.TLS {
  286. if ok, _ := c.Extension("STARTTLS"); ok {
  287. if err = c.StartTLS(&tls.Config{
  288. InsecureSkipVerify: cfg.SkipVerify,
  289. ServerName: cfg.Host,
  290. }); err != nil {
  291. return err
  292. }
  293. } else {
  294. return errors.New("SMTP server unsupports TLS")
  295. }
  296. }
  297. if ok, _ := c.Extension("AUTH"); ok {
  298. if err = c.Auth(a); err != nil {
  299. return err
  300. }
  301. return nil
  302. }
  303. return ErrUnsupportedLoginType
  304. }
  305. // Query if name/passwd can login against the LDAP directory pool
  306. // Create a local user if success
  307. // Return the same LoginUserPlain semantic
  308. func LoginUserSMTPSource(u *User, name, passwd string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
  309. // Verify allowed domains.
  310. if len(cfg.AllowedDomains) > 0 {
  311. idx := strings.Index(name, "@")
  312. if idx == -1 {
  313. return nil, ErrUserNotExist{0, name}
  314. } else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) {
  315. return nil, ErrUserNotExist{0, name}
  316. }
  317. }
  318. var auth smtp.Auth
  319. if cfg.Auth == SMTP_PLAIN {
  320. auth = smtp.PlainAuth("", name, passwd, cfg.Host)
  321. } else if cfg.Auth == SMTP_LOGIN {
  322. auth = LoginAuth(name, passwd)
  323. } else {
  324. return nil, errors.New("Unsupported SMTP auth type")
  325. }
  326. if err := SMTPAuth(auth, cfg); err != nil {
  327. // Check standard error format first,
  328. // then fallback to worse case.
  329. tperr, ok := err.(*textproto.Error)
  330. if (ok && tperr.Code == 535) ||
  331. strings.Contains(err.Error(), "Username and Password not accepted") {
  332. return nil, ErrUserNotExist{0, name}
  333. }
  334. return nil, err
  335. }
  336. if !autoRegister {
  337. return u, nil
  338. }
  339. var loginName = name
  340. idx := strings.Index(name, "@")
  341. if idx > -1 {
  342. loginName = name[:idx]
  343. }
  344. // fake a local user creation
  345. u = &User{
  346. LowerName: strings.ToLower(loginName),
  347. Name: strings.ToLower(loginName),
  348. LoginType: LOGIN_SMTP,
  349. LoginSource: sourceID,
  350. LoginName: name,
  351. IsActive: true,
  352. Passwd: passwd,
  353. Email: name,
  354. }
  355. err := CreateUser(u)
  356. return u, err
  357. }
  358. // __________ _____ _____
  359. // \______ \/ _ \ / \
  360. // | ___/ /_\ \ / \ / \
  361. // | | / | \/ Y \
  362. // |____| \____|__ /\____|__ /
  363. // \/ \/
  364. // Query if name/passwd can login against PAM
  365. // Create a local user if success
  366. // Return the same LoginUserPlain semantic
  367. func LoginUserPAMSource(u *User, name, passwd string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
  368. if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
  369. if strings.Contains(err.Error(), "Authentication failure") {
  370. return nil, ErrUserNotExist{0, name}
  371. }
  372. return nil, err
  373. }
  374. if !autoRegister {
  375. return u, nil
  376. }
  377. // fake a local user creation
  378. u = &User{
  379. LowerName: strings.ToLower(name),
  380. Name: name,
  381. LoginType: LOGIN_PAM,
  382. LoginSource: sourceID,
  383. LoginName: name,
  384. IsActive: true,
  385. Passwd: passwd,
  386. Email: name,
  387. }
  388. return u, CreateUser(u)
  389. }
  390. func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
  391. if !source.IsActived {
  392. return nil, ErrLoginSourceNotActived
  393. }
  394. switch source.Type {
  395. case LOGIN_LDAP, LOGIN_DLDAP:
  396. return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
  397. case LOGIN_SMTP:
  398. return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
  399. case LOGIN_PAM:
  400. return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
  401. }
  402. return nil, ErrUnsupportedLoginType
  403. }
  404. // UserSignIn validates user name and password.
  405. func UserSignIn(uname, passwd string) (*User, error) {
  406. var u *User
  407. if strings.Contains(uname, "@") {
  408. u = &User{Email: strings.ToLower(uname)}
  409. } else {
  410. u = &User{LowerName: strings.ToLower(uname)}
  411. }
  412. userExists, err := x.Get(u)
  413. if err != nil {
  414. return nil, err
  415. }
  416. if userExists {
  417. switch u.LoginType {
  418. case LOGIN_NOTYPE, LOGIN_PLAIN:
  419. if u.ValidatePassword(passwd) {
  420. return u, nil
  421. }
  422. return nil, ErrUserNotExist{u.Id, u.Name}
  423. default:
  424. var source LoginSource
  425. hasSource, err := x.Id(u.LoginSource).Get(&source)
  426. if err != nil {
  427. return nil, err
  428. } else if !hasSource {
  429. return nil, ErrLoginSourceNotExist
  430. }
  431. return ExternalUserLogin(u, u.LoginName, passwd, &source, false)
  432. }
  433. }
  434. var sources []LoginSource
  435. if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
  436. return nil, err
  437. }
  438. for _, source := range sources {
  439. u, err := ExternalUserLogin(nil, uname, passwd, &source, true)
  440. if err == nil {
  441. return u, nil
  442. }
  443. log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
  444. }
  445. return nil, ErrUserNotExist{u.Id, u.Name}
  446. }