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
1e5133a9
Commit
1e5133a9
authored
Oct 08, 2016
by
Eric Chiang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Documentation/proposals: add a proposal for keeping data in-sync during refreshes
parent
8518c301
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
150 additions
and
0 deletions
+150
-0
upstream-refreshing.md
Documentation/proposals/upstream-refreshing.md
+150
-0
No files found.
Documentation/proposals/upstream-refreshing.md
0 → 100644
View file @
1e5133a9
# Proposal: upstream refreshing
## TL;DR
Today, if a user deletes their Google account, dex will keep allowing clients to
refresh tokens on that user's behalf because dex never checks back in with
Google.
This is a proposal to change the connector package so the dex can check back
in with Google.
## The problem
When dex is federaing to an upstream identity provider (IDP), we want to ensure
claims being passed onto clients remain fresh. This includes data such as Google
accounts display names, LDAP group membership, account deactivations. Changes to
these on an upstream IDP should always be reflected in the claims dex passes to
its own clients.
Refresh tokens make this complicated. When refreshing a token, unlike normal
logins, dex doesn't have the opportunity to prompt for user interaction. For
example, if dex is proxying to a LDAP server, it won't have the user's username
and passwords.
Dex can't do this today because connectors have no concept of checking back in
with an upstream provider (with the sole exception of groups). They're only
called during the initial login, and never consulted when dex needs to mint a
new refresh token for a client. Additionally, connectors aren't actually aware
of the scopes being requested by the client, so they don't know when they should
setup the ability to check back in and have to treat every request identically.
## Changes to the connector package
The biggest changes proposed impact the connector package and connector
implementations.
1.
Connectors should be consulted when dex attempts to refresh a token.
2.
Connectors should be aware of the scopes requested by the client.
The second bullet is important because of the first. If a client isn't
requesting a refresh token, the connector shouldn't do the extra work, such as
requesting additional upstream scopes.
to address the first point, a top level
`Scopes`
object will be added to the
connector package to express the scopes requested by the client. The
`CallbackConnector`
and
`PasswordConnector`
will be updated accordingly.
```
go
// Scopes represents additional data requested by the clients about the end user.
type
Scopes
struct
{
// The client has requested a refresh token from the server.
OfflineAccess
bool
// The client has requested group information about the end user.
Groups
bool
}
// CallbackConnector is an interface implemented by connectors which use an OAuth
// style redirect flow to determine user information.
type
CallbackConnector
interface
{
// The initial URL to redirect the user to.
//
// OAuth2 implementations should request different scopes from the upstream
// identity provider based on the scopes requested by the downstream client.
// For example, if the downstream client requests a refresh token from the
// server, the connector should also request a token from the provider.
LoginURL
(
s
Scopes
,
callbackURL
,
state
string
)
(
string
,
error
)
// Handle the callback to the server and return an identity.
HandleCallback
(
s
Scopes
,
r
*
http
.
Request
)
(
identity
Identity
,
state
string
,
err
error
)
}
// PasswordConnector is an interface implemented by connectors which take a
// username and password.
type
PasswordConnector
interface
{
Login
(
s
Scopes
,
username
,
password
string
)
(
identity
Identity
,
validPassword
bool
,
err
error
)
}
```
The existing
`GroupsConnector`
plays two roles.
1.
The connector only attempts to grab groups when the downstream client requests it.
2.
Allow group information to be refreshed.
The first issue is remedied by the added
`Scopes`
struct. This proposal also
hopes to generalize the need of the second role by adding a more general
`RefreshConnector`
:
```
go
type
Identity
struct
{
// Existing fields...
// Groups are added to the identity object, since connectors are now told
// if they're being requested.
// The set of groups a user is a member of.
Groups
[]
string
}
// RefreshConnector is a connector that can update the client claims.
type
RefreshConnector
interface
{
// Refresh is called when a client attempts to claim a refresh token. The
// connector should attempt to update the identity object to reflect any
// changes since the token was last refreshed.
Refresh
(
s
Scopes
,
identity
Identity
)
(
Identity
,
error
)
// TODO(ericchiang): Should we allow connectors to indicate that the user has
// been delete or an upstream token has been revoked? This would allow us to
// know when we should remove the downstream refresh token, and when there was
// just a server error, but might be hard to determine for certain protocols.
// Might be safer to always delete the downstream token if the Refresh()
// method returns an error.
}
```
## Example changes to the "passwordDB" connector
The
`passwordDB`
connector is the internal connector maintained by the server.
As an example, these are the changes to that connector if this change was
accepted.
```
go
func
(
db
passwordDB
)
Login
(
s
connector
.
Scopes
,
username
,
password
string
)
(
connector
.
Identity
,
bool
,
error
)
{
// No change to existing implementation. Scopes can be ignored since we'll
// always have access to the password objects.
}
func
(
db
passwordDB
)
Refresh
(
s
connector
.
Scopes
,
identity
connector
.
Identity
)
(
connector
.
Identity
,
error
)
{
// If the user has been deleted, the refresh token will be rejected.
p
,
err
:=
db
.
s
.
GetPassword
(
identity
.
Email
)
if
err
!=
nil
{
if
err
==
storage
.
ErrNotFound
{
return
connector
.
Identity
{},
errors
.
New
(
"user not found"
)
}
return
connector
.
Identity
{},
fmt
.
Errorf
(
"get password: %v"
,
err
)
}
// User removed but a new user with the same email exists.
if
p
.
UserID
!=
identity
.
UserID
{
return
connector
.
Identity
{},
errors
.
New
(
"user not found"
)
}
// If a user has updated their username, that will be reflected in the
// refreshed token.
identity
.
Username
=
p
.
Username
return
identity
,
nil
}
```
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