config.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. AuthStyle: oauth2.AuthStyleInParams,
  73. },
  74. RedirectURL: cfg.BaseURL + "/api/oauth/digitalocean/callback",
  75. Scopes: cfg.Scopes,
  76. }
  77. }
  78. func NewGoogleClient(cfg *Config) *oauth2.Config {
  79. return &oauth2.Config{
  80. ClientID: cfg.ClientID,
  81. ClientSecret: cfg.ClientSecret,
  82. Endpoint: oauth2.Endpoint{
  83. AuthURL: GoogleAuthURL,
  84. TokenURL: GoogleTokenURL,
  85. },
  86. RedirectURL: cfg.BaseURL + "/api/oauth/google/callback",
  87. Scopes: cfg.Scopes,
  88. }
  89. }
  90. func NewSlackClient(cfg *Config) *oauth2.Config {
  91. return &oauth2.Config{
  92. ClientID: cfg.ClientID,
  93. ClientSecret: cfg.ClientSecret,
  94. Endpoint: oauth2.Endpoint{
  95. AuthURL: SlackAuthURL,
  96. TokenURL: SlackTokenURL,
  97. },
  98. RedirectURL: cfg.BaseURL + "/api/oauth/slack/callback",
  99. Scopes: cfg.Scopes,
  100. }
  101. }
  102. func CreateRandomState() string {
  103. b := make([]byte, 16)
  104. rand.Read(b)
  105. state := base64.URLEncoding.EncodeToString(b)
  106. return state
  107. }
  108. // MakeUpdateOAuthIntegrationTokenFunction creates a function to be passed to GetAccessToken that updates the OauthIntegration
  109. // if it needs to be updated
  110. func MakeUpdateOAuthIntegrationTokenFunction(
  111. o *integrations.OAuthIntegration,
  112. repo repository.Repository) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  113. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  114. o.AccessToken = accessToken
  115. o.RefreshToken = refreshToken
  116. o.Expiry = expiry
  117. _, err := repo.OAuthIntegration().UpdateOAuthIntegration(o)
  118. return err
  119. }
  120. }
  121. // MakeUpdateGithubAppOauthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GithubAppOauthIntegration
  122. // if it needs to be updated
  123. func MakeUpdateGithubAppOauthIntegrationFunction(
  124. o *integrations.GithubAppOAuthIntegration,
  125. repo repository.Repository) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  126. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  127. o.AccessToken = accessToken
  128. o.RefreshToken = refreshToken
  129. o.Expiry = expiry
  130. _, err := repo.GithubAppOAuthIntegration().UpdateGithubAppOauthIntegration(o)
  131. return err
  132. }
  133. }
  134. // MakeUpdateGitlabAppOAuthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GitlabAppOAuthIntegration
  135. // if it needs to be updated
  136. func MakeUpdateGitlabAppOAuthIntegrationFunction(
  137. projectID uint,
  138. o *integrations.GitlabAppOAuthIntegration,
  139. repo repository.Repository,
  140. ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  141. return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
  142. o, err := repo.OAuthIntegration().ReadOAuthIntegration(projectID, o.OAuthIntegrationID)
  143. if err != nil {
  144. return err
  145. }
  146. o.AccessToken = accessToken
  147. o.RefreshToken = refreshToken
  148. o.Expiry = expiry
  149. _, err = repo.OAuthIntegration().UpdateOAuthIntegration(o)
  150. return err
  151. }
  152. }
  153. // GetAccessToken retrieves an access token for a given client. It updates the
  154. // access token in the DB if necessary
  155. func GetAccessToken(
  156. prevToken integrations.SharedOAuthModel,
  157. conf *oauth2.Config,
  158. updateToken func(accessToken []byte, refreshToken []byte, expiry time.Time) error,
  159. ) (string, *time.Time, error) {
  160. expiry := prevToken.Expiry
  161. if conf.Endpoint.AuthURL == DOAuthURL && expiry.IsZero() {
  162. // manually set the expiry so refresh token is used
  163. expiry = time.Now().Add(-1 * time.Minute)
  164. }
  165. tokSource := conf.TokenSource(context.TODO(), &oauth2.Token{
  166. AccessToken: string(prevToken.AccessToken),
  167. RefreshToken: string(prevToken.RefreshToken),
  168. TokenType: "Bearer",
  169. Expiry: expiry,
  170. })
  171. token, err := tokSource.Token()
  172. if err != nil {
  173. return "", nil, err
  174. }
  175. if token.AccessToken != string(prevToken.AccessToken) {
  176. err := updateToken([]byte(token.AccessToken), []byte(token.RefreshToken), token.Expiry)
  177. if err != nil {
  178. return "", nil, err
  179. }
  180. }
  181. return token.AccessToken, &token.Expiry, nil
  182. }