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
8669167b
Commit
8669167b
authored
Aug 01, 2016
by
Eric Chiang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
user api: accept bearer tokens with multiple audiences
parent
1e0ee1e4
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
148 additions
and
41 deletions
+148
-41
user.go
server/user.go
+43
-16
api.go
user/api/api.go
+25
-18
api_test.go
user/api/api_test.go
+80
-7
No files found.
server/user.go
View file @
8669167b
...
...
@@ -2,7 +2,6 @@ package server
import
(
"encoding/json"
"errors"
"net/http"
"net/url"
"strconv"
...
...
@@ -262,18 +261,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
clientID
,
ok
,
err
:=
claims
.
StringClaim
(
"aud"
)
if
err
!=
nil
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: %q"
,
err
)
return
api
.
Creds
{},
err
// The "aud" claim is allowed to be both a list of clients or a single client. Check for both cases.
clientIDs
,
ok
,
err
:=
claims
.
StringsClaim
(
"aud"
)
if
err
!=
nil
||
!
ok
{
clientID
,
ok
,
err
:=
claims
.
StringClaim
(
"aud"
)
if
err
!=
nil
{
log
.
Errorf
(
"userMgmtServer: GetCreds failed to parse 'aud' claim: %q"
,
err
)
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
if
!
ok
||
clientID
==
""
{
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
clientIDs
=
[]
string
{
clientID
}
}
if
!
ok
||
clientID
==
""
{
return
api
.
Creds
{},
errors
.
New
(
"no aud(client ID) claim"
)
if
len
(
clientIDs
)
==
0
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: no client in audience"
)
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
verifier
:=
s
.
jwtvFactory
(
clientID
)
// Verify that the JWT is signed by this server, has the correct issuer, hasn't expired, etc.
// While we don't actualy care which client the token was issued for (we'll check that later),
// go-oidc doesn't provide any methods which don't require passing a client ID.
//
// TODO(ericchiang): Add a verifier to go-oidc that doesn't require a client ID.
verifier
:=
s
.
jwtvFactory
(
clientIDs
[
0
])
if
err
:=
verifier
.
Verify
(
jwt
);
err
!=
nil
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: %q"
,
err
)
log
.
Errorf
(
"userMgmtServer: GetCreds err:
failed to verify token
%q"
,
err
)
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
...
...
@@ -295,18 +308,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
return
api
.
Creds
{},
err
}
isAdmin
,
err
:=
s
.
cm
.
IsDexAdmin
(
clientID
)
if
err
!=
nil
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: %q"
,
err
)
return
api
.
Creds
{},
err
i
:=
0
for
_
,
clientID
:=
range
clientIDs
{
// Make sure the client actually exists.
isAdmin
,
err
:=
s
.
cm
.
IsDexAdmin
(
clientID
)
if
err
!=
nil
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: failed to get client %v"
,
err
)
return
api
.
Creds
{},
err
}
// If the endpoint requires an admin client, filter out clients which are not admins.
if
requiresAdmin
&&
!
isAdmin
{
continue
}
clientIDs
[
i
]
=
clientID
i
++
}
if
requiresAdmin
&&
!
isAdmin
{
clientIDs
=
clientIDs
[
:
i
]
if
len
(
clientIDs
)
==
0
{
return
api
.
Creds
{},
api
.
ErrorForbidden
}
return
api
.
Creds
{
ClientID
:
clientID
,
User
:
usr
,
ClientID
s
:
clientIDs
,
User
:
usr
,
},
nil
}
...
...
user/api/api.go
View file @
8669167b
...
...
@@ -98,8 +98,9 @@ type Emailer interface {
}
type
Creds
struct
{
ClientID
string
User
user
.
User
// IDTokens can be issued for multiple clients.
ClientIDs
[]
string
User
user
.
User
}
// TODO(ericchiang): Don't pass a dbMap. See #385.
...
...
@@ -144,6 +145,22 @@ func (u *UsersAPI) DisableUser(creds Creds, userID string, disable bool) (schema
},
nil
}
// validRedirectURL finds the first client for which the redirect URL is valid. If found it returns the client_id of the client.
func
validRedirectURL
(
clientManager
*
clientmanager
.
ClientManager
,
redirectURL
url
.
URL
,
clientIDs
[]
string
)
(
string
,
error
)
{
// Find the first client with a valid redirectURL.
for
_
,
clientID
:=
range
clientIDs
{
metadata
,
err
:=
clientManager
.
Metadata
(
clientID
)
if
err
!=
nil
{
return
""
,
mapError
(
err
)
}
if
_
,
err
:=
client
.
ValidRedirectURL
(
&
redirectURL
,
metadata
.
RedirectURIs
);
err
==
nil
{
return
clientID
,
nil
}
}
return
""
,
ErrorInvalidRedirectURL
}
func
(
u
*
UsersAPI
)
CreateUser
(
creds
Creds
,
usr
schema
.
User
,
redirURL
url
.
URL
)
(
schema
.
UserCreateResponse
,
error
)
{
log
.
Infof
(
"userAPI: CreateUser"
)
if
!
u
.
Authorize
(
creds
)
{
...
...
@@ -155,14 +172,9 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
return
schema
.
UserCreateResponse
{},
mapError
(
err
)
}
metadata
,
err
:=
u
.
clientManager
.
Metadata
(
creds
.
ClientID
)
if
err
!=
nil
{
return
schema
.
UserCreateResponse
{},
mapError
(
err
)
}
validRedirURL
,
err
:=
client
.
ValidRedirectURL
(
&
redirURL
,
metadata
.
RedirectURIs
)
clientID
,
err
:=
validRedirectURL
(
u
.
clientManager
,
redirURL
,
creds
.
ClientIDs
)
if
err
!=
nil
{
return
schema
.
UserCreateResponse
{},
ErrorInvalidRedirectURL
return
schema
.
UserCreateResponse
{},
err
}
id
,
err
:=
u
.
userManager
.
CreateUser
(
schemaUserToUser
(
usr
),
user
.
Password
(
hash
),
u
.
localConnectorID
)
...
...
@@ -177,7 +189,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
usr
=
userToSchemaUser
(
userUser
)
url
,
err
:=
u
.
emailer
.
SendInviteEmail
(
usr
.
Email
,
validRedirURL
,
creds
.
C
lientID
)
url
,
err
:=
u
.
emailer
.
SendInviteEmail
(
usr
.
Email
,
redirURL
,
c
lientID
)
// An email is sent only if we don't get a link and there's no error.
emailSent
:=
err
==
nil
&&
url
==
nil
...
...
@@ -200,14 +212,9 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
return
schema
.
ResendEmailInvitationResponse
{},
ErrorUnauthorized
}
metadata
,
err
:=
u
.
clientManager
.
Metadata
(
creds
.
ClientID
)
if
err
!=
nil
{
return
schema
.
ResendEmailInvitationResponse
{},
mapError
(
err
)
}
validRedirURL
,
err
:=
client
.
ValidRedirectURL
(
&
redirURL
,
metadata
.
RedirectURIs
)
clientID
,
err
:=
validRedirectURL
(
u
.
clientManager
,
redirURL
,
creds
.
ClientIDs
)
if
err
!=
nil
{
return
schema
.
ResendEmailInvitationResponse
{},
ErrorInvalidRedirectURL
return
schema
.
ResendEmailInvitationResponse
{},
err
}
// Retrieve user to check if it's already created
...
...
@@ -221,7 +228,7 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
return
schema
.
ResendEmailInvitationResponse
{},
ErrorVerifiedEmail
}
url
,
err
:=
u
.
emailer
.
SendInviteEmail
(
userUser
.
Email
,
validRedirURL
,
creds
.
C
lientID
)
url
,
err
:=
u
.
emailer
.
SendInviteEmail
(
userUser
.
Email
,
redirURL
,
c
lientID
)
// An email is sent only if we don't get a link and there's no error.
emailSent
:=
err
==
nil
&&
url
==
nil
...
...
user/api/api_test.go
View file @
8669167b
...
...
@@ -51,15 +51,16 @@ func (t *testEmailer) sendEmail(email string, redirectURL url.URL, clientID stri
}
var
(
clock
=
clockwork
.
NewFakeClock
()
goodClientID
=
"client.example.com"
clock
=
clockwork
.
NewFakeClock
()
goodClientID
=
"client.example.com"
nonAdminClientID
=
"user.example.com"
goodCreds
=
Creds
{
User
:
user
.
User
{
ID
:
"ID-1"
,
Admin
:
true
,
},
ClientID
:
goodClientID
,
ClientID
s
:
[]
string
{
goodClientID
}
,
}
badCreds
=
Creds
{
...
...
@@ -68,13 +69,21 @@ var (
},
}
credsWithMultipleAudiences
=
Creds
{
User
:
user
.
User
{
ID
:
"ID-1"
,
Admin
:
true
,
},
ClientIDs
:
[]
string
{
nonAdminClientID
,
goodClientID
},
}
disabledCreds
=
Creds
{
User
:
user
.
User
{
ID
:
"ID-1"
,
Admin
:
true
,
Disabled
:
true
,
},
ClientID
:
goodClientID
,
ClientID
s
:
[]
string
{
goodClientID
}
,
}
resetPasswordURL
=
url
.
URL
{
...
...
@@ -87,6 +96,11 @@ var (
Host
:
goodClientID
,
Path
:
"/callback"
,
}
validRedirURL2
=
url
.
URL
{
Scheme
:
"http"
,
Host
:
nonAdminClientID
,
Path
:
"/callback"
,
}
)
func
makeTestFixtures
()
(
*
UsersAPI
,
*
testEmailer
)
{
...
...
@@ -169,6 +183,17 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
},
},
}
ci2
:=
client
.
Client
{
Credentials
:
oidc
.
ClientCredentials
{
ID
:
nonAdminClientID
,
Secret
:
base64
.
URLEncoding
.
EncodeToString
([]
byte
(
"anothersecret"
)),
},
Metadata
:
oidc
.
ClientMetadata
{
RedirectURIs
:
[]
url
.
URL
{
validRedirURL2
,
},
},
}
clientIDGenerator
:=
func
(
hostport
string
)
(
string
,
error
)
{
return
hostport
,
nil
...
...
@@ -176,7 +201,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
secGen
:=
func
()
([]
byte
,
error
)
{
return
[]
byte
(
"secret"
),
nil
}
clientRepo
,
err
:=
db
.
NewClientRepoFromClients
(
dbMap
,
[]
client
.
LoadableClient
{{
Client
:
ci
}})
clientRepo
,
err
:=
db
.
NewClientRepoFromClients
(
dbMap
,
[]
client
.
LoadableClient
{{
Client
:
ci
}
,
{
Client
:
ci2
}
})
if
err
!=
nil
{
panic
(
"Failed to create client manager: "
+
err
.
Error
())
}
...
...
@@ -223,6 +248,10 @@ func TestGetUser(t *testing.T) {
id
:
"NO_ID"
,
wantErr
:
ErrorResourceNotFound
,
},
{
creds
:
credsWithMultipleAudiences
,
id
:
"ID-1"
,
},
}
for
i
,
tt
:=
range
tests
{
...
...
@@ -318,6 +347,7 @@ func TestCreateUser(t *testing.T) {
cantEmail
bool
wantResponse
schema
.
UserCreateResponse
wantClientID
string
wantErr
error
}{
{
...
...
@@ -340,6 +370,29 @@ func TestCreateUser(t *testing.T) {
CreatedAt
:
clock
.
Now
()
.
Format
(
time
.
RFC3339
),
},
},
wantClientID
:
goodClientID
,
},
{
creds
:
credsWithMultipleAudiences
,
usr
:
schema
.
User
{
Email
:
"newuser01@example.com"
,
DisplayName
:
"New User"
,
EmailVerified
:
true
,
Admin
:
false
,
},
redirURL
:
validRedirURL
,
wantResponse
:
schema
.
UserCreateResponse
{
EmailSent
:
true
,
User
:
&
schema
.
User
{
Email
:
"newuser01@example.com"
,
DisplayName
:
"New User"
,
EmailVerified
:
true
,
Admin
:
false
,
CreatedAt
:
clock
.
Now
()
.
Format
(
time
.
RFC3339
),
},
},
wantClientID
:
goodClientID
,
},
{
creds
:
goodCreds
,
...
...
@@ -362,6 +415,7 @@ func TestCreateUser(t *testing.T) {
},
ResetPasswordLink
:
resetPasswordURL
.
String
(),
},
wantClientID
:
goodClientID
,
},
{
creds
:
goodCreds
,
...
...
@@ -397,6 +451,7 @@ func TestCreateUser(t *testing.T) {
if
tt
.
wantErr
!=
nil
{
if
err
!=
tt
.
wantErr
{
t
.
Errorf
(
"case %d: want=%q, got=%q"
,
i
,
tt
.
wantErr
,
err
)
continue
}
tok
:=
""
...
...
@@ -420,11 +475,13 @@ func TestCreateUser(t *testing.T) {
}
if
err
!=
nil
{
t
.
Errorf
(
"case %d: want nil err, got: %q "
,
i
,
err
)
continue
}
newID
:=
response
.
User
.
Id
if
newID
==
""
{
t
.
Errorf
(
"case %d: expected non-empty newID"
,
i
)
continue
}
tt
.
wantResponse
.
User
.
Id
=
newID
...
...
@@ -436,7 +493,7 @@ func TestCreateUser(t *testing.T) {
wantEmalier
:=
testEmailer
{
cantEmail
:
tt
.
cantEmail
,
lastEmail
:
tt
.
usr
.
Email
,
lastClientID
:
tt
.
creds
.
ClientID
,
lastClientID
:
tt
.
want
ClientID
,
lastRedirectURL
:
tt
.
redirURL
,
lastWasInvite
:
true
,
}
...
...
@@ -497,6 +554,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantResponse
schema
.
ResendEmailInvitationResponse
wantErr
error
wantClientID
string
}{
{
creds
:
goodCreds
,
...
...
@@ -507,6 +565,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantResponse
:
schema
.
ResendEmailInvitationResponse
{
EmailSent
:
true
,
},
wantClientID
:
goodClientID
,
},
{
creds
:
goodCreds
,
...
...
@@ -519,6 +578,20 @@ func TestResendEmailInvitation(t *testing.T) {
EmailSent
:
false
,
ResetPasswordLink
:
resetPasswordURL
.
String
(),
},
wantClientID
:
goodClientID
,
},
{
creds
:
credsWithMultipleAudiences
,
userID
:
"ID-1"
,
email
:
"id1@example.com"
,
redirURL
:
validRedirURL
,
cantEmail
:
true
,
wantResponse
:
schema
.
ResendEmailInvitationResponse
{
EmailSent
:
false
,
ResetPasswordLink
:
resetPasswordURL
.
String
(),
},
wantClientID
:
goodClientID
,
},
{
creds
:
badCreds
,
...
...
@@ -576,7 +649,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantEmailer
:=
testEmailer
{
cantEmail
:
tt
.
cantEmail
,
lastEmail
:
tt
.
email
,
lastClientID
:
tt
.
creds
.
ClientID
,
lastClientID
:
tt
.
want
ClientID
,
lastRedirectURL
:
tt
.
redirURL
,
lastWasInvite
:
true
,
}
...
...
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