Skip to content

Commit c1451ca

Browse files
authored
chore: implement yaml parsing for external auth configs (coder#11268)
* chore: yaml parsing for external auth configs * Also unmarshal and check the output again
1 parent 016b3ef commit c1451ca

File tree

4 files changed

+122
-17
lines changed

4 files changed

+122
-17
lines changed

cli/testdata/server-config.yaml.golden

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ client:
440440
# Support links to display in the top right drop down menu.
441441
# (default: <unset>, type: struct[[]codersdk.LinkConfig])
442442
supportLinks: []
443+
# External Authentication providers.
444+
# (default: <unset>, type: struct[[]codersdk.ExternalAuthConfig])
445+
externalAuthProviders: []
443446
# Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By
444447
# default, this will pick the best available wgtunnel server hosted by Coder. e.g.
445448
# "tunnel.example.com".

codersdk/deployment.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -329,33 +329,33 @@ type TraceConfig struct {
329329

330330
type ExternalAuthConfig struct {
331331
// Type is the type of external auth config.
332-
Type string `json:"type"`
333-
ClientID string `json:"client_id"`
332+
Type string `json:"type" yaml:"type"`
333+
ClientID string `json:"client_id" yaml:"client_id"`
334334
ClientSecret string `json:"-" yaml:"client_secret"`
335335
// ID is a unique identifier for the auth config.
336336
// It defaults to `type` when not provided.
337-
ID string `json:"id"`
338-
AuthURL string `json:"auth_url"`
339-
TokenURL string `json:"token_url"`
340-
ValidateURL string `json:"validate_url"`
341-
AppInstallURL string `json:"app_install_url"`
342-
AppInstallationsURL string `json:"app_installations_url"`
343-
NoRefresh bool `json:"no_refresh"`
344-
Scopes []string `json:"scopes"`
345-
ExtraTokenKeys []string `json:"extra_token_keys"`
346-
DeviceFlow bool `json:"device_flow"`
347-
DeviceCodeURL string `json:"device_code_url"`
337+
ID string `json:"id" yaml:"id"`
338+
AuthURL string `json:"auth_url" yaml:"auth_url"`
339+
TokenURL string `json:"token_url" yaml:"token_url"`
340+
ValidateURL string `json:"validate_url" yaml:"validate_url"`
341+
AppInstallURL string `json:"app_install_url" yaml:"app_install_url"`
342+
AppInstallationsURL string `json:"app_installations_url" yaml:"app_installations_url"`
343+
NoRefresh bool `json:"no_refresh" yaml:"no_refresh"`
344+
Scopes []string `json:"scopes" yaml:"scopes"`
345+
ExtraTokenKeys []string `json:"extra_token_keys" yaml:"extra_token_keys"`
346+
DeviceFlow bool `json:"device_flow" yaml:"device_flow"`
347+
DeviceCodeURL string `json:"device_code_url" yaml:"device_code_url"`
348348
// Regex allows API requesters to match an auth config by
349349
// a string (e.g. coder.com) instead of by it's type.
350350
//
351351
// Git clone makes use of this by parsing the URL from:
352352
// 'Username for "https://github.com":'
353353
// And sending it to the Coder server to match against the Regex.
354-
Regex string `json:"regex"`
354+
Regex string `json:"regex" yaml:"regex"`
355355
// DisplayName is shown in the UI to identify the auth config.
356-
DisplayName string `json:"display_name"`
356+
DisplayName string `json:"display_name" yaml:"display_name"`
357357
// DisplayIcon is a URL to an icon to display in the UI.
358-
DisplayIcon string `json:"display_icon"`
358+
DisplayIcon string `json:"display_icon" yaml:"display_icon"`
359359
}
360360

361361
type ProvisionerConfig struct {
@@ -1788,7 +1788,7 @@ Write out the current server config as YAML to stdout.`,
17881788
Description: "External Authentication providers.",
17891789
// We need extra scrutiny to ensure this works, is documented, and
17901790
// tested before enabling.
1791-
// YAML: "gitAuthProviders",
1791+
YAML: "externalAuthProviders",
17921792
Value: &c.ExternalAuthConfigs,
17931793
Hidden: true,
17941794
},

codersdk/deployment_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package codersdk_test
22

33
import (
4+
"bytes"
5+
"embed"
6+
"fmt"
7+
"runtime"
48
"strings"
59
"testing"
610
"time"
711

812
"github.com/stretchr/testify/require"
13+
"gopkg.in/yaml.v3"
914

1015
"github.com/coder/coder/v2/cli/clibase"
1116
"github.com/coder/coder/v2/codersdk"
@@ -277,3 +282,78 @@ func TestDeploymentValues_DurationFormatNanoseconds(t *testing.T) {
277282
t.FailNow()
278283
}
279284
}
285+
286+
//go:embed testdata/*
287+
var testData embed.FS
288+
289+
func TestExternalAuthYAMLConfig(t *testing.T) {
290+
t.Parallel()
291+
292+
if runtime.GOOS == "windows" {
293+
// The windows marshal function uses different line endings.
294+
// Not worth the effort getting this to work on windows.
295+
t.SkipNow()
296+
}
297+
298+
file := func(t *testing.T, name string) string {
299+
data, err := testData.ReadFile(fmt.Sprintf("testdata/%s", name))
300+
require.NoError(t, err, "read testdata file %q", name)
301+
return string(data)
302+
}
303+
githubCfg := codersdk.ExternalAuthConfig{
304+
Type: "github",
305+
ClientID: "client_id",
306+
ClientSecret: "client_secret",
307+
ID: "id",
308+
AuthURL: "https://example.com/auth",
309+
TokenURL: "https://example.com/token",
310+
ValidateURL: "https://example.com/validate",
311+
AppInstallURL: "https://example.com/install",
312+
AppInstallationsURL: "https://example.com/installations",
313+
NoRefresh: true,
314+
Scopes: []string{"user:email", "read:org"},
315+
ExtraTokenKeys: []string{"extra", "token"},
316+
DeviceFlow: true,
317+
DeviceCodeURL: "https://example.com/device",
318+
Regex: "^https://example.com/.*$",
319+
DisplayName: "GitHub",
320+
DisplayIcon: "/static/icons/github.svg",
321+
}
322+
323+
// Input the github section twice for testing a slice of configs.
324+
inputYAML := func() string {
325+
f := file(t, "githubcfg.yaml")
326+
lines := strings.SplitN(f, "\n", 2)
327+
// Append github config twice
328+
return f + lines[1]
329+
}()
330+
331+
expected := []codersdk.ExternalAuthConfig{
332+
githubCfg, githubCfg,
333+
}
334+
335+
dv := codersdk.DeploymentValues{}
336+
opts := dv.Options()
337+
// replace any tabs with the proper space indentation
338+
inputYAML = strings.ReplaceAll(inputYAML, "\t", " ")
339+
340+
// This is the order things are done in the cli, so just
341+
// keep it the same.
342+
var n yaml.Node
343+
err := yaml.Unmarshal([]byte(inputYAML), &n)
344+
require.NoError(t, err)
345+
346+
err = n.Decode(&opts)
347+
require.NoError(t, err)
348+
require.ElementsMatchf(t, expected, dv.ExternalAuthConfigs.Value, "from yaml")
349+
350+
var out bytes.Buffer
351+
enc := yaml.NewEncoder(&out)
352+
enc.SetIndent(2)
353+
err = enc.Encode(dv.ExternalAuthConfigs)
354+
require.NoError(t, err)
355+
356+
// Because we only marshal the 1 section, the correct section name is not applied.
357+
output := strings.Replace(out.String(), "value:", "externalAuthProviders:", 1)
358+
require.Equal(t, inputYAML, output, "re-marshaled is the same as input")
359+
}

codersdk/testdata/githubcfg.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
externalAuthProviders:
2+
- type: github
3+
client_id: client_id
4+
client_secret: client_secret
5+
id: id
6+
auth_url: https://example.com/auth
7+
token_url: https://example.com/token
8+
validate_url: https://example.com/validate
9+
app_install_url: https://example.com/install
10+
app_installations_url: https://example.com/installations
11+
no_refresh: true
12+
scopes:
13+
- user:email
14+
- read:org
15+
extra_token_keys:
16+
- extra
17+
- token
18+
device_flow: true
19+
device_code_url: https://example.com/device
20+
regex: ^https://example.com/.*$
21+
display_name: GitHub
22+
display_icon: /static/icons/github.svg

0 commit comments

Comments
 (0)