Skip to content
This repository has been archived by the owner on Jun 19, 2019. It is now read-only.

Create custom user subclass OAuth2TokenUser #64

Open
rjw57 opened this issue Mar 9, 2018 · 0 comments
Open

Create custom user subclass OAuth2TokenUser #64

rjw57 opened this issue Mar 9, 2018 · 0 comments

Comments

@rjw57
Copy link
Member

rjw57 commented Mar 9, 2018

As raised in a comment on e978edd, we have extra information we wish to associate with a user which could be expressed via a custom user model.

We need to make sure the user model can accommodate "plain ol' users" which can log into, e.g., the admin but also users created implicitly via API authentications. Something like:

from some.module import OAuth2TokenUser

# can create user as before
u1 = OAuth2TokenUser.objects.get_or_create_user(username="some_admin")
assert u1.scheme is None
assert u1.identifier is None
assert u1.username == "some_admin"

# can also create from token dictionary
token = {
  "sub": "mock:test0001",
  # ...
}

u2 = OAuth2TokenUser.objects.get_or_create_from_token(token)
# subject is a unique field
assert u2.subject == "mock:test0001"
assert u2.username is not None

# (scheme, identifier) as a pair are a unique index in the DB.
assert u2.scheme == "mock"
assert u2.identifier == "test0001"

Ideally the username should be some unique value which is derived from the subject, perhaps something like:

import json
from hashlib import sha256

def username_from_scheme_and_identifier(scheme, identifier):
    h = sha256(json.dumps({"scheme": scheme, "identifier": identifier}))
    return "{}-{}-{}".format(scheme, identifier, h.hexdigest()[:8])

def get_or_create_oauth2_token_user(token):
    u = OAuth2TokenUser.objects.filter(subject=token['sub']).first()
    if u is not None:
        return u

    scheme, identifier = token['sub'].split(":")

    # this method *must* call create_user or, otherwise, ensure that set_unusable_password()
    # is called on the object
    return OAuth2TokenUser.objects.create_oauth2_token_user(
        username=username_from_scheme_and_identifier(scheme, identifier),
        subject=token['sub'], scheme=scheme, identifier=itentifier
    )
rjw57 referenced this issue Mar 9, 2018
IMPORTANT: this commit removes the previous LOOKUP_SELF setting and
replaces it with LOOKUP_ROOT which points to the root of the lookupproxy
API server. Deployments will need to be re-configured.

Previously we (ab)used the token passed to us from the user to retrieve
their lookup profile. The token is only conveniently present at
authorisation time and so the authorisation logic was combined with
fetching and caching the lookup resource for the current user.

Aside from complication the authorisation flow, it slightly violated the
spirit of OAuth2 tokens in that we were not accessing lookup *on behalf*
of the user. We were, in fact, accessing lookup on behalf of the IAR
backend application.

We also encoded the lookup scheme and identifier in the username which,
if not exactly brittle, was ugly.

Re-work the lookup caching logic to be separate from the authentication
logic:

1. Explicitly record the mapping between lookup scheme/identifier pairs
   and Django users by way of a new UserLookup model.

2. Separate the caching logic out into a new accessor function,
   assets.lookup.get_person_for_user(), which takes a Django user and
   returns the lookup person resource for that user. The resource is
   fetched from an in-memory cache if present or fetched from lookup if
   not. When fetching data from lookup, the IAR backend obtains its own
   token with a lookup:anonymous scope and uses that token to talk to
   lookup. The avoids the need to keep the user's token around.

None of the existing permissions logic has been changed but now we
explicitly ask for a lookup resource for a user *at the point we need
it* rather than relying on it having been cached as part of the
authentication flow.

Since the user token no longer requires a lookup:anonymous scope, remove
it from the Swagger UI.

The default cached person resource lifetime is configurable through a
setting.

We update the scripts/create-client.sh script to create a backend client
capable of requesting hydra.introspect and lookup:anonymous scopes and a
client used for testing the UI.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant