| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- package oauth
- import (
- "context"
- "crypto/rand"
- "encoding/base64"
- "time"
- "github.com/porter-dev/porter/internal/models/integrations"
- "github.com/porter-dev/porter/internal/repository"
- "golang.org/x/oauth2"
- )
- type Config struct {
- ClientID string
- ClientSecret string
- Scopes []string
- BaseURL string
- }
- // GithubAppConf is standard oauth2 config but it need to keeps track of the app name and webhook secret
- type GithubAppConf struct {
- AppName string
- WebhookSecret string
- SecretPath string
- AppID int64
- oauth2.Config
- }
- const (
- GithubAuthURL string = "https://github.com/login/oauth/authorize"
- GithubTokenURL string = "https://github.com/login/oauth/access_token"
- DOAuthURL string = "https://cloud.digitalocean.com/v1/oauth/authorize"
- DOTokenURL string = "https://cloud.digitalocean.com/v1/oauth/token"
- GoogleAuthURL string = "https://accounts.google.com/o/oauth2/v2/auth"
- GoogleTokenURL string = "https://oauth2.googleapis.com/token"
- SlackAuthURL string = "https://slack.com/oauth/v2/authorize"
- SlackTokenURL string = "https://slack.com/api/oauth.v2.access"
- )
- func NewGithubClient(cfg *Config) *oauth2.Config {
- return &oauth2.Config{
- ClientID: cfg.ClientID,
- ClientSecret: cfg.ClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: GithubAuthURL,
- TokenURL: GithubTokenURL,
- },
- RedirectURL: cfg.BaseURL + "/api/oauth/github/callback",
- Scopes: cfg.Scopes,
- }
- }
- func NewGithubAppClient(cfg *Config, name string, secret string, secretPath string, appID int64) *GithubAppConf {
- return &GithubAppConf{
- AppName: name,
- WebhookSecret: secret,
- SecretPath: secretPath,
- AppID: appID,
- Config: oauth2.Config{
- ClientID: cfg.ClientID,
- ClientSecret: cfg.ClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: GithubAuthURL,
- TokenURL: GithubTokenURL,
- },
- RedirectURL: cfg.BaseURL + "/api/oauth/github-app/callback",
- Scopes: cfg.Scopes,
- },
- }
- }
- func NewDigitalOceanClient(cfg *Config) *oauth2.Config {
- return &oauth2.Config{
- ClientID: cfg.ClientID,
- ClientSecret: cfg.ClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: DOAuthURL,
- TokenURL: DOTokenURL,
- AuthStyle: oauth2.AuthStyleInParams,
- },
- RedirectURL: cfg.BaseURL + "/api/oauth/digitalocean/callback",
- Scopes: cfg.Scopes,
- }
- }
- func NewGoogleClient(cfg *Config) *oauth2.Config {
- return &oauth2.Config{
- ClientID: cfg.ClientID,
- ClientSecret: cfg.ClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: GoogleAuthURL,
- TokenURL: GoogleTokenURL,
- },
- RedirectURL: cfg.BaseURL + "/api/oauth/google/callback",
- Scopes: cfg.Scopes,
- }
- }
- func NewSlackClient(cfg *Config) *oauth2.Config {
- return &oauth2.Config{
- ClientID: cfg.ClientID,
- ClientSecret: cfg.ClientSecret,
- Endpoint: oauth2.Endpoint{
- AuthURL: SlackAuthURL,
- TokenURL: SlackTokenURL,
- },
- RedirectURL: cfg.BaseURL + "/api/oauth/slack/callback",
- Scopes: cfg.Scopes,
- }
- }
- func CreateRandomState() string {
- b := make([]byte, 16)
- rand.Read(b)
- state := base64.URLEncoding.EncodeToString(b)
- return state
- }
- // MakeUpdateOAuthIntegrationTokenFunction creates a function to be passed to GetAccessToken that updates the OauthIntegration
- // if it needs to be updated
- func MakeUpdateOAuthIntegrationTokenFunction(
- o *integrations.OAuthIntegration,
- repo repository.Repository,
- ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- o.AccessToken = accessToken
- o.RefreshToken = refreshToken
- o.Expiry = expiry
- _, err := repo.OAuthIntegration().UpdateOAuthIntegration(o)
- return err
- }
- }
- // MakeUpdateGithubAppOauthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GithubAppOauthIntegration
- // if it needs to be updated
- func MakeUpdateGithubAppOauthIntegrationFunction(
- o *integrations.GithubAppOAuthIntegration,
- repo repository.Repository,
- ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- o.AccessToken = accessToken
- o.RefreshToken = refreshToken
- o.Expiry = expiry
- _, err := repo.GithubAppOAuthIntegration().UpdateGithubAppOauthIntegration(o)
- return err
- }
- }
- // MakeUpdateGitlabAppOAuthIntegrationFunction creates a function to be passed to GetAccessToken that updates the GitlabAppOAuthIntegration
- // if it needs to be updated
- func MakeUpdateGitlabAppOAuthIntegrationFunction(
- projectID uint,
- o *integrations.GitlabAppOAuthIntegration,
- repo repository.Repository,
- ) func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- return func(accessToken []byte, refreshToken []byte, expiry time.Time) error {
- o, err := repo.OAuthIntegration().ReadOAuthIntegration(projectID, o.OAuthIntegrationID)
- if err != nil {
- return err
- }
- o.AccessToken = accessToken
- o.RefreshToken = refreshToken
- o.Expiry = expiry
- _, err = repo.OAuthIntegration().UpdateOAuthIntegration(o)
- return err
- }
- }
- // GetAccessToken retrieves an access token for a given client. It updates the
- // access token in the DB if necessary
- func GetAccessToken(
- prevToken integrations.SharedOAuthModel,
- conf *oauth2.Config,
- updateToken func(accessToken []byte, refreshToken []byte, expiry time.Time) error,
- ) (string, *time.Time, error) {
- expiry := prevToken.Expiry
- if conf.Endpoint.AuthURL == DOAuthURL && expiry.IsZero() {
- // manually set the expiry so refresh token is used
- expiry = time.Now().Add(-1 * time.Minute)
- }
- tokSource := conf.TokenSource(context.TODO(), &oauth2.Token{
- AccessToken: string(prevToken.AccessToken),
- RefreshToken: string(prevToken.RefreshToken),
- TokenType: "Bearer",
- Expiry: expiry,
- })
- token, err := tokSource.Token()
- if err != nil {
- return "", nil, err
- }
- if token.AccessToken != string(prevToken.AccessToken) {
- err := updateToken([]byte(token.AccessToken), []byte(token.RefreshToken), token.Expiry)
- if err != nil {
- return "", nil, err
- }
- }
- return token.AccessToken, &token.Expiry, nil
- }
|