forked from mystiq/dex
Merge branch 'master' into feature/static_password_env
This commit is contained in:
commit
9560899496
46 changed files with 897 additions and 279 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -69,8 +69,8 @@ jobs:
|
|||
DEX_KEYSTONE_ADMIN_USER: demo
|
||||
DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS
|
||||
|
||||
- name: Run Kubernetes tests
|
||||
run: ./scripts/test-k8s.sh
|
||||
- name: Run linter
|
||||
run: make lint
|
||||
|
||||
# Ensure proto generation doesn't depend on external packages.
|
||||
- name: Verify proto
|
||||
|
|
35
.golangci.yml
Normal file
35
.golangci.yml
Normal file
|
@ -0,0 +1,35 @@
|
|||
run:
|
||||
skip-dirs:
|
||||
- vendor
|
||||
|
||||
linters-settings:
|
||||
golint:
|
||||
min-confidence: 0.1
|
||||
goimports:
|
||||
local-prefixes: github.com/dexidp/dex
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- funlen
|
||||
- maligned
|
||||
- wsl
|
||||
|
||||
# TODO: fix me
|
||||
- unparam
|
||||
- golint
|
||||
- goconst
|
||||
- staticcheck
|
||||
- nakedret
|
||||
- errcheck
|
||||
- gosec
|
||||
- gochecknoinits
|
||||
- gochecknoglobals
|
||||
- prealloc
|
||||
- scopelint
|
||||
- lll
|
||||
- dupl
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- godox
|
|
@ -48,7 +48,6 @@ install:
|
|||
|
||||
script:
|
||||
- make testall
|
||||
- ./scripts/test-k8s.sh
|
||||
- make verify-proto # Ensure proto generation doesn't depend on external packages.
|
||||
|
||||
notifications:
|
||||
|
|
79
Documentation/connectors/openshift.md
Normal file
79
Documentation/connectors/openshift.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Authentication using OpenShift
|
||||
|
||||
## Overview
|
||||
|
||||
Dex can make use of users and groups defined within OpenShift by querying the platform provided OAuth server.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
### Creating an OAuth Client
|
||||
|
||||
Two forms of OAuth Clients can be utilized:
|
||||
|
||||
* [Using a Service Account as an OAuth Client](https://docs.openshift.com/container-platform/latest/authentication/using-service-accounts-as-oauth-client.html) (Recommended)
|
||||
* [Registering An Additional OAuth Client](https://docs.openshift.com/container-platform/latest/authentication/configuring-internal-oauth.html#oauth-register-additional-client_configuring-internal-oauth)
|
||||
|
||||
#### Using a Service Account as an OAuth Client
|
||||
|
||||
OpenShift Service Accounts can be used as a constrained form of OAuth client. Making use of a Service Account to represent an OAuth Client is the recommended option as it does not require elevated privileged within the OpenShift cluster. Create a new Service Account or make use of an existing Service Account.
|
||||
|
||||
Patch the Service Account to add an annotation for location of the Redirect URI
|
||||
|
||||
```
|
||||
oc patch serviceaccount <name> --type='json' -p='[{"op": "add", "path": "/metadata/annotations/serviceaccounts.openshift.io~1oauth-redirecturi.dex", "value":"https:///<dex_url>/callback"}]'
|
||||
```
|
||||
|
||||
The Client ID for a Service Account representing an OAuth Client takes the form `
|
||||
|
||||
The Client Secret for a Service Account representing an OAuth Client is the long lived OAuth Token that is configued for the Service Account. Execute the following command to retrieve the OAuth Token.
|
||||
|
||||
```
|
||||
oc serviceaccounts get-token <name>
|
||||
```
|
||||
|
||||
#### Registering An Additional OAuth Client
|
||||
|
||||
Instead of using a constrained form of Service Account to represent an OAuth Client, an additional OAuthClient resource can be created.
|
||||
|
||||
Create a new OAuthClient resource similar to the following:
|
||||
|
||||
```yaml
|
||||
kind: OAuthClient
|
||||
apiVersion: oauth.openshift.io/v1
|
||||
metadata:
|
||||
name: dex
|
||||
# The value that should be utilized as the `client_secret`
|
||||
secret: "<clientSecret>"
|
||||
# List of valid addresses for the callback. Ensure one of the values that are provided is `(dex issuer)/callback`
|
||||
redirectURIs:
|
||||
- "https:///<dex_url>/callback"
|
||||
grantMethod: prompt
|
||||
```
|
||||
|
||||
### Dex Configuration
|
||||
|
||||
The following is an example of a configuration for `examples/config-dev.yaml`:
|
||||
|
||||
```yaml
|
||||
connectors:
|
||||
- type: openshift
|
||||
# Required field for connector id.
|
||||
id: openshift
|
||||
# Required field for connector name.
|
||||
name: OpenShift
|
||||
config:
|
||||
# OpenShift API
|
||||
issuer: https://api.mycluster.example.com:6443
|
||||
# Credentials can be string literals or pulled from the environment.
|
||||
clientID: $OPENSHIFT_OAUTH_CLIENT_ID
|
||||
clientSecret: $OPENSHIFT_OAUTH_CLIENT_SECRET
|
||||
redirectURI: http://127.0.0.1:5556/dex/
|
||||
# Optional: Specify whether to communicate to OpenShift without validating SSL ceertificates
|
||||
insecureCA: false
|
||||
# Optional: The location of file containing SSL certificates to commmunicate to OpenShift
|
||||
rootCA: /etc/ssl/openshift.pem
|
||||
# Optional list of required groups a user mmust be a member of
|
||||
groups:
|
||||
- users
|
||||
```
|
|
@ -1,21 +1,5 @@
|
|||
# Running integration tests
|
||||
|
||||
## Kubernetes
|
||||
|
||||
Kubernetes tests run against a Kubernetes API server, and are enabled by the `DEX_KUBECONFIG` environment variable:
|
||||
|
||||
```
|
||||
$ export DEX_KUBECONFIG=~/.kube/config
|
||||
$ go test -v -i ./storage/kubernetes
|
||||
$ go test -v ./storage/kubernetes
|
||||
```
|
||||
|
||||
These tests can be executed locally using docker by running the following script:
|
||||
|
||||
```
|
||||
$ ./scripts/test-k8s.sh
|
||||
```
|
||||
|
||||
## Postgres
|
||||
|
||||
Running database tests locally requires:
|
||||
|
|
|
@ -14,11 +14,13 @@ Steps:
|
|||
```yaml
|
||||
frontend:
|
||||
dir: /path/to/custom/web
|
||||
issuer: my-dex
|
||||
extra:
|
||||
tos_footer_link: "https://example.com/terms"
|
||||
client_logo_url: "../theme/client-logo.png"
|
||||
foo: "bar"
|
||||
```
|
||||
5. Set the `frontend.dir` value to your own `web` directory.
|
||||
6. Write the issuer in the `issuer` directory in order to modify the Dex title and the `Log in to <<dex>>` tag.
|
||||
|
||||
To test your templates simply run Dex with a valid configuration and go through a login flow.
|
45
Makefile
45
Makefile
|
@ -2,7 +2,6 @@ PROJ=dex
|
|||
ORG_PATH=github.com/dexidp
|
||||
REPO_PATH=$(ORG_PATH)/$(PROJ)
|
||||
export PATH := $(PWD)/bin:$(PATH)
|
||||
THIS_DIRECTORY:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||
|
||||
VERSION ?= $(shell ./scripts/git-version)
|
||||
|
||||
|
@ -18,6 +17,9 @@ export GOBIN=$(PWD)/bin
|
|||
|
||||
LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)"
|
||||
|
||||
# Dependency versions
|
||||
GOLANGCI_VERSION = 1.21.0
|
||||
|
||||
build: bin/dex bin/example-app bin/grpc-client
|
||||
|
||||
bin/dex:
|
||||
|
@ -39,20 +41,38 @@ revendor:
|
|||
@go mod vendor -v
|
||||
@go mod verify
|
||||
|
||||
test:
|
||||
test: bin/test/kube-apiserver bin/test/etcd
|
||||
@go test -v ./...
|
||||
|
||||
testrace:
|
||||
testrace: bin/test/kube-apiserver bin/test/etcd
|
||||
@go test -v --race ./...
|
||||
|
||||
vet:
|
||||
@go vet ./...
|
||||
export TEST_ASSET_KUBE_APISERVER=$(abspath bin/test/kube-apiserver)
|
||||
export TEST_ASSET_ETCD=$(abspath bin/test/etcd)
|
||||
|
||||
fmt:
|
||||
@./scripts/gofmt ./...
|
||||
bin/test/kube-apiserver:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/kube-apiserver-$(shell uname)-x86_64 > bin/test/kube-apiserver
|
||||
chmod +x bin/test/kube-apiserver
|
||||
|
||||
lint: bin/golint
|
||||
@./bin/golint -set_exit_status $(shell go list ./...)
|
||||
bin/test/etcd:
|
||||
@mkdir -p bin/test
|
||||
curl -L https://storage.googleapis.com/k8s-c10s-test-binaries/etcd-$(shell uname)-x86_64 > bin/test/etcd
|
||||
chmod +x bin/test/etcd
|
||||
|
||||
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}
|
||||
@mv bin/golangci-lint $@
|
||||
|
||||
.PHONY: lint
|
||||
lint: bin/golangci-lint ## Run linter
|
||||
bin/golangci-lint run
|
||||
|
||||
.PHONY: fix
|
||||
fix: bin/golangci-lint ## Fix lint violations
|
||||
bin/golangci-lint run --fix
|
||||
|
||||
.PHONY: docker-image
|
||||
docker-image:
|
||||
|
@ -73,14 +93,11 @@ bin/protoc: scripts/get-protoc
|
|||
bin/protoc-gen-go:
|
||||
@go install -v $(REPO_PATH)/vendor/github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
bin/golint:
|
||||
@go install -v $(THIS_DIRECTORY)/vendor/golang.org/x/lint/golint
|
||||
|
||||
clean:
|
||||
@rm -rf bin/
|
||||
|
||||
testall: testrace vet fmt lint
|
||||
testall: testrace
|
||||
|
||||
FORCE:
|
||||
|
||||
.PHONY: test testrace vet fmt lint testall
|
||||
.PHONY: test testrace testall
|
||||
|
|
|
@ -74,6 +74,7 @@ Dex implements the following connectors:
|
|||
| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | no | beta | |
|
||||
| [AuthProxy](Documentation/connectors/authproxy.md) | no | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
|
||||
| [Bitbucket Cloud](Documentation/connectors/bitbucketcloud.md) | yes | yes | no | alpha | |
|
||||
| [OpenShift](Documentation/connectors/openshift.md) | no | yes | no | stable | |
|
||||
|
||||
Stable, beta, and alpha are defined as:
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"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"
|
||||
)
|
||||
|
@ -215,7 +216,6 @@ logger:
|
|||
if diff := pretty.Compare(c, want); diff != "" {
|
||||
t.Errorf("got!=want: %s", diff)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestUnmarshalConfigWithEnv(t *testing.T) {
|
||||
|
|
|
@ -182,7 +182,6 @@ func serve(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("failed to initialize storage connectors: %v", err)
|
||||
}
|
||||
storageConnectors[i] = conn
|
||||
|
||||
}
|
||||
|
||||
if c.EnablePasswordDB {
|
||||
|
|
|
@ -143,7 +143,7 @@ func cmd() *cobra.Command {
|
|||
ctx := oidc.ClientContext(context.Background(), a.client)
|
||||
provider, err := oidc.NewProvider(ctx, issuerURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to query provider %q: %v", issuerURL, err)
|
||||
return fmt.Errorf("failed to query provider %q: %v", issuerURL, err)
|
||||
}
|
||||
|
||||
var s struct {
|
||||
|
@ -153,7 +153,7 @@ func cmd() *cobra.Command {
|
|||
ScopesSupported []string `json:"scopes_supported"`
|
||||
}
|
||||
if err := provider.Claims(&s); err != nil {
|
||||
return fmt.Errorf("Failed to parse provider scopes_supported: %v", err)
|
||||
return fmt.Errorf("failed to parse provider scopes_supported: %v", err)
|
||||
}
|
||||
|
||||
if len(s.ScopesSupported) == 0 {
|
||||
|
|
|
@ -41,7 +41,6 @@ type Config struct {
|
|||
|
||||
// Open returns a strategy for logging in through Bitbucket.
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
|
||||
b := bitbucketConnector{
|
||||
redirectURI: c.RedirectURI,
|
||||
teams: c.Teams,
|
||||
|
@ -373,7 +372,6 @@ type userTeamsResponse struct {
|
|||
}
|
||||
|
||||
func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client) ([]string, error) {
|
||||
|
||||
var teams []string
|
||||
apiURL := b.apiURL + "/teams?role=member"
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
)
|
||||
|
||||
func TestUserGroups(t *testing.T) {
|
||||
|
||||
teamsResponse := userTeamsResponse{
|
||||
pagedResponse: pagedResponse{
|
||||
Size: 3,
|
||||
|
@ -46,7 +45,6 @@ func TestUserGroups(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUserWithoutTeams(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/teams?role=member": userTeamsResponse{},
|
||||
})
|
||||
|
@ -61,7 +59,6 @@ func TestUserWithoutTeams(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/user": user{Username: "some-login"},
|
||||
"/user/emails": userEmailResponse{
|
||||
|
|
|
@ -67,7 +67,6 @@ type Org struct {
|
|||
|
||||
// Open returns a strategy for logging in through GitHub.
|
||||
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
|
||||
|
||||
if c.Org != "" {
|
||||
// Return error if both 'org' and 'orgs' fields are used.
|
||||
if len(c.Orgs) > 0 {
|
||||
|
@ -107,7 +106,6 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error)
|
|||
if g.httpClient, err = newHTTPClient(g.rootCA); err != nil {
|
||||
return nil, fmt.Errorf("failed to create HTTP client: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
g.loadAllGroups = c.LoadAllGroups
|
||||
|
||||
|
@ -144,7 +142,7 @@ type githubConnector struct {
|
|||
hostName string
|
||||
// Used to support untrusted/self-signed CA certs.
|
||||
rootCA string
|
||||
// HTTP Client that trusts the custom delcared rootCA cert.
|
||||
// HTTP Client that trusts the custom declared rootCA cert.
|
||||
httpClient *http.Client
|
||||
// optional choice between 'name' (default) or 'slug'
|
||||
teamNameField string
|
||||
|
@ -206,7 +204,7 @@ func (e *oauth2Error) Error() string {
|
|||
return e.error + ": " + e.errorDescription
|
||||
}
|
||||
|
||||
// newHTTPClient returns a new HTTP client that trusts the custom delcared rootCA cert.
|
||||
// newHTTPClient returns a new HTTP client that trusts the custom declared rootCA cert.
|
||||
func newHTTPClient(rootCA string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCA)
|
||||
|
|
|
@ -126,7 +126,6 @@ func TestUserGroupsWithTeamNameAndSlugFieldConfig(t *testing.T) {
|
|||
|
||||
// tests that the users login is used as their username when they have no username set
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]testResponse{
|
||||
"/user": {data: user{Login: "some-login", ID: 12345678}},
|
||||
"/user/emails": {data: []userEmail{{
|
||||
|
@ -168,7 +167,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]testResponse{
|
||||
"/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}},
|
||||
"/user/emails": {data: []userEmail{{
|
||||
|
|
|
@ -65,7 +65,6 @@ func TestUserGroupsWithoutOrgs(t *testing.T) {
|
|||
|
||||
// tests that the email is used as their username when they have no username set
|
||||
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
|
@ -102,7 +101,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
|
@ -130,7 +128,6 @@ func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoginWithTeamWhitelisted(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
|
@ -158,7 +155,6 @@ func TestLoginWithTeamWhitelisted(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoginWithTeamNonWhitelisted(t *testing.T) {
|
||||
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
|
|
|
@ -11,12 +11,12 @@ import (
|
|||
|
||||
"github.com/coreos/go-oidc"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
admin "google.golang.org/api/admin/directory/v1"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
pkg_groups "github.com/dexidp/dex/pkg/groups"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
"golang.org/x/oauth2/google"
|
||||
admin "google.golang.org/api/admin/directory/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -105,7 +105,6 @@ type googleConnector struct {
|
|||
redirectURI string
|
||||
oauth2Config *oauth2.Config
|
||||
verifier *oidc.IDTokenVerifier
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
hostedDomains []string
|
||||
|
|
|
@ -150,7 +150,6 @@ func (p *conn) Prompt() string { return "username" }
|
|||
|
||||
func (p *conn) Refresh(
|
||||
ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
|
||||
|
||||
token, err := p.getAdminToken(ctx)
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err)
|
||||
|
@ -210,6 +209,8 @@ func (p *conn) getAdminToken(ctx context.Context) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
token := resp.Header.Get("X-Subject-Token")
|
||||
return token, nil
|
||||
}
|
||||
|
@ -229,6 +230,7 @@ func (p *conn) checkIfUserExists(ctx context.Context, userID string, token strin
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return true, nil
|
||||
|
|
|
@ -154,7 +154,12 @@ func delete(t *testing.T, token, id, uri string) {
|
|||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Auth-Token", token)
|
||||
client.Do(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
func createGroup(t *testing.T, token, description, name string) string {
|
||||
|
@ -208,7 +213,13 @@ func addUserToGroup(t *testing.T, token, groupID, userID string) error {
|
|||
return err
|
||||
}
|
||||
req.Header.Set("X-Auth-Token", token)
|
||||
client.Do(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -274,7 +285,7 @@ func TestUseRefreshToken(t *testing.T) {
|
|||
delete(t, token, groupID, groupsURL)
|
||||
|
||||
expectEquals(t, 1, len(identityRefresh.Groups))
|
||||
expectEquals(t, testGroup, string(identityRefresh.Groups[0]))
|
||||
expectEquals(t, testGroup, identityRefresh.Groups[0])
|
||||
}
|
||||
|
||||
func TestUseRefreshTokenUserDeleted(t *testing.T) {
|
||||
|
|
|
@ -189,7 +189,6 @@ func (c *Config) OpenConnector(logger log.Logger) (interface {
|
|||
}
|
||||
|
||||
func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
|
||||
|
||||
requiredFields := []struct {
|
||||
name string
|
||||
val string
|
||||
|
@ -365,7 +364,6 @@ func (c *ldapConnector) identityFromEntry(user ldap.Entry) (ident connector.Iden
|
|||
}
|
||||
|
||||
func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.Entry, found bool, err error) {
|
||||
|
||||
filter := fmt.Sprintf("(%s=%s)", c.UserSearch.Username, ldap.EscapeFilter(username))
|
||||
if c.UserSearch.Filter != "" {
|
||||
filter = fmt.Sprintf("(&%s%s)", c.UserSearch.Filter, filter)
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc"
|
||||
|
@ -85,18 +84,6 @@ func knownBrokenAuthHeaderProvider(issuerURL string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// golang.org/x/oauth2 doesn't do internal locking. Need to do it in this
|
||||
// package ourselves and hope that other packages aren't calling it at the
|
||||
// same time.
|
||||
var registerMu = new(sync.Mutex)
|
||||
|
||||
func registerBrokenAuthHeaderProvider(url string) {
|
||||
registerMu.Lock()
|
||||
defer registerMu.Unlock()
|
||||
|
||||
oauth2.RegisterBrokenAuthHeaderProvider(url)
|
||||
}
|
||||
|
||||
// Open returns a connector which can be used to login users through an upstream
|
||||
// OpenID Connect provider.
|
||||
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
|
||||
|
@ -108,13 +95,15 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
return nil, fmt.Errorf("failed to get provider: %v", err)
|
||||
}
|
||||
|
||||
endpoint := provider.Endpoint()
|
||||
|
||||
if c.BasicAuthUnsupported != nil {
|
||||
// Setting "basicAuthUnsupported" always overrides our detection.
|
||||
if *c.BasicAuthUnsupported {
|
||||
registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
|
||||
endpoint.AuthStyle = oauth2.AuthStyleInParams
|
||||
}
|
||||
} else if knownBrokenAuthHeaderProvider(c.Issuer) {
|
||||
registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL)
|
||||
endpoint.AuthStyle = oauth2.AuthStyleInParams
|
||||
}
|
||||
|
||||
scopes := []string{oidc.ScopeOpenID}
|
||||
|
@ -131,7 +120,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
|
|||
oauth2Config: &oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
Endpoint: provider.Endpoint(),
|
||||
Endpoint: endpoint,
|
||||
Scopes: scopes,
|
||||
RedirectURL: c.RedirectURI,
|
||||
},
|
||||
|
@ -273,15 +262,25 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
|
|||
if !found {
|
||||
return identity, fmt.Errorf("missing \"%s\" claim", userNameKey)
|
||||
}
|
||||
|
||||
hasEmailScope := false
|
||||
for _, s := range c.oauth2Config.Scopes {
|
||||
if s == "email" {
|
||||
hasEmailScope = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
email, found := claims["email"].(string)
|
||||
if !found {
|
||||
if !found && hasEmailScope {
|
||||
return identity, errors.New("missing \"email\" claim")
|
||||
}
|
||||
|
||||
emailVerified, found := claims["email_verified"].(bool)
|
||||
if !found {
|
||||
if c.insecureSkipEmailVerified {
|
||||
emailVerified = true
|
||||
} else {
|
||||
} else if hasEmailScope {
|
||||
return identity, errors.New("missing \"email_verified\" claim")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
)
|
||||
|
||||
func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
|
||||
|
@ -49,8 +50,10 @@ func TestHandleCallback(t *testing.T) {
|
|||
userIDKey string
|
||||
userNameKey string
|
||||
insecureSkipEmailVerified bool
|
||||
scopes []string
|
||||
expectUserID string
|
||||
expectUserName string
|
||||
expectedEmailField string
|
||||
token map[string]interface{}
|
||||
}{
|
||||
{
|
||||
|
@ -59,6 +62,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
userNameKey: "", // not configured
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "emailvalue",
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
|
@ -71,6 +75,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
insecureSkipEmailVerified: true,
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "emailvalue",
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
|
@ -82,6 +87,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
userIDKey: "name",
|
||||
expectUserID: "namevalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "emailvalue",
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
|
@ -94,6 +100,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
userNameKey: "user_name",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "username",
|
||||
expectedEmailField: "emailvalue",
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"user_name": "username",
|
||||
|
@ -101,6 +108,33 @@ func TestHandleCallback(t *testing.T) {
|
|||
"email_verified": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "emptyEmailScope",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "",
|
||||
scopes: []string{"groups"},
|
||||
insecureSkipEmailVerified: true,
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
"user_name": "username",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "emptyEmailScopeButEmailProvided",
|
||||
expectUserID: "subvalue",
|
||||
expectUserName: "namevalue",
|
||||
expectedEmailField: "emailvalue",
|
||||
scopes: []string{"groups"},
|
||||
insecureSkipEmailVerified: true,
|
||||
token: map[string]interface{}{
|
||||
"sub": "subvalue",
|
||||
"name": "namevalue",
|
||||
"user_name": "username",
|
||||
"email": "emailvalue",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
@ -110,16 +144,25 @@ func TestHandleCallback(t *testing.T) {
|
|||
t.Fatal("failed to setup test server", err)
|
||||
}
|
||||
defer testServer.Close()
|
||||
|
||||
var scopes []string
|
||||
if len(tc.scopes) > 0 {
|
||||
scopes = tc.scopes
|
||||
} else {
|
||||
scopes = []string{"email", "groups"}
|
||||
}
|
||||
serverURL := testServer.URL
|
||||
basicAuth := true
|
||||
config := Config{
|
||||
Issuer: serverURL,
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Scopes: []string{"groups"},
|
||||
Scopes: scopes,
|
||||
RedirectURI: fmt.Sprintf("%s/callback", serverURL),
|
||||
UserIDKey: tc.userIDKey,
|
||||
UserNameKey: tc.userNameKey,
|
||||
InsecureSkipEmailVerified: tc.insecureSkipEmailVerified,
|
||||
BasicAuthUnsupported: &basicAuth,
|
||||
}
|
||||
|
||||
conn, err := newConnector(config)
|
||||
|
@ -139,7 +182,7 @@ func TestHandleCallback(t *testing.T) {
|
|||
|
||||
expectEquals(t, identity.UserID, tc.expectUserID)
|
||||
expectEquals(t, identity.Username, tc.expectUserName)
|
||||
expectEquals(t, identity.Email, "emailvalue")
|
||||
expectEquals(t, identity.Email, tc.expectedEmailField)
|
||||
expectEquals(t, identity.EmailVerified, true)
|
||||
})
|
||||
}
|
||||
|
|
254
connector/openshift/openshift.go
Normal file
254
connector/openshift/openshift.go
Normal file
|
@ -0,0 +1,254 @@
|
|||
package openshift
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/dexidp/dex/pkg/groups"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
|
||||
"github.com/dexidp/dex/storage/kubernetes/k8sapi"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Config holds configuration options for OpenShift login
|
||||
type Config struct {
|
||||
Issuer string `json:"issuer"`
|
||||
ClientID string `json:"clientID"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
RedirectURI string `json:"redirectURI"`
|
||||
Groups []string `json:"groups"`
|
||||
InsecureCA bool `json:"insecureCA"`
|
||||
RootCA string `json:"rootCA"`
|
||||
}
|
||||
|
||||
var (
|
||||
_ connector.CallbackConnector = (*openshiftConnector)(nil)
|
||||
)
|
||||
|
||||
type openshiftConnector struct {
|
||||
apiURL string
|
||||
redirectURI string
|
||||
clientID string
|
||||
clientSecret string
|
||||
cancel context.CancelFunc
|
||||
logger log.Logger
|
||||
httpClient *http.Client
|
||||
oauth2Config *oauth2.Config
|
||||
insecureCA bool
|
||||
rootCA string
|
||||
groups []string
|
||||
}
|
||||
|
||||
type user struct {
|
||||
k8sapi.TypeMeta `json:",inline"`
|
||||
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
||||
Identities []string `json:"identities" protobuf:"bytes,3,rep,name=identities"`
|
||||
FullName string `json:"fullName,omitempty" protobuf:"bytes,2,opt,name=fullName"`
|
||||
Groups []string `json:"groups" protobuf:"bytes,4,rep,name=groups"`
|
||||
}
|
||||
|
||||
// Open returns a connector which can be used to login users through an upstream
|
||||
// OpenShift OAuth2 provider.
|
||||
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
wellKnownURL := strings.TrimSuffix(c.Issuer, "/") + "/.well-known/oauth-authorization-server"
|
||||
req, err := http.NewRequest(http.MethodGet, wellKnownURL, nil)
|
||||
|
||||
openshiftConnector := openshiftConnector{
|
||||
apiURL: c.Issuer,
|
||||
cancel: cancel,
|
||||
clientID: c.ClientID,
|
||||
clientSecret: c.ClientSecret,
|
||||
insecureCA: c.InsecureCA,
|
||||
logger: logger,
|
||||
redirectURI: c.RedirectURI,
|
||||
rootCA: c.RootCA,
|
||||
groups: c.Groups,
|
||||
}
|
||||
|
||||
if openshiftConnector.httpClient, err = newHTTPClient(c.InsecureCA, c.RootCA); err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to create HTTP client: %v", err)
|
||||
}
|
||||
|
||||
var metadata struct {
|
||||
Auth string `json:"authorization_endpoint"`
|
||||
Token string `json:"token_endpoint"`
|
||||
}
|
||||
|
||||
resp, err := openshiftConnector.httpClient.Do(req.WithContext(ctx))
|
||||
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to query OpenShift endpoint %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("discovery through endpoint %s failed to decode body: %v",
|
||||
wellKnownURL, err)
|
||||
}
|
||||
|
||||
openshiftConnector.oauth2Config = &oauth2.Config{
|
||||
ClientID: c.ClientID,
|
||||
ClientSecret: c.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: metadata.Auth, TokenURL: metadata.Token,
|
||||
},
|
||||
Scopes: []string{"user:info"},
|
||||
RedirectURL: c.RedirectURI,
|
||||
}
|
||||
return &openshiftConnector, nil
|
||||
}
|
||||
|
||||
func (c *openshiftConnector) Close() error {
|
||||
c.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginURL returns the URL to redirect the user to login with.
|
||||
func (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
|
||||
if c.redirectURI != callbackURL {
|
||||
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
|
||||
}
|
||||
return c.oauth2Config.AuthCodeURL(state), nil
|
||||
}
|
||||
|
||||
type oauth2Error struct {
|
||||
error string
|
||||
errorDescription string
|
||||
}
|
||||
|
||||
func (e *oauth2Error) Error() string {
|
||||
if e.errorDescription == "" {
|
||||
return e.error
|
||||
}
|
||||
return e.error + ": " + e.errorDescription
|
||||
}
|
||||
|
||||
// HandleCallback parses the request and returns the user's identity
|
||||
func (c *openshiftConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
return identity, &oauth2Error{errType, q.Get("error_description")}
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
if c.httpClient != nil {
|
||||
ctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)
|
||||
}
|
||||
|
||||
token, err := c.oauth2Config.Exchange(ctx, q.Get("code"))
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("oidc: failed to get token: %v", err)
|
||||
}
|
||||
|
||||
client := c.oauth2Config.Client(ctx, token)
|
||||
|
||||
user, err := c.user(ctx, client)
|
||||
|
||||
if err != nil {
|
||||
return identity, fmt.Errorf("openshift: get user: %v", err)
|
||||
}
|
||||
|
||||
if len(c.groups) > 0 {
|
||||
validGroups := validateAllowedGroups(user.Groups, c.groups)
|
||||
|
||||
if !validGroups {
|
||||
return identity, fmt.Errorf("openshift: user %q is not in any of the required groups", user.Name)
|
||||
}
|
||||
}
|
||||
|
||||
identity = connector.Identity{
|
||||
UserID: user.UID,
|
||||
Username: user.Name,
|
||||
PreferredUsername: user.Name,
|
||||
Groups: user.Groups,
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// user function returns the OpenShift user associated with the authenticated user
|
||||
func (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {
|
||||
url := c.apiURL + "/apis/user.openshift.io/v1/users/~"
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("new req: %v", err)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("get URL %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("read body: %v", err)
|
||||
}
|
||||
return u, fmt.Errorf("%s: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
|
||||
return u, fmt.Errorf("JSON decode: %v", err)
|
||||
}
|
||||
|
||||
return u, err
|
||||
}
|
||||
|
||||
func validateAllowedGroups(userGroups, allowedGroups []string) bool {
|
||||
matchingGroups := groups.Filter(userGroups, allowedGroups)
|
||||
|
||||
return len(matchingGroups) != 0
|
||||
}
|
||||
|
||||
// newHTTPClient returns a new HTTP client
|
||||
func newHTTPClient(insecureCA bool, rootCA string) (*http.Client, error) {
|
||||
tlsConfig := tls.Config{}
|
||||
|
||||
if insecureCA {
|
||||
tlsConfig = tls.Config{InsecureSkipVerify: true}
|
||||
} else if rootCA != "" {
|
||||
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
|
||||
rootCABytes, err := ioutil.ReadFile(rootCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read root-ca: %v", err)
|
||||
}
|
||||
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
|
||||
return nil, fmt.Errorf("no certs found in root CA file %q", rootCA)
|
||||
}
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tlsConfig,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
218
connector/openshift/openshift_test.go
Normal file
218
connector/openshift/openshift_test.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
package openshift
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
|
||||
"github.com/dexidp/dex/storage/kubernetes/k8sapi"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{})
|
||||
defer s.Close()
|
||||
|
||||
hostURL, err := url.Parse(s.URL)
|
||||
expectNil(t, err)
|
||||
|
||||
_, err = http.NewRequest("GET", hostURL.String(), nil)
|
||||
expectNil(t, err)
|
||||
|
||||
c := Config{
|
||||
Issuer: s.URL,
|
||||
ClientID: "testClientId",
|
||||
ClientSecret: "testClientSecret",
|
||||
RedirectURI: "https://localhost/callback",
|
||||
InsecureCA: true,
|
||||
}
|
||||
|
||||
logger := logrus.New()
|
||||
|
||||
oconfig, err := c.Open("id", logger)
|
||||
|
||||
oc, ok := oconfig.(*openshiftConnector)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, ok, true)
|
||||
expectEquals(t, oc.apiURL, s.URL)
|
||||
expectEquals(t, oc.clientID, "testClientId")
|
||||
expectEquals(t, oc.clientSecret, "testClientSecret")
|
||||
expectEquals(t, oc.redirectURI, "https://localhost/callback")
|
||||
expectEquals(t, oc.oauth2Config.Endpoint.AuthURL, fmt.Sprintf("%s/oauth/authorize", s.URL))
|
||||
expectEquals(t, oc.oauth2Config.Endpoint.TokenURL, fmt.Sprintf("%s/oauth/token", s.URL))
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/apis/user.openshift.io/v1/users/~": user{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "jdoe",
|
||||
},
|
||||
FullName: "John Doe",
|
||||
Groups: []string{"users"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
hostURL, err := url.Parse(s.URL)
|
||||
expectNil(t, err)
|
||||
|
||||
_, err = http.NewRequest("GET", hostURL.String(), nil)
|
||||
expectNil(t, err)
|
||||
|
||||
h, err := newHTTPClient(true, "")
|
||||
|
||||
expectNil(t, err)
|
||||
|
||||
oc := openshiftConnector{apiURL: s.URL, httpClient: h}
|
||||
u, err := oc.user(context.Background(), h)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, u.Name, "jdoe")
|
||||
expectEquals(t, u.FullName, "John Doe")
|
||||
expectEquals(t, len(u.Groups), 1)
|
||||
}
|
||||
|
||||
func TestVerifySingleGroupFn(t *testing.T) {
|
||||
allowedGroups := []string{"users"}
|
||||
groupMembership := []string{"users", "org1"}
|
||||
|
||||
validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)
|
||||
|
||||
expectEquals(t, validGroupMembership, true)
|
||||
}
|
||||
|
||||
func TestVerifySingleGroupFailureFn(t *testing.T) {
|
||||
allowedGroups := []string{"admins"}
|
||||
groupMembership := []string{"users"}
|
||||
|
||||
validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)
|
||||
|
||||
expectEquals(t, validGroupMembership, false)
|
||||
}
|
||||
|
||||
func TestVerifyMultipleGroupFn(t *testing.T) {
|
||||
allowedGroups := []string{"users", "admins"}
|
||||
groupMembership := []string{"users", "org1"}
|
||||
|
||||
validGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)
|
||||
|
||||
expectEquals(t, validGroupMembership, true)
|
||||
}
|
||||
|
||||
func TestVerifyGroup(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/apis/user.openshift.io/v1/users/~": user{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "jdoe",
|
||||
},
|
||||
FullName: "John Doe",
|
||||
Groups: []string{"users"},
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
hostURL, err := url.Parse(s.URL)
|
||||
expectNil(t, err)
|
||||
|
||||
_, err = http.NewRequest("GET", hostURL.String(), nil)
|
||||
expectNil(t, err)
|
||||
|
||||
h, err := newHTTPClient(true, "")
|
||||
|
||||
expectNil(t, err)
|
||||
|
||||
oc := openshiftConnector{apiURL: s.URL, httpClient: h}
|
||||
u, err := oc.user(context.Background(), h)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, u.Name, "jdoe")
|
||||
expectEquals(t, u.FullName, "John Doe")
|
||||
expectEquals(t, len(u.Groups), 1)
|
||||
}
|
||||
|
||||
func TestCallbackIdentity(t *testing.T) {
|
||||
s := newTestServer(map[string]interface{}{
|
||||
"/apis/user.openshift.io/v1/users/~": user{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "jdoe",
|
||||
UID: "12345",
|
||||
},
|
||||
FullName: "John Doe",
|
||||
Groups: []string{"users"},
|
||||
},
|
||||
"/oauth/token": map[string]interface{}{
|
||||
"access_token": "oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC",
|
||||
"expires_in": "30",
|
||||
},
|
||||
})
|
||||
defer s.Close()
|
||||
|
||||
hostURL, err := url.Parse(s.URL)
|
||||
expectNil(t, err)
|
||||
|
||||
req, err := http.NewRequest("GET", hostURL.String(), nil)
|
||||
expectNil(t, err)
|
||||
|
||||
h, err := newHTTPClient(true, "")
|
||||
|
||||
expectNil(t, err)
|
||||
|
||||
oc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/oauth/authorize", s.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", s.URL),
|
||||
},
|
||||
}}
|
||||
identity, err := oc.HandleCallback(connector.Scopes{Groups: true}, req)
|
||||
|
||||
expectNil(t, err)
|
||||
expectEquals(t, identity.UserID, "12345")
|
||||
expectEquals(t, identity.Username, "jdoe")
|
||||
expectEquals(t, identity.PreferredUsername, "jdoe")
|
||||
expectEquals(t, len(identity.Groups), 1)
|
||||
expectEquals(t, identity.Groups[0], "users")
|
||||
}
|
||||
|
||||
func newTestServer(responses map[string]interface{}) *httptest.Server {
|
||||
var s *httptest.Server
|
||||
s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
responses["/.well-known/oauth-authorization-server"] = map[string]interface{}{
|
||||
"issuer": s.URL,
|
||||
"authorization_endpoint": fmt.Sprintf("%s/oauth/authorize", s.URL),
|
||||
"token_endpoint": fmt.Sprintf("%s/oauth/token", s.URL),
|
||||
"scopes_supported": []string{"user:full", "user:info", "user:check-access", "user:list-scoped-projects", "user:list-projects"},
|
||||
"response_types_supported": []string{"token", "code"},
|
||||
"grant_types_supported": []string{"authorization_code", "implicit"},
|
||||
"code_challenge_methods_supported": []string{"plain", "S256"},
|
||||
}
|
||||
|
||||
response := responses[r.RequestURI]
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func expectNil(t *testing.T, a interface{}) {
|
||||
if a != nil {
|
||||
t.Errorf("Expected %+v to equal nil", a)
|
||||
}
|
||||
}
|
||||
|
||||
func expectEquals(t *testing.T, a interface{}, b interface{}) {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("Expected %+v to equal %+v", a, b)
|
||||
}
|
||||
}
|
|
@ -14,11 +14,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
"github.com/dexidp/dex/pkg/groups"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
"github.com/russellhaering/goxmldsig/etreeutils"
|
||||
)
|
||||
|
||||
// nolint
|
||||
|
@ -248,7 +249,6 @@ type provider struct {
|
|||
}
|
||||
|
||||
func (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) {
|
||||
|
||||
r := &authnRequest{
|
||||
ProtocolBinding: bindingPOST,
|
||||
ID: id,
|
||||
|
@ -325,7 +325,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
|
|||
|
||||
// Status is a required element.
|
||||
if resp.Status == nil {
|
||||
return ident, fmt.Errorf("Response did not contain a Status element")
|
||||
return ident, fmt.Errorf("response did not contain a Status element")
|
||||
}
|
||||
|
||||
if err = p.validateStatus(resp.Status); err != nil {
|
||||
|
@ -398,7 +398,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
|
|||
|
||||
if len(p.allowedGroups) > 0 && (!s.Groups || p.groupsAttr == "") {
|
||||
// allowedGroups set but no groups or groupsAttr. Disallowing.
|
||||
return ident, fmt.Errorf("User not a member of allowed groups")
|
||||
return ident, fmt.Errorf("user not a member of allowed groups")
|
||||
}
|
||||
|
||||
// Grab the groups.
|
||||
|
@ -427,7 +427,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
|
|||
|
||||
if len(groupMatches) == 0 {
|
||||
// No group membership matches found, disallowing
|
||||
return ident, fmt.Errorf("User not a member of allowed groups")
|
||||
return ident, fmt.Errorf("user not a member of allowed groups")
|
||||
}
|
||||
|
||||
// Otherwise, we're good
|
||||
|
@ -468,7 +468,7 @@ func (p *provider) validateStatus(status *status) error {
|
|||
func (p *provider) validateSubject(subject *subject, inResponseTo string) error {
|
||||
// Optional according to the spec, but again, we're going to be strict here.
|
||||
if len(subject.SubjectConfirmations) == 0 {
|
||||
return fmt.Errorf("Subject contained no SubjectConfirmations")
|
||||
return fmt.Errorf("subject contained no SubjectConfirmations")
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
|
1
go.mod
1
go.mod
|
@ -57,6 +57,7 @@ require (
|
|||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
gopkg.in/square/go-jose.v2 v2.3.1
|
||||
sigs.k8s.io/testing_frameworks v0.1.2
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
|
13
go.sum
13
go.sum
|
@ -92,6 +92,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM
|
|||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
|
@ -172,9 +173,11 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
|
@ -273,6 +276,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -300,6 +304,9 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -317,6 +324,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -363,6 +371,8 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
|
||||
|
@ -373,6 +383,7 @@ gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
|
|||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -385,3 +396,5 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
|
||||
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
result=$( go fmt $@ )
|
||||
if [[ $result != "" ]]; then
|
||||
>&2 echo "The following files are not formatted correctly: $result"
|
||||
exit 1
|
||||
fi
|
|
@ -1,56 +0,0 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
TEMPDIR=$( mktemp -d )
|
||||
|
||||
cat << EOF > $TEMPDIR/kubeconfig
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- name: local
|
||||
cluster:
|
||||
server: http://localhost:8080
|
||||
users:
|
||||
- name: local
|
||||
user:
|
||||
contexts:
|
||||
- context:
|
||||
cluster: local
|
||||
user: local
|
||||
EOF
|
||||
|
||||
cleanup () {
|
||||
docker rm -f $( cat $TEMPDIR/etcd )
|
||||
docker rm -f $( cat $TEMPDIR/kube-apiserver )
|
||||
rm -rf $TEMPDIR
|
||||
}
|
||||
|
||||
trap "{ CODE=$?; cleanup ; exit $CODE; }" EXIT
|
||||
|
||||
docker run \
|
||||
--cidfile=$TEMPDIR/etcd \
|
||||
-d \
|
||||
--net=host \
|
||||
gcr.io/google_containers/etcd:3.1.10 \
|
||||
etcd
|
||||
|
||||
docker run \
|
||||
--cidfile=$TEMPDIR/kube-apiserver \
|
||||
-d \
|
||||
-v $TEMPDIR:/var/run/kube-test:ro \
|
||||
--net=host \
|
||||
gcr.io/google_containers/kube-apiserver-amd64:v1.7.4 \
|
||||
kube-apiserver \
|
||||
--etcd-servers=http://localhost:2379 \
|
||||
--service-cluster-ip-range=10.0.0.1/16 \
|
||||
--insecure-bind-address=0.0.0.0 \
|
||||
--insecure-port=8080
|
||||
|
||||
until $(curl --output /dev/null --silent --head --fail http://localhost:8080/healthz); do
|
||||
printf '.'
|
||||
sleep 1
|
||||
done
|
||||
echo "API server ready"
|
||||
|
||||
export DEX_KUBECONFIG=$TEMPDIR/kubeconfig
|
||||
go test -v -i ./storage/kubernetes
|
||||
go test -v ./storage/kubernetes
|
|
@ -218,7 +218,6 @@ func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq)
|
|||
return nil, fmt.Errorf("delete password: %v", err)
|
||||
}
|
||||
return &api.DeletePasswordResp{}, nil
|
||||
|
||||
}
|
||||
|
||||
func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.VersionResp, error) {
|
||||
|
@ -248,7 +247,6 @@ func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*a
|
|||
return &api.ListPasswordResp{
|
||||
Passwords: passwords,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) {
|
||||
|
|
|
@ -167,7 +167,6 @@ func TestPassword(t *testing.T) {
|
|||
if _, err := client.DeletePassword(ctx, &deleteReq); err != nil {
|
||||
t.Fatalf("Unable to delete password: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Ensures checkCost returns expected values
|
||||
|
@ -495,7 +494,6 @@ func TestUpdateClient(t *testing.T) {
|
|||
if tc.cleanup != nil {
|
||||
tc.cleanup(t, tc.req.Id)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@ func (s *Server) newHealthChecker(ctx context.Context) http.Handler {
|
|||
return h
|
||||
}
|
||||
|
||||
// healthChecker periodically performs health checks on server dependenices.
|
||||
// Currently, it only checks that the storage layer is avialable.
|
||||
// healthChecker periodically performs health checks on server dependencies.
|
||||
// Currently, it only checks that the storage layer is available.
|
||||
type healthChecker struct {
|
||||
s *Server
|
||||
|
||||
|
@ -259,16 +259,15 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
connectorInfos := make([]connectorInfo, len(connectors))
|
||||
i := 0
|
||||
for _, conn := range connectors {
|
||||
connectorInfos[i] = connectorInfo{
|
||||
for index, conn := range connectors {
|
||||
connectorInfos[index] = connectorInfo{
|
||||
ID: conn.ID,
|
||||
Name: conn.Name,
|
||||
Type: conn.Type,
|
||||
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
|
||||
// on create the auth request.
|
||||
URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID,
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if err := s.templates.login(r, w, connectorInfos, r.URL.Path); err != nil {
|
||||
|
@ -922,7 +921,6 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
|
|||
deleteToken = true
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
s.writeAccessToken(w, idToken, accessToken, refreshToken, expiry)
|
||||
|
|
|
@ -24,7 +24,6 @@ func TestHandleHealth(t *testing.T) {
|
|||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 got %d", rr.Code)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type badStorage struct {
|
||||
|
|
|
@ -14,6 +14,10 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/dexidp/dex/connector"
|
||||
|
@ -28,13 +32,10 @@ import (
|
|||
"github.com/dexidp/dex/connector/microsoft"
|
||||
"github.com/dexidp/dex/connector/mock"
|
||||
"github.com/dexidp/dex/connector/oidc"
|
||||
"github.com/dexidp/dex/connector/openshift"
|
||||
"github.com/dexidp/dex/connector/saml"
|
||||
"github.com/dexidp/dex/pkg/log"
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/felixge/httpsnoop"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// LocalConnector is the local passwordDB connector which is an internal
|
||||
|
@ -461,6 +462,7 @@ var ConnectorsConfig = map[string]func() ConnectorConfig{
|
|||
"linkedin": func() ConnectorConfig { return new(linkedin.Config) },
|
||||
"microsoft": func() ConnectorConfig { return new(microsoft.Config) },
|
||||
"bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) },
|
||||
"openshift": func() ConnectorConfig { return new(openshift.Config) },
|
||||
// Keep around for backwards compatibility.
|
||||
"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
|
||||
}
|
||||
|
|
|
@ -590,6 +590,8 @@ func TestOAuth2CodeFlow(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -726,6 +728,8 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -799,7 +803,6 @@ func TestCrossClientScopes(t *testing.T) {
|
|||
if !reflect.DeepEqual(idToken.Audience, expAudience) {
|
||||
t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience)
|
||||
}
|
||||
|
||||
}
|
||||
if gotState := q.Get("state"); gotState != state {
|
||||
t.Errorf("state did not match, want=%q got=%q", state, gotState)
|
||||
|
@ -848,6 +851,8 @@ func TestCrossClientScopes(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -921,7 +926,6 @@ func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) {
|
|||
if !reflect.DeepEqual(idToken.Audience, expAudience) {
|
||||
t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience)
|
||||
}
|
||||
|
||||
}
|
||||
if gotState := q.Get("state"); gotState != state {
|
||||
t.Errorf("state did not match, want=%q got=%q", state, gotState)
|
||||
|
@ -969,6 +973,8 @@ func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -1058,7 +1064,6 @@ func TestPasswordDB(t *testing.T) {
|
|||
t.Errorf("%s: %s", tc.name, diff)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPasswordDBUsernamePrompt(t *testing.T) {
|
||||
|
@ -1225,9 +1230,11 @@ func TestRefreshTokenFlow(t *testing.T) {
|
|||
RedirectURL: redirectURL,
|
||||
}
|
||||
|
||||
if _, err = http.Get(oauth2Client.server.URL + "/login"); err != nil {
|
||||
resp, err := http.Get(oauth2Client.server.URL + "/login")
|
||||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
tok := &oauth2.Token{
|
||||
RefreshToken: oauth2Client.token.RefreshToken,
|
||||
|
@ -1235,9 +1242,11 @@ func TestRefreshTokenFlow(t *testing.T) {
|
|||
}
|
||||
|
||||
// Login in again to receive a new token.
|
||||
if _, err = http.Get(oauth2Client.server.URL + "/login"); err != nil {
|
||||
resp, err = http.Get(oauth2Client.server.URL + "/login")
|
||||
if err != nil {
|
||||
t.Fatalf("get failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// try to refresh expired token with old refresh token.
|
||||
if _, err := oauth2Client.config.TokenSource(ctx, tok).Token(); err == nil {
|
||||
|
|
|
@ -47,19 +47,6 @@ type webConfig struct {
|
|||
extra map[string]string
|
||||
}
|
||||
|
||||
func join(base, path string) string {
|
||||
b := strings.HasSuffix(base, "/")
|
||||
p := strings.HasPrefix(path, "/")
|
||||
switch {
|
||||
case b && p:
|
||||
return base + path[1:]
|
||||
case b || p:
|
||||
return base + path
|
||||
default:
|
||||
return base + "/" + path
|
||||
}
|
||||
}
|
||||
|
||||
func dirExists(dir string) error {
|
||||
stat, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
|
@ -189,7 +176,6 @@ func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
|
|||
//assetPath is static/main.css
|
||||
//relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css"
|
||||
func relativeURL(serverPath, reqPath, assetPath string) string {
|
||||
|
||||
splitPath := func(p string) []string {
|
||||
res := []string{}
|
||||
parts := strings.Split(path.Clean(p), "/")
|
||||
|
@ -220,6 +206,7 @@ func relativeURL(serverPath, reqPath, assetPath string) string {
|
|||
server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath)
|
||||
|
||||
// Remove common prefix of request path with server path
|
||||
// nolint: ineffassign
|
||||
server, req = stripCommonParts(server, req)
|
||||
|
||||
// Remove common prefix of request path with asset path
|
||||
|
@ -246,6 +233,7 @@ type connectorInfo struct {
|
|||
ID string
|
||||
Name string
|
||||
URL string
|
||||
Type string
|
||||
}
|
||||
|
||||
type byName []connectorInfo
|
||||
|
|
|
@ -161,6 +161,8 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) {
|
|||
t.Fatalf("failed to delete auth request: %v", err)
|
||||
}
|
||||
|
||||
_, err = s.GetAuthRequest(a1.ID)
|
||||
mustBeErrNotFound(t, "auth request", err)
|
||||
}
|
||||
|
||||
func testAuthCodeCRUD(t *testing.T, s storage.Storage) {
|
||||
|
@ -214,7 +216,7 @@ func testAuthCodeCRUD(t *testing.T, s storage.Storage) {
|
|||
|
||||
got, err := s.GetAuthCode(a1.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get auth req: %v", err)
|
||||
t.Fatalf("failed to get auth code: %v", err)
|
||||
}
|
||||
if a1.Expiry.Unix() != got.Expiry.Unix() {
|
||||
t.Errorf("auth code expiry did not match want=%s vs got=%s", a1.Expiry, got.Expiry)
|
||||
|
@ -509,7 +511,6 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) {
|
|||
|
||||
_, err = s.GetPassword(password1.Email)
|
||||
mustBeErrNotFound(t, "password", err)
|
||||
|
||||
}
|
||||
|
||||
func testOfflineSessionCRUD(t *testing.T, s storage.Storage) {
|
||||
|
@ -814,7 +815,7 @@ func testGC(t *testing.T, s storage.Storage) {
|
|||
}
|
||||
}
|
||||
if _, err := s.GetAuthRequest(a.ID); err != nil {
|
||||
t.Errorf("expected to be able to get auth code after GC: %v", err)
|
||||
t.Errorf("expected to be able to get auth request after GC: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -825,7 +826,7 @@ func testGC(t *testing.T, s storage.Storage) {
|
|||
}
|
||||
|
||||
if _, err := s.GetAuthRequest(a.ID); err == nil {
|
||||
t.Errorf("expected auth code to be GC'd")
|
||||
t.Errorf("expected auth request to be GC'd")
|
||||
} else if err != storage.ErrNotFound {
|
||||
t.Errorf("expected storage.ErrNotFound, got %v", err)
|
||||
}
|
||||
|
|
|
@ -135,8 +135,8 @@ func testKeysConcurrentUpdate(t *testing.T, s storage.Storage) {
|
|||
for i := 0; i < 2; i++ {
|
||||
n := time.Now().UTC().Round(time.Second)
|
||||
keys1 := storage.Keys{
|
||||
SigningKey: jsonWebKeys[0].Private,
|
||||
SigningKeyPub: jsonWebKeys[0].Public,
|
||||
SigningKey: jsonWebKeys[i].Private,
|
||||
SigningKeyPub: jsonWebKeys[i].Public,
|
||||
NextRotation: n,
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok
|
|||
return c.txnUpdate(ctx, keyID(refreshTokenPrefix, id), func(currentValue []byte) ([]byte, error) {
|
||||
var current RefreshToken
|
||||
if len(currentValue) > 0 {
|
||||
if err := json.Unmarshal([]byte(currentValue), ¤t); err != nil {
|
||||
if err := json.Unmarshal(currentValue, ¤t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,14 +55,14 @@ type client struct {
|
|||
}
|
||||
|
||||
// idToName maps an arbitrary ID, such as an email or client ID to a Kubernetes object name.
|
||||
func (c *client) idToName(s string) string {
|
||||
return idToName(s, c.hash)
|
||||
func (cli *client) idToName(s string) string {
|
||||
return idToName(s, cli.hash)
|
||||
}
|
||||
|
||||
// offlineTokenName maps two arbitrary IDs, to a single Kubernetes object name.
|
||||
// This is used when more than one field is used to uniquely identify the object.
|
||||
func (c *client) offlineTokenName(userID string, connID string) string {
|
||||
return offlineTokenName(userID, connID, c.hash)
|
||||
func (cli *client) offlineTokenName(userID string, connID string) string {
|
||||
return offlineTokenName(userID, connID, cli.hash)
|
||||
}
|
||||
|
||||
// Kubernetes names must match the regexp '[a-z0-9]([-a-z0-9]*[a-z0-9])?'.
|
||||
|
@ -79,7 +79,7 @@ func offlineTokenName(userID string, connID string, h func() hash.Hash) string {
|
|||
return strings.TrimRight(encoding.EncodeToString(hash.Sum(nil)), "=")
|
||||
}
|
||||
|
||||
func (c *client) urlFor(apiVersion, namespace, resource, name string) string {
|
||||
func (cli *client) urlFor(apiVersion, namespace, resource, name string) string {
|
||||
basePath := "apis/"
|
||||
if apiVersion == "v1" {
|
||||
basePath = "api/"
|
||||
|
@ -91,10 +91,10 @@ func (c *client) urlFor(apiVersion, namespace, resource, name string) string {
|
|||
} else {
|
||||
p = path.Join(basePath, apiVersion, resource, name)
|
||||
}
|
||||
if strings.HasSuffix(c.baseURL, "/") {
|
||||
return c.baseURL + p
|
||||
if strings.HasSuffix(cli.baseURL, "/") {
|
||||
return cli.baseURL + p
|
||||
}
|
||||
return c.baseURL + "/" + p
|
||||
return cli.baseURL + "/" + p
|
||||
}
|
||||
|
||||
// Define an error interface so we can get at the underlying status code if it's
|
||||
|
@ -156,13 +156,13 @@ func closeResp(r *http.Response) {
|
|||
r.Body.Close()
|
||||
}
|
||||
|
||||
func (c *client) get(resource, name string, v interface{}) error {
|
||||
return c.getResource(c.apiVersion, c.namespace, resource, name, v)
|
||||
func (cli *client) get(resource, name string, v interface{}) error {
|
||||
return cli.getResource(cli.apiVersion, cli.namespace, resource, name, v)
|
||||
}
|
||||
|
||||
func (c *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {
|
||||
url := c.urlFor(apiVersion, namespace, resource, name)
|
||||
resp, err := c.client.Get(url)
|
||||
func (cli *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {
|
||||
url := cli.urlFor(apiVersion, namespace, resource, name)
|
||||
resp, err := cli.client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -173,22 +173,22 @@ func (c *client) getResource(apiVersion, namespace, resource, name string, v int
|
|||
return json.NewDecoder(resp.Body).Decode(v)
|
||||
}
|
||||
|
||||
func (c *client) list(resource string, v interface{}) error {
|
||||
return c.get(resource, "", v)
|
||||
func (cli *client) list(resource string, v interface{}) error {
|
||||
return cli.get(resource, "", v)
|
||||
}
|
||||
|
||||
func (c *client) post(resource string, v interface{}) error {
|
||||
return c.postResource(c.apiVersion, c.namespace, resource, v)
|
||||
func (cli *client) post(resource string, v interface{}) error {
|
||||
return cli.postResource(cli.apiVersion, cli.namespace, resource, v)
|
||||
}
|
||||
|
||||
func (c *client) postResource(apiVersion, namespace, resource string, v interface{}) error {
|
||||
func (cli *client) postResource(apiVersion, namespace, resource string, v interface{}) error {
|
||||
body, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal object: %v", err)
|
||||
}
|
||||
|
||||
url := c.urlFor(apiVersion, namespace, resource, "")
|
||||
resp, err := c.client.Post(url, "application/json", bytes.NewReader(body))
|
||||
url := cli.urlFor(apiVersion, namespace, resource, "")
|
||||
resp, err := cli.client.Post(url, "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -196,13 +196,13 @@ func (c *client) postResource(apiVersion, namespace, resource string, v interfac
|
|||
return checkHTTPErr(resp, http.StatusCreated)
|
||||
}
|
||||
|
||||
func (c *client) delete(resource, name string) error {
|
||||
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
|
||||
func (cli *client) delete(resource, name string) error {
|
||||
url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create delete request: %v", err)
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
resp, err := cli.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete request: %v", err)
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ func (c *client) delete(resource, name string) error {
|
|||
return checkHTTPErr(resp, http.StatusOK)
|
||||
}
|
||||
|
||||
func (c *client) deleteAll(resource string) error {
|
||||
func (cli *client) deleteAll(resource string) error {
|
||||
var list struct {
|
||||
k8sapi.TypeMeta `json:",inline"`
|
||||
k8sapi.ListMeta `json:"metadata,omitempty"`
|
||||
|
@ -219,24 +219,24 @@ func (c *client) deleteAll(resource string) error {
|
|||
k8sapi.ObjectMeta `json:"metadata,omitempty"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := c.list(resource, &list); err != nil {
|
||||
if err := cli.list(resource, &list); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range list.Items {
|
||||
if err := c.delete(resource, item.Name); err != nil {
|
||||
if err := cli.delete(resource, item.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) put(resource, name string, v interface{}) error {
|
||||
func (cli *client) put(resource, name string, v interface{}) error {
|
||||
body, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal object: %v", err)
|
||||
}
|
||||
|
||||
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
|
||||
url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create patch request: %v", err)
|
||||
|
@ -244,7 +244,7 @@ func (c *client) put(resource, name string, v interface{}) error {
|
|||
|
||||
req.Header.Set("Content-Length", strconv.Itoa(len(body)))
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
resp, err := cli.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("patch request: %v", err)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ type CustomResourceDefinitionNames struct {
|
|||
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"`
|
||||
}
|
||||
|
||||
// ResourceScope is an enum defining the different scopes availabe to a custom resource
|
||||
// ResourceScope is an enum defining the different scopes available to a custom resource
|
||||
type ResourceScope string
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,39 +1,104 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"sigs.k8s.io/testing_frameworks/integration"
|
||||
|
||||
"github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/storage/conformance"
|
||||
)
|
||||
|
||||
const testKubeConfigEnv = "DEX_KUBECONFIG"
|
||||
const kubeconfigTemplate = `apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- name: local
|
||||
cluster:
|
||||
server: SERVERURL
|
||||
users:
|
||||
- name: local
|
||||
user:
|
||||
contexts:
|
||||
- context:
|
||||
cluster: local
|
||||
user: local
|
||||
`
|
||||
|
||||
func TestLoadClient(t *testing.T) {
|
||||
loadClient(t)
|
||||
func TestStorage(t *testing.T) {
|
||||
if os.Getenv("TEST_ASSET_KUBE_APISERVER") == "" || os.Getenv("TEST_ASSET_ETCD") == "" {
|
||||
t.Skip("control plane binaries are missing")
|
||||
}
|
||||
|
||||
suite.Run(t, new(StorageTestSuite))
|
||||
}
|
||||
|
||||
func loadClient(t *testing.T) *client {
|
||||
type StorageTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
controlPlane *integration.ControlPlane
|
||||
|
||||
client *client
|
||||
}
|
||||
|
||||
func (s *StorageTestSuite) SetupSuite() {
|
||||
s.controlPlane = &integration.ControlPlane{}
|
||||
|
||||
err := s.controlPlane.Start()
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *StorageTestSuite) TearDownSuite() {
|
||||
s.controlPlane.Stop()
|
||||
}
|
||||
|
||||
func (s *StorageTestSuite) SetupTest() {
|
||||
f, err := ioutil.TempFile("", "dex-kubeconfig-*")
|
||||
s.Require().NoError(err)
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(strings.ReplaceAll(kubeconfigTemplate, "SERVERURL", s.controlPlane.APIURL().String()))
|
||||
s.Require().NoError(err)
|
||||
|
||||
config := Config{
|
||||
KubeConfigFile: os.Getenv(testKubeConfigEnv),
|
||||
}
|
||||
if config.KubeConfigFile == "" {
|
||||
t.Skipf("test environment variable %q not set, skipping", testKubeConfigEnv)
|
||||
KubeConfigFile: f.Name(),
|
||||
}
|
||||
|
||||
logger := &logrus.Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: &logrus.TextFormatter{DisableColors: true},
|
||||
Level: logrus.DebugLevel,
|
||||
}
|
||||
s, err := config.open(logger, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
client, err := config.open(logger, true)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.client = client
|
||||
}
|
||||
|
||||
func (s *StorageTestSuite) TestStorage() {
|
||||
newStorage := func() storage.Storage {
|
||||
for _, resource := range []string{
|
||||
resourceAuthCode,
|
||||
resourceAuthRequest,
|
||||
resourceClient,
|
||||
resourceRefreshToken,
|
||||
resourceKeys,
|
||||
resourcePassword,
|
||||
} {
|
||||
if err := s.client.deleteAll(resource); err != nil {
|
||||
s.T().Fatalf("delete all %q failed: %v", resource, err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
return s.client
|
||||
}
|
||||
|
||||
conformance.RunTests(s.T(), newStorage)
|
||||
conformance.RunTransactionTests(s.T(), newStorage)
|
||||
}
|
||||
|
||||
func TestURLFor(t *testing.T) {
|
||||
|
@ -83,27 +148,3 @@ func TestURLFor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorage(t *testing.T) {
|
||||
client := loadClient(t)
|
||||
newStorage := func() storage.Storage {
|
||||
for _, resource := range []string{
|
||||
resourceAuthCode,
|
||||
resourceAuthRequest,
|
||||
resourceClient,
|
||||
resourceRefreshToken,
|
||||
resourceKeys,
|
||||
resourcePassword,
|
||||
} {
|
||||
if err := client.deleteAll(resource); err != nil {
|
||||
// Fatalf sometimes doesn't print the error message.
|
||||
fmt.Fprintf(os.Stderr, "delete all %q failed: %v\n", resource, err)
|
||||
t.Fatalf("delete all %q failed: %v", resource, err)
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
conformance.RunTests(t, newStorage)
|
||||
conformance.RunTransactionTests(t, newStorage)
|
||||
}
|
||||
|
|
|
@ -308,10 +308,17 @@ func (s *MySQL) open(logger log.Logger) (*conn, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if s.MaxIdleConns == 0 {
|
||||
/*Override default behaviour to fix https://github.com/dexidp/dex/issues/1608*/
|
||||
db.SetMaxIdleConns(0)
|
||||
} else {
|
||||
db.SetMaxIdleConns(s.MaxIdleConns)
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == mysqlErrUnknownSysVar {
|
||||
logger.Info("reconnecting with MySQL pre-5.7.20 compatibilty mode")
|
||||
logger.Info("reconnecting with MySQL pre-5.7.20 compatibility mode")
|
||||
|
||||
// MySQL 5.7.20 introduced transaction_isolation and deprecated tx_isolation.
|
||||
// MySQL 8.0 doesn't have tx_isolation at all.
|
||||
|
|
|
@ -169,7 +169,6 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest)
|
|||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (c *conn) GetAuthRequest(id string) (storage.AuthRequest, error) {
|
||||
|
|
|
@ -68,12 +68,12 @@ body {
|
|||
background-size: contain;
|
||||
}
|
||||
|
||||
.dex-btn-icon--bitbucket {
|
||||
.dex-btn-icon--bitbucket-cloud {
|
||||
background-color: #205081;
|
||||
background-image: url(../static/img/bitbucket-icon.svg);
|
||||
}
|
||||
|
||||
.dex-btn-icon--ldap, .dex-btn-icon--tectonic-ldap {
|
||||
.dex-btn-icon--ldap {
|
||||
background-color: #84B6EF;
|
||||
background-image: url(../static/img/ldap-icon.svg);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<div class="theme-form-row">
|
||||
<a href="{{ $c.URL }}" target="_self">
|
||||
<button class="dex-btn theme-btn-provider">
|
||||
<span class="dex-btn-icon dex-btn-icon--{{ $c.ID }}"></span>
|
||||
<span class="dex-btn-icon dex-btn-icon--{{ $c.Type }}"></span>
|
||||
<span class="dex-btn-text">Log in with {{ $c.Name }}</span>
|
||||
</button>
|
||||
</a>
|
||||
|
|
Loading…
Reference in a new issue