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.

513 lines
12 KiB

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