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.

oauth2.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "context"
  6. "crypto/sha256"
  7. "encoding/base32"
  8. "encoding/base64"
  9. "fmt"
  10. "net"
  11. "net/url"
  12. "strings"
  13. "code.gitea.io/gitea/models/db"
  14. "code.gitea.io/gitea/modules/timeutil"
  15. "code.gitea.io/gitea/modules/util"
  16. uuid "github.com/google/uuid"
  17. "golang.org/x/crypto/bcrypt"
  18. "xorm.io/builder"
  19. "xorm.io/xorm"
  20. )
  21. // OAuth2Application represents an OAuth2 client (RFC 6749)
  22. type OAuth2Application struct {
  23. ID int64 `xorm:"pk autoincr"`
  24. UID int64 `xorm:"INDEX"`
  25. Name string
  26. ClientID string `xorm:"unique"`
  27. ClientSecret string
  28. // OAuth defines both Confidential and Public client types
  29. // https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
  30. // "Authorization servers MUST record the client type in the client registration details"
  31. // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
  32. ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"`
  33. RedirectURIs []string `xorm:"redirect_uris JSON TEXT"`
  34. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  35. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  36. }
  37. func init() {
  38. db.RegisterModel(new(OAuth2Application))
  39. db.RegisterModel(new(OAuth2AuthorizationCode))
  40. db.RegisterModel(new(OAuth2Grant))
  41. }
  42. // TableName sets the table name to `oauth2_application`
  43. func (app *OAuth2Application) TableName() string {
  44. return "oauth2_application"
  45. }
  46. // PrimaryRedirectURI returns the first redirect uri or an empty string if empty
  47. func (app *OAuth2Application) PrimaryRedirectURI() string {
  48. if len(app.RedirectURIs) == 0 {
  49. return ""
  50. }
  51. return app.RedirectURIs[0]
  52. }
  53. // ContainsRedirectURI checks if redirectURI is allowed for app
  54. func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
  55. if !app.ConfidentialClient {
  56. uri, err := url.Parse(redirectURI)
  57. // ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
  58. if err == nil && uri.Scheme == "http" && uri.Port() != "" {
  59. ip := net.ParseIP(uri.Hostname())
  60. if ip != nil && ip.IsLoopback() {
  61. // strip port
  62. uri.Host = uri.Hostname()
  63. if util.IsStringInSlice(uri.String(), app.RedirectURIs, true) {
  64. return true
  65. }
  66. }
  67. }
  68. }
  69. return util.IsStringInSlice(redirectURI, app.RedirectURIs, true)
  70. }
  71. // Base32 characters, but lowercased.
  72. const lowerBase32Chars = "abcdefghijklmnopqrstuvwxyz234567"
  73. // base32 encoder that uses lowered characters without padding.
  74. var base32Lower = base32.NewEncoding(lowerBase32Chars).WithPadding(base32.NoPadding)
  75. // GenerateClientSecret will generate the client secret and returns the plaintext and saves the hash at the database
  76. func (app *OAuth2Application) GenerateClientSecret() (string, error) {
  77. rBytes, err := util.CryptoRandomBytes(32)
  78. if err != nil {
  79. return "", err
  80. }
  81. // Add a prefix to the base32, this is in order to make it easier
  82. // for code scanners to grab sensitive tokens.
  83. clientSecret := "gto_" + base32Lower.EncodeToString(rBytes)
  84. hashedSecret, err := bcrypt.GenerateFromPassword([]byte(clientSecret), bcrypt.DefaultCost)
  85. if err != nil {
  86. return "", err
  87. }
  88. app.ClientSecret = string(hashedSecret)
  89. if _, err := db.GetEngine(db.DefaultContext).ID(app.ID).Cols("client_secret").Update(app); err != nil {
  90. return "", err
  91. }
  92. return clientSecret, nil
  93. }
  94. // ValidateClientSecret validates the given secret by the hash saved in database
  95. func (app *OAuth2Application) ValidateClientSecret(secret []byte) bool {
  96. return bcrypt.CompareHashAndPassword([]byte(app.ClientSecret), secret) == nil
  97. }
  98. // GetGrantByUserID returns a OAuth2Grant by its user and application ID
  99. func (app *OAuth2Application) GetGrantByUserID(ctx context.Context, userID int64) (grant *OAuth2Grant, err error) {
  100. grant = new(OAuth2Grant)
  101. if has, err := db.GetEngine(ctx).Where("user_id = ? AND application_id = ?", userID, app.ID).Get(grant); err != nil {
  102. return nil, err
  103. } else if !has {
  104. return nil, nil
  105. }
  106. return grant, nil
  107. }
  108. // CreateGrant generates a grant for an user
  109. func (app *OAuth2Application) CreateGrant(ctx context.Context, userID int64, scope string) (*OAuth2Grant, error) {
  110. grant := &OAuth2Grant{
  111. ApplicationID: app.ID,
  112. UserID: userID,
  113. Scope: scope,
  114. }
  115. err := db.Insert(ctx, grant)
  116. if err != nil {
  117. return nil, err
  118. }
  119. return grant, nil
  120. }
  121. // GetOAuth2ApplicationByClientID returns the oauth2 application with the given client_id. Returns an error if not found.
  122. func GetOAuth2ApplicationByClientID(ctx context.Context, clientID string) (app *OAuth2Application, err error) {
  123. app = new(OAuth2Application)
  124. has, err := db.GetEngine(ctx).Where("client_id = ?", clientID).Get(app)
  125. if !has {
  126. return nil, ErrOAuthClientIDInvalid{ClientID: clientID}
  127. }
  128. return app, err
  129. }
  130. // GetOAuth2ApplicationByID returns the oauth2 application with the given id. Returns an error if not found.
  131. func GetOAuth2ApplicationByID(ctx context.Context, id int64) (app *OAuth2Application, err error) {
  132. app = new(OAuth2Application)
  133. has, err := db.GetEngine(ctx).ID(id).Get(app)
  134. if err != nil {
  135. return nil, err
  136. }
  137. if !has {
  138. return nil, ErrOAuthApplicationNotFound{ID: id}
  139. }
  140. return app, nil
  141. }
  142. // GetOAuth2ApplicationsByUserID returns all oauth2 applications owned by the user
  143. func GetOAuth2ApplicationsByUserID(ctx context.Context, userID int64) (apps []*OAuth2Application, err error) {
  144. apps = make([]*OAuth2Application, 0)
  145. err = db.GetEngine(ctx).Where("uid = ?", userID).Find(&apps)
  146. return apps, err
  147. }
  148. // CreateOAuth2ApplicationOptions holds options to create an oauth2 application
  149. type CreateOAuth2ApplicationOptions struct {
  150. Name string
  151. UserID int64
  152. ConfidentialClient bool
  153. RedirectURIs []string
  154. }
  155. // CreateOAuth2Application inserts a new oauth2 application
  156. func CreateOAuth2Application(ctx context.Context, opts CreateOAuth2ApplicationOptions) (*OAuth2Application, error) {
  157. clientID := uuid.New().String()
  158. app := &OAuth2Application{
  159. UID: opts.UserID,
  160. Name: opts.Name,
  161. ClientID: clientID,
  162. RedirectURIs: opts.RedirectURIs,
  163. ConfidentialClient: opts.ConfidentialClient,
  164. }
  165. if err := db.Insert(ctx, app); err != nil {
  166. return nil, err
  167. }
  168. return app, nil
  169. }
  170. // UpdateOAuth2ApplicationOptions holds options to update an oauth2 application
  171. type UpdateOAuth2ApplicationOptions struct {
  172. ID int64
  173. Name string
  174. UserID int64
  175. ConfidentialClient bool
  176. RedirectURIs []string
  177. }
  178. // UpdateOAuth2Application updates an oauth2 application
  179. func UpdateOAuth2Application(opts UpdateOAuth2ApplicationOptions) (*OAuth2Application, error) {
  180. ctx, committer, err := db.TxContext(db.DefaultContext)
  181. if err != nil {
  182. return nil, err
  183. }
  184. defer committer.Close()
  185. app, err := GetOAuth2ApplicationByID(ctx, opts.ID)
  186. if err != nil {
  187. return nil, err
  188. }
  189. if app.UID != opts.UserID {
  190. return nil, fmt.Errorf("UID mismatch")
  191. }
  192. app.Name = opts.Name
  193. app.RedirectURIs = opts.RedirectURIs
  194. app.ConfidentialClient = opts.ConfidentialClient
  195. if err = updateOAuth2Application(ctx, app); err != nil {
  196. return nil, err
  197. }
  198. app.ClientSecret = ""
  199. return app, committer.Commit()
  200. }
  201. func updateOAuth2Application(ctx context.Context, app *OAuth2Application) error {
  202. if _, err := db.GetEngine(ctx).ID(app.ID).UseBool("confidential_client").Update(app); err != nil {
  203. return err
  204. }
  205. return nil
  206. }
  207. func deleteOAuth2Application(ctx context.Context, id, userid int64) error {
  208. sess := db.GetEngine(ctx)
  209. // the userid could be 0 if the app is instance-wide
  210. if deleted, err := sess.Where(builder.Eq{"id": id, "uid": userid}).Delete(&OAuth2Application{}); err != nil {
  211. return err
  212. } else if deleted == 0 {
  213. return ErrOAuthApplicationNotFound{ID: id}
  214. }
  215. codes := make([]*OAuth2AuthorizationCode, 0)
  216. // delete correlating auth codes
  217. if err := sess.Join("INNER", "oauth2_grant",
  218. "oauth2_authorization_code.grant_id = oauth2_grant.id AND oauth2_grant.application_id = ?", id).Find(&codes); err != nil {
  219. return err
  220. }
  221. codeIDs := make([]int64, 0, len(codes))
  222. for _, grant := range codes {
  223. codeIDs = append(codeIDs, grant.ID)
  224. }
  225. if _, err := sess.In("id", codeIDs).Delete(new(OAuth2AuthorizationCode)); err != nil {
  226. return err
  227. }
  228. if _, err := sess.Where("application_id = ?", id).Delete(new(OAuth2Grant)); err != nil {
  229. return err
  230. }
  231. return nil
  232. }
  233. // DeleteOAuth2Application deletes the application with the given id and the grants and auth codes related to it. It checks if the userid was the creator of the app.
  234. func DeleteOAuth2Application(id, userid int64) error {
  235. ctx, committer, err := db.TxContext(db.DefaultContext)
  236. if err != nil {
  237. return err
  238. }
  239. defer committer.Close()
  240. if err := deleteOAuth2Application(ctx, id, userid); err != nil {
  241. return err
  242. }
  243. return committer.Commit()
  244. }
  245. // ListOAuth2Applications returns a list of oauth2 applications belongs to given user.
  246. func ListOAuth2Applications(uid int64, listOptions db.ListOptions) ([]*OAuth2Application, int64, error) {
  247. sess := db.GetEngine(db.DefaultContext).
  248. Where("uid=?", uid).
  249. Desc("id")
  250. if listOptions.Page != 0 {
  251. sess = db.SetSessionPagination(sess, &listOptions)
  252. apps := make([]*OAuth2Application, 0, listOptions.PageSize)
  253. total, err := sess.FindAndCount(&apps)
  254. return apps, total, err
  255. }
  256. apps := make([]*OAuth2Application, 0, 5)
  257. total, err := sess.FindAndCount(&apps)
  258. return apps, total, err
  259. }
  260. //////////////////////////////////////////////////////
  261. // OAuth2AuthorizationCode is a code to obtain an access token in combination with the client secret once. It has a limited lifetime.
  262. type OAuth2AuthorizationCode struct {
  263. ID int64 `xorm:"pk autoincr"`
  264. Grant *OAuth2Grant `xorm:"-"`
  265. GrantID int64
  266. Code string `xorm:"INDEX unique"`
  267. CodeChallenge string
  268. CodeChallengeMethod string
  269. RedirectURI string
  270. ValidUntil timeutil.TimeStamp `xorm:"index"`
  271. }
  272. // TableName sets the table name to `oauth2_authorization_code`
  273. func (code *OAuth2AuthorizationCode) TableName() string {
  274. return "oauth2_authorization_code"
  275. }
  276. // GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty.
  277. func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (redirect *url.URL, err error) {
  278. if redirect, err = url.Parse(code.RedirectURI); err != nil {
  279. return
  280. }
  281. q := redirect.Query()
  282. if state != "" {
  283. q.Set("state", state)
  284. }
  285. q.Set("code", code.Code)
  286. redirect.RawQuery = q.Encode()
  287. return redirect, err
  288. }
  289. // Invalidate deletes the auth code from the database to invalidate this code
  290. func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error {
  291. _, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code)
  292. return err
  293. }
  294. // ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation.
  295. func (code *OAuth2AuthorizationCode) ValidateCodeChallenge(verifier string) bool {
  296. switch code.CodeChallengeMethod {
  297. case "S256":
  298. // base64url(SHA256(verifier)) see https://tools.ietf.org/html/rfc7636#section-4.6
  299. h := sha256.Sum256([]byte(verifier))
  300. hashedVerifier := base64.RawURLEncoding.EncodeToString(h[:])
  301. return hashedVerifier == code.CodeChallenge
  302. case "plain":
  303. return verifier == code.CodeChallenge
  304. case "":
  305. return true
  306. default:
  307. // unsupported method -> return false
  308. return false
  309. }
  310. }
  311. // GetOAuth2AuthorizationByCode returns an authorization by its code
  312. func GetOAuth2AuthorizationByCode(ctx context.Context, code string) (auth *OAuth2AuthorizationCode, err error) {
  313. auth = new(OAuth2AuthorizationCode)
  314. if has, err := db.GetEngine(ctx).Where("code = ?", code).Get(auth); err != nil {
  315. return nil, err
  316. } else if !has {
  317. return nil, nil
  318. }
  319. auth.Grant = new(OAuth2Grant)
  320. if has, err := db.GetEngine(ctx).ID(auth.GrantID).Get(auth.Grant); err != nil {
  321. return nil, err
  322. } else if !has {
  323. return nil, nil
  324. }
  325. return auth, nil
  326. }
  327. //////////////////////////////////////////////////////
  328. // OAuth2Grant represents the permission of an user for a specific application to access resources
  329. type OAuth2Grant struct {
  330. ID int64 `xorm:"pk autoincr"`
  331. UserID int64 `xorm:"INDEX unique(user_application)"`
  332. Application *OAuth2Application `xorm:"-"`
  333. ApplicationID int64 `xorm:"INDEX unique(user_application)"`
  334. Counter int64 `xorm:"NOT NULL DEFAULT 1"`
  335. Scope string `xorm:"TEXT"`
  336. Nonce string `xorm:"TEXT"`
  337. CreatedUnix timeutil.TimeStamp `xorm:"created"`
  338. UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
  339. }
  340. // TableName sets the table name to `oauth2_grant`
  341. func (grant *OAuth2Grant) TableName() string {
  342. return "oauth2_grant"
  343. }
  344. // GenerateNewAuthorizationCode generates a new authorization code for a grant and saves it to the database
  345. func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redirectURI, codeChallenge, codeChallengeMethod string) (code *OAuth2AuthorizationCode, err error) {
  346. rBytes, err := util.CryptoRandomBytes(32)
  347. if err != nil {
  348. return &OAuth2AuthorizationCode{}, err
  349. }
  350. // Add a prefix to the base32, this is in order to make it easier
  351. // for code scanners to grab sensitive tokens.
  352. codeSecret := "gta_" + base32Lower.EncodeToString(rBytes)
  353. code = &OAuth2AuthorizationCode{
  354. Grant: grant,
  355. GrantID: grant.ID,
  356. RedirectURI: redirectURI,
  357. Code: codeSecret,
  358. CodeChallenge: codeChallenge,
  359. CodeChallengeMethod: codeChallengeMethod,
  360. }
  361. if err := db.Insert(ctx, code); err != nil {
  362. return nil, err
  363. }
  364. return code, nil
  365. }
  366. // IncreaseCounter increases the counter and updates the grant
  367. func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error {
  368. _, err := db.GetEngine(ctx).ID(grant.ID).Incr("counter").Update(new(OAuth2Grant))
  369. if err != nil {
  370. return err
  371. }
  372. updatedGrant, err := GetOAuth2GrantByID(ctx, grant.ID)
  373. if err != nil {
  374. return err
  375. }
  376. grant.Counter = updatedGrant.Counter
  377. return nil
  378. }
  379. // ScopeContains returns true if the grant scope contains the specified scope
  380. func (grant *OAuth2Grant) ScopeContains(scope string) bool {
  381. for _, currentScope := range strings.Split(grant.Scope, " ") {
  382. if scope == currentScope {
  383. return true
  384. }
  385. }
  386. return false
  387. }
  388. // SetNonce updates the current nonce value of a grant
  389. func (grant *OAuth2Grant) SetNonce(ctx context.Context, nonce string) error {
  390. grant.Nonce = nonce
  391. _, err := db.GetEngine(ctx).ID(grant.ID).Cols("nonce").Update(grant)
  392. if err != nil {
  393. return err
  394. }
  395. return nil
  396. }
  397. // GetOAuth2GrantByID returns the grant with the given ID
  398. func GetOAuth2GrantByID(ctx context.Context, id int64) (grant *OAuth2Grant, err error) {
  399. grant = new(OAuth2Grant)
  400. if has, err := db.GetEngine(ctx).ID(id).Get(grant); err != nil {
  401. return nil, err
  402. } else if !has {
  403. return nil, nil
  404. }
  405. return grant, err
  406. }
  407. // GetOAuth2GrantsByUserID lists all grants of a certain user
  408. func GetOAuth2GrantsByUserID(ctx context.Context, uid int64) ([]*OAuth2Grant, error) {
  409. type joinedOAuth2Grant struct {
  410. Grant *OAuth2Grant `xorm:"extends"`
  411. Application *OAuth2Application `xorm:"extends"`
  412. }
  413. var results *xorm.Rows
  414. var err error
  415. if results, err = db.GetEngine(ctx).
  416. Table("oauth2_grant").
  417. Where("user_id = ?", uid).
  418. Join("INNER", "oauth2_application", "application_id = oauth2_application.id").
  419. Rows(new(joinedOAuth2Grant)); err != nil {
  420. return nil, err
  421. }
  422. defer results.Close()
  423. grants := make([]*OAuth2Grant, 0)
  424. for results.Next() {
  425. joinedGrant := new(joinedOAuth2Grant)
  426. if err := results.Scan(joinedGrant); err != nil {
  427. return nil, err
  428. }
  429. joinedGrant.Grant.Application = joinedGrant.Application
  430. grants = append(grants, joinedGrant.Grant)
  431. }
  432. return grants, nil
  433. }
  434. // RevokeOAuth2Grant deletes the grant with grantID and userID
  435. func RevokeOAuth2Grant(ctx context.Context, grantID, userID int64) error {
  436. _, err := db.GetEngine(ctx).Where(builder.Eq{"id": grantID, "user_id": userID}).Delete(&OAuth2Grant{})
  437. return err
  438. }
  439. // ErrOAuthClientIDInvalid will be thrown if client id cannot be found
  440. type ErrOAuthClientIDInvalid struct {
  441. ClientID string
  442. }
  443. // IsErrOauthClientIDInvalid checks if an error is a ErrOAuthClientIDInvalid.
  444. func IsErrOauthClientIDInvalid(err error) bool {
  445. _, ok := err.(ErrOAuthClientIDInvalid)
  446. return ok
  447. }
  448. // Error returns the error message
  449. func (err ErrOAuthClientIDInvalid) Error() string {
  450. return fmt.Sprintf("Client ID invalid [Client ID: %s]", err.ClientID)
  451. }
  452. // Unwrap unwraps this as a ErrNotExist err
  453. func (err ErrOAuthClientIDInvalid) Unwrap() error {
  454. return util.ErrNotExist
  455. }
  456. // ErrOAuthApplicationNotFound will be thrown if id cannot be found
  457. type ErrOAuthApplicationNotFound struct {
  458. ID int64
  459. }
  460. // IsErrOAuthApplicationNotFound checks if an error is a ErrReviewNotExist.
  461. func IsErrOAuthApplicationNotFound(err error) bool {
  462. _, ok := err.(ErrOAuthApplicationNotFound)
  463. return ok
  464. }
  465. // Error returns the error message
  466. func (err ErrOAuthApplicationNotFound) Error() string {
  467. return fmt.Sprintf("OAuth application not found [ID: %d]", err.ID)
  468. }
  469. // Unwrap unwraps this as a ErrNotExist err
  470. func (err ErrOAuthApplicationNotFound) Unwrap() error {
  471. return util.ErrNotExist
  472. }
  473. // GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources
  474. func GetActiveOAuth2ProviderSources() ([]*Source, error) {
  475. sources := make([]*Source, 0, 1)
  476. if err := db.GetEngine(db.DefaultContext).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil {
  477. return nil, err
  478. }
  479. return sources, nil
  480. }
  481. // GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
  482. func GetActiveOAuth2SourceByName(name string) (*Source, error) {
  483. authSource := new(Source)
  484. has, err := db.GetEngine(db.DefaultContext).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
  485. if err != nil {
  486. return nil, err
  487. }
  488. if !has {
  489. return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
  490. }
  491. return authSource, nil
  492. }
  493. func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error {
  494. deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID})
  495. if _, err := db.GetEngine(ctx).In("grant_id", deleteCond).
  496. Delete(&OAuth2AuthorizationCode{}); err != nil {
  497. return err
  498. }
  499. if err := db.DeleteBeans(ctx,
  500. &OAuth2Application{UID: userID},
  501. &OAuth2Grant{UserID: userID},
  502. ); err != nil {
  503. return fmt.Errorf("DeleteBeans: %w", err)
  504. }
  505. return nil
  506. }