Files
coder/coderd/gitauth/config_test.go
Kyle Carberry 34467a3289 feat: add github device flow for authentication (#8232)
* feat: add github device flow for authentication

This will allow us to add a GitHub OAuth provider out-of-the-box
to reduce setup requirements.

* Improve askpass view

* Add routes to improve clarity of git auth

* Redesign the git auth page

* Refactor to add a page view

* Fix sideways layout

* Remove legacy notify

* Fix git auth redirects

* Add E2E tests

* Fix route documentation

* Fix imports

* Remove unused imports

* Fix E2E web test

* Fix friendly message appearance

* Fix layout shifting for full-screen sign-in

* Fix height going to 100%

* Fix comments
2023-06-29 18:58:01 +00:00

202 lines
5.4 KiB
Go

package gitauth_test
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/dbfake"
"github.com/coder/coder/coderd/database/dbgen"
"github.com/coder/coder/coderd/gitauth"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/testutil"
)
func TestRefreshToken(t *testing.T) {
t.Parallel()
t.Run("FalseIfNoRefresh", func(t *testing.T) {
t.Parallel()
config := &gitauth.Config{
NoRefresh: true,
}
_, refreshed, err := config.RefreshToken(context.Background(), nil, database.GitAuthLink{
OAuthExpiry: time.Time{},
})
require.NoError(t, err)
require.False(t, refreshed)
})
t.Run("FalseIfTokenSourceFails", func(t *testing.T) {
t.Parallel()
config := &gitauth.Config{
OAuth2Config: &testutil.OAuth2Config{
TokenSourceFunc: func() (*oauth2.Token, error) {
return nil, xerrors.New("failure")
},
},
}
_, refreshed, err := config.RefreshToken(context.Background(), nil, database.GitAuthLink{})
require.NoError(t, err)
require.False(t, refreshed)
})
t.Run("ValidateServerError", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Failure"))
}))
config := &gitauth.Config{
OAuth2Config: &testutil.OAuth2Config{},
ValidateURL: srv.URL,
}
_, _, err := config.RefreshToken(context.Background(), nil, database.GitAuthLink{})
require.ErrorContains(t, err, "Failure")
})
t.Run("ValidateFailure", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Not permitted"))
}))
config := &gitauth.Config{
OAuth2Config: &testutil.OAuth2Config{},
ValidateURL: srv.URL,
}
_, refreshed, err := config.RefreshToken(context.Background(), nil, database.GitAuthLink{})
require.NoError(t, err)
require.False(t, refreshed)
})
t.Run("ValidateNoUpdate", func(t *testing.T) {
t.Parallel()
validated := make(chan struct{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
close(validated)
}))
accessToken := "testing"
config := &gitauth.Config{
OAuth2Config: &testutil.OAuth2Config{
Token: &oauth2.Token{
AccessToken: accessToken,
},
},
ValidateURL: srv.URL,
}
_, valid, err := config.RefreshToken(context.Background(), nil, database.GitAuthLink{
OAuthAccessToken: accessToken,
})
require.NoError(t, err)
require.True(t, valid)
<-validated
})
t.Run("Updates", func(t *testing.T) {
t.Parallel()
config := &gitauth.Config{
ID: "test",
OAuth2Config: &testutil.OAuth2Config{
Token: &oauth2.Token{
AccessToken: "updated",
},
},
}
db := dbfake.New()
link := dbgen.GitAuthLink(t, db, database.GitAuthLink{
ProviderID: config.ID,
OAuthAccessToken: "initial",
})
_, valid, err := config.RefreshToken(context.Background(), db, link)
require.NoError(t, err)
require.True(t, valid)
})
}
func TestConvertYAML(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
Name string
Input []codersdk.GitAuthConfig
Output []*gitauth.Config
Error string
}{{
Name: "InvalidType",
Input: []codersdk.GitAuthConfig{{
Type: "moo",
}},
Error: "unknown git provider type",
}, {
Name: "InvalidID",
Input: []codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitHub),
ID: "$hi$",
}},
Error: "doesn't have a valid id",
}, {
Name: "NoClientID",
Input: []codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitHub),
}},
Error: "client_id must be provided",
}, {
Name: "DuplicateType",
Input: []codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitHub),
ClientID: "example",
ClientSecret: "example",
}, {
Type: string(codersdk.GitProviderGitHub),
}},
Error: "multiple github git auth providers provided",
}, {
Name: "InvalidRegex",
Input: []codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitHub),
ClientID: "example",
ClientSecret: "example",
Regex: `\K`,
}},
Error: "compile regex for git auth provider",
}, {
Name: "NoDeviceURL",
Input: []codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitLab),
ClientID: "example",
ClientSecret: "example",
DeviceFlow: true,
}},
Error: "device auth url must be provided",
}} {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
output, err := gitauth.ConvertConfig(tc.Input, &url.URL{})
if tc.Error != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.Error)
return
}
require.Equal(t, tc.Output, output)
})
}
t.Run("CustomScopesAndEndpoint", func(t *testing.T) {
t.Parallel()
config, err := gitauth.ConvertConfig([]codersdk.GitAuthConfig{{
Type: string(codersdk.GitProviderGitLab),
ClientID: "id",
ClientSecret: "secret",
AuthURL: "https://auth.com",
TokenURL: "https://token.com",
Scopes: []string{"read"},
}}, &url.URL{})
require.NoError(t, err)
require.Equal(t, "https://auth.com?client_id=id&redirect_uri=%2Fgitauth%2Fgitlab%2Fcallback&response_type=code&scope=read", config[0].AuthCodeURL(""))
})
}