Skip to content

chore: implement yaml parsing for external auth configs #11268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/testdata/server-config.yaml.golden
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand Down
34 changes: 17 additions & 17 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
},
Expand Down
80 changes: 80 additions & 0 deletions codersdk/deployment_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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",
}
Comment on lines +303 to +321
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hard-coding this and having to keep it in sync with the golden file, how about marshalling and unmarshalling the config to/from YAML and asserting that the two are the same byte-for-byte?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do both, but I should still have the golden file imo. The actual struct is clibase.Struct[[]ExternalAuthConfig]. So it might unmarshal/marshal, but the nested key structure could be messed up. We want to make sure the actual Go slice type is at the right yaml level (top level) imo.


// 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 changes: 22 additions & 0 deletions codersdk/testdata/githubcfg.yaml
Original file line number Diff line number Diff line change
@@ -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