mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: Add no_refresh
option to Git auth configs (#5097)
This allows organizations to disable refreshing Git tokens and instead prompt for authentication again.
This commit is contained in:
@ -22,6 +22,12 @@ type Config struct {
|
|||||||
Regex *regexp.Regexp
|
Regex *regexp.Regexp
|
||||||
// Type is the type of provider.
|
// Type is the type of provider.
|
||||||
Type codersdk.GitProvider
|
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
|
// ConvertConfig converts the YAML configuration entry to the
|
||||||
@ -107,6 +113,7 @@ func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Con
|
|||||||
ID: entry.ID,
|
ID: entry.ID,
|
||||||
Regex: regex,
|
Regex: regex,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
|
NoRefresh: entry.NoRefresh,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return configs, nil
|
return configs, nil
|
||||||
|
@ -1154,6 +1154,15 @@ func (api *API) workspaceAgentsGitAuth(rw http.ResponseWriter, r *http.Request)
|
|||||||
return
|
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{
|
token, err := gitAuthConfig.TokenSource(ctx, &oauth2.Token{
|
||||||
AccessToken: gitAuthLink.OAuthAccessToken,
|
AccessToken: gitAuthLink.OAuthAccessToken,
|
||||||
RefreshToken: gitAuthLink.OAuthRefreshToken,
|
RefreshToken: gitAuthLink.OAuthRefreshToken,
|
||||||
|
@ -17,11 +17,13 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"cdr.dev/slog/sloggers/slogtest"
|
||||||
"github.com/coder/coder/agent"
|
"github.com/coder/coder/agent"
|
||||||
"github.com/coder/coder/coderd/coderdtest"
|
"github.com/coder/coder/coderd/coderdtest"
|
||||||
|
"github.com/coder/coder/coderd/database"
|
||||||
"github.com/coder/coder/coderd/gitauth"
|
"github.com/coder/coder/coderd/gitauth"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
"github.com/coder/coder/provisioner/echo"
|
"github.com/coder/coder/provisioner/echo"
|
||||||
@ -884,6 +886,72 @@ func TestWorkspaceAgentsGitAuth(t *testing.T) {
|
|||||||
resp = gitAuthCallback(t, "github", client)
|
resp = gitAuthCallback(t, "github", client)
|
||||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
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.Run("FullFlow", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{
|
client := coderdtest.New(t, &coderdtest.Options{
|
||||||
|
@ -125,6 +125,7 @@ type GitAuthConfig struct {
|
|||||||
AuthURL string `json:"auth_url"`
|
AuthURL string `json:"auth_url"`
|
||||||
TokenURL string `json:"token_url"`
|
TokenURL string `json:"token_url"`
|
||||||
Regex string `json:"regex"`
|
Regex string `json:"regex"`
|
||||||
|
NoRefresh bool `json:"no_refresh"`
|
||||||
Scopes []string `json:"scopes"`
|
Scopes []string `json:"scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +362,7 @@ export interface GitAuthConfig {
|
|||||||
readonly auth_url: string
|
readonly auth_url: string
|
||||||
readonly token_url: string
|
readonly token_url: string
|
||||||
readonly regex: string
|
readonly regex: string
|
||||||
|
readonly no_refresh: boolean
|
||||||
readonly scopes: string[]
|
readonly scopes: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user