Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
8c9c2518
Commit
8c9c2518
authored
Apr 17, 2017
by
rithu john
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
server: account for dynamically changing connector object in storage.
parent
2b8caf9b
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
400 additions
and
137 deletions
+400
-137
config.go
cmd/dex/config.go
+28
-34
config_test.go
cmd/dex/config_test.go
+1
-1
serve.go
cmd/dex/serve.go
+29
-31
handlers.go
server/handlers.go
+30
-21
server.go
server/server.go
+129
-22
server_test.go
server/server_test.go
+13
-23
conformance.go
storage/conformance/conformance.go
+4
-0
static_test.go
storage/memory/static_test.go
+91
-0
static.go
storage/static.go
+70
-0
storage.go
storage/storage.go
+5
-5
No files found.
cmd/dex/config.go
View file @
8c9c2518
...
@@ -9,13 +9,6 @@ import (
...
@@ -9,13 +9,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/bcrypt"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/connector/github"
"github.com/coreos/dex/connector/gitlab"
"github.com/coreos/dex/connector/ldap"
"github.com/coreos/dex/connector/mock"
"github.com/coreos/dex/connector/oidc"
"github.com/coreos/dex/connector/saml"
"github.com/coreos/dex/server"
"github.com/coreos/dex/server"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage/kubernetes"
"github.com/coreos/dex/storage/kubernetes"
...
@@ -25,17 +18,20 @@ import (
...
@@ -25,17 +18,20 @@ import (
// Config is the config format for the main application.
// Config is the config format for the main application.
type
Config
struct
{
type
Config
struct
{
Issuer
string
`json:"issuer"`
Issuer
string
`json:"issuer"`
Storage
Storage
`json:"storage"`
Storage
Storage
`json:"storage"`
Connectors
[]
Connector
`json:"connectors"`
Web
Web
`json:"web"`
Web
Web
`json:"web"`
OAuth2
OAuth2
`json:"oauth2"`
OAuth2
OAuth2
`json:"oauth2"`
GRPC
GRPC
`json:"grpc"`
GRPC
GRPC
`json:"grpc"`
Expiry
Expiry
`json:"expiry"`
Expiry
Expiry
`json:"expiry"`
Logger
Logger
`json:"logger"`
Logger
Logger
`json:"logger"`
Frontend
server
.
WebConfig
`json:"frontend"`
Frontend
server
.
WebConfig
`json:"frontend"`
// StaticConnectors are user defined connectors specified in the ConfigMap
// Write operations, like updating a connector, will fail.
StaticConnectors
[]
Connector
`json:"connectors"`
// StaticClients cause the server to use this list of clients rather than
// StaticClients cause the server to use this list of clients rather than
// querying the storage. Write operations, like creating a client, will fail.
// querying the storage. Write operations, like creating a client, will fail.
StaticClients
[]
storage
.
Client
`json:"staticClients"`
StaticClients
[]
storage
.
Client
`json:"staticClients"`
...
@@ -170,24 +166,7 @@ type Connector struct {
...
@@ -170,24 +166,7 @@ type Connector struct {
Name
string
`json:"name"`
Name
string
`json:"name"`
ID
string
`json:"id"`
ID
string
`json:"id"`
Config
ConnectorConfig
`json:"config"`
Config
server
.
ConnectorConfig
`json:"config"`
}
// ConnectorConfig is a configuration that can open a connector.
type
ConnectorConfig
interface
{
Open
(
logrus
.
FieldLogger
)
(
connector
.
Connector
,
error
)
}
var
connectors
=
map
[
string
]
func
()
ConnectorConfig
{
"mockCallback"
:
func
()
ConnectorConfig
{
return
new
(
mock
.
CallbackConfig
)
},
"mockPassword"
:
func
()
ConnectorConfig
{
return
new
(
mock
.
PasswordConfig
)
},
"ldap"
:
func
()
ConnectorConfig
{
return
new
(
ldap
.
Config
)
},
"github"
:
func
()
ConnectorConfig
{
return
new
(
github
.
Config
)
},
"gitlab"
:
func
()
ConnectorConfig
{
return
new
(
gitlab
.
Config
)
},
"oidc"
:
func
()
ConnectorConfig
{
return
new
(
oidc
.
Config
)
},
"saml"
:
func
()
ConnectorConfig
{
return
new
(
saml
.
Config
)
},
// Keep around for backwards compatibility.
"samlExperimental"
:
func
()
ConnectorConfig
{
return
new
(
saml
.
Config
)
},
}
}
// UnmarshalJSON allows Connector to implement the unmarshaler interface to
// UnmarshalJSON allows Connector to implement the unmarshaler interface to
...
@@ -203,7 +182,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
...
@@ -203,7 +182,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
if
err
:=
json
.
Unmarshal
(
b
,
&
conn
);
err
!=
nil
{
if
err
:=
json
.
Unmarshal
(
b
,
&
conn
);
err
!=
nil
{
return
fmt
.
Errorf
(
"parse connector: %v"
,
err
)
return
fmt
.
Errorf
(
"parse connector: %v"
,
err
)
}
}
f
,
ok
:=
connectors
[
conn
.
Type
]
f
,
ok
:=
server
.
ConnectorsConfig
[
conn
.
Type
]
if
!
ok
{
if
!
ok
{
return
fmt
.
Errorf
(
"unknown connector type %q"
,
conn
.
Type
)
return
fmt
.
Errorf
(
"unknown connector type %q"
,
conn
.
Type
)
}
}
...
@@ -224,6 +203,21 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
...
@@ -224,6 +203,21 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
return
nil
return
nil
}
}
// ToStorageConnector converts an object to storage connector type.
func
ToStorageConnector
(
c
Connector
)
(
storage
.
Connector
,
error
)
{
data
,
err
:=
json
.
Marshal
(
c
.
Config
)
if
err
!=
nil
{
return
storage
.
Connector
{},
fmt
.
Errorf
(
"failed to marshal connector config: %v"
,
err
)
}
return
storage
.
Connector
{
ID
:
c
.
ID
,
Type
:
c
.
Type
,
Name
:
c
.
Name
,
Config
:
data
,
},
nil
}
// Expiry holds configuration for the validity period of components.
// Expiry holds configuration for the validity period of components.
type
Expiry
struct
{
type
Expiry
struct
{
// SigningKeys defines the duration of time after which the SigningKeys will be rotated.
// SigningKeys defines the duration of time after which the SigningKeys will be rotated.
...
...
cmd/dex/config_test.go
View file @
8c9c2518
...
@@ -86,7 +86,7 @@ logger:
...
@@ -86,7 +86,7 @@ logger:
},
},
},
},
},
},
Connectors
:
[]
Connector
{
Static
Connectors
:
[]
Connector
{
{
{
Type
:
"mockCallback"
,
Type
:
"mockCallback"
,
ID
:
"mock"
,
ID
:
"mock"
,
...
...
cmd/dex/serve.go
View file @
8c9c2518
...
@@ -74,7 +74,6 @@ func serve(cmd *cobra.Command, args []string) error {
...
@@ -74,7 +74,6 @@ func serve(cmd *cobra.Command, args []string) error {
errMsg
string
errMsg
string
}{
}{
{
c
.
Issuer
==
""
,
"no issuer specified in config file"
},
{
c
.
Issuer
==
""
,
"no issuer specified in config file"
},
{
len
(
c
.
Connectors
)
==
0
&&
!
c
.
EnablePasswordDB
,
"no connectors supplied in config file"
},
{
!
c
.
EnablePasswordDB
&&
len
(
c
.
StaticPasswords
)
!=
0
,
"cannot specify static passwords without enabling password db"
},
{
!
c
.
EnablePasswordDB
&&
len
(
c
.
StaticPasswords
)
!=
0
,
"cannot specify static passwords without enabling password db"
},
{
c
.
Storage
.
Config
==
nil
,
"no storage suppied in config file"
},
{
c
.
Storage
.
Config
==
nil
,
"no storage suppied in config file"
},
{
c
.
Web
.
HTTP
==
""
&&
c
.
Web
.
HTTPS
==
""
,
"must supply a HTTP/HTTPS address to listen on"
},
{
c
.
Web
.
HTTP
==
""
&&
c
.
Web
.
HTTPS
==
""
,
"must supply a HTTP/HTTPS address to listen on"
},
...
@@ -128,34 +127,6 @@ func serve(cmd *cobra.Command, args []string) error {
...
@@ -128,34 +127,6 @@ func serve(cmd *cobra.Command, args []string) error {
}
}
}
}
connectors
:=
make
([]
server
.
Connector
,
len
(
c
.
Connectors
))
for
i
,
conn
:=
range
c
.
Connectors
{
if
conn
.
ID
==
""
{
return
fmt
.
Errorf
(
"invalid config: no ID field for connector %d"
,
i
)
}
if
conn
.
Config
==
nil
{
return
fmt
.
Errorf
(
"invalid config: no config field for connector %q"
,
conn
.
ID
)
}
if
conn
.
Name
==
""
{
return
fmt
.
Errorf
(
"invalid config: no Name field for connector %q"
,
conn
.
ID
)
}
logger
.
Infof
(
"config connector: %s"
,
conn
.
ID
)
connectorLogger
:=
logger
.
WithField
(
"connector"
,
conn
.
Name
)
c
,
err
:=
conn
.
Config
.
Open
(
connectorLogger
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to create connector %s: %v"
,
conn
.
ID
,
err
)
}
connectors
[
i
]
=
server
.
Connector
{
ID
:
conn
.
ID
,
DisplayName
:
conn
.
Name
,
Connector
:
c
,
}
}
if
c
.
EnablePasswordDB
{
logger
.
Infof
(
"config connector: local passwords enabled"
)
}
s
,
err
:=
c
.
Storage
.
Config
.
Open
(
logger
)
s
,
err
:=
c
.
Storage
.
Config
.
Open
(
logger
)
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to initialize storage: %v"
,
err
)
return
fmt
.
Errorf
(
"failed to initialize storage: %v"
,
err
)
...
@@ -176,6 +147,35 @@ func serve(cmd *cobra.Command, args []string) error {
...
@@ -176,6 +147,35 @@ func serve(cmd *cobra.Command, args []string) error {
s
=
storage
.
WithStaticPasswords
(
s
,
passwords
)
s
=
storage
.
WithStaticPasswords
(
s
,
passwords
)
}
}
if
c
.
EnablePasswordDB
{
c
.
StaticConnectors
=
append
(
c
.
StaticConnectors
,
Connector
{
ID
:
server
.
LocalConnector
,
Name
:
"Email"
,
Type
:
server
.
LocalConnector
,
})
logger
.
Infof
(
"config connector: local passwords enabled"
)
}
storageConnectors
:=
make
([]
storage
.
Connector
,
len
(
c
.
StaticConnectors
))
for
i
,
c
:=
range
c
.
StaticConnectors
{
if
c
.
ID
==
""
||
c
.
Name
==
""
||
c
.
Type
==
""
{
return
fmt
.
Errorf
(
"invalid config: ID, Type and Name fields are required for a connector"
)
}
if
c
.
Config
==
nil
{
return
fmt
.
Errorf
(
"invalid config: no config field for connector %q"
,
c
.
ID
)
}
logger
.
Infof
(
"config connector: %s"
,
c
.
ID
)
// convert to a storage connector object
conn
,
err
:=
ToStorageConnector
(
c
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to initialize storage connectors: %v"
,
err
)
}
storageConnectors
[
i
]
=
conn
}
s
=
storage
.
WithStaticConnectors
(
s
,
storageConnectors
)
if
len
(
c
.
OAuth2
.
ResponseTypes
)
>
0
{
if
len
(
c
.
OAuth2
.
ResponseTypes
)
>
0
{
logger
.
Infof
(
"config response types accepted: %s"
,
c
.
OAuth2
.
ResponseTypes
)
logger
.
Infof
(
"config response types accepted: %s"
,
c
.
OAuth2
.
ResponseTypes
)
}
}
...
@@ -194,10 +194,8 @@ func serve(cmd *cobra.Command, args []string) error {
...
@@ -194,10 +194,8 @@ func serve(cmd *cobra.Command, args []string) error {
SkipApprovalScreen
:
c
.
OAuth2
.
SkipApprovalScreen
,
SkipApprovalScreen
:
c
.
OAuth2
.
SkipApprovalScreen
,
AllowedOrigins
:
c
.
Web
.
AllowedOrigins
,
AllowedOrigins
:
c
.
Web
.
AllowedOrigins
,
Issuer
:
c
.
Issuer
,
Issuer
:
c
.
Issuer
,
Connectors
:
connectors
,
Storage
:
s
,
Storage
:
s
,
Web
:
c
.
Frontend
,
Web
:
c
.
Frontend
,
EnablePasswordDB
:
c
.
EnablePasswordDB
,
Logger
:
logger
,
Logger
:
logger
,
Now
:
now
,
Now
:
now
,
}
}
...
...
server/handlers.go
View file @
8c9c2518
...
@@ -167,24 +167,31 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
...
@@ -167,24 +167,31 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
return
return
}
}
if
len
(
s
.
connectors
)
==
1
{
connectors
,
e
:=
s
.
storage
.
ListConnectors
()
for
id
:=
range
s
.
connectors
{
if
e
!=
nil
{
s
.
logger
.
Errorf
(
"Failed to get list of connectors: %v"
,
err
)
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
"Failed to retrieve connector list."
)
return
}
if
len
(
connectors
)
==
1
{
for
_
,
c
:=
range
connectors
{
// 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.
http
.
Redirect
(
w
,
r
,
s
.
absPath
(
"/auth"
,
id
)
+
"?req="
+
authReq
.
ID
,
http
.
StatusFound
)
http
.
Redirect
(
w
,
r
,
s
.
absPath
(
"/auth"
,
c
.
ID
)
+
"?req="
+
authReq
.
ID
,
http
.
StatusFound
)
return
return
}
}
}
}
connectorInfos
:=
make
([]
connectorInfo
,
len
(
s
.
connectors
))
connectorInfos
:=
make
([]
connectorInfo
,
len
(
connectors
))
i
:=
0
i
:=
0
for
id
,
conn
:=
range
s
.
connectors
{
for
_
,
conn
:=
range
connectors
{
connectorInfos
[
i
]
=
connectorInfo
{
connectorInfos
[
i
]
=
connectorInfo
{
ID
:
id
,
ID
:
conn
.
ID
,
Name
:
conn
.
Display
Name
,
Name
:
conn
.
Name
,
// 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"
,
id
)
+
"?req="
+
authReq
.
ID
,
URL
:
s
.
absPath
(
"/auth"
,
conn
.
ID
)
+
"?req="
+
authReq
.
ID
,
}
}
i
++
i
++
}
}
...
@@ -196,10 +203,10 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
...
@@ -196,10 +203,10 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
func
(
s
*
Server
)
handleConnectorLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
func
(
s
*
Server
)
handleConnectorLogin
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
connID
:=
mux
.
Vars
(
r
)[
"connector"
]
connID
:=
mux
.
Vars
(
r
)[
"connector"
]
conn
,
ok
:=
s
.
connectors
[
connID
]
conn
,
err
:=
s
.
getConnector
(
connID
)
if
!
ok
{
if
err
!=
nil
{
s
.
logger
.
Errorf
(
"Failed to create authorization request
."
)
s
.
logger
.
Errorf
(
"Failed to create authorization request
: %v"
,
err
)
s
.
renderError
(
w
,
http
.
StatusBadRequest
,
"Requested resource does not exist
.
"
)
s
.
renderError
(
w
,
http
.
StatusBadRequest
,
"Requested resource does not exist"
)
return
return
}
}
...
@@ -339,8 +346,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
...
@@ -339,8 +346,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
return
return
}
}
conn
,
ok
:=
s
.
connectors
[
authReq
.
ConnectorID
]
conn
,
err
:=
s
.
getConnector
(
authReq
.
ConnectorID
)
if
!
ok
{
if
err
!=
nil
{
s
.
logger
.
Errorf
(
"Failed to get connector with id %q : %v"
,
authReq
.
ConnectorID
,
err
)
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
"Requested resource does not exist."
)
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
"Requested resource does not exist."
)
return
return
}
}
...
@@ -649,13 +657,14 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
...
@@ -649,13 +657,14 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
// Ensure the connector supports refresh tokens.
// Ensure the connector supports refresh tokens.
//
//
// Connectors like `saml` do not implement RefreshConnector.
// Connectors like `saml` do not implement RefreshConnector.
conn
,
ok
:=
s
.
connectors
[
authCode
.
ConnectorID
]
conn
,
err
:=
s
.
getConnector
(
authCode
.
ConnectorID
)
if
!
ok
{
if
err
!=
nil
{
s
.
logger
.
Errorf
(
"connector
ID not found: %q"
,
authCode
.
ConnectorID
)
s
.
logger
.
Errorf
(
"connector
with ID %q not found: %v"
,
authCode
.
ConnectorID
,
err
)
s
.
tokenErrHelper
(
w
,
errServerError
,
""
,
http
.
StatusInternalServerError
)
s
.
tokenErrHelper
(
w
,
errServerError
,
""
,
http
.
StatusInternalServerError
)
return
false
return
false
}
}
_
,
ok
=
conn
.
Connector
.
(
connector
.
RefreshConnector
)
_
,
ok
:=
conn
.
Connector
.
(
connector
.
RefreshConnector
)
if
!
ok
{
if
!
ok
{
return
false
return
false
}
}
...
@@ -841,9 +850,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
...
@@ -841,9 +850,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
scopes
=
requestedScopes
scopes
=
requestedScopes
}
}
conn
,
ok
:=
s
.
connectors
[
refresh
.
ConnectorID
]
conn
,
err
:=
s
.
getConnector
(
refresh
.
ConnectorID
)
if
!
ok
{
if
err
!=
nil
{
s
.
logger
.
Errorf
(
"connector
ID not found: %q"
,
refresh
.
ConnectorID
)
s
.
logger
.
Errorf
(
"connector
with ID %q not found: %v"
,
refresh
.
ConnectorID
,
err
)
s
.
tokenErrHelper
(
w
,
errServerError
,
""
,
http
.
StatusInternalServerError
)
s
.
tokenErrHelper
(
w
,
errServerError
,
""
,
http
.
StatusInternalServerError
)
return
return
}
}
...
...
server/server.go
View file @
8c9c2518
...
@@ -2,11 +2,13 @@ package server
...
@@ -2,11 +2,13 @@ package server
import
(
import
(
"context"
"context"
"encoding/json"
"errors"
"errors"
"fmt"
"fmt"
"net/http"
"net/http"
"net/url"
"net/url"
"path"
"path"
"sync"
"sync/atomic"
"sync/atomic"
"time"
"time"
...
@@ -17,14 +19,23 @@ import (
...
@@ -17,14 +19,23 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/mux"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/connector"
"github.com/coreos/dex/connector/github"
"github.com/coreos/dex/connector/gitlab"
"github.com/coreos/dex/connector/ldap"
"github.com/coreos/dex/connector/mock"
"github.com/coreos/dex/connector/oidc"
"github.com/coreos/dex/connector/saml"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage"
)
)
// Connector is a connector with metadata.
// LocalConnector is the local passwordDB connector which is an internal
// connector maintained by the server.
const
LocalConnector
=
"local"
// Connector is a connector with resource version metadata.
type
Connector
struct
{
type
Connector
struct
{
ID
string
ResourceVersion
string
DisplayName
string
Connector
connector
.
Connector
Connector
connector
.
Connector
}
}
// Config holds the server's configuration options.
// Config holds the server's configuration options.
...
@@ -36,9 +47,6 @@ type Config struct {
...
@@ -36,9 +47,6 @@ type Config struct {
// The backing persistence layer.
// The backing persistence layer.
Storage
storage
.
Storage
Storage
storage
.
Storage
// Strategies for federated identity.
Connectors
[]
Connector
// Valid values are "code" to enable the code flow and "token" to enable the implicit
// Valid values are "code" to enable the code flow and "token" to enable the implicit
// flow. If no response types are supplied this value defaults to "code".
// flow. If no response types are supplied this value defaults to "code".
SupportedResponseTypes
[]
string
SupportedResponseTypes
[]
string
...
@@ -60,8 +68,6 @@ type Config struct {
...
@@ -60,8 +68,6 @@ type Config struct {
// If specified, the server will use this function for determining time.
// If specified, the server will use this function for determining time.
Now
func
()
time
.
Time
Now
func
()
time
.
Time
EnablePasswordDB
bool
Web
WebConfig
Web
WebConfig
Logger
logrus
.
FieldLogger
Logger
logrus
.
FieldLogger
...
@@ -103,7 +109,9 @@ func value(val, defaultValue time.Duration) time.Duration {
...
@@ -103,7 +109,9 @@ func value(val, defaultValue time.Duration) time.Duration {
type
Server
struct
{
type
Server
struct
{
issuerURL
url
.
URL
issuerURL
url
.
URL
// Read-only map of connector IDs to connectors.
// mutex for the connectors map.
mu
sync
.
Mutex
// Map of connector IDs to connectors.
connectors
map
[
string
]
Connector
connectors
map
[
string
]
Connector
storage
storage
.
Storage
storage
storage
.
Storage
...
@@ -137,17 +145,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
...
@@ -137,17 +145,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"server: can't parse issuer URL"
)
return
nil
,
fmt
.
Errorf
(
"server: can't parse issuer URL"
)
}
}
if
c
.
EnablePasswordDB
{
c
.
Connectors
=
append
(
c
.
Connectors
,
Connector
{
ID
:
"local"
,
DisplayName
:
"Email"
,
Connector
:
newPasswordDB
(
c
.
Storage
),
})
}
if
len
(
c
.
Connectors
)
==
0
{
return
nil
,
errors
.
New
(
"server: no connectors specified"
)
}
if
c
.
Storage
==
nil
{
if
c
.
Storage
==
nil
{
return
nil
,
errors
.
New
(
"server: storage cannot be nil"
)
return
nil
,
errors
.
New
(
"server: storage cannot be nil"
)
}
}
...
@@ -195,8 +193,21 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
...
@@ -195,8 +193,21 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
logger
:
c
.
Logger
,
logger
:
c
.
Logger
,
}
}
for
_
,
conn
:=
range
c
.
Connectors
{
// Retrieves connector objects in backend storage. This list includes the static connectors
s
.
connectors
[
conn
.
ID
]
=
conn
// defined in the ConfigMap and dynamic connectors retrieved from the storage.
storageConnectors
,
err
:=
c
.
Storage
.
ListConnectors
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"server: failed to list connector objects from storage: %v"
,
err
)
}
if
len
(
storageConnectors
)
==
0
&&
len
(
s
.
connectors
)
==
0
{
return
nil
,
errors
.
New
(
"server: no connectors specified"
)
}
for
_
,
conn
:=
range
storageConnectors
{
if
_
,
err
:=
s
.
OpenConnector
(
conn
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"server: Failed to open connector %s: %v"
,
conn
.
ID
,
err
)
}
}
}
r
:=
mux
.
NewRouter
()
r
:=
mux
.
NewRouter
()
...
@@ -362,3 +373,99 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura
...
@@ -362,3 +373,99 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura
}()
}()
return
return
}
}
// ConnectorConfig is a configuration that can open a connector.
type
ConnectorConfig
interface
{
Open
(
logrus
.
FieldLogger
)
(
connector
.
Connector
,
error
)
}
// ConnectorsConfig variable provides an easy way to return a config struct
// depending on the connector type.
var
ConnectorsConfig
=
map
[
string
]
func
()
ConnectorConfig
{
"mockCallback"
:
func
()
ConnectorConfig
{
return
new
(
mock
.
CallbackConfig
)
},
"mockPassword"
:
func
()
ConnectorConfig
{
return
new
(
mock
.
PasswordConfig
)
},
"ldap"
:
func
()
ConnectorConfig
{
return
new
(
ldap
.
Config
)
},
"github"
:
func
()
ConnectorConfig
{
return
new
(
github
.
Config
)
},
"gitlab"
:
func
()
ConnectorConfig
{
return
new
(
gitlab
.
Config
)
},
"oidc"
:
func
()
ConnectorConfig
{
return
new
(
oidc
.
Config
)
},
"saml"
:
func
()
ConnectorConfig
{
return
new
(
saml
.
Config
)
},
// Keep around for backwards compatibility.
"samlExperimental"
:
func
()
ConnectorConfig
{
return
new
(
saml
.
Config
)
},
}
// openConnector will parse the connector config and open the connector.
func
openConnector
(
logger
logrus
.
FieldLogger
,
conn
storage
.
Connector
)
(
connector
.
Connector
,
error
)
{
var
c
connector
.
Connector
f
,
ok
:=
ConnectorsConfig
[
conn
.
Type
]
if
!
ok
{
return
c
,
fmt
.
Errorf
(
"unknown connector type %q"
,
conn
.
Type
)
}
connConfig
:=
f
()
if
len
(
conn
.
Config
)
!=
0
{
data
:=
[]
byte
(
string
(
conn
.
Config
))
if
err
:=
json
.
Unmarshal
(
data
,
connConfig
);
err
!=
nil
{
return
c
,
fmt
.
Errorf
(
"parse connector config: %v"
,
err
)
}
}
c
,
err
:=
connConfig
.
Open
(
logger
)
if
err
!=
nil
{
return
c
,
fmt
.
Errorf
(
"failed to create connector %s: %v"
,
conn
.
ID
,
err
)
}
return
c
,
nil
}
// OpenConnector updates server connector map with specified connector object.
func
(
s
*
Server
)
OpenConnector
(
conn
storage
.
Connector
)
(
Connector
,
error
)
{
var
c
connector
.
Connector
if
conn
.
Type
==
LocalConnector
{
c
=
newPasswordDB
(
s
.
storage
)
}
else
{
var
err
error
c
,
err
=
openConnector
(
s
.
logger
.
WithField
(
"connector"
,
conn
.
Name
),
conn
)
if
err
!=
nil
{
return
Connector
{},
fmt
.
Errorf
(
"failed to open connector: %v"
,
err
)
}
}
connector
:=
Connector
{
ResourceVersion
:
conn
.
ResourceVersion
,
Connector
:
c
,
}
s
.
mu
.
Lock
()
s
.
connectors
[
conn
.
ID
]
=
connector
s
.
mu
.
Unlock
()
return
connector
,
nil
}
// getConnector retrieves the connector object with the given id from the storage
// and updates the connector list for server if necessary.
func
(
s
*
Server
)
getConnector
(
id
string
)
(
Connector
,
error
)
{
storageConnector
,
err
:=
s
.
storage
.
GetConnector
(
id
)
if
err
!=
nil
{
return
Connector
{},
fmt
.
Errorf
(
"failed to get connector object from storage: %v"
,
err
)
}
var
conn
Connector
var
ok
bool
s
.
mu
.
Lock
()
conn
,
ok
=
s
.
connectors
[
id
]
s
.
mu
.
Unlock
()
if
!
ok
||
storageConnector
.
ResourceVersion
!=
conn
.
ResourceVersion
{
// Connector object does not exist in server connectors map or
// has been updated in the storage. Need to get latest.
conn
,
err
:=
s
.
OpenConnector
(
storageConnector
)
if
err
!=
nil
{
return
Connector
{},
fmt
.
Errorf
(
"failed to open connector: %v"
,
err
)
}
return
conn
,
nil
}
return
conn
,
nil
}
server/server_test.go
View file @
8c9c2518
...
@@ -89,13 +89,6 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
...
@@ -89,13 +89,6 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
config
:=
Config
{
config
:=
Config
{
Issuer
:
s
.
URL
,
Issuer
:
s
.
URL
,
Storage
:
memory
.
New
(
logger
),
Storage
:
memory
.
New
(
logger
),
Connectors
:
[]
Connector
{
{
ID
:
"mock"
,
DisplayName
:
"Mock"
,
Connector
:
mock
.
NewCallbackConnector
(
logger
),
},
},
Web
:
WebConfig
{
Web
:
WebConfig
{
Dir
:
filepath
.
Join
(
os
.
Getenv
(
"GOPATH"
),
"src/github.com/coreos/dex/web"
),
Dir
:
filepath
.
Join
(
os
.
Getenv
(
"GOPATH"
),
"src/github.com/coreos/dex/web"
),
},
},
...
@@ -106,6 +99,16 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
...
@@ -106,6 +99,16 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
}
}
s
.
URL
=
config
.
Issuer
s
.
URL
=
config
.
Issuer
connector
:=
storage
.
Connector
{
ID
:
"mock"
,
Type
:
"mockCallback"
,
Name
:
"Mock"
,
ResourceVersion
:
"1"
,
}
if
err
:=
config
.
Storage
.
CreateConnector
(
connector
);
err
!=
nil
{
t
.
Fatalf
(
"create connector: %v"
,
err
)
}
var
err
error
var
err
error
if
server
,
err
=
newServer
(
ctx
,
config
,
staticRotationStrategy
(
testKey
));
err
!=
nil
{
if
server
,
err
=
newServer
(
ctx
,
config
,
staticRotationStrategy
(
testKey
));
err
!=
nil
{
t
.
Fatal
(
err
)
t
.
Fatal
(
err
)
...
@@ -416,29 +419,16 @@ func TestOAuth2CodeFlow(t *testing.T) {
...
@@ -416,29 +419,16 @@ func TestOAuth2CodeFlow(t *testing.T) {
defer
cancel
()
defer
cancel
()
// Setup a dex server.
// Setup a dex server.
logger
:=
&
logrus
.
Logger
{
Out
:
os
.
Stderr
,
Formatter
:
&
logrus
.
TextFormatter
{
DisableColors
:
true
},
Level
:
logrus
.
DebugLevel
,
}
httpServer
,
s
:=
newTestServer
(
ctx
,
t
,
func
(
c
*
Config
)
{
httpServer
,
s
:=
newTestServer
(
ctx
,
t
,
func
(
c
*
Config
)
{
c
.
Issuer
=
c
.
Issuer
+
"/non-root-path"
c
.
Issuer
=
c
.
Issuer
+
"/non-root-path"
c
.
Now
=
now
c
.
Now
=
now
c
.
IDTokensValidFor
=
idTokensValidFor
c
.
IDTokensValidFor
=
idTokensValidFor
// Testing connector that redirects without interaction with
// the user.
conn
=
mock
.
NewCallbackConnector
(
logger
)
.
(
*
mock
.
Callback
)
c
.
Connectors
=
[]
Connector
{
{
ID
:
"mock"
,
DisplayName
:
"mock"
,
Connector
:
conn
,
},
}
})
})
defer
httpServer
.
Close
()
defer
httpServer
.
Close
()
mockConn
:=
s
.
connectors
[
"mock"
]
conn
=
mockConn
.
Connector
.
(
*
mock
.
Callback
)
// Query server's provider metadata.
// Query server's provider metadata.
p
,
err
:=
oidc
.
NewProvider
(
ctx
,
httpServer
.
URL
)
p
,
err
:=
oidc
.
NewProvider
(
ctx
,
httpServer
.
URL
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
storage/conformance/conformance.go
View file @
8c9c2518
...
@@ -628,6 +628,10 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) {
...
@@ -628,6 +628,10 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) {
c1
.
Type
=
"oidc"
c1
.
Type
=
"oidc"
getAndCompare
(
id1
,
c1
)
getAndCompare
(
id1
,
c1
)
if
_
,
err
:=
s
.
ListConnectors
();
err
!=
nil
{
t
.
Fatalf
(
"failed to list connectors: %v"
,
err
)
}
if
err
:=
s
.
DeleteConnector
(
c1
.
ID
);
err
!=
nil
{
if
err
:=
s
.
DeleteConnector
(
c1
.
ID
);
err
!=
nil
{
t
.
Fatalf
(
"failed to delete connector: %v"
,
err
)
t
.
Fatalf
(
"failed to delete connector: %v"
,
err
)
}
}
...
...
storage/memory/static_test.go
View file @
8c9c2518
...
@@ -190,3 +190,94 @@ func TestStaticPasswords(t *testing.T) {
...
@@ -190,3 +190,94 @@ func TestStaticPasswords(t *testing.T) {
}
}
}
}
}
}
func
TestStaticConnectors
(
t
*
testing
.
T
)
{
logger
:=
&
logrus
.
Logger
{
Out
:
os
.
Stderr
,
Formatter
:
&
logrus
.
TextFormatter
{
DisableColors
:
true
},
Level
:
logrus
.
DebugLevel
,
}
backing
:=
New
(
logger
)
config1
:=
[]
byte
(
`{"issuer": "https://accounts.google.com"}`
)
config2
:=
[]
byte
(
`{"host": "ldap.example.com:636"}`
)
config3
:=
[]
byte
(
`{"issuer": "https://example.com"}`
)
c1
:=
storage
.
Connector
{
ID
:
storage
.
NewID
(),
Type
:
"oidc"
,
Name
:
"oidc"
,
ResourceVersion
:
"1"
,
Config
:
config1
}
c2
:=
storage
.
Connector
{
ID
:
storage
.
NewID
(),
Type
:
"ldap"
,
Name
:
"ldap"
,
ResourceVersion
:
"1"
,
Config
:
config2
}
c3
:=
storage
.
Connector
{
ID
:
storage
.
NewID
(),
Type
:
"saml"
,
Name
:
"saml"
,
ResourceVersion
:
"1"
,
Config
:
config3
}
backing
.
CreateConnector
(
c1
)
s
:=
storage
.
WithStaticConnectors
(
backing
,
[]
storage
.
Connector
{
c2
})
tests
:=
[]
struct
{
name
string
action
func
()
error
wantErr
bool
}{
{
name
:
"get connector from static storage"
,
action
:
func
()
error
{
_
,
err
:=
s
.
GetConnector
(
c2
.
ID
)
return
err
},
},
{
name
:
"get connector from backing storage"
,
action
:
func
()
error
{
_
,
err
:=
s
.
GetConnector
(
c1
.
ID
)
return
err
},
},
{
name
:
"update static connector"
,
action
:
func
()
error
{
updater
:=
func
(
c
storage
.
Connector
)
(
storage
.
Connector
,
error
)
{
c
.
Name
=
"New"
return
c
,
nil
}
return
s
.
UpdateConnector
(
c2
.
ID
,
updater
)
},
wantErr
:
true
,
},
{
name
:
"update non-static connector"
,
action
:
func
()
error
{
updater
:=
func
(
c
storage
.
Connector
)
(
storage
.
Connector
,
error
)
{
c
.
Name
=
"New"
return
c
,
nil
}
return
s
.
UpdateConnector
(
c1
.
ID
,
updater
)
},
},
{
name
:
"list connectors"
,
action
:
func
()
error
{
connectors
,
err
:=
s
.
ListConnectors
()
if
err
!=
nil
{
return
err
}
if
n
:=
len
(
connectors
);
n
!=
2
{
return
fmt
.
Errorf
(
"expected 2 connectors got %d"
,
n
)
}
return
nil
},
},
{
name
:
"create connector"
,
action
:
func
()
error
{
return
s
.
CreateConnector
(
c3
)
},
},
}
for
_
,
tc
:=
range
tests
{
err
:=
tc
.
action
()
if
err
!=
nil
&&
!
tc
.
wantErr
{
t
.
Errorf
(
"%s: %v"
,
tc
.
name
,
err
)
}
if
err
==
nil
&&
tc
.
wantErr
{
t
.
Errorf
(
"%s: expected error, didn't get one"
,
tc
.
name
)
}
}
}
storage/static.go
View file @
8c9c2518
...
@@ -150,3 +150,73 @@ func (s staticPasswordsStorage) UpdatePassword(email string, updater func(old Pa
...
@@ -150,3 +150,73 @@ func (s staticPasswordsStorage) UpdatePassword(email string, updater func(old Pa
}
}
return
s
.
Storage
.
UpdatePassword
(
email
,
updater
)
return
s
.
Storage
.
UpdatePassword
(
email
,
updater
)
}
}
// staticConnectorsStorage represents a storage with read-only set of connectors.
type
staticConnectorsStorage
struct
{
Storage
// A read-only set of connectors.
connectors
[]
Connector
connectorsByID
map
[
string
]
Connector
}
// WithStaticConnectors returns a storage with a read-only set of Connectors. Write actions,
// such as updating existing Connectors, will fail.
func
WithStaticConnectors
(
s
Storage
,
staticConnectors
[]
Connector
)
Storage
{
connectorsByID
:=
make
(
map
[
string
]
Connector
,
len
(
staticConnectors
))
for
_
,
c
:=
range
staticConnectors
{
connectorsByID
[
c
.
ID
]
=
c
}
return
staticConnectorsStorage
{
s
,
staticConnectors
,
connectorsByID
}
}
func
(
s
staticConnectorsStorage
)
isStatic
(
id
string
)
bool
{
_
,
ok
:=
s
.
connectorsByID
[
id
]
return
ok
}
func
(
s
staticConnectorsStorage
)
GetConnector
(
id
string
)
(
Connector
,
error
)
{
if
connector
,
ok
:=
s
.
connectorsByID
[
id
];
ok
{
return
connector
,
nil
}
return
s
.
Storage
.
GetConnector
(
id
)
}
func
(
s
staticConnectorsStorage
)
ListConnectors
()
([]
Connector
,
error
)
{
connectors
,
err
:=
s
.
Storage
.
ListConnectors
()
if
err
!=
nil
{
return
nil
,
err
}
n
:=
0
for
_
,
connector
:=
range
connectors
{
// If an entry has the same id as those provided in the static
// values, prefer the static value.
if
!
s
.
isStatic
(
connector
.
ID
)
{
connectors
[
n
]
=
connector
n
++
}
}
return
append
(
connectors
[
:
n
],
s
.
connectors
...
),
nil
}
func
(
s
staticConnectorsStorage
)
CreateConnector
(
c
Connector
)
error
{
if
s
.
isStatic
(
c
.
ID
)
{
return
errors
.
New
(
"static connectors: read-only cannot create connector"
)
}
return
s
.
Storage
.
CreateConnector
(
c
)
}
func
(
s
staticConnectorsStorage
)
DeleteConnector
(
id
string
)
error
{
if
s
.
isStatic
(
id
)
{
return
errors
.
New
(
"static connectors: read-only cannot delete connector"
)
}
return
s
.
Storage
.
DeleteConnector
(
id
)
}
func
(
s
staticConnectorsStorage
)
UpdateConnector
(
id
string
,
updater
func
(
old
Connector
)
(
Connector
,
error
))
error
{
if
s
.
isStatic
(
id
)
{
return
errors
.
New
(
"static connectors: read-only cannot update connector"
)
}
return
s
.
Storage
.
UpdateConnector
(
id
,
updater
)
}
storage/storage.go
View file @
8c9c2518
...
@@ -298,17 +298,17 @@ type Password struct {
...
@@ -298,17 +298,17 @@ type Password struct {
// Connector is an object that contains the metadata about connectors used to login to Dex.
// Connector is an object that contains the metadata about connectors used to login to Dex.
type
Connector
struct
{
type
Connector
struct
{
// ID that will uniquely identify the connector object.
// ID that will uniquely identify the connector object.
ID
string
ID
string
`json:"id"`
// The Type of the connector. E.g. 'oidc' or 'ldap'
// The Type of the connector. E.g. 'oidc' or 'ldap'
Type
string
Type
string
`json:"type"`
// The Name of the connector that is used when displaying it to the end user.
// The Name of the connector that is used when displaying it to the end user.
Name
string
Name
string
`json:"name"`
// ResourceVersion is the static versioning used to keep track of dynamic configuration
// ResourceVersion is the static versioning used to keep track of dynamic configuration
// changes to the connector object made by the API calls.
// changes to the connector object made by the API calls.
ResourceVersion
string
ResourceVersion
string
`json:"resourceVersion"`
// Config holds all the configuration information specific to the connector type. Since there
// Config holds all the configuration information specific to the connector type. Since there
// no generic struct we can use for this purpose, it is stored as a byte stream.
// no generic struct we can use for this purpose, it is stored as a byte stream.
Config
[]
byte
Config
[]
byte
`json:"email"`
}
}
// VerificationKey is a rotated signing key which can still be used to verify
// VerificationKey is a rotated signing key which can still be used to verify
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment