Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
D
dex
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
hdacloud
dex
Commits
1e5133a9
Commit
1e5133a9
authored
8 years ago
by
Eric Chiang
Browse files
Options
Downloads
Patches
Plain Diff
Documentation/proposals: add a proposal for keeping data in-sync during refreshes
parent
8518c301
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
Documentation/proposals/upstream-refreshing.md
+150
-0
150 additions, 0 deletions
Documentation/proposals/upstream-refreshing.md
with
150 additions
and
0 deletions
Documentation/proposals/upstream-refreshing.md
0 → 100644
+
150
−
0
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
}
```
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment