chore: implement yaml parsing for external auth configs (#11268)

* chore: yaml parsing for external auth configs
* Also unmarshal and check the output again
This commit is contained in:
Steven Masley 2023-12-19 12:09:45 -06:00 committed by GitHub
parent 016b3ef5a2
commit c1451ca4da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 122 additions and 17 deletions

View File

@ -440,6 +440,9 @@ client:
# Support links to display in the top right drop down menu.
# (default: <unset>, type: struct[[]codersdk.LinkConfig])
supportLinks: []
# External Authentication providers.
# (default: <unset>, type: struct[[]codersdk.ExternalAuthConfig])
externalAuthProviders: []
# Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By
# default, this will pick the best available wgtunnel server hosted by Coder. e.g.
# "tunnel.example.com".

View File

@ -329,33 +329,33 @@ type TraceConfig struct {
type ExternalAuthConfig struct {
// Type is the type of external auth config.
Type string `json:"type"`
ClientID string `json:"client_id"`
Type string `json:"type" yaml:"type"`
ClientID string `json:"client_id" yaml:"client_id"`
ClientSecret string `json:"-" yaml:"client_secret"`
// ID is a unique identifier for the auth config.
// It defaults to `type` when not provided.
ID string `json:"id"`
AuthURL string `json:"auth_url"`
TokenURL string `json:"token_url"`
ValidateURL string `json:"validate_url"`
AppInstallURL string `json:"app_install_url"`
AppInstallationsURL string `json:"app_installations_url"`
NoRefresh bool `json:"no_refresh"`
Scopes []string `json:"scopes"`
ExtraTokenKeys []string `json:"extra_token_keys"`
DeviceFlow bool `json:"device_flow"`
DeviceCodeURL string `json:"device_code_url"`
ID string `json:"id" yaml:"id"`
AuthURL string `json:"auth_url" yaml:"auth_url"`
TokenURL string `json:"token_url" yaml:"token_url"`
ValidateURL string `json:"validate_url" yaml:"validate_url"`
AppInstallURL string `json:"app_install_url" yaml:"app_install_url"`
AppInstallationsURL string `json:"app_installations_url" yaml:"app_installations_url"`
NoRefresh bool `json:"no_refresh" yaml:"no_refresh"`
Scopes []string `json:"scopes" yaml:"scopes"`
ExtraTokenKeys []string `json:"extra_token_keys" yaml:"extra_token_keys"`
DeviceFlow bool `json:"device_flow" yaml:"device_flow"`
DeviceCodeURL string `json:"device_code_url" yaml:"device_code_url"`
// Regex allows API requesters to match an auth config by
// a string (e.g. coder.com) instead of by it's type.
//
// Git clone makes use of this by parsing the URL from:
// 'Username for "https://github.com":'
// And sending it to the Coder server to match against the Regex.
Regex string `json:"regex"`
Regex string `json:"regex" yaml:"regex"`
// DisplayName is shown in the UI to identify the auth config.
DisplayName string `json:"display_name"`
DisplayName string `json:"display_name" yaml:"display_name"`
// DisplayIcon is a URL to an icon to display in the UI.
DisplayIcon string `json:"display_icon"`
DisplayIcon string `json:"display_icon" yaml:"display_icon"`
}
type ProvisionerConfig struct {
@ -1788,7 +1788,7 @@ Write out the current server config as YAML to stdout.`,
Description: "External Authentication providers.",
// We need extra scrutiny to ensure this works, is documented, and
// tested before enabling.
// YAML: "gitAuthProviders",
YAML: "externalAuthProviders",
Value: &c.ExternalAuthConfigs,
Hidden: true,
},

View File

@ -1,11 +1,16 @@
package codersdk_test
import (
"bytes"
"embed"
"fmt"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/codersdk"
@ -277,3 +282,78 @@ func TestDeploymentValues_DurationFormatNanoseconds(t *testing.T) {
t.FailNow()
}
}
//go:embed testdata/*
var testData embed.FS
func TestExternalAuthYAMLConfig(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
// The windows marshal function uses different line endings.
// Not worth the effort getting this to work on windows.
t.SkipNow()
}
file := func(t *testing.T, name string) string {
data, err := testData.ReadFile(fmt.Sprintf("testdata/%s", name))
require.NoError(t, err, "read testdata file %q", name)
return string(data)
}
githubCfg := codersdk.ExternalAuthConfig{
Type: "github",
ClientID: "client_id",
ClientSecret: "client_secret",
ID: "id",
AuthURL: "https://example.com/auth",
TokenURL: "https://example.com/token",
ValidateURL: "https://example.com/validate",
AppInstallURL: "https://example.com/install",
AppInstallationsURL: "https://example.com/installations",
NoRefresh: true,
Scopes: []string{"user:email", "read:org"},
ExtraTokenKeys: []string{"extra", "token"},
DeviceFlow: true,
DeviceCodeURL: "https://example.com/device",
Regex: "^https://example.com/.*$",
DisplayName: "GitHub",
DisplayIcon: "/static/icons/github.svg",
}
// Input the github section twice for testing a slice of configs.
inputYAML := func() string {
f := file(t, "githubcfg.yaml")
lines := strings.SplitN(f, "\n", 2)
// Append github config twice
return f + lines[1]
}()
expected := []codersdk.ExternalAuthConfig{
githubCfg, githubCfg,
}
dv := codersdk.DeploymentValues{}
opts := dv.Options()
// replace any tabs with the proper space indentation
inputYAML = strings.ReplaceAll(inputYAML, "\t", " ")
// This is the order things are done in the cli, so just
// keep it the same.
var n yaml.Node
err := yaml.Unmarshal([]byte(inputYAML), &n)
require.NoError(t, err)
err = n.Decode(&opts)
require.NoError(t, err)
require.ElementsMatchf(t, expected, dv.ExternalAuthConfigs.Value, "from yaml")
var out bytes.Buffer
enc := yaml.NewEncoder(&out)
enc.SetIndent(2)
err = enc.Encode(dv.ExternalAuthConfigs)
require.NoError(t, err)
// Because we only marshal the 1 section, the correct section name is not applied.
output := strings.Replace(out.String(), "value:", "externalAuthProviders:", 1)
require.Equal(t, inputYAML, output, "re-marshaled is the same as input")
}

22
codersdk/testdata/githubcfg.yaml vendored Normal file
View File

@ -0,0 +1,22 @@
externalAuthProviders:
- type: github
client_id: client_id
client_secret: client_secret
id: id
auth_url: https://example.com/auth
token_url: https://example.com/token
validate_url: https://example.com/validate
app_install_url: https://example.com/install
app_installations_url: https://example.com/installations
no_refresh: true
scopes:
- user:email
- read:org
extra_token_keys:
- extra
- token
device_flow: true
device_code_url: https://example.com/device
regex: ^https://example.com/.*$
display_name: GitHub
display_icon: /static/icons/github.svg