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
...
@@ -2,7 +2,6 @@ package server
import
(
import
(
"encoding/json"
"encoding/json"
"errors"
"net/http"
"net/http"
"net/url"
"net/url"
"strconv"
"strconv"
...
@@ -262,18 +261,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
...
@@ -262,18 +261,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
return
api
.
Creds
{},
api
.
ErrorUnauthorized
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
}
clientID
,
ok
,
err
:=
claims
.
StringClaim
(
"aud"
)
// The "aud" claim is allowed to be both a list of clients or a single client. Check for both cases.
if
err
!=
nil
{
clientIDs
,
ok
,
err
:=
claims
.
StringsClaim
(
"aud"
)
log
.
Errorf
(
"userMgmtServer: GetCreds err: %q"
,
err
)
if
err
!=
nil
||
!
ok
{
return
api
.
Creds
{},
err
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
==
""
{
if
len
(
clientIDs
)
==
0
{
return
api
.
Creds
{},
errors
.
New
(
"no aud(client ID) claim"
)
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
{
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
return
api
.
Creds
{},
api
.
ErrorUnauthorized
}
}
...
@@ -295,18 +308,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
...
@@ -295,18 +308,32 @@ func (s *UserMgmtServer) getCreds(r *http.Request, requiresAdmin bool) (api.Cred
return
api
.
Creds
{},
err
return
api
.
Creds
{},
err
}
}
isAdmin
,
err
:=
s
.
cm
.
IsDexAdmin
(
clientID
)
i
:=
0
if
err
!=
nil
{
for
_
,
clientID
:=
range
clientIDs
{
log
.
Errorf
(
"userMgmtServer: GetCreds err: %q"
,
err
)
// Make sure the client actually exists.
return
api
.
Creds
{},
err
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
{},
api
.
ErrorForbidden
}
}
return
api
.
Creds
{
return
api
.
Creds
{
ClientID
:
clientID
,
ClientID
s
:
clientIDs
,
User
:
usr
,
User
:
usr
,
},
nil
},
nil
}
}
...
...
user/api/api.go
View file @
8669167b
...
@@ -98,8 +98,9 @@ type Emailer interface {
...
@@ -98,8 +98,9 @@ type Emailer interface {
}
}
type
Creds
struct
{
type
Creds
struct
{
ClientID
string
// IDTokens can be issued for multiple clients.
User
user
.
User
ClientIDs
[]
string
User
user
.
User
}
}
// TODO(ericchiang): Don't pass a dbMap. See #385.
// TODO(ericchiang): Don't pass a dbMap. See #385.
...
@@ -144,6 +145,22 @@ func (u *UsersAPI) DisableUser(creds Creds, userID string, disable bool) (schema
...
@@ -144,6 +145,22 @@ func (u *UsersAPI) DisableUser(creds Creds, userID string, disable bool) (schema
},
nil
},
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
)
{
func
(
u
*
UsersAPI
)
CreateUser
(
creds
Creds
,
usr
schema
.
User
,
redirURL
url
.
URL
)
(
schema
.
UserCreateResponse
,
error
)
{
log
.
Infof
(
"userAPI: CreateUser"
)
log
.
Infof
(
"userAPI: CreateUser"
)
if
!
u
.
Authorize
(
creds
)
{
if
!
u
.
Authorize
(
creds
)
{
...
@@ -155,14 +172,9 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
...
@@ -155,14 +172,9 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
return
schema
.
UserCreateResponse
{},
mapError
(
err
)
return
schema
.
UserCreateResponse
{},
mapError
(
err
)
}
}
metadata
,
err
:=
u
.
clientManager
.
Metadata
(
creds
.
ClientID
)
clientID
,
err
:=
validRedirectURL
(
u
.
clientManager
,
redirURL
,
creds
.
ClientIDs
)
if
err
!=
nil
{
return
schema
.
UserCreateResponse
{},
mapError
(
err
)
}
validRedirURL
,
err
:=
client
.
ValidRedirectURL
(
&
redirURL
,
metadata
.
RedirectURIs
)
if
err
!=
nil
{
if
err
!=
nil
{
return
schema
.
UserCreateResponse
{},
ErrorInvalidRedirectURL
return
schema
.
UserCreateResponse
{},
err
}
}
id
,
err
:=
u
.
userManager
.
CreateUser
(
schemaUserToUser
(
usr
),
user
.
Password
(
hash
),
u
.
localConnectorID
)
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
...
@@ -177,7 +189,7 @@ func (u *UsersAPI) CreateUser(creds Creds, usr schema.User, redirURL url.URL) (s
usr
=
userToSchemaUser
(
userUser
)
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.
// An email is sent only if we don't get a link and there's no error.
emailSent
:=
err
==
nil
&&
url
==
nil
emailSent
:=
err
==
nil
&&
url
==
nil
...
@@ -200,14 +212,9 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
...
@@ -200,14 +212,9 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
return
schema
.
ResendEmailInvitationResponse
{},
ErrorUnauthorized
return
schema
.
ResendEmailInvitationResponse
{},
ErrorUnauthorized
}
}
metadata
,
err
:=
u
.
clientManager
.
Metadata
(
creds
.
ClientID
)
clientID
,
err
:=
validRedirectURL
(
u
.
clientManager
,
redirURL
,
creds
.
ClientIDs
)
if
err
!=
nil
{
return
schema
.
ResendEmailInvitationResponse
{},
mapError
(
err
)
}
validRedirURL
,
err
:=
client
.
ValidRedirectURL
(
&
redirURL
,
metadata
.
RedirectURIs
)
if
err
!=
nil
{
if
err
!=
nil
{
return
schema
.
ResendEmailInvitationResponse
{},
ErrorInvalidRedirectURL
return
schema
.
ResendEmailInvitationResponse
{},
err
}
}
// Retrieve user to check if it's already created
// Retrieve user to check if it's already created
...
@@ -221,7 +228,7 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
...
@@ -221,7 +228,7 @@ func (u *UsersAPI) ResendEmailInvitation(creds Creds, userID string, redirURL ur
return
schema
.
ResendEmailInvitationResponse
{},
ErrorVerifiedEmail
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.
// An email is sent only if we don't get a link and there's no error.
emailSent
:=
err
==
nil
&&
url
==
nil
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
...
@@ -51,15 +51,16 @@ func (t *testEmailer) sendEmail(email string, redirectURL url.URL, clientID stri
}
}
var
(
var
(
clock
=
clockwork
.
NewFakeClock
()
clock
=
clockwork
.
NewFakeClock
()
goodClientID
=
"client.example.com"
goodClientID
=
"client.example.com"
nonAdminClientID
=
"user.example.com"
goodCreds
=
Creds
{
goodCreds
=
Creds
{
User
:
user
.
User
{
User
:
user
.
User
{
ID
:
"ID-1"
,
ID
:
"ID-1"
,
Admin
:
true
,
Admin
:
true
,
},
},
ClientID
:
goodClientID
,
ClientID
s
:
[]
string
{
goodClientID
}
,
}
}
badCreds
=
Creds
{
badCreds
=
Creds
{
...
@@ -68,13 +69,21 @@ var (
...
@@ -68,13 +69,21 @@ var (
},
},
}
}
credsWithMultipleAudiences
=
Creds
{
User
:
user
.
User
{
ID
:
"ID-1"
,
Admin
:
true
,
},
ClientIDs
:
[]
string
{
nonAdminClientID
,
goodClientID
},
}
disabledCreds
=
Creds
{
disabledCreds
=
Creds
{
User
:
user
.
User
{
User
:
user
.
User
{
ID
:
"ID-1"
,
ID
:
"ID-1"
,
Admin
:
true
,
Admin
:
true
,
Disabled
:
true
,
Disabled
:
true
,
},
},
ClientID
:
goodClientID
,
ClientID
s
:
[]
string
{
goodClientID
}
,
}
}
resetPasswordURL
=
url
.
URL
{
resetPasswordURL
=
url
.
URL
{
...
@@ -87,6 +96,11 @@ var (
...
@@ -87,6 +96,11 @@ var (
Host
:
goodClientID
,
Host
:
goodClientID
,
Path
:
"/callback"
,
Path
:
"/callback"
,
}
}
validRedirURL2
=
url
.
URL
{
Scheme
:
"http"
,
Host
:
nonAdminClientID
,
Path
:
"/callback"
,
}
)
)
func
makeTestFixtures
()
(
*
UsersAPI
,
*
testEmailer
)
{
func
makeTestFixtures
()
(
*
UsersAPI
,
*
testEmailer
)
{
...
@@ -169,6 +183,17 @@ 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
)
{
clientIDGenerator
:=
func
(
hostport
string
)
(
string
,
error
)
{
return
hostport
,
nil
return
hostport
,
nil
...
@@ -176,7 +201,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
...
@@ -176,7 +201,7 @@ func makeTestFixtures() (*UsersAPI, *testEmailer) {
secGen
:=
func
()
([]
byte
,
error
)
{
secGen
:=
func
()
([]
byte
,
error
)
{
return
[]
byte
(
"secret"
),
nil
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
{
if
err
!=
nil
{
panic
(
"Failed to create client manager: "
+
err
.
Error
())
panic
(
"Failed to create client manager: "
+
err
.
Error
())
}
}
...
@@ -223,6 +248,10 @@ func TestGetUser(t *testing.T) {
...
@@ -223,6 +248,10 @@ func TestGetUser(t *testing.T) {
id
:
"NO_ID"
,
id
:
"NO_ID"
,
wantErr
:
ErrorResourceNotFound
,
wantErr
:
ErrorResourceNotFound
,
},
},
{
creds
:
credsWithMultipleAudiences
,
id
:
"ID-1"
,
},
}
}
for
i
,
tt
:=
range
tests
{
for
i
,
tt
:=
range
tests
{
...
@@ -318,6 +347,7 @@ func TestCreateUser(t *testing.T) {
...
@@ -318,6 +347,7 @@ func TestCreateUser(t *testing.T) {
cantEmail
bool
cantEmail
bool
wantResponse
schema
.
UserCreateResponse
wantResponse
schema
.
UserCreateResponse
wantClientID
string
wantErr
error
wantErr
error
}{
}{
{
{
...
@@ -340,6 +370,29 @@ func TestCreateUser(t *testing.T) {
...
@@ -340,6 +370,29 @@ func TestCreateUser(t *testing.T) {
CreatedAt
:
clock
.
Now
()
.
Format
(
time
.
RFC3339
),
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
,
creds
:
goodCreds
,
...
@@ -362,6 +415,7 @@ func TestCreateUser(t *testing.T) {
...
@@ -362,6 +415,7 @@ func TestCreateUser(t *testing.T) {
},
},
ResetPasswordLink
:
resetPasswordURL
.
String
(),
ResetPasswordLink
:
resetPasswordURL
.
String
(),
},
},
wantClientID
:
goodClientID
,
},
},
{
{
creds
:
goodCreds
,
creds
:
goodCreds
,
...
@@ -397,6 +451,7 @@ func TestCreateUser(t *testing.T) {
...
@@ -397,6 +451,7 @@ func TestCreateUser(t *testing.T) {
if
tt
.
wantErr
!=
nil
{
if
tt
.
wantErr
!=
nil
{
if
err
!=
tt
.
wantErr
{
if
err
!=
tt
.
wantErr
{
t
.
Errorf
(
"case %d: want=%q, got=%q"
,
i
,
tt
.
wantErr
,
err
)
t
.
Errorf
(
"case %d: want=%q, got=%q"
,
i
,
tt
.
wantErr
,
err
)
continue
}
}
tok
:=
""
tok
:=
""
...
@@ -420,11 +475,13 @@ func TestCreateUser(t *testing.T) {
...
@@ -420,11 +475,13 @@ func TestCreateUser(t *testing.T) {
}
}
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Errorf
(
"case %d: want nil err, got: %q "
,
i
,
err
)
t
.
Errorf
(
"case %d: want nil err, got: %q "
,
i
,
err
)
continue
}
}
newID
:=
response
.
User
.
Id
newID
:=
response
.
User
.
Id
if
newID
==
""
{
if
newID
==
""
{
t
.
Errorf
(
"case %d: expected non-empty newID"
,
i
)
t
.
Errorf
(
"case %d: expected non-empty newID"
,
i
)
continue
}
}
tt
.
wantResponse
.
User
.
Id
=
newID
tt
.
wantResponse
.
User
.
Id
=
newID
...
@@ -436,7 +493,7 @@ func TestCreateUser(t *testing.T) {
...
@@ -436,7 +493,7 @@ func TestCreateUser(t *testing.T) {
wantEmalier
:=
testEmailer
{
wantEmalier
:=
testEmailer
{
cantEmail
:
tt
.
cantEmail
,
cantEmail
:
tt
.
cantEmail
,
lastEmail
:
tt
.
usr
.
Email
,
lastEmail
:
tt
.
usr
.
Email
,
lastClientID
:
tt
.
creds
.
ClientID
,
lastClientID
:
tt
.
want
ClientID
,
lastRedirectURL
:
tt
.
redirURL
,
lastRedirectURL
:
tt
.
redirURL
,
lastWasInvite
:
true
,
lastWasInvite
:
true
,
}
}
...
@@ -497,6 +554,7 @@ func TestResendEmailInvitation(t *testing.T) {
...
@@ -497,6 +554,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantResponse
schema
.
ResendEmailInvitationResponse
wantResponse
schema
.
ResendEmailInvitationResponse
wantErr
error
wantErr
error
wantClientID
string
}{
}{
{
{
creds
:
goodCreds
,
creds
:
goodCreds
,
...
@@ -507,6 +565,7 @@ func TestResendEmailInvitation(t *testing.T) {
...
@@ -507,6 +565,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantResponse
:
schema
.
ResendEmailInvitationResponse
{
wantResponse
:
schema
.
ResendEmailInvitationResponse
{
EmailSent
:
true
,
EmailSent
:
true
,
},
},
wantClientID
:
goodClientID
,
},
},
{
{
creds
:
goodCreds
,
creds
:
goodCreds
,
...
@@ -519,6 +578,20 @@ func TestResendEmailInvitation(t *testing.T) {
...
@@ -519,6 +578,20 @@ func TestResendEmailInvitation(t *testing.T) {
EmailSent
:
false
,
EmailSent
:
false
,
ResetPasswordLink
:
resetPasswordURL
.
String
(),
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
,
creds
:
badCreds
,
...
@@ -576,7 +649,7 @@ func TestResendEmailInvitation(t *testing.T) {
...
@@ -576,7 +649,7 @@ func TestResendEmailInvitation(t *testing.T) {
wantEmailer
:=
testEmailer
{
wantEmailer
:=
testEmailer
{
cantEmail
:
tt
.
cantEmail
,
cantEmail
:
tt
.
cantEmail
,
lastEmail
:
tt
.
email
,
lastEmail
:
tt
.
email
,
lastClientID
:
tt
.
creds
.
ClientID
,
lastClientID
:
tt
.
want
ClientID
,
lastRedirectURL
:
tt
.
redirURL
,
lastRedirectURL
:
tt
.
redirURL
,
lastWasInvite
:
true
,
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