mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
11 KiB
471 lines
11 KiB
package main |
|
|
|
import ( |
|
"log/slog" |
|
"os" |
|
"testing" |
|
|
|
"github.com/ghodss/yaml" |
|
"github.com/kylelemons/godebug/pretty" |
|
|
|
"github.com/dexidp/dex/connector/mock" |
|
"github.com/dexidp/dex/connector/oidc" |
|
"github.com/dexidp/dex/server" |
|
"github.com/dexidp/dex/storage" |
|
"github.com/dexidp/dex/storage/sql" |
|
) |
|
|
|
var _ = yaml.YAMLToJSON |
|
|
|
func boolPtr(v bool) *bool { |
|
return &v |
|
} |
|
|
|
func TestValidConfiguration(t *testing.T) { |
|
configuration := Config{ |
|
Issuer: "http://127.0.0.1:5556/dex", |
|
Storage: Storage{ |
|
Type: "sqlite3", |
|
Config: &sql.SQLite3{ |
|
File: "examples/dex.db", |
|
}, |
|
}, |
|
Web: Web{ |
|
HTTP: "127.0.0.1:5556", |
|
}, |
|
StaticConnectors: []Connector{ |
|
{ |
|
Type: "mockCallback", |
|
ID: "mock", |
|
Name: "Example", |
|
Config: &mock.CallbackConfig{}, |
|
}, |
|
}, |
|
} |
|
|
|
if err := configuration.Validate(); err != nil { |
|
t.Fatalf("this configuration should have been valid: %v", err) |
|
} |
|
} |
|
|
|
func TestInvalidConfiguration(t *testing.T) { |
|
configuration := Config{} |
|
err := configuration.Validate() |
|
if err == nil { |
|
t.Fatal("this configuration should be invalid") |
|
} |
|
got := err.Error() |
|
wanted := `invalid Config: |
|
- no issuer specified in config file |
|
- no storage supplied in config file |
|
- must supply a HTTP/HTTPS address to listen on` |
|
if got != wanted { |
|
t.Fatalf("Expected error message to be %q, got %q", wanted, got) |
|
} |
|
} |
|
|
|
func TestUnmarshalConfig(t *testing.T) { |
|
rawConfig := []byte(` |
|
issuer: http://127.0.0.1:5556/dex |
|
storage: |
|
type: postgres |
|
config: |
|
host: 10.0.0.1 |
|
port: 65432 |
|
maxOpenConns: 5 |
|
maxIdleConns: 3 |
|
connMaxLifetime: 30 |
|
connectionTimeout: 3 |
|
web: |
|
https: 127.0.0.1:5556 |
|
tlsMinVersion: 1.3 |
|
tlsMaxVersion: 1.2 |
|
headers: |
|
Strict-Transport-Security: "max-age=31536000; includeSubDomains" |
|
|
|
frontend: |
|
dir: ./web |
|
extra: |
|
foo: bar |
|
|
|
staticClients: |
|
- id: example-app |
|
redirectURIs: |
|
- 'http://127.0.0.1:5555/callback' |
|
name: 'Example App' |
|
secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |
|
|
|
oauth2: |
|
alwaysShowLoginScreen: true |
|
grantTypes: |
|
- refresh_token |
|
- "urn:ietf:params:oauth:grant-type:token-exchange" |
|
|
|
connectors: |
|
- type: mockCallback |
|
id: mock |
|
name: Example |
|
- type: oidc |
|
id: google |
|
name: Google |
|
config: |
|
issuer: https://accounts.google.com |
|
clientID: foo |
|
clientSecret: bar |
|
redirectURI: http://127.0.0.1:5556/dex/callback/google |
|
|
|
enablePasswordDB: true |
|
staticPasswords: |
|
- email: "admin@example.com" |
|
# bcrypt hash of the string "password" |
|
hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" |
|
username: "admin" |
|
name: "Admin User" |
|
emailVerified: false |
|
preferredUsername: "admin-public" |
|
groups: |
|
- "team-a" |
|
- "team-a/admins" |
|
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" |
|
- email: "foo@example.com" |
|
# base64'd value of the same bcrypt hash above. We want to be able to parse both of these |
|
hash: "JDJhJDEwJDMzRU1UMGNWWVZsUHk2V0FNQ0xzY2VMWWpXaHVIcGJ6NXl1Wnh1L0dBRmowM0o5THl0anV5" |
|
username: "foo" |
|
userID: "41331323-6f44-45e6-b3b9-2c4b60c02be5" |
|
|
|
expiry: |
|
signingKeys: "7h" |
|
idTokens: "25h" |
|
authRequests: "25h" |
|
deviceRequests: "10m" |
|
|
|
logger: |
|
level: "debug" |
|
format: "json" |
|
|
|
additionalFeatures: [ |
|
"ConnectorsCRUD" |
|
] |
|
`) |
|
|
|
want := Config{ |
|
Issuer: "http://127.0.0.1:5556/dex", |
|
Storage: Storage{ |
|
Type: "postgres", |
|
Config: &sql.Postgres{ |
|
NetworkDB: sql.NetworkDB{ |
|
Host: "10.0.0.1", |
|
Port: 65432, |
|
MaxOpenConns: 5, |
|
MaxIdleConns: 3, |
|
ConnMaxLifetime: 30, |
|
ConnectionTimeout: 3, |
|
}, |
|
}, |
|
}, |
|
Web: Web{ |
|
HTTPS: "127.0.0.1:5556", |
|
TLSMinVersion: "1.3", |
|
TLSMaxVersion: "1.2", |
|
Headers: Headers{ |
|
StrictTransportSecurity: "max-age=31536000; includeSubDomains", |
|
}, |
|
}, |
|
Frontend: server.WebConfig{ |
|
Dir: "./web", |
|
Extra: map[string]string{ |
|
"foo": "bar", |
|
}, |
|
}, |
|
StaticClients: []storage.Client{ |
|
{ |
|
ID: "example-app", |
|
Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", |
|
Name: "Example App", |
|
RedirectURIs: []string{ |
|
"http://127.0.0.1:5555/callback", |
|
}, |
|
}, |
|
}, |
|
OAuth2: OAuth2{ |
|
AlwaysShowLoginScreen: true, |
|
GrantTypes: []string{ |
|
"refresh_token", |
|
"urn:ietf:params:oauth:grant-type:token-exchange", |
|
}, |
|
}, |
|
StaticConnectors: []Connector{ |
|
{ |
|
Type: "mockCallback", |
|
ID: "mock", |
|
Name: "Example", |
|
Config: &mock.CallbackConfig{}, |
|
}, |
|
{ |
|
Type: "oidc", |
|
ID: "google", |
|
Name: "Google", |
|
Config: &oidc.Config{ |
|
Issuer: "https://accounts.google.com", |
|
ClientID: "foo", |
|
ClientSecret: "bar", |
|
RedirectURI: "http://127.0.0.1:5556/dex/callback/google", |
|
}, |
|
}, |
|
}, |
|
EnablePasswordDB: true, |
|
StaticPasswords: []password{ |
|
{ |
|
Email: "admin@example.com", |
|
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), |
|
Username: "admin", |
|
Name: "Admin User", |
|
EmailVerified: boolPtr(false), |
|
PreferredUsername: "admin-public", |
|
UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466", |
|
Groups: []string{ |
|
"team-a", |
|
"team-a/admins", |
|
}, |
|
}, |
|
{ |
|
Email: "foo@example.com", |
|
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), |
|
Username: "foo", |
|
UserID: "41331323-6f44-45e6-b3b9-2c4b60c02be5", |
|
}, |
|
}, |
|
Expiry: Expiry{ |
|
SigningKeys: "7h", |
|
IDTokens: "25h", |
|
AuthRequests: "25h", |
|
DeviceRequests: "10m", |
|
}, |
|
Logger: Logger{ |
|
Level: slog.LevelDebug, |
|
Format: "json", |
|
}, |
|
} |
|
|
|
var c Config |
|
if err := yaml.Unmarshal(rawConfig, &c); err != nil { |
|
t.Fatalf("failed to decode config: %v", err) |
|
} |
|
|
|
if diff := pretty.Compare(c, want); diff != "" { |
|
t.Errorf("got!=want: %s", diff) |
|
} |
|
} |
|
|
|
func TestUnmarshalConfigWithEnvNoExpand(t *testing.T) { |
|
// If the env variable DEX_EXPAND_ENV is set and has a "falsy" value, os.ExpandEnv is disabled. |
|
// ParseBool: "It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False." |
|
checkUnmarshalConfigWithEnv(t, "0", false) |
|
checkUnmarshalConfigWithEnv(t, "f", false) |
|
checkUnmarshalConfigWithEnv(t, "F", false) |
|
checkUnmarshalConfigWithEnv(t, "FALSE", false) |
|
checkUnmarshalConfigWithEnv(t, "false", false) |
|
checkUnmarshalConfigWithEnv(t, "False", false) |
|
os.Unsetenv("DEX_EXPAND_ENV") |
|
} |
|
|
|
func TestUnmarshalConfigWithEnvExpand(t *testing.T) { |
|
// If the env variable DEX_EXPAND_ENV is unset or has a "truthy" or unknown value, os.ExpandEnv is enabled. |
|
// ParseBool: "It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False." |
|
checkUnmarshalConfigWithEnv(t, "1", true) |
|
checkUnmarshalConfigWithEnv(t, "t", true) |
|
checkUnmarshalConfigWithEnv(t, "T", true) |
|
checkUnmarshalConfigWithEnv(t, "TRUE", true) |
|
checkUnmarshalConfigWithEnv(t, "true", true) |
|
checkUnmarshalConfigWithEnv(t, "True", true) |
|
// Values that can't be parsed as bool: |
|
checkUnmarshalConfigWithEnv(t, "UNSET", true) |
|
checkUnmarshalConfigWithEnv(t, "", true) |
|
checkUnmarshalConfigWithEnv(t, "whatever - true is default", true) |
|
os.Unsetenv("DEX_EXPAND_ENV") |
|
} |
|
|
|
func checkUnmarshalConfigWithEnv(t *testing.T, dexExpandEnv string, wantExpandEnv bool) { |
|
// For hashFromEnv: |
|
os.Setenv("DEX_FOO_USER_PASSWORD", "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy") |
|
// For os.ExpandEnv ($VAR -> value_of_VAR): |
|
os.Setenv("DEX_FOO_POSTGRES_HOST", "10.0.0.1") |
|
os.Setenv("DEX_FOO_POSTGRES_PASSWORD", `psql"test\pass`) |
|
os.Setenv("DEX_FOO_OIDC_CLIENT_SECRET", `abc"def\ghi`) |
|
if dexExpandEnv != "UNSET" { |
|
os.Setenv("DEX_EXPAND_ENV", dexExpandEnv) |
|
} else { |
|
os.Unsetenv("DEX_EXPAND_ENV") |
|
} |
|
|
|
rawConfig := []byte(` |
|
issuer: http://127.0.0.1:5556/dex |
|
storage: |
|
type: postgres |
|
config: |
|
# Env variables are expanded in raw YAML source. |
|
# Single quotes work fine, as long as the env variable doesn't contain any. |
|
host: '$DEX_FOO_POSTGRES_HOST' |
|
password: '$DEX_FOO_POSTGRES_PASSWORD' |
|
port: 65432 |
|
maxOpenConns: 5 |
|
maxIdleConns: 3 |
|
connMaxLifetime: 30 |
|
connectionTimeout: 3 |
|
web: |
|
http: 127.0.0.1:5556 |
|
|
|
frontend: |
|
dir: ./web |
|
extra: |
|
foo: bar |
|
|
|
staticClients: |
|
- id: example-app |
|
redirectURIs: |
|
- 'http://127.0.0.1:5555/callback' |
|
name: 'Example App' |
|
secret: ZXhhbXBsZS1hcHAtc2VjcmV0 |
|
|
|
oauth2: |
|
alwaysShowLoginScreen: true |
|
|
|
connectors: |
|
- type: mockCallback |
|
id: mock |
|
name: Example |
|
- type: oidc |
|
id: google |
|
name: Google |
|
config: |
|
issuer: https://accounts.google.com |
|
clientID: foo |
|
# Env variables are expanded in raw YAML source. |
|
# Single quotes work fine, as long as the env variable doesn't contain any. |
|
clientSecret: '$DEX_FOO_OIDC_CLIENT_SECRET' |
|
redirectURI: http://127.0.0.1:5556/dex/callback/google |
|
|
|
enablePasswordDB: true |
|
staticPasswords: |
|
- email: "admin@example.com" |
|
# bcrypt hash of the string "password" |
|
hash: "$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy" |
|
username: "admin" |
|
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" |
|
- email: "foo@example.com" |
|
hashFromEnv: "DEX_FOO_USER_PASSWORD" |
|
username: "foo" |
|
userID: "41331323-6f44-45e6-b3b9-2c4b60c02be5" |
|
|
|
expiry: |
|
signingKeys: "7h" |
|
idTokens: "25h" |
|
authRequests: "25h" |
|
|
|
logger: |
|
level: "debug" |
|
format: "json" |
|
`) |
|
|
|
// This is not a valid hostname. It's only used to check whether os.ExpandEnv was applied or not. |
|
wantPostgresHost := "$DEX_FOO_POSTGRES_HOST" |
|
wantPostgresPassword := "$DEX_FOO_POSTGRES_PASSWORD" |
|
wantOidcClientSecret := "$DEX_FOO_OIDC_CLIENT_SECRET" |
|
if wantExpandEnv { |
|
wantPostgresHost = "10.0.0.1" |
|
wantPostgresPassword = `psql"test\pass` |
|
wantOidcClientSecret = `abc"def\ghi` |
|
} |
|
|
|
want := Config{ |
|
Issuer: "http://127.0.0.1:5556/dex", |
|
Storage: Storage{ |
|
Type: "postgres", |
|
Config: &sql.Postgres{ |
|
NetworkDB: sql.NetworkDB{ |
|
Host: wantPostgresHost, |
|
Password: wantPostgresPassword, |
|
Port: 65432, |
|
MaxOpenConns: 5, |
|
MaxIdleConns: 3, |
|
ConnMaxLifetime: 30, |
|
ConnectionTimeout: 3, |
|
}, |
|
}, |
|
}, |
|
Web: Web{ |
|
HTTP: "127.0.0.1:5556", |
|
}, |
|
Frontend: server.WebConfig{ |
|
Dir: "./web", |
|
Extra: map[string]string{ |
|
"foo": "bar", |
|
}, |
|
}, |
|
StaticClients: []storage.Client{ |
|
{ |
|
ID: "example-app", |
|
Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0", |
|
Name: "Example App", |
|
RedirectURIs: []string{ |
|
"http://127.0.0.1:5555/callback", |
|
}, |
|
}, |
|
}, |
|
OAuth2: OAuth2{ |
|
AlwaysShowLoginScreen: true, |
|
}, |
|
StaticConnectors: []Connector{ |
|
{ |
|
Type: "mockCallback", |
|
ID: "mock", |
|
Name: "Example", |
|
Config: &mock.CallbackConfig{}, |
|
}, |
|
{ |
|
Type: "oidc", |
|
ID: "google", |
|
Name: "Google", |
|
Config: &oidc.Config{ |
|
Issuer: "https://accounts.google.com", |
|
ClientID: "foo", |
|
ClientSecret: wantOidcClientSecret, |
|
RedirectURI: "http://127.0.0.1:5556/dex/callback/google", |
|
}, |
|
}, |
|
}, |
|
EnablePasswordDB: true, |
|
StaticPasswords: []password{ |
|
{ |
|
Email: "admin@example.com", |
|
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), |
|
Username: "admin", |
|
UserID: "08a8684b-db88-4b73-90a9-3cd1661f5466", |
|
}, |
|
{ |
|
Email: "foo@example.com", |
|
Hash: []byte("$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy"), |
|
Username: "foo", |
|
UserID: "41331323-6f44-45e6-b3b9-2c4b60c02be5", |
|
}, |
|
}, |
|
Expiry: Expiry{ |
|
SigningKeys: "7h", |
|
IDTokens: "25h", |
|
AuthRequests: "25h", |
|
}, |
|
Logger: Logger{ |
|
Level: slog.LevelDebug, |
|
Format: "json", |
|
}, |
|
} |
|
|
|
var c Config |
|
if err := yaml.Unmarshal(rawConfig, &c); err != nil { |
|
t.Fatalf("failed to decode config: %v", err) |
|
} |
|
|
|
if diff := pretty.Compare(c, want); diff != "" { |
|
t.Errorf("got!=want: %s", diff) |
|
} |
|
}
|
|
|