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
49e59fb5
Unverified
Commit
49e59fb5
authored
May 24, 2019
by
Stephan Renatus
Committed by
GitHub
May 24, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1448 from cappyzawa/user-id-key
oidc: Make userID configurable
parents
59560c99
96508368
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
247 additions
and
13 deletions
+247
-13
oidc.md
Documentation/connectors/oidc.md
+6
-0
oidc.go
connector/oidc/oidc.go
+35
-13
oidc_test.go
connector/oidc/oidc_test.go
+206
-0
No files found.
Documentation/connectors/oidc.md
View file @
49e59fb5
...
...
@@ -66,6 +66,12 @@ connectors:
# all the claims requested.
# https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
# getUserInfo: true
# The set claim is used as user id.
# Default: sub
# Claims list at https://openid.net/specs/openid-connect-core-1_0.html#Claims
#
# userIdKey: nickname
```
[
oidc-doc
]:
openid-connect.md
...
...
connector/oidc/oidc.go
View file @
49e59fb5
...
...
@@ -44,6 +44,9 @@ type Config struct {
// the token. This is especially useful where upstreams return "thin"
// id tokens
GetUserInfo
bool
`json:"getUserInfo"`
// Configurable key which contains the user id claim
UserIDKey
string
`json:"userIDKey"`
}
// Domains that don't support basic auth. golang.org/x/oauth2 has an internal
...
...
@@ -127,6 +130,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
hostedDomains
:
c
.
HostedDomains
,
insecureSkipEmailVerified
:
c
.
InsecureSkipEmailVerified
,
getUserInfo
:
c
.
GetUserInfo
,
userIDKey
:
c
.
UserIDKey
,
},
nil
}
...
...
@@ -146,6 +150,7 @@ type oidcConnector struct {
hostedDomains
[]
string
insecureSkipEmailVerified
bool
getUserInfo
bool
userIDKey
string
}
func
(
c
*
oidcConnector
)
Close
()
error
{
...
...
@@ -199,33 +204,41 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide
return
identity
,
fmt
.
Errorf
(
"oidc: failed to verify ID Token: %v"
,
err
)
}
var
claims
struct
{
Username
string
`json:"name"`
Email
string
`json:"email"`
EmailVerified
bool
`json:"email_verified"`
HostedDomain
string
`json:"hd"`
}
var
claims
map
[
string
]
interface
{}
if
err
:=
idToken
.
Claims
(
&
claims
);
err
!=
nil
{
return
identity
,
fmt
.
Errorf
(
"oidc: failed to decode claims: %v"
,
err
)
}
name
,
found
:=
claims
[
"name"
]
.
(
string
)
if
!
found
{
return
identity
,
errors
.
New
(
"missing
\"
name
\"
claim"
)
}
email
,
found
:=
claims
[
"email"
]
.
(
string
)
if
!
found
{
return
identity
,
errors
.
New
(
"missing
\"
email
\"
claim"
)
}
emailVerified
,
found
:=
claims
[
"email_verified"
]
.
(
bool
)
if
!
found
{
return
identity
,
errors
.
New
(
"missing
\"
email_verified
\"
claim"
)
}
hostedDomain
,
_
:=
claims
[
"hd"
]
.
(
string
)
if
len
(
c
.
hostedDomains
)
>
0
{
found
:=
false
for
_
,
domain
:=
range
c
.
hostedDomains
{
if
claims
.
H
ostedDomain
==
domain
{
if
h
ostedDomain
==
domain
{
found
=
true
break
}
}
if
!
found
{
return
identity
,
fmt
.
Errorf
(
"oidc: unexpected hd claim %v"
,
claims
.
H
ostedDomain
)
return
identity
,
fmt
.
Errorf
(
"oidc: unexpected hd claim %v"
,
h
ostedDomain
)
}
}
if
c
.
insecureSkipEmailVerified
{
claims
.
EmailVerified
=
true
emailVerified
=
true
}
if
c
.
getUserInfo
{
...
...
@@ -240,10 +253,19 @@ func (c *oidcConnector) HandleCallback(s connector.Scopes, r *http.Request) (ide
identity
=
connector
.
Identity
{
UserID
:
idToken
.
Subject
,
Username
:
claims
.
Username
,
Email
:
claims
.
Email
,
EmailVerified
:
claims
.
EmailVerified
,
Username
:
name
,
Email
:
email
,
EmailVerified
:
emailVerified
,
}
if
c
.
userIDKey
!=
""
{
userID
,
found
:=
claims
[
c
.
userIDKey
]
.
(
string
)
if
!
found
{
return
identity
,
fmt
.
Errorf
(
"oidc: not found %v claim"
,
c
.
userIDKey
)
}
identity
.
UserID
=
userID
}
return
identity
,
nil
}
...
...
connector/oidc/oidc_test.go
View file @
49e59fb5
package
oidc
import
(
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
"github.com/dexidp/dex/connector"
"github.com/sirupsen/logrus"
"gopkg.in/square/go-jose.v2"
)
func
TestKnownBrokenAuthHeaderProvider
(
t
*
testing
.
T
)
{
...
...
@@ -23,3 +40,192 @@ func TestKnownBrokenAuthHeaderProvider(t *testing.T) {
}
}
}
func
TestHandleCallback
(
t
*
testing
.
T
)
{
t
.
Helper
()
tests
:=
[]
struct
{
name
string
userIDKey
string
expectUserID
string
}{
{
"simpleCase"
,
""
,
"sub"
},
{
"withUserIDKey"
,
"name"
,
"name"
},
}
for
_
,
tc
:=
range
tests
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
testServer
,
err
:=
setupServer
()
if
err
!=
nil
{
t
.
Fatal
(
"failed to setup test server"
,
err
)
}
defer
testServer
.
Close
()
serverURL
:=
testServer
.
URL
config
:=
Config
{
Issuer
:
serverURL
,
ClientID
:
"clientID"
,
ClientSecret
:
"clientSecret"
,
Scopes
:
[]
string
{
"groups"
},
RedirectURI
:
fmt
.
Sprintf
(
"%s/callback"
,
serverURL
),
UserIDKey
:
tc
.
userIDKey
,
}
conn
,
err
:=
newConnector
(
config
)
if
err
!=
nil
{
t
.
Fatal
(
"failed to create new connector"
,
err
)
}
req
,
err
:=
newRequestWithAuthCode
(
testServer
.
URL
,
"someCode"
)
if
err
!=
nil
{
t
.
Fatal
(
"failed to create request"
,
err
)
}
identity
,
err
:=
conn
.
HandleCallback
(
connector
.
Scopes
{
Groups
:
true
},
req
)
if
err
!=
nil
{
t
.
Fatal
(
"handle callback failed"
,
err
)
}
expectEquals
(
t
,
identity
.
UserID
,
tc
.
expectUserID
)
expectEquals
(
t
,
identity
.
Username
,
"name"
)
expectEquals
(
t
,
identity
.
Email
,
"email"
)
expectEquals
(
t
,
identity
.
EmailVerified
,
true
)
})
}
}
func
setupServer
()
(
*
httptest
.
Server
,
error
)
{
key
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
1024
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to generate rsa key: %v"
,
err
)
}
jwk
:=
jose
.
JSONWebKey
{
Key
:
key
,
KeyID
:
"keyId"
,
Algorithm
:
"RSA"
,
}
mux
:=
http
.
NewServeMux
()
mux
.
HandleFunc
(
"/keys"
,
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
json
.
NewEncoder
(
w
)
.
Encode
(
&
map
[
string
]
interface
{}{
"keys"
:
[]
map
[
string
]
interface
{}{{
"alg"
:
jwk
.
Algorithm
,
"kty"
:
jwk
.
Algorithm
,
"kid"
:
jwk
.
KeyID
,
"n"
:
n
(
&
key
.
PublicKey
),
"e"
:
e
(
&
key
.
PublicKey
),
}},
})
})
mux
.
HandleFunc
(
"/token"
,
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
url
:=
fmt
.
Sprintf
(
"http://%s"
,
r
.
Host
)
token
,
err
:=
newToken
(
&
jwk
,
map
[
string
]
interface
{}{
"iss"
:
url
,
"aud"
:
"clientID"
,
"exp"
:
time
.
Now
()
.
Add
(
time
.
Hour
)
.
Unix
(),
"sub"
:
"sub"
,
"name"
:
"name"
,
"email"
:
"email"
,
"email_verified"
:
true
,
})
if
err
!=
nil
{
w
.
WriteHeader
(
http
.
StatusInternalServerError
)
}
w
.
Header
()
.
Add
(
"Content-Type"
,
"application/json"
)
json
.
NewEncoder
(
w
)
.
Encode
(
&
map
[
string
]
string
{
"access_token"
:
token
,
"id_token"
:
token
,
"token_type"
:
"Bearer"
,
})
})
mux
.
HandleFunc
(
"/.well-known/openid-configuration"
,
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
url
:=
fmt
.
Sprintf
(
"http://%s"
,
r
.
Host
)
json
.
NewEncoder
(
w
)
.
Encode
(
&
map
[
string
]
string
{
"issuer"
:
url
,
"token_endpoint"
:
fmt
.
Sprintf
(
"%s/token"
,
url
),
"authorization_endpoint"
:
fmt
.
Sprintf
(
"%s/authorize"
,
url
),
"userinfo_endpoint"
:
fmt
.
Sprintf
(
"%s/userinfo"
,
url
),
"jwks_uri"
:
fmt
.
Sprintf
(
"%s/keys"
,
url
),
})
})
return
httptest
.
NewServer
(
mux
),
nil
}
func
newToken
(
key
*
jose
.
JSONWebKey
,
claims
map
[
string
]
interface
{})
(
string
,
error
)
{
signingKey
:=
jose
.
SigningKey
{
Key
:
key
,
Algorithm
:
jose
.
RS256
,
}
signer
,
err
:=
jose
.
NewSigner
(
signingKey
,
&
jose
.
SignerOptions
{})
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to create new signer: %v"
,
err
)
}
payload
,
err
:=
json
.
Marshal
(
claims
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to marshal claims: %v"
,
err
)
}
signature
,
err
:=
signer
.
Sign
(
payload
)
if
err
!=
nil
{
return
""
,
fmt
.
Errorf
(
"failed to sign: %v"
,
err
)
}
return
signature
.
CompactSerialize
()
}
func
newConnector
(
config
Config
)
(
*
oidcConnector
,
error
)
{
logger
:=
logrus
.
New
()
conn
,
err
:=
config
.
Open
(
"id"
,
logger
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"unable to open: %v"
,
err
)
}
oidcConn
,
ok
:=
conn
.
(
*
oidcConnector
)
if
!
ok
{
return
nil
,
errors
.
New
(
"failed to convert to oidcConnector"
)
}
return
oidcConn
,
nil
}
func
newRequestWithAuthCode
(
serverURL
string
,
code
string
)
(
*
http
.
Request
,
error
)
{
req
,
err
:=
http
.
NewRequest
(
"GET"
,
serverURL
,
nil
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to create request: %v"
,
err
)
}
values
:=
req
.
URL
.
Query
()
values
.
Add
(
"code"
,
code
)
req
.
URL
.
RawQuery
=
values
.
Encode
()
return
req
,
nil
}
func
n
(
pub
*
rsa
.
PublicKey
)
string
{
return
encode
(
pub
.
N
.
Bytes
())
}
func
e
(
pub
*
rsa
.
PublicKey
)
string
{
data
:=
make
([]
byte
,
8
)
binary
.
BigEndian
.
PutUint64
(
data
,
uint64
(
pub
.
E
))
return
encode
(
bytes
.
TrimLeft
(
data
,
"
\x00
"
))
}
func
encode
(
payload
[]
byte
)
string
{
result
:=
base64
.
URLEncoding
.
EncodeToString
(
payload
)
return
strings
.
TrimRight
(
result
,
"="
)
}
func
expectEquals
(
t
*
testing
.
T
,
a
interface
{},
b
interface
{})
{
if
!
reflect
.
DeepEqual
(
a
,
b
)
{
t
.
Errorf
(
"Expected %+v to equal %+v"
,
a
,
b
)
}
}
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