login_source_files.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Copyright 2020 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 db
  5. import (
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/pkg/errors"
  13. "gopkg.in/ini.v1"
  14. "gogs.io/gogs/internal/auth"
  15. "gogs.io/gogs/internal/auth/github"
  16. "gogs.io/gogs/internal/auth/ldap"
  17. "gogs.io/gogs/internal/auth/pam"
  18. "gogs.io/gogs/internal/auth/smtp"
  19. "gogs.io/gogs/internal/errutil"
  20. "gogs.io/gogs/internal/osutil"
  21. )
  22. // loginSourceFilesStore is the in-memory interface for login source files stored on file system.
  23. //
  24. // NOTE: All methods are sorted in alphabetical order.
  25. type loginSourceFilesStore interface {
  26. // GetByID returns a clone of login source by given ID.
  27. GetByID(id int64) (*LoginSource, error)
  28. // Len returns number of login sources.
  29. Len() int
  30. // List returns a list of login sources filtered by options.
  31. List(opts ListLoginSourceOptions) []*LoginSource
  32. // Update updates in-memory copy of the authentication source.
  33. Update(source *LoginSource)
  34. }
  35. var _ loginSourceFilesStore = (*loginSourceFiles)(nil)
  36. // loginSourceFiles contains authentication sources configured and loaded from local files.
  37. type loginSourceFiles struct {
  38. sync.RWMutex
  39. sources []*LoginSource
  40. clock func() time.Time
  41. }
  42. var _ errutil.NotFound = (*ErrLoginSourceNotExist)(nil)
  43. type ErrLoginSourceNotExist struct {
  44. args errutil.Args
  45. }
  46. func IsErrLoginSourceNotExist(err error) bool {
  47. _, ok := err.(ErrLoginSourceNotExist)
  48. return ok
  49. }
  50. func (err ErrLoginSourceNotExist) Error() string {
  51. return fmt.Sprintf("login source does not exist: %v", err.args)
  52. }
  53. func (ErrLoginSourceNotExist) NotFound() bool {
  54. return true
  55. }
  56. func (s *loginSourceFiles) GetByID(id int64) (*LoginSource, error) {
  57. s.RLock()
  58. defer s.RUnlock()
  59. for _, source := range s.sources {
  60. if source.ID == id {
  61. return source, nil
  62. }
  63. }
  64. return nil, ErrLoginSourceNotExist{args: errutil.Args{"id": id}}
  65. }
  66. func (s *loginSourceFiles) Len() int {
  67. s.RLock()
  68. defer s.RUnlock()
  69. return len(s.sources)
  70. }
  71. func (s *loginSourceFiles) List(opts ListLoginSourceOptions) []*LoginSource {
  72. s.RLock()
  73. defer s.RUnlock()
  74. list := make([]*LoginSource, 0, s.Len())
  75. for _, source := range s.sources {
  76. if opts.OnlyActivated && !source.IsActived {
  77. continue
  78. }
  79. list = append(list, source)
  80. }
  81. return list
  82. }
  83. func (s *loginSourceFiles) Update(source *LoginSource) {
  84. s.Lock()
  85. defer s.Unlock()
  86. source.Updated = s.clock()
  87. for _, old := range s.sources {
  88. if old.ID == source.ID {
  89. *old = *source
  90. } else if source.IsDefault {
  91. old.IsDefault = false
  92. }
  93. }
  94. }
  95. // loadLoginSourceFiles loads login sources from file system.
  96. func loadLoginSourceFiles(authdPath string, clock func() time.Time) (loginSourceFilesStore, error) {
  97. if !osutil.IsDir(authdPath) {
  98. return &loginSourceFiles{clock: clock}, nil
  99. }
  100. store := &loginSourceFiles{clock: clock}
  101. return store, filepath.Walk(authdPath, func(path string, info os.FileInfo, err error) error {
  102. if err != nil {
  103. return err
  104. }
  105. if path == authdPath || !strings.HasSuffix(path, ".conf") {
  106. return nil
  107. } else if info.IsDir() {
  108. return filepath.SkipDir
  109. }
  110. authSource, err := ini.Load(path)
  111. if err != nil {
  112. return errors.Wrap(err, "load file")
  113. }
  114. authSource.NameMapper = ini.TitleUnderscore
  115. // Set general attributes
  116. s := authSource.Section("")
  117. loginSource := &LoginSource{
  118. ID: s.Key("id").MustInt64(),
  119. Name: s.Key("name").String(),
  120. IsActived: s.Key("is_activated").MustBool(),
  121. IsDefault: s.Key("is_default").MustBool(),
  122. File: &loginSourceFile{
  123. path: path,
  124. file: authSource,
  125. },
  126. }
  127. fi, err := os.Stat(path)
  128. if err != nil {
  129. return errors.Wrap(err, "stat file")
  130. }
  131. loginSource.Updated = fi.ModTime()
  132. // Parse authentication source file
  133. authType := s.Key("type").String()
  134. cfgSection := authSource.Section("config")
  135. switch authType {
  136. case "ldap_bind_dn":
  137. var cfg ldap.Config
  138. err = cfgSection.MapTo(&cfg)
  139. if err != nil {
  140. return errors.Wrap(err, `map "config" section`)
  141. }
  142. loginSource.Type = auth.LDAP
  143. loginSource.Provider = ldap.NewProvider(false, &cfg)
  144. case "ldap_simple_auth":
  145. var cfg ldap.Config
  146. err = cfgSection.MapTo(&cfg)
  147. if err != nil {
  148. return errors.Wrap(err, `map "config" section`)
  149. }
  150. loginSource.Type = auth.DLDAP
  151. loginSource.Provider = ldap.NewProvider(true, &cfg)
  152. case "smtp":
  153. var cfg smtp.Config
  154. err = cfgSection.MapTo(&cfg)
  155. if err != nil {
  156. return errors.Wrap(err, `map "config" section`)
  157. }
  158. loginSource.Type = auth.SMTP
  159. loginSource.Provider = smtp.NewProvider(&cfg)
  160. case "pam":
  161. var cfg pam.Config
  162. err = cfgSection.MapTo(&cfg)
  163. if err != nil {
  164. return errors.Wrap(err, `map "config" section`)
  165. }
  166. loginSource.Type = auth.PAM
  167. loginSource.Provider = pam.NewProvider(&cfg)
  168. case "github":
  169. var cfg github.Config
  170. err = cfgSection.MapTo(&cfg)
  171. if err != nil {
  172. return errors.Wrap(err, `map "config" section`)
  173. }
  174. loginSource.Type = auth.GitHub
  175. loginSource.Provider = github.NewProvider(&cfg)
  176. default:
  177. return fmt.Errorf("unknown type %q", authType)
  178. }
  179. store.sources = append(store.sources, loginSource)
  180. return nil
  181. })
  182. }
  183. // loginSourceFileStore is the persistent interface for a login source file.
  184. type loginSourceFileStore interface {
  185. // SetGeneral sets new value to the given key in the general (default) section.
  186. SetGeneral(name, value string)
  187. // SetConfig sets new values to the "config" section.
  188. SetConfig(cfg interface{}) error
  189. // Save persists values to file system.
  190. Save() error
  191. }
  192. var _ loginSourceFileStore = (*loginSourceFile)(nil)
  193. type loginSourceFile struct {
  194. path string
  195. file *ini.File
  196. }
  197. func (f *loginSourceFile) SetGeneral(name, value string) {
  198. f.file.Section("").Key(name).SetValue(value)
  199. }
  200. func (f *loginSourceFile) SetConfig(cfg interface{}) error {
  201. return f.file.Section("config").ReflectFrom(cfg)
  202. }
  203. func (f *loginSourceFile) Save() error {
  204. return f.file.SaveTo(f.path)
  205. }