Skip to content

Commit

Permalink
feat: Add no_refresh option to Git auth configs (#5097)
Browse files Browse the repository at this point in the history
This allows organizations to disable refreshing Git tokens
and instead prompt for authentication again.
  • Loading branch information
kylecarbs committed Nov 15, 2022
1 parent 2a46702 commit fc0a493
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 0 deletions.
7 changes: 7 additions & 0 deletions coderd/gitauth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ type Config struct {
Regex *regexp.Regexp
// Type is the type of provider.
Type codersdk.GitProvider
// NoRefresh stops Coder from using the refresh token
// to renew the access token.
//
// Some organizations have security policies that require
// re-authentication for every token.
NoRefresh bool
}

// ConvertConfig converts the YAML configuration entry to the
Expand Down Expand Up @@ -107,6 +113,7 @@ func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Con
ID: entry.ID,
Regex: regex,
Type: typ,
NoRefresh: entry.NoRefresh,
})
}
return configs, nil
Expand Down
9 changes: 9 additions & 0 deletions coderd/workspaceagents.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,15 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
return
}

// If the token is expired and refresh is disabled, we prompt
// the user to authenticate again.
if gitAuthConfig.NoRefresh && gitAuthLink.OAuthExpiry.Before(database.Now()) {
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentGitAuthResponse{
URL: redirectURL.String(),
})
return
}

token, err := gitAuthConfig.TokenSource(ctx, &oauth2.Token{
AccessToken: gitAuthLink.OAuthAccessToken,
RefreshToken: gitAuthLink.OAuthRefreshToken,
Expand Down
68 changes: 68 additions & 0 deletions coderd/workspaceagents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/agent"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/gitauth"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
Expand Down Expand Up @@ -884,6 +886,72 @@ func TestWorkspaceAgentsGitAuth(t *testing.T) {
resp = gitAuthCallback(t, "github", client)
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
})

t.Run("ExpiredNoRefresh", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
GitAuthConfigs: []*gitauth.Config{{
OAuth2Config: &oauth2Config{
token: &oauth2.Token{
AccessToken: "token",
RefreshToken: "something",
Expiry: database.Now().Add(-time.Hour),
},
},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.GitProviderGitHub,
NoRefresh: true,
}},
})
user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "example",
Type: "aws_instance",
Agents: []*proto.Agent{{
Id: uuid.NewString(),
Auth: &proto.Agent_Token{
Token: authToken,
},
}},
}},
},
},
}},
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)

agentClient := codersdk.New(client.URL)
agentClient.SetSessionToken(authToken)

token, err := agentClient.WorkspaceAgentGitAuth(context.Background(), "github.com/asd/asd", false)
require.NoError(t, err)
require.NotEmpty(t, token.URL)

// In the configuration, we set our OAuth provider
// to return an expired token. Coder consumes this
// and stores it.
resp := gitAuthCallback(t, "github", client)
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)

// Because the token is expired and `NoRefresh` is specified,
// a redirect URL should be returned again.
token, err = agentClient.WorkspaceAgentGitAuth(context.Background(), "github.com/asd/asd", false)
require.NoError(t, err)
require.NotEmpty(t, token.URL)
})

t.Run("FullFlow", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
Expand Down
1 change: 1 addition & 0 deletions codersdk/deploymentconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ type GitAuthConfig struct {
AuthURL string `json:"auth_url"`
TokenURL string `json:"token_url"`
Regex string `json:"regex"`
NoRefresh bool `json:"no_refresh"`
Scopes []string `json:"scopes"`
}

Expand Down
1 change: 1 addition & 0 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ export interface GitAuthConfig {
readonly auth_url: string
readonly token_url: string
readonly regex: string
readonly no_refresh: boolean
readonly scopes: string[]
}

Expand Down

0 comments on commit fc0a493

Please sign in to comment.