Merge branch 'master' into feature/static_password_env

This commit is contained in:
Krishna Durai 2020-01-06 23:21:20 -08:00 committed by GitHub
commit 9560899496
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 897 additions and 279 deletions

View File

@ -69,8 +69,8 @@ jobs:
DEX_KEYSTONE_ADMIN_USER: demo DEX_KEYSTONE_ADMIN_USER: demo
DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS
- name: Run Kubernetes tests - name: Run linter
run: ./scripts/test-k8s.sh run: make lint
# Ensure proto generation doesn't depend on external packages. # Ensure proto generation doesn't depend on external packages.
- name: Verify proto - name: Verify proto

35
.golangci.yml Normal file
View 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

View File

@ -48,7 +48,6 @@ install:
script: script:
- make testall - make testall
- ./scripts/test-k8s.sh
- make verify-proto # Ensure proto generation doesn't depend on external packages. - make verify-proto # Ensure proto generation doesn't depend on external packages.
notifications: notifications:

View 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
```

View File

@ -1,21 +1,5 @@
# Running integration tests # 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 ## Postgres
Running database tests locally requires: Running database tests locally requires:

View File

@ -14,11 +14,13 @@ Steps:
```yaml ```yaml
frontend: frontend:
dir: /path/to/custom/web dir: /path/to/custom/web
issuer: my-dex
extra: extra:
tos_footer_link: "https://example.com/terms" tos_footer_link: "https://example.com/terms"
client_logo_url: "../theme/client-logo.png" client_logo_url: "../theme/client-logo.png"
foo: "bar" foo: "bar"
``` ```
5. Set the `frontend.dir` value to your own `web` directory. 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. To test your templates simply run Dex with a valid configuration and go through a login flow.

View File

@ -2,7 +2,6 @@ PROJ=dex
ORG_PATH=github.com/dexidp ORG_PATH=github.com/dexidp
REPO_PATH=$(ORG_PATH)/$(PROJ) REPO_PATH=$(ORG_PATH)/$(PROJ)
export PATH := $(PWD)/bin:$(PATH) export PATH := $(PWD)/bin:$(PATH)
THIS_DIRECTORY:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
VERSION ?= $(shell ./scripts/git-version) VERSION ?= $(shell ./scripts/git-version)
@ -18,6 +17,9 @@ export GOBIN=$(PWD)/bin
LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)" 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 build: bin/dex bin/example-app bin/grpc-client
bin/dex: bin/dex:
@ -39,20 +41,38 @@ revendor:
@go mod vendor -v @go mod vendor -v
@go mod verify @go mod verify
test: test: bin/test/kube-apiserver bin/test/etcd
@go test -v ./... @go test -v ./...
testrace: testrace: bin/test/kube-apiserver bin/test/etcd
@go test -v --race ./... @go test -v --race ./...
vet: export TEST_ASSET_KUBE_APISERVER=$(abspath bin/test/kube-apiserver)
@go vet ./... export TEST_ASSET_ETCD=$(abspath bin/test/etcd)
fmt: bin/test/kube-apiserver:
@./scripts/gofmt ./... @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/test/etcd:
@./bin/golint -set_exit_status $(shell go list ./...) @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 .PHONY: docker-image
docker-image: docker-image:
@ -73,14 +93,11 @@ bin/protoc: scripts/get-protoc
bin/protoc-gen-go: bin/protoc-gen-go:
@go install -v $(REPO_PATH)/vendor/github.com/golang/protobuf/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: clean:
@rm -rf bin/ @rm -rf bin/
testall: testrace vet fmt lint testall: testrace
FORCE: FORCE:
.PHONY: test testrace vet fmt lint testall .PHONY: test testrace testall

View File

@ -74,6 +74,7 @@ Dex implements the following connectors:
| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | no | beta | | | [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. | | [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 | | | [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: Stable, beta, and alpha are defined as:

View File

@ -11,6 +11,7 @@ import (
"github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/connector/mock"
"github.com/dexidp/dex/connector/oidc" "github.com/dexidp/dex/connector/oidc"
"github.com/dexidp/dex/server"
"github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage"
"github.com/dexidp/dex/storage/sql" "github.com/dexidp/dex/storage/sql"
) )
@ -215,7 +216,6 @@ logger:
if diff := pretty.Compare(c, want); diff != "" { if diff := pretty.Compare(c, want); diff != "" {
t.Errorf("got!=want: %s", diff) t.Errorf("got!=want: %s", diff)
} }
} }
func TestUnmarshalConfigWithEnv(t *testing.T) { func TestUnmarshalConfigWithEnv(t *testing.T) {

View File

@ -182,7 +182,6 @@ func serve(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to initialize storage connectors: %v", err) return fmt.Errorf("failed to initialize storage connectors: %v", err)
} }
storageConnectors[i] = conn storageConnectors[i] = conn
} }
if c.EnablePasswordDB { if c.EnablePasswordDB {

View File

@ -143,7 +143,7 @@ func cmd() *cobra.Command {
ctx := oidc.ClientContext(context.Background(), a.client) ctx := oidc.ClientContext(context.Background(), a.client)
provider, err := oidc.NewProvider(ctx, issuerURL) provider, err := oidc.NewProvider(ctx, issuerURL)
if err != nil { 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 { var s struct {
@ -153,7 +153,7 @@ func cmd() *cobra.Command {
ScopesSupported []string `json:"scopes_supported"` ScopesSupported []string `json:"scopes_supported"`
} }
if err := provider.Claims(&s); err != nil { 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 { if len(s.ScopesSupported) == 0 {

View File

@ -41,7 +41,6 @@ type Config struct {
// Open returns a strategy for logging in through Bitbucket. // Open returns a strategy for logging in through Bitbucket.
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
b := bitbucketConnector{ b := bitbucketConnector{
redirectURI: c.RedirectURI, redirectURI: c.RedirectURI,
teams: c.Teams, teams: c.Teams,
@ -373,7 +372,6 @@ type userTeamsResponse struct {
} }
func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client) ([]string, error) { func (b *bitbucketConnector) userTeams(ctx context.Context, client *http.Client) ([]string, error) {
var teams []string var teams []string
apiURL := b.apiURL + "/teams?role=member" apiURL := b.apiURL + "/teams?role=member"

View File

@ -14,7 +14,6 @@ import (
) )
func TestUserGroups(t *testing.T) { func TestUserGroups(t *testing.T) {
teamsResponse := userTeamsResponse{ teamsResponse := userTeamsResponse{
pagedResponse: pagedResponse{ pagedResponse: pagedResponse{
Size: 3, Size: 3,
@ -46,7 +45,6 @@ func TestUserGroups(t *testing.T) {
} }
func TestUserWithoutTeams(t *testing.T) { func TestUserWithoutTeams(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/teams?role=member": userTeamsResponse{}, "/teams?role=member": userTeamsResponse{},
}) })
@ -61,7 +59,6 @@ func TestUserWithoutTeams(t *testing.T) {
} }
func TestUsernameIncludedInFederatedIdentity(t *testing.T) { func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/user": user{Username: "some-login"}, "/user": user{Username: "some-login"},
"/user/emails": userEmailResponse{ "/user/emails": userEmailResponse{

View File

@ -67,7 +67,6 @@ type Org struct {
// Open returns a strategy for logging in through GitHub. // Open returns a strategy for logging in through GitHub.
func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) { func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
if c.Org != "" { if c.Org != "" {
// Return error if both 'org' and 'orgs' fields are used. // Return error if both 'org' and 'orgs' fields are used.
if len(c.Orgs) > 0 { 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 { if g.httpClient, err = newHTTPClient(g.rootCA); err != nil {
return nil, fmt.Errorf("failed to create HTTP client: %v", err) return nil, fmt.Errorf("failed to create HTTP client: %v", err)
} }
} }
g.loadAllGroups = c.LoadAllGroups g.loadAllGroups = c.LoadAllGroups
@ -144,7 +142,7 @@ type githubConnector struct {
hostName string hostName string
// Used to support untrusted/self-signed CA certs. // Used to support untrusted/self-signed CA certs.
rootCA string rootCA string
// HTTP Client that trusts the custom delcared rootCA cert. // HTTP Client that trusts the custom declared rootCA cert.
httpClient *http.Client httpClient *http.Client
// optional choice between 'name' (default) or 'slug' // optional choice between 'name' (default) or 'slug'
teamNameField string teamNameField string
@ -206,7 +204,7 @@ func (e *oauth2Error) Error() string {
return e.error + ": " + e.errorDescription 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) { func newHTTPClient(rootCA string) (*http.Client, error) {
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()} tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
rootCABytes, err := ioutil.ReadFile(rootCA) rootCABytes, err := ioutil.ReadFile(rootCA)

View File

@ -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 // tests that the users login is used as their username when they have no username set
func TestUsernameIncludedInFederatedIdentity(t *testing.T) { func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
s := newTestServer(map[string]testResponse{ s := newTestServer(map[string]testResponse{
"/user": {data: user{Login: "some-login", ID: 12345678}}, "/user": {data: user{Login: "some-login", ID: 12345678}},
"/user/emails": {data: []userEmail{{ "/user/emails": {data: []userEmail{{
@ -168,7 +167,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
} }
func TestLoginUsedAsIDWhenConfigured(t *testing.T) { func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
s := newTestServer(map[string]testResponse{ s := newTestServer(map[string]testResponse{
"/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}}, "/user": {data: user{Login: "some-login", ID: 12345678, Name: "Joe Bloggs"}},
"/user/emails": {data: []userEmail{{ "/user/emails": {data: []userEmail{{

View File

@ -65,7 +65,6 @@ func TestUserGroupsWithoutOrgs(t *testing.T) {
// tests that the email is used as their username when they have no username set // tests that the email is used as their username when they have no username set
func TestUsernameIncludedInFederatedIdentity(t *testing.T) { func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678}, "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678},
"/oauth/token": map[string]interface{}{ "/oauth/token": map[string]interface{}{
@ -102,7 +101,6 @@ func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
} }
func TestLoginUsedAsIDWhenConfigured(t *testing.T) { func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"}, "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
"/oauth/token": map[string]interface{}{ "/oauth/token": map[string]interface{}{
@ -130,7 +128,6 @@ func TestLoginUsedAsIDWhenConfigured(t *testing.T) {
} }
func TestLoginWithTeamWhitelisted(t *testing.T) { func TestLoginWithTeamWhitelisted(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs"}, "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs"},
"/oauth/token": map[string]interface{}{ "/oauth/token": map[string]interface{}{
@ -158,7 +155,6 @@ func TestLoginWithTeamWhitelisted(t *testing.T) {
} }
func TestLoginWithTeamNonWhitelisted(t *testing.T) { func TestLoginWithTeamNonWhitelisted(t *testing.T) {
s := newTestServer(map[string]interface{}{ s := newTestServer(map[string]interface{}{
"/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"}, "/api/v4/user": gitlabUser{Email: "some@email.com", ID: 12345678, Name: "Joe Bloggs", Username: "joebloggs"},
"/oauth/token": map[string]interface{}{ "/oauth/token": map[string]interface{}{

View File

@ -11,12 +11,12 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1"
"github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector"
pkg_groups "github.com/dexidp/dex/pkg/groups" pkg_groups "github.com/dexidp/dex/pkg/groups"
"github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/pkg/log"
"golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1"
) )
const ( const (
@ -105,7 +105,6 @@ type googleConnector struct {
redirectURI string redirectURI string
oauth2Config *oauth2.Config oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier verifier *oidc.IDTokenVerifier
ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
logger log.Logger logger log.Logger
hostedDomains []string hostedDomains []string

View File

@ -150,7 +150,6 @@ func (p *conn) Prompt() string { return "username" }
func (p *conn) Refresh( func (p *conn) Refresh(
ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) { ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
token, err := p.getAdminToken(ctx) token, err := p.getAdminToken(ctx)
if err != nil { if err != nil {
return identity, fmt.Errorf("keystone: failed to obtain admin token: %v", err) 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 { if err != nil {
return "", err return "", err
} }
defer resp.Body.Close()
token := resp.Header.Get("X-Subject-Token") token := resp.Header.Get("X-Subject-Token")
return token, nil return token, nil
} }
@ -229,6 +230,7 @@ func (p *conn) checkIfUserExists(ctx context.Context, userID string, token strin
if err != nil { if err != nil {
return false, err return false, err
} }
defer resp.Body.Close()
if resp.StatusCode == 200 { if resp.StatusCode == 200 {
return true, nil return true, nil

View File

@ -154,7 +154,12 @@ func delete(t *testing.T, token, id, uri string) {
t.Fatalf("error: %v", err) t.Fatalf("error: %v", err)
} }
req.Header.Set("X-Auth-Token", token) 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 { 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 return err
} }
req.Header.Set("X-Auth-Token", token) 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 return nil
} }
@ -274,7 +285,7 @@ func TestUseRefreshToken(t *testing.T) {
delete(t, token, groupID, groupsURL) delete(t, token, groupID, groupsURL)
expectEquals(t, 1, len(identityRefresh.Groups)) expectEquals(t, 1, len(identityRefresh.Groups))
expectEquals(t, testGroup, string(identityRefresh.Groups[0])) expectEquals(t, testGroup, identityRefresh.Groups[0])
} }
func TestUseRefreshTokenUserDeleted(t *testing.T) { func TestUseRefreshTokenUserDeleted(t *testing.T) {

View File

@ -189,7 +189,6 @@ func (c *Config) OpenConnector(logger log.Logger) (interface {
} }
func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) { func (c *Config) openConnector(logger log.Logger) (*ldapConnector, error) {
requiredFields := []struct { requiredFields := []struct {
name string name string
val 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) { 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)) filter := fmt.Sprintf("(%s=%s)", c.UserSearch.Username, ldap.EscapeFilter(username))
if c.UserSearch.Filter != "" { if c.UserSearch.Filter != "" {
filter = fmt.Sprintf("(&%s%s)", c.UserSearch.Filter, filter) filter = fmt.Sprintf("(&%s%s)", c.UserSearch.Filter, filter)

View File

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync"
"time" "time"
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
@ -85,18 +84,6 @@ func knownBrokenAuthHeaderProvider(issuerURL string) bool {
return false 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 // Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider. // OpenID Connect provider.
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) { 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) return nil, fmt.Errorf("failed to get provider: %v", err)
} }
endpoint := provider.Endpoint()
if c.BasicAuthUnsupported != nil { if c.BasicAuthUnsupported != nil {
// Setting "basicAuthUnsupported" always overrides our detection. // Setting "basicAuthUnsupported" always overrides our detection.
if *c.BasicAuthUnsupported { if *c.BasicAuthUnsupported {
registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL) endpoint.AuthStyle = oauth2.AuthStyleInParams
} }
} else if knownBrokenAuthHeaderProvider(c.Issuer) { } else if knownBrokenAuthHeaderProvider(c.Issuer) {
registerBrokenAuthHeaderProvider(provider.Endpoint().TokenURL) endpoint.AuthStyle = oauth2.AuthStyleInParams
} }
scopes := []string{oidc.ScopeOpenID} scopes := []string{oidc.ScopeOpenID}
@ -131,7 +120,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
oauth2Config: &oauth2.Config{ oauth2Config: &oauth2.Config{
ClientID: clientID, ClientID: clientID,
ClientSecret: c.ClientSecret, ClientSecret: c.ClientSecret,
Endpoint: provider.Endpoint(), Endpoint: endpoint,
Scopes: scopes, Scopes: scopes,
RedirectURL: c.RedirectURI, RedirectURL: c.RedirectURI,
}, },
@ -273,15 +262,25 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I
if !found { if !found {
return identity, fmt.Errorf("missing \"%s\" claim", userNameKey) 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) email, found := claims["email"].(string)
if !found { if !found && hasEmailScope {
return identity, errors.New("missing \"email\" claim") return identity, errors.New("missing \"email\" claim")
} }
emailVerified, found := claims["email_verified"].(bool) emailVerified, found := claims["email_verified"].(bool)
if !found { if !found {
if c.insecureSkipEmailVerified { if c.insecureSkipEmailVerified {
emailVerified = true emailVerified = true
} else { } else if hasEmailScope {
return identity, errors.New("missing \"email_verified\" claim") return identity, errors.New("missing \"email_verified\" claim")
} }
} }

View File

@ -16,9 +16,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/dexidp/dex/connector"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"github.com/dexidp/dex/connector"
) )
func TestKnownBrokenAuthHeaderProvider(t *testing.T) { func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
@ -49,8 +50,10 @@ func TestHandleCallback(t *testing.T) {
userIDKey string userIDKey string
userNameKey string userNameKey string
insecureSkipEmailVerified bool insecureSkipEmailVerified bool
scopes []string
expectUserID string expectUserID string
expectUserName string expectUserName string
expectedEmailField string
token map[string]interface{} token map[string]interface{}
}{ }{
{ {
@ -59,6 +62,7 @@ func TestHandleCallback(t *testing.T) {
userNameKey: "", // not configured userNameKey: "", // not configured
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue",
token: map[string]interface{}{ token: map[string]interface{}{
"sub": "subvalue", "sub": "subvalue",
"name": "namevalue", "name": "namevalue",
@ -71,6 +75,7 @@ func TestHandleCallback(t *testing.T) {
insecureSkipEmailVerified: true, insecureSkipEmailVerified: true,
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue",
token: map[string]interface{}{ token: map[string]interface{}{
"sub": "subvalue", "sub": "subvalue",
"name": "namevalue", "name": "namevalue",
@ -82,6 +87,7 @@ func TestHandleCallback(t *testing.T) {
userIDKey: "name", userIDKey: "name",
expectUserID: "namevalue", expectUserID: "namevalue",
expectUserName: "namevalue", expectUserName: "namevalue",
expectedEmailField: "emailvalue",
token: map[string]interface{}{ token: map[string]interface{}{
"sub": "subvalue", "sub": "subvalue",
"name": "namevalue", "name": "namevalue",
@ -94,6 +100,7 @@ func TestHandleCallback(t *testing.T) {
userNameKey: "user_name", userNameKey: "user_name",
expectUserID: "subvalue", expectUserID: "subvalue",
expectUserName: "username", expectUserName: "username",
expectedEmailField: "emailvalue",
token: map[string]interface{}{ token: map[string]interface{}{
"sub": "subvalue", "sub": "subvalue",
"user_name": "username", "user_name": "username",
@ -101,6 +108,33 @@ func TestHandleCallback(t *testing.T) {
"email_verified": true, "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 { for _, tc := range tests {
@ -110,16 +144,25 @@ func TestHandleCallback(t *testing.T) {
t.Fatal("failed to setup test server", err) t.Fatal("failed to setup test server", err)
} }
defer testServer.Close() defer testServer.Close()
var scopes []string
if len(tc.scopes) > 0 {
scopes = tc.scopes
} else {
scopes = []string{"email", "groups"}
}
serverURL := testServer.URL serverURL := testServer.URL
basicAuth := true
config := Config{ config := Config{
Issuer: serverURL, Issuer: serverURL,
ClientID: "clientID", ClientID: "clientID",
ClientSecret: "clientSecret", ClientSecret: "clientSecret",
Scopes: []string{"groups"}, Scopes: scopes,
RedirectURI: fmt.Sprintf("%s/callback", serverURL), RedirectURI: fmt.Sprintf("%s/callback", serverURL),
UserIDKey: tc.userIDKey, UserIDKey: tc.userIDKey,
UserNameKey: tc.userNameKey, UserNameKey: tc.userNameKey,
InsecureSkipEmailVerified: tc.insecureSkipEmailVerified, InsecureSkipEmailVerified: tc.insecureSkipEmailVerified,
BasicAuthUnsupported: &basicAuth,
} }
conn, err := newConnector(config) conn, err := newConnector(config)
@ -139,7 +182,7 @@ func TestHandleCallback(t *testing.T) {
expectEquals(t, identity.UserID, tc.expectUserID) expectEquals(t, identity.UserID, tc.expectUserID)
expectEquals(t, identity.Username, tc.expectUserName) expectEquals(t, identity.Username, tc.expectUserName)
expectEquals(t, identity.Email, "emailvalue") expectEquals(t, identity.Email, tc.expectedEmailField)
expectEquals(t, identity.EmailVerified, true) expectEquals(t, identity.EmailVerified, true)
}) })
} }

View 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
}

View 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)
}
}

View File

@ -14,11 +14,12 @@ import (
"time" "time"
"github.com/beevik/etree" "github.com/beevik/etree"
dsig "github.com/russellhaering/goxmldsig"
"github.com/russellhaering/goxmldsig/etreeutils"
"github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector"
"github.com/dexidp/dex/pkg/groups" "github.com/dexidp/dex/pkg/groups"
"github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/pkg/log"
dsig "github.com/russellhaering/goxmldsig"
"github.com/russellhaering/goxmldsig/etreeutils"
) )
// nolint // nolint
@ -248,7 +249,6 @@ type provider struct {
} }
func (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) { func (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) {
r := &authnRequest{ r := &authnRequest{
ProtocolBinding: bindingPOST, ProtocolBinding: bindingPOST,
ID: id, ID: id,
@ -325,7 +325,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
// Status is a required element. // Status is a required element.
if resp.Status == nil { 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 { 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 == "") { if len(p.allowedGroups) > 0 && (!s.Groups || p.groupsAttr == "") {
// allowedGroups set but no groups or groupsAttr. Disallowing. // 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. // Grab the groups.
@ -427,7 +427,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
if len(groupMatches) == 0 { if len(groupMatches) == 0 {
// No group membership matches found, disallowing // 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 // Otherwise, we're good
@ -468,7 +468,7 @@ func (p *provider) validateStatus(status *status) error {
func (p *provider) validateSubject(subject *subject, inResponseTo string) error { func (p *provider) validateSubject(subject *subject, inResponseTo string) error {
// Optional according to the spec, but again, we're going to be strict here. // Optional according to the spec, but again, we're going to be strict here.
if len(subject.SubjectConfirmations) == 0 { if len(subject.SubjectConfirmations) == 0 {
return fmt.Errorf("Subject contained no SubjectConfirmations") return fmt.Errorf("subject contained no SubjectConfirmations")
} }
var errs []error var errs []error

1
go.mod
View File

@ -57,6 +57,7 @@ require (
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/ldap.v2 v2.5.1 gopkg.in/ldap.v2 v2.5.1
gopkg.in/square/go-jose.v2 v2.3.1 gopkg.in/square/go-jose.v2 v2.3.1
sigs.k8s.io/testing_frameworks v0.1.2
) )
go 1.13 go 1.13

13
go.sum
View File

@ -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/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.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/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.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 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 h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 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/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.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 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 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 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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= 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-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 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.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.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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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 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 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-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 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= 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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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-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.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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/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= 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=

View File

@ -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

View File

@ -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

View File

@ -218,7 +218,6 @@ func (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq)
return nil, fmt.Errorf("delete password: %v", err) return nil, fmt.Errorf("delete password: %v", err)
} }
return &api.DeletePasswordResp{}, nil return &api.DeletePasswordResp{}, nil
} }
func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.VersionResp, error) { 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{ return &api.ListPasswordResp{
Passwords: passwords, Passwords: passwords,
}, nil }, nil
} }
func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) { func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) {

View File

@ -167,7 +167,6 @@ func TestPassword(t *testing.T) {
if _, err := client.DeletePassword(ctx, &deleteReq); err != nil { if _, err := client.DeletePassword(ctx, &deleteReq); err != nil {
t.Fatalf("Unable to delete password: %v", err) t.Fatalf("Unable to delete password: %v", err)
} }
} }
// Ensures checkCost returns expected values // Ensures checkCost returns expected values
@ -495,7 +494,6 @@ func TestUpdateClient(t *testing.T) {
if tc.cleanup != nil { if tc.cleanup != nil {
tc.cleanup(t, tc.req.Id) tc.cleanup(t, tc.req.Id)
} }
}) })
} }
} }

View File

@ -45,8 +45,8 @@ func (s *Server) newHealthChecker(ctx context.Context) http.Handler {
return h return h
} }
// healthChecker periodically performs health checks on server dependenices. // healthChecker periodically performs health checks on server dependencies.
// Currently, it only checks that the storage layer is avialable. // Currently, it only checks that the storage layer is available.
type healthChecker struct { type healthChecker struct {
s *Server s *Server
@ -259,16 +259,15 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
} }
connectorInfos := make([]connectorInfo, len(connectors)) connectorInfos := make([]connectorInfo, len(connectors))
i := 0 for index, conn := range connectors {
for _, conn := range connectors { connectorInfos[index] = connectorInfo{
connectorInfos[i] = connectorInfo{
ID: conn.ID, ID: conn.ID,
Name: conn.Name, Name: conn.Name,
Type: conn.Type,
// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter // TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
// on create the auth request. // on create the auth request.
URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID, URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID,
} }
i++
} }
if err := s.templates.login(r, w, connectorInfos, r.URL.Path); err != nil { 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 deleteToken = true
return return
} }
} }
} }
s.writeAccessToken(w, idToken, accessToken, refreshToken, expiry) s.writeAccessToken(w, idToken, accessToken, refreshToken, expiry)

View File

@ -24,7 +24,6 @@ func TestHandleHealth(t *testing.T) {
if rr.Code != http.StatusOK { if rr.Code != http.StatusOK {
t.Errorf("expected 200 got %d", rr.Code) t.Errorf("expected 200 got %d", rr.Code)
} }
} }
type badStorage struct { type badStorage struct {

View File

@ -14,6 +14,10 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/felixge/httpsnoop"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"github.com/dexidp/dex/connector" "github.com/dexidp/dex/connector"
@ -28,13 +32,10 @@ import (
"github.com/dexidp/dex/connector/microsoft" "github.com/dexidp/dex/connector/microsoft"
"github.com/dexidp/dex/connector/mock" "github.com/dexidp/dex/connector/mock"
"github.com/dexidp/dex/connector/oidc" "github.com/dexidp/dex/connector/oidc"
"github.com/dexidp/dex/connector/openshift"
"github.com/dexidp/dex/connector/saml" "github.com/dexidp/dex/connector/saml"
"github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/pkg/log"
"github.com/dexidp/dex/storage" "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 // 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) }, "linkedin": func() ConnectorConfig { return new(linkedin.Config) },
"microsoft": func() ConnectorConfig { return new(microsoft.Config) }, "microsoft": func() ConnectorConfig { return new(microsoft.Config) },
"bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) }, "bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) },
"openshift": func() ConnectorConfig { return new(openshift.Config) },
// Keep around for backwards compatibility. // Keep around for backwards compatibility.
"samlExperimental": func() ConnectorConfig { return new(saml.Config) }, "samlExperimental": func() ConnectorConfig { return new(saml.Config) },
} }

View File

@ -590,6 +590,8 @@ func TestOAuth2CodeFlow(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("get failed: %v", err) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -726,6 +728,8 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("get failed: %v", err) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -799,7 +803,6 @@ func TestCrossClientScopes(t *testing.T) {
if !reflect.DeepEqual(idToken.Audience, expAudience) { if !reflect.DeepEqual(idToken.Audience, expAudience) {
t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience) t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience)
} }
} }
if gotState := q.Get("state"); gotState != state { if gotState := q.Get("state"); gotState != state {
t.Errorf("state did not match, want=%q got=%q", state, gotState) t.Errorf("state did not match, want=%q got=%q", state, gotState)
@ -848,6 +851,8 @@ func TestCrossClientScopes(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("get failed: %v", err) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -921,7 +926,6 @@ func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) {
if !reflect.DeepEqual(idToken.Audience, expAudience) { if !reflect.DeepEqual(idToken.Audience, expAudience) {
t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience) t.Errorf("expected audience %q, got %q", expAudience, idToken.Audience)
} }
} }
if gotState := q.Get("state"); gotState != state { if gotState := q.Get("state"); gotState != state {
t.Errorf("state did not match, want=%q got=%q", state, gotState) t.Errorf("state did not match, want=%q got=%q", state, gotState)
@ -969,6 +973,8 @@ func TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("get failed: %v", err) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil { if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1058,7 +1064,6 @@ func TestPasswordDB(t *testing.T) {
t.Errorf("%s: %s", tc.name, diff) t.Errorf("%s: %s", tc.name, diff)
} }
} }
} }
func TestPasswordDBUsernamePrompt(t *testing.T) { func TestPasswordDBUsernamePrompt(t *testing.T) {
@ -1225,9 +1230,11 @@ func TestRefreshTokenFlow(t *testing.T) {
RedirectURL: redirectURL, 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) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
tok := &oauth2.Token{ tok := &oauth2.Token{
RefreshToken: oauth2Client.token.RefreshToken, RefreshToken: oauth2Client.token.RefreshToken,
@ -1235,9 +1242,11 @@ func TestRefreshTokenFlow(t *testing.T) {
} }
// Login in again to receive a new token. // 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) t.Fatalf("get failed: %v", err)
} }
defer resp.Body.Close()
// try to refresh expired token with old refresh token. // try to refresh expired token with old refresh token.
if _, err := oauth2Client.config.TokenSource(ctx, tok).Token(); err == nil { if _, err := oauth2Client.config.TokenSource(ctx, tok).Token(); err == nil {

View File

@ -47,19 +47,6 @@ type webConfig struct {
extra map[string]string 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 { func dirExists(dir string) error {
stat, err := os.Stat(dir) stat, err := os.Stat(dir)
if err != nil { if err != nil {
@ -189,7 +176,6 @@ func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
//assetPath is static/main.css //assetPath is static/main.css
//relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css" //relativeURL("/dex", "/dex/auth", "static/main.css") = "../static/main.css"
func relativeURL(serverPath, reqPath, assetPath string) string { func relativeURL(serverPath, reqPath, assetPath string) string {
splitPath := func(p string) []string { splitPath := func(p string) []string {
res := []string{} res := []string{}
parts := strings.Split(path.Clean(p), "/") 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) server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath)
// Remove common prefix of request path with server path // Remove common prefix of request path with server path
// nolint: ineffassign
server, req = stripCommonParts(server, req) server, req = stripCommonParts(server, req)
// Remove common prefix of request path with asset path // Remove common prefix of request path with asset path
@ -246,6 +233,7 @@ type connectorInfo struct {
ID string ID string
Name string Name string
URL string URL string
Type string
} }
type byName []connectorInfo type byName []connectorInfo

View File

@ -161,6 +161,8 @@ func testAuthRequestCRUD(t *testing.T, s storage.Storage) {
t.Fatalf("failed to delete auth request: %v", err) 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) { 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) got, err := s.GetAuthCode(a1.ID)
if err != nil { 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() { if a1.Expiry.Unix() != got.Expiry.Unix() {
t.Errorf("auth code expiry did not match want=%s vs got=%s", a1.Expiry, got.Expiry) 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) _, err = s.GetPassword(password1.Email)
mustBeErrNotFound(t, "password", err) mustBeErrNotFound(t, "password", err)
} }
func testOfflineSessionCRUD(t *testing.T, s storage.Storage) { 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 { 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 { 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 { } else if err != storage.ErrNotFound {
t.Errorf("expected storage.ErrNotFound, got %v", err) t.Errorf("expected storage.ErrNotFound, got %v", err)
} }

View File

@ -135,8 +135,8 @@ func testKeysConcurrentUpdate(t *testing.T, s storage.Storage) {
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
n := time.Now().UTC().Round(time.Second) n := time.Now().UTC().Round(time.Second)
keys1 := storage.Keys{ keys1 := storage.Keys{
SigningKey: jsonWebKeys[0].Private, SigningKey: jsonWebKeys[i].Private,
SigningKeyPub: jsonWebKeys[0].Public, SigningKeyPub: jsonWebKeys[i].Public,
NextRotation: n, NextRotation: n,
} }

View File

@ -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) { return c.txnUpdate(ctx, keyID(refreshTokenPrefix, id), func(currentValue []byte) ([]byte, error) {
var current RefreshToken var current RefreshToken
if len(currentValue) > 0 { if len(currentValue) > 0 {
if err := json.Unmarshal([]byte(currentValue), &current); err != nil { if err := json.Unmarshal(currentValue, &current); err != nil {
return nil, err return nil, err
} }
} }

View File

@ -55,14 +55,14 @@ type client struct {
} }
// idToName maps an arbitrary ID, such as an email or client ID to a Kubernetes object name. // idToName maps an arbitrary ID, such as an email or client ID to a Kubernetes object name.
func (c *client) idToName(s string) string { func (cli *client) idToName(s string) string {
return idToName(s, c.hash) return idToName(s, cli.hash)
} }
// offlineTokenName maps two arbitrary IDs, to a single Kubernetes object name. // 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. // This is used when more than one field is used to uniquely identify the object.
func (c *client) offlineTokenName(userID string, connID string) string { func (cli *client) offlineTokenName(userID string, connID string) string {
return offlineTokenName(userID, connID, c.hash) return offlineTokenName(userID, connID, cli.hash)
} }
// Kubernetes names must match the regexp '[a-z0-9]([-a-z0-9]*[a-z0-9])?'. // 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)), "=") 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/" basePath := "apis/"
if apiVersion == "v1" { if apiVersion == "v1" {
basePath = "api/" basePath = "api/"
@ -91,10 +91,10 @@ func (c *client) urlFor(apiVersion, namespace, resource, name string) string {
} else { } else {
p = path.Join(basePath, apiVersion, resource, name) p = path.Join(basePath, apiVersion, resource, name)
} }
if strings.HasSuffix(c.baseURL, "/") { if strings.HasSuffix(cli.baseURL, "/") {
return c.baseURL + p 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 // 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() r.Body.Close()
} }
func (c *client) get(resource, name string, v interface{}) error { func (cli *client) get(resource, name string, v interface{}) error {
return c.getResource(c.apiVersion, c.namespace, resource, name, v) return cli.getResource(cli.apiVersion, cli.namespace, resource, name, v)
} }
func (c *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error { func (cli *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {
url := c.urlFor(apiVersion, namespace, resource, name) url := cli.urlFor(apiVersion, namespace, resource, name)
resp, err := c.client.Get(url) resp, err := cli.client.Get(url)
if err != nil { if err != nil {
return err return err
} }
@ -173,22 +173,22 @@ func (c *client) getResource(apiVersion, namespace, resource, name string, v int
return json.NewDecoder(resp.Body).Decode(v) return json.NewDecoder(resp.Body).Decode(v)
} }
func (c *client) list(resource string, v interface{}) error { func (cli *client) list(resource string, v interface{}) error {
return c.get(resource, "", v) return cli.get(resource, "", v)
} }
func (c *client) post(resource string, v interface{}) error { func (cli *client) post(resource string, v interface{}) error {
return c.postResource(c.apiVersion, c.namespace, resource, v) 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) body, err := json.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("marshal object: %v", err) return fmt.Errorf("marshal object: %v", err)
} }
url := c.urlFor(apiVersion, namespace, resource, "") url := cli.urlFor(apiVersion, namespace, resource, "")
resp, err := c.client.Post(url, "application/json", bytes.NewReader(body)) resp, err := cli.client.Post(url, "application/json", bytes.NewReader(body))
if err != nil { if err != nil {
return err return err
} }
@ -196,13 +196,13 @@ func (c *client) postResource(apiVersion, namespace, resource string, v interfac
return checkHTTPErr(resp, http.StatusCreated) return checkHTTPErr(resp, http.StatusCreated)
} }
func (c *client) delete(resource, name string) error { func (cli *client) delete(resource, name string) error {
url := c.urlFor(c.apiVersion, c.namespace, resource, name) url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
req, err := http.NewRequest("DELETE", url, nil) req, err := http.NewRequest("DELETE", url, nil)
if err != nil { if err != nil {
return fmt.Errorf("create delete request: %v", err) return fmt.Errorf("create delete request: %v", err)
} }
resp, err := c.client.Do(req) resp, err := cli.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("delete request: %v", err) return fmt.Errorf("delete request: %v", err)
} }
@ -210,7 +210,7 @@ func (c *client) delete(resource, name string) error {
return checkHTTPErr(resp, http.StatusOK) return checkHTTPErr(resp, http.StatusOK)
} }
func (c *client) deleteAll(resource string) error { func (cli *client) deleteAll(resource string) error {
var list struct { var list struct {
k8sapi.TypeMeta `json:",inline"` k8sapi.TypeMeta `json:",inline"`
k8sapi.ListMeta `json:"metadata,omitempty"` k8sapi.ListMeta `json:"metadata,omitempty"`
@ -219,24 +219,24 @@ func (c *client) deleteAll(resource string) error {
k8sapi.ObjectMeta `json:"metadata,omitempty"` k8sapi.ObjectMeta `json:"metadata,omitempty"`
} `json:"items"` } `json:"items"`
} }
if err := c.list(resource, &list); err != nil { if err := cli.list(resource, &list); err != nil {
return err return err
} }
for _, item := range list.Items { 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 err
} }
} }
return nil 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) body, err := json.Marshal(v)
if err != nil { if err != nil {
return fmt.Errorf("marshal object: %v", err) 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)) req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
if err != nil { if err != nil {
return fmt.Errorf("create patch request: %v", err) 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))) req.Header.Set("Content-Length", strconv.Itoa(len(body)))
resp, err := c.client.Do(req) resp, err := cli.client.Do(req)
if err != nil { if err != nil {
return fmt.Errorf("patch request: %v", err) return fmt.Errorf("patch request: %v", err)
} }

View File

@ -43,7 +43,7 @@ type CustomResourceDefinitionNames struct {
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"` 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 type ResourceScope string
const ( const (

View File

@ -1,39 +1,104 @@
package kubernetes package kubernetes
import ( import (
"fmt" "io/ioutil"
"os" "os"
"strings"
"testing" "testing"
"github.com/sirupsen/logrus" "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"
"github.com/dexidp/dex/storage/conformance" "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) { func TestStorage(t *testing.T) {
loadClient(t) if os.Getenv("TEST_ASSET_KUBE_APISERVER") == "" || os.Getenv("TEST_ASSET_ETCD") == "" {
t.Skip("control plane binaries are missing")
} }
func loadClient(t *testing.T) *client { suite.Run(t, new(StorageTestSuite))
}
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{ config := Config{
KubeConfigFile: os.Getenv(testKubeConfigEnv), KubeConfigFile: f.Name(),
}
if config.KubeConfigFile == "" {
t.Skipf("test environment variable %q not set, skipping", testKubeConfigEnv)
} }
logger := &logrus.Logger{ logger := &logrus.Logger{
Out: os.Stderr, Out: os.Stderr,
Formatter: &logrus.TextFormatter{DisableColors: true}, Formatter: &logrus.TextFormatter{DisableColors: true},
Level: logrus.DebugLevel, Level: logrus.DebugLevel,
} }
s, err := config.open(logger, true)
if err != nil { client, err := config.open(logger, true)
t.Fatal(err) s.Require().NoError(err)
s.client = client
} }
return s
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.client
}
conformance.RunTests(s.T(), newStorage)
conformance.RunTransactionTests(s.T(), newStorage)
} }
func TestURLFor(t *testing.T) { 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)
}

View File

@ -308,10 +308,17 @@ func (s *MySQL) open(logger log.Logger) (*conn, error) {
return nil, err 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() err = db.Ping()
if err != nil { if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == mysqlErrUnknownSysVar { 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 5.7.20 introduced transaction_isolation and deprecated tx_isolation.
// MySQL 8.0 doesn't have tx_isolation at all. // MySQL 8.0 doesn't have tx_isolation at all.

View File

@ -169,7 +169,6 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest)
} }
return nil return nil
}) })
} }
func (c *conn) GetAuthRequest(id string) (storage.AuthRequest, error) { func (c *conn) GetAuthRequest(id string) (storage.AuthRequest, error) {

View File

@ -68,12 +68,12 @@ body {
background-size: contain; background-size: contain;
} }
.dex-btn-icon--bitbucket { .dex-btn-icon--bitbucket-cloud {
background-color: #205081; background-color: #205081;
background-image: url(../static/img/bitbucket-icon.svg); 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-color: #84B6EF;
background-image: url(../static/img/ldap-icon.svg); background-image: url(../static/img/ldap-icon.svg);
} }

View File

@ -7,7 +7,7 @@
<div class="theme-form-row"> <div class="theme-form-row">
<a href="{{ $c.URL }}" target="_self"> <a href="{{ $c.URL }}" target="_self">
<button class="dex-btn theme-btn-provider"> <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> <span class="dex-btn-text">Log in with {{ $c.Name }}</span>
</button> </button>
</a> </a>