refresh_token.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. package toil
  2. import (
  3. "context"
  4. "encoding/json"
  5. "net/http"
  6. "time"
  7. )
  8. func (f *Client) authTokenInvalid() bool {
  9. if f.authToken == "" ||
  10. f.refreshCount > MaxAuthTokenRefreshes ||
  11. time.Until(f.tokenExpiry) <= time.Duration(0) {
  12. return true
  13. }
  14. return false
  15. }
  16. func (f *Client) resetRefreshToken() {
  17. f.refreshToken = ""
  18. f.refreshCount = 0
  19. f.tokenExpiry = time.Time{}
  20. return
  21. }
  22. func (f *Client) processAuthResponse(
  23. resp *http.Response,
  24. ) error {
  25. /* TODO: Is this actually a failure? For the life of me, I
  26. can't recall why this was necessary. A successful
  27. authentication with status 204 is presented here:
  28. https://www.cisco.com/c/en/us/support/docs/security/firepower-management-center/215918-how-to-generate-authentication-token-for.html
  29. */
  30. if resp.StatusCode != FMCStatusCodeNoContent {
  31. return newFMCError(resp, f.host, "authentication failure")
  32. }
  33. f.tokenExpiry = NewAuthTokenExpiry(time.Now())
  34. f.authToken = resp.Header.Get("X-Auth-Access-Token")
  35. f.refreshToken = resp.Header.Get("X-Auth-Refresh-Token")
  36. return nil
  37. }
  38. // See https://www.cisco.com/c/en/us/td/docs/security/firepower/620/api/REST/Firepower_Management_Center_REST_API_Quick_Start_Guide_620/Connecting_with_a_Client.html
  39. func (f *Client) Authenticate() error {
  40. f.authMutex.Lock()
  41. defer f.authMutex.Unlock()
  42. if f.authTokenInvalid() ||
  43. maxAuthTokenRefreshesHasBeenReached(f.refreshCount) ||
  44. authTokenRefreshDeadlineHasPassed(f.tokenExpiry) {
  45. if f.watchdogCancel != nil {
  46. f.watchdogCancel()
  47. }
  48. f.resetRefreshToken()
  49. req, err := makeAuthAccessRequest(
  50. f.ctx,
  51. f.host,
  52. f.user,
  53. f.pass,
  54. )
  55. if err != nil {
  56. return err
  57. }
  58. resp, err := f.client.Do(req)
  59. if err != nil {
  60. return err
  61. }
  62. var domains []nameToUUID
  63. json.Unmarshal(
  64. []byte(resp.Header.Get("domains")),
  65. &domains,
  66. )
  67. for i := 0; i < len(domains); i++ {
  68. f.domains[domains[i].Name] = domains[i].UUID
  69. }
  70. if err = f.processAuthResponse(resp); err != nil {
  71. return err
  72. }
  73. info("authenticated to FMC")
  74. ctx, cancel := context.WithCancel(f.ctx)
  75. f.watchdogCancel = cancel
  76. f.startTokenWatchdog(ctx)
  77. } else {
  78. if !withinAuthTokenRefreshWindow(f.tokenExpiry) {
  79. return nil
  80. }
  81. req, err := makeAuthRefreshRequest(
  82. f.ctx,
  83. f.host,
  84. f.authToken,
  85. f.refreshToken,
  86. )
  87. if err != nil {
  88. return err
  89. }
  90. resp, err := f.client.Do(req)
  91. if err != nil {
  92. return err
  93. }
  94. if err = f.processAuthResponse(resp); err != nil {
  95. return err
  96. }
  97. info("refreshed FMC auth token")
  98. f.refreshCount++
  99. }
  100. return nil
  101. }
  102. func (f *Client) startTokenWatchdog(ctx context.Context) {
  103. info("starting auth token watchdog")
  104. go func() {
  105. defer info("stopping auth token watchdog")
  106. for {
  107. select {
  108. case <-ctx.Done():
  109. return
  110. case <-time.After(100 * time.Millisecond):
  111. if f.authTokenInvalid() {
  112. info("auth token has invalidated")
  113. return
  114. }
  115. if err := f.Authenticate(); err != nil {
  116. warn(err.Error())
  117. return
  118. }
  119. }
  120. }
  121. }()
  122. return
  123. }