apikey.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package dataprovider
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "strings"
  19. "time"
  20. "github.com/alexedwards/argon2id"
  21. "golang.org/x/crypto/bcrypt"
  22. "github.com/drakkan/sftpgo/v2/internal/logger"
  23. "github.com/drakkan/sftpgo/v2/internal/util"
  24. )
  25. // APIKeyScope defines the supported API key scopes
  26. type APIKeyScope int
  27. // Supported API key scopes
  28. const (
  29. // the API key will be used for an admin
  30. APIKeyScopeAdmin APIKeyScope = iota + 1
  31. // the API key will be used for a user
  32. APIKeyScopeUser
  33. )
  34. // APIKey defines a SFTPGo API key.
  35. // API keys can be used as authentication alternative to short lived tokens
  36. // for REST API
  37. type APIKey struct {
  38. // Database unique identifier
  39. ID int64 `json:"-"`
  40. // Unique key identifier, used for key lookups.
  41. // The generated key is in the format `KeyID.hash(Key)` so we can split
  42. // and lookup by KeyID and then verify if the key matches the recorded hash
  43. KeyID string `json:"id"`
  44. // User friendly key name
  45. Name string `json:"name"`
  46. // we store the hash of the key, this is just like a password
  47. Key string `json:"key,omitempty"`
  48. Scope APIKeyScope `json:"scope"`
  49. CreatedAt int64 `json:"created_at"`
  50. UpdatedAt int64 `json:"updated_at"`
  51. // 0 means never used
  52. LastUseAt int64 `json:"last_use_at,omitempty"`
  53. // 0 means never expire
  54. ExpiresAt int64 `json:"expires_at,omitempty"`
  55. Description string `json:"description,omitempty"`
  56. // Username associated with this API key.
  57. // If empty and the scope is APIKeyScopeUser the key is valid for any user
  58. User string `json:"user,omitempty"`
  59. // Admin username associated with this API key.
  60. // If empty and the scope is APIKeyScopeAdmin the key is valid for any admin
  61. Admin string `json:"admin,omitempty"`
  62. // these fields are for internal use
  63. userID int64
  64. adminID int64
  65. plainKey string
  66. }
  67. func (k *APIKey) getACopy() APIKey {
  68. return APIKey{
  69. ID: k.ID,
  70. KeyID: k.KeyID,
  71. Name: k.Name,
  72. Key: k.Key,
  73. Scope: k.Scope,
  74. CreatedAt: k.CreatedAt,
  75. UpdatedAt: k.UpdatedAt,
  76. LastUseAt: k.LastUseAt,
  77. ExpiresAt: k.ExpiresAt,
  78. Description: k.Description,
  79. User: k.User,
  80. Admin: k.Admin,
  81. userID: k.userID,
  82. adminID: k.adminID,
  83. }
  84. }
  85. // RenderAsJSON implements the renderer interface used within plugins
  86. func (k *APIKey) RenderAsJSON(reload bool) ([]byte, error) {
  87. if reload {
  88. apiKey, err := provider.apiKeyExists(k.KeyID)
  89. if err != nil {
  90. providerLog(logger.LevelError, "unable to reload api key before rendering as json: %v", err)
  91. return nil, err
  92. }
  93. apiKey.HideConfidentialData()
  94. return json.Marshal(apiKey)
  95. }
  96. k.HideConfidentialData()
  97. return json.Marshal(k)
  98. }
  99. // HideConfidentialData hides API key confidential data
  100. func (k *APIKey) HideConfidentialData() {
  101. k.Key = ""
  102. }
  103. func (k *APIKey) hashKey() error {
  104. if k.Key != "" && !util.IsStringPrefixInSlice(k.Key, internalHashPwdPrefixes) {
  105. if config.PasswordHashing.Algo == HashingAlgoBcrypt {
  106. hashed, err := bcrypt.GenerateFromPassword([]byte(k.Key), config.PasswordHashing.BcryptOptions.Cost)
  107. if err != nil {
  108. return err
  109. }
  110. k.Key = string(hashed)
  111. } else {
  112. hashed, err := argon2id.CreateHash(k.Key, argon2Params)
  113. if err != nil {
  114. return err
  115. }
  116. k.Key = hashed
  117. }
  118. }
  119. return nil
  120. }
  121. func (k *APIKey) generateKey() {
  122. if k.KeyID != "" || k.Key != "" {
  123. return
  124. }
  125. k.KeyID = util.GenerateUniqueID()
  126. k.Key = util.GenerateUniqueID()
  127. k.plainKey = k.Key
  128. }
  129. // DisplayKey returns the key to show to the user
  130. func (k *APIKey) DisplayKey() string {
  131. return fmt.Sprintf("%v.%v", k.KeyID, k.plainKey)
  132. }
  133. func (k *APIKey) validate() error {
  134. if k.Name == "" {
  135. return util.NewValidationError("name is mandatory")
  136. }
  137. if k.Scope != APIKeyScopeAdmin && k.Scope != APIKeyScopeUser {
  138. return util.NewValidationError(fmt.Sprintf("invalid scope: %v", k.Scope))
  139. }
  140. k.generateKey()
  141. if err := k.hashKey(); err != nil {
  142. return err
  143. }
  144. if k.User != "" && k.Admin != "" {
  145. return util.NewValidationError("an API key can be related to a user or an admin, not both")
  146. }
  147. if k.Scope == APIKeyScopeAdmin {
  148. k.User = ""
  149. }
  150. if k.Scope == APIKeyScopeUser {
  151. k.Admin = ""
  152. }
  153. if k.User != "" {
  154. _, err := provider.userExists(k.User, "")
  155. if err != nil {
  156. return util.NewValidationError(fmt.Sprintf("unable to check API key user %v: %v", k.User, err))
  157. }
  158. }
  159. if k.Admin != "" {
  160. _, err := provider.adminExists(k.Admin)
  161. if err != nil {
  162. return util.NewValidationError(fmt.Sprintf("unable to check API key admin %v: %v", k.Admin, err))
  163. }
  164. }
  165. return nil
  166. }
  167. // Authenticate tries to authenticate the provided plain key
  168. func (k *APIKey) Authenticate(plainKey string) error {
  169. if k.ExpiresAt > 0 && k.ExpiresAt < util.GetTimeAsMsSinceEpoch(time.Now()) {
  170. return fmt.Errorf("API key %q is expired, expiration timestamp: %v current timestamp: %v", k.KeyID,
  171. k.ExpiresAt, util.GetTimeAsMsSinceEpoch(time.Now()))
  172. }
  173. if config.PasswordCaching {
  174. found, match := cachedAPIKeys.Check(k.KeyID, plainKey, k.Key)
  175. if found {
  176. if !match {
  177. return ErrInvalidCredentials
  178. }
  179. return nil
  180. }
  181. }
  182. if strings.HasPrefix(k.Key, bcryptPwdPrefix) {
  183. if err := bcrypt.CompareHashAndPassword([]byte(k.Key), []byte(plainKey)); err != nil {
  184. return ErrInvalidCredentials
  185. }
  186. } else if strings.HasPrefix(k.Key, argonPwdPrefix) {
  187. match, err := argon2id.ComparePasswordAndHash(plainKey, k.Key)
  188. if err != nil || !match {
  189. return ErrInvalidCredentials
  190. }
  191. }
  192. cachedAPIKeys.Add(k.KeyID, plainKey, k.Key)
  193. return nil
  194. }