config.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package oauth
  2. import (
  3. "context"
  4. "crypto/rand"
  5. "encoding/base64"
  6. "time"
  7. "github.com/porter-dev/porter/internal/models/integrations"
  8. "github.com/porter-dev/porter/internal/repository"
  9. "golang.org/x/oauth2"
  10. )
  11. type Config struct {
  12. ClientID string
  13. ClientSecret string
  14. Scopes []string
  15. BaseURL string
  16. }
  17. // GithubAppConf is standard oauth2 config but it need to keeps track of the app name and webhook secret
  18. type GithubAppConf struct {
  19. AppName string
  20. WebhookSecret string
  21. SecretPath string
  22. AppID int64
  23. oauth2.Config
  24. }
  25. const (
  26. GithubAuthURL string = "https://github.com/login/oauth/authorize"
  27. GithubTokenURL string = "https://github.com/login/oauth/access_token"
  28. DOAuthURL string = "https://cloud.digitalocean.com/v1/oauth/authorize"
  29. DOTokenURL string = "https://cloud.digitalocean.com/v1/oauth/token"
  30. GoogleAuthURL string = "https://accounts.google.com/o/oauth2/v2/auth"
  31. GoogleTokenURL string = "https://oauth2.googleapis.com/token"
  32. SlackAuthURL string = "https://slack.com/oauth/v2/authorize"
  33. SlackTokenURL string = "https://slack.com/api/oauth.v2.access"
  34. )
  35. func NewGithubClient(cfg *Config) *oauth2.Config {
  36. return &oauth2.Config{
  37. ClientID: cfg.ClientID,
  38. ClientSecret: cfg.ClientSecret,
  39. Endpoint: oauth2.Endpoint{
  40. AuthURL: GithubAuthURL,
  41. TokenURL: GithubTokenURL,
  42. },
  43. RedirectURL: cfg.BaseURL + "/api/oauth/github/callback",
  44. Scopes: cfg.Scopes,
  45. }
  46. }
  47. func NewGithubAppClient(cfg *Config, name string, secret string, secretPath string, appID int64) *GithubAppConf {
  48. return &GithubAppConf{
  49. AppName: name,
  50. WebhookSecret: secret,
  51. SecretPath: secretPath,
  52. AppID: appID,
  53. Config: oauth2.Config{
  54. ClientID: cfg.ClientID,
  55. ClientSecret: cfg.ClientSecret,
  56. Endpoint: oauth2.Endpoint{
  57. AuthURL: GithubAuthURL,
  58. TokenURL: GithubTokenURL,
  59. },
  60. RedirectURL: cfg.BaseURL + "/api/oauth/github-app/callback",
  61. Scopes: cfg.Scopes,
  62. },
  63. }
  64. }
  65. func NewDigitalOceanClient(cfg *Config) *oauth2.Config {
  66. return &oauth2.Config{
  67. ClientID: cfg.ClientID,
  68. ClientSecret: cfg.ClientSecret,
  69. Endpoint: oauth2.Endpoint{
  70. AuthURL: DOAuthURL,
  71. TokenURL: DOTokenURL,
  72. },
  73. RedirectURL: cfg.BaseURL + "/api/oauth/digitalocean/callback",
  74. Scopes: cfg.Scopes,
  75. }
  76. }
  77. func NewGoogleClient(cfg *Config) *oauth2.Config {
  78. return &oauth2.Config{
  79. ClientID: cfg.ClientID,
  80. ClientSecret: cfg.ClientSecret,
  81. Endpoint: oauth2.Endpoint{
  82. AuthURL: GoogleAuthURL,
  83. TokenURL: GoogleTokenURL,
  84. },
  85. RedirectURL: cfg.BaseURL + "/api/oauth/google/callback",
  86. Scopes: cfg.Scopes,
  87. }
  88. }
  89. func NewSlackClient(cfg *Config) *oauth2.Config {
  90. return &oauth2.Config{
  91. ClientID: cfg.ClientID,
  92. ClientSecret: cfg.ClientSecret,
  93. Endpoint: oauth2.Endpoint{
  94. AuthURL: SlackAuthURL,
  95. TokenURL: SlackTokenURL,
  96. },
  97. RedirectURL: cfg.BaseURL + "/api/oauth/slack/callback",
  98. Scopes: cfg.Scopes,
  99. }
  100. }
  101. func CreateRandomState() string {
  102. b := make([]byte, 16)
  103. rand.Read(b)
  104. state := base64.URLEncoding.EncodeToString(b)
  105. return state
  106. }
  107. // MakeUpdateOAuthIntegrationTokenFunction creates a function to be passed to GetAccessToken that updates the OauthIntegration
  108. // if it needs to be updated
  109. func MakeUpdateOAuthIntegrationTokenFunction(
  110. o *integrations.OAuthIntegration,
  111. repo repository.Repository) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  112. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  113. o.AccessToken = accessToken
  114. o.RefreshToken = refreshToken
  115. o.Expiry = expiry
  116. _, err := repo.OAuthIntegration().UpdateOAuthIntegration(o)
  117. return err
  118. }
  119. }
  120. // MakeUpdateGithubAppOauthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GithubAppOauthIntegration
  121. // if it needs to be updated
  122. func MakeUpdateGithubAppOauthIntegrationFunction(
  123. o *integrations.GithubAppOAuthIntegration,
  124. repo repository.Repository) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  125. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  126. o.AccessToken = accessToken
  127. o.RefreshToken = refreshToken
  128. o.Expiry = expiry
  129. _, err := repo.GithubAppOAuthIntegration().UpdateGithubAppOauthIntegration(o)
  130. return err
  131. }
  132. }
  133. // GetAccessToken retrieves an access token for a given client. It updates the
  134. // access token in the DB if necessary
  135. func GetAccessToken(
  136. prevToken integrations.SharedOAuthModel,
  137. conf *oauth2.Config,
  138. updateToken func(accessToken []byte, refreshToken []byte, expiry time.Time) error,
  139. ) (string, *time.Time, error) {
  140. expiry := prevToken.Expiry
  141. if conf.Endpoint.AuthURL == DOAuthURL && expiry.IsZero() {
  142. // manually set the expiry so refresh token is used
  143. expiry = time.Now().Add(-1 * time.Minute)
  144. }
  145. tokSource := conf.TokenSource(context.TODO(), &oauth2.Token{
  146. AccessToken: string(prevToken.AccessToken),
  147. RefreshToken: string(prevToken.RefreshToken),
  148. TokenType: "Bearer",
  149. Expiry: expiry,
  150. })
  151. token, err := tokSource.Token()
  152. if err != nil {
  153. return "", nil, err
  154. }
  155. if token.AccessToken != string(prevToken.AccessToken) {
  156. err := updateToken([]byte(token.AccessToken), []byte(token.RefreshToken), token.Expiry)
  157. if err != nil {
  158. return "", nil, err
  159. }
  160. }
  161. return token.AccessToken, &token.Expiry, nil
  162. }