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
c113df96
Commit
c113df96
authored
Aug 20, 2016
by
Eric Chiang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
*: support the implicit flow
parent
dfa840d2
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
100 additions
and
38 deletions
+100
-38
config.go
cmd/dex/config.go
+6
-0
handlers.go
server/handlers.go
+44
-24
oauth2.go
server/oauth2.go
+26
-10
server.go
server/server.go
+24
-4
No files found.
cmd/dex/config.go
View file @
c113df96
...
...
@@ -19,10 +19,16 @@ type Config struct {
Storage
Storage
`yaml:"storage"`
Connectors
[]
Connector
`yaml:"connectors"`
Web
Web
`yaml:"web"`
OAuth2
OAuth2
`yaml:"oauth2"`
StaticClients
[]
storage
.
Client
`yaml:"staticClients"`
}
// OAuth2 describes enabled OAuth2 extensions.
type
OAuth2
struct
{
ResponseTypes
[]
string
`yaml:"responseTypes"`
}
// Web is the config format for the HTTP server.
type
Web
struct
{
HTTP
string
`yaml:"http"`
...
...
server/handlers.go
View file @
c113df96
...
...
@@ -102,7 +102,7 @@ func (s *Server) handleDiscovery(w http.ResponseWriter, r *http.Request) {
// handleAuthorization handles the OAuth2 auth endpoint.
func
(
s
*
Server
)
handleAuthorization
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
authReq
,
err
:=
parseAuthorizationRequest
(
s
.
storage
,
r
)
authReq
,
err
:=
parseAuthorizationRequest
(
s
.
storage
,
s
.
supportedResponseTypes
,
r
)
if
err
!=
nil
{
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
err
.
Type
,
err
.
Description
)
return
...
...
@@ -318,35 +318,55 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
}
return
}
code
:=
storage
.
AuthCode
{
ID
:
storage
.
NewID
(),
ClientID
:
authReq
.
ClientID
,
ConnectorID
:
authReq
.
ConnectorID
,
Nonce
:
authReq
.
Nonce
,
Scopes
:
authReq
.
Scopes
,
Claims
:
*
authReq
.
Claims
,
Expiry
:
s
.
now
()
.
Add
(
time
.
Minute
*
5
),
RedirectURI
:
authReq
.
RedirectURI
,
}
if
err
:=
s
.
storage
.
CreateAuthCode
(
code
);
err
!=
nil
{
log
.
Printf
(
"Failed to create auth code: %v"
,
err
)
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
errServerError
,
""
)
return
}
if
authReq
.
RedirectURI
==
"urn:ietf:wg:oauth:2.0:oob"
{
// TODO(ericchiang): Add a proper template.
fmt
.
Fprintf
(
w
,
"Code: %s"
,
code
.
ID
)
return
}
u
,
err
:=
url
.
Parse
(
authReq
.
RedirectURI
)
if
err
!=
nil
{
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
errServerError
,
"Invalid redirect URI."
)
return
}
q
:=
u
.
Query
()
q
.
Set
(
"code"
,
code
.
ID
)
for
_
,
responseType
:=
range
authReq
.
ResponseTypes
{
switch
responseType
{
case
responseTypeCode
:
code
:=
storage
.
AuthCode
{
ID
:
storage
.
NewID
(),
ClientID
:
authReq
.
ClientID
,
ConnectorID
:
authReq
.
ConnectorID
,
Nonce
:
authReq
.
Nonce
,
Scopes
:
authReq
.
Scopes
,
Claims
:
*
authReq
.
Claims
,
Expiry
:
s
.
now
()
.
Add
(
time
.
Minute
*
5
),
RedirectURI
:
authReq
.
RedirectURI
,
}
if
err
:=
s
.
storage
.
CreateAuthCode
(
code
);
err
!=
nil
{
log
.
Printf
(
"Failed to create auth code: %v"
,
err
)
s
.
renderError
(
w
,
http
.
StatusInternalServerError
,
errServerError
,
""
)
return
}
if
authReq
.
RedirectURI
==
redirectURIOOB
{
// TODO(ericchiang): Add a proper template.
fmt
.
Fprintf
(
w
,
"Code: %s"
,
code
.
ID
)
return
}
q
.
Set
(
"code"
,
code
.
ID
)
case
responseTypeToken
:
idToken
,
expiry
,
err
:=
s
.
newIDToken
(
authReq
.
ClientID
,
*
authReq
.
Claims
,
authReq
.
Scopes
,
authReq
.
Nonce
)
if
err
!=
nil
{
log
.
Printf
(
"failed to create ID token: %v"
,
err
)
tokenErr
(
w
,
errServerError
,
""
,
http
.
StatusInternalServerError
)
return
}
v
:=
url
.
Values
{}
v
.
Set
(
"access_token"
,
storage
.
NewID
())
v
.
Set
(
"token_type"
,
"bearer"
)
v
.
Set
(
"id_token"
,
idToken
)
v
.
Set
(
"state"
,
authReq
.
State
)
v
.
Set
(
"expires_in"
,
strconv
.
Itoa
(
int
(
expiry
.
Sub
(
s
.
now
()))))
u
.
Fragment
=
v
.
Encode
()
}
}
q
.
Set
(
"state"
,
authReq
.
State
)
u
.
RawQuery
=
q
.
Encode
()
http
.
Redirect
(
w
,
r
,
u
.
String
(),
http
.
StatusSeeOther
)
...
...
server/oauth2.go
View file @
c113df96
...
...
@@ -77,6 +77,10 @@ const (
scopeCrossClientPrefix
=
"oauth2:server:client_id:"
)
const
(
redirectURIOOB
=
"urn:ietf:wg:oauth:2.0:oob"
)
const
(
grantTypeAuthorizationCode
=
"authorization_code"
grantTypeRefreshToken
=
"refresh_token"
...
...
@@ -88,12 +92,6 @@ const (
responseTypeIDToken
=
"id_token"
// ID Token in url fragment
)
var
validResponseTypes
=
map
[
string
]
bool
{
"code"
:
true
,
"token"
:
true
,
"id_token"
:
true
,
}
type
audience
[]
string
func
(
a
audience
)
MarshalJSON
()
([]
byte
,
error
)
{
...
...
@@ -182,7 +180,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str
// parse the initial request from the OAuth2 client.
//
// For correctness the logic is largely copied from https://github.com/RangelReale/osin.
func
parseAuthorizationRequest
(
s
storage
.
Storage
,
r
*
http
.
Request
)
(
req
storage
.
AuthRequest
,
oauth2Err
*
authErr
)
{
func
parseAuthorizationRequest
(
s
storage
.
Storage
,
supportedResponseTypes
map
[
string
]
bool
,
r
*
http
.
Request
)
(
req
storage
.
AuthRequest
,
oauth2Err
*
authErr
)
{
if
err
:=
r
.
ParseForm
();
err
!=
nil
{
return
req
,
&
authErr
{
""
,
""
,
errInvalidRequest
,
"Failed to parse request."
}
}
...
...
@@ -252,9 +250,27 @@ func parseAuthorizationRequest(s storage.Storage, r *http.Request) (req storage.
return
req
,
newErr
(
"invalid_scope"
,
"Client can't request scope(s) %q"
,
invalidScopes
)
}
nonce
:=
r
.
Form
.
Get
(
"nonce"
)
responseTypes
:=
strings
.
Split
(
r
.
Form
.
Get
(
"response_type"
),
" "
)
for
_
,
responseType
:=
range
responseTypes
{
if
!
validResponseTypes
[
responseType
]
{
if
!
supportedResponseTypes
[
responseType
]
{
return
req
,
newErr
(
"invalid_request"
,
"Invalid response type %q"
,
responseType
)
}
switch
responseType
{
case
responseTypeCode
:
case
responseTypeToken
:
// Implicit flow requires a nonce value.
// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest
if
nonce
==
""
{
return
req
,
newErr
(
"invalid_request"
,
"Response type 'token' requires a 'nonce' value."
)
}
if
redirectURI
==
redirectURIOOB
{
err
:=
fmt
.
Sprintf
(
"Cannot use response type 'token' with redirect_uri '%s'."
,
redirectURIOOB
)
return
req
,
newErr
(
"invalid_request"
,
err
)
}
default
:
return
req
,
newErr
(
"invalid_request"
,
"Invalid response type %q"
,
responseType
)
}
}
...
...
@@ -263,7 +279,7 @@ func parseAuthorizationRequest(s storage.Storage, r *http.Request) (req storage.
ID
:
storage
.
NewID
(),
ClientID
:
client
.
ID
,
State
:
r
.
Form
.
Get
(
"state"
),
Nonce
:
r
.
Form
.
Get
(
"nonce"
)
,
Nonce
:
nonce
,
ForceApprovalPrompt
:
r
.
Form
.
Get
(
"approval_prompt"
)
==
"force"
,
Scopes
:
scopes
,
RedirectURI
:
redirectURI
,
...
...
@@ -308,7 +324,7 @@ func validateRedirectURI(client storage.Client, redirectURI string) bool {
return
false
}
if
redirectURI
==
"urn:ietf:wg:oauth:2.0:oob"
{
if
redirectURI
==
redirectURIOOB
{
return
true
}
if
!
strings
.
HasPrefix
(
redirectURI
,
"http://localhost:"
)
{
...
...
server/server.go
View file @
c113df96
...
...
@@ -23,6 +23,8 @@ type Connector struct {
}
// Config holds the server's configuration options.
//
// Multiple servers using the same storage are expected to be configured identically.
type
Config
struct
{
Issuer
string
...
...
@@ -32,8 +34,10 @@ type Config struct {
// Strategies for federated identity.
Connectors
[]
Connector
// NOTE: Multiple servers using the same storage are expected to set rotation and
// validity periods to the same values.
// 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".
SupportedResponseTypes
[]
string
RotateKeysAfter
time
.
Duration
// Defaults to 6 hours.
IDTokensValidFor
time
.
Duration
// Defaults to 24 hours
...
...
@@ -63,6 +67,8 @@ type Server struct {
// No package level API to set this, only used in tests.
skipApproval
bool
supportedResponseTypes
map
[
string
]
bool
now
func
()
time
.
Time
idTokensValidFor
time
.
Duration
...
...
@@ -87,6 +93,19 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
if
c
.
Storage
==
nil
{
return
nil
,
errors
.
New
(
"server: storage cannot be nil"
)
}
if
len
(
c
.
SupportedResponseTypes
)
==
0
{
c
.
SupportedResponseTypes
=
[]
string
{
responseTypeCode
}
}
supported
:=
make
(
map
[
string
]
bool
)
for
_
,
respType
:=
range
c
.
SupportedResponseTypes
{
switch
respType
{
case
responseTypeCode
,
responseTypeToken
:
default
:
return
nil
,
fmt
.
Errorf
(
"unsupported response_type %q"
,
respType
)
}
supported
[
respType
]
=
true
}
now
:=
c
.
Now
if
now
==
nil
{
...
...
@@ -102,8 +121,9 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
),
now
,
),
idTokensValidFor
:
value
(
c
.
IDTokensValidFor
,
24
*
time
.
Hour
),
now
:
now
,
supportedResponseTypes
:
supported
,
idTokensValidFor
:
value
(
c
.
IDTokensValidFor
,
24
*
time
.
Hour
),
now
:
now
,
}
for
_
,
conn
:=
range
c
.
Connectors
{
...
...
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