From cbc40588cb906eca5b7a2987e4e9a576d29f3b8c Mon Sep 17 00:00:00 2001 From: jakedt Date: Tue, 25 Mar 2014 12:42:40 -0400 Subject: [PATCH] Finally figure out what the data field is supposed to be for and use it to implement and fix 3LO. --- data/database.py | 4 ++-- data/model/oauth.py | 22 +++++++++++++++++----- endpoints/web.py | 14 ++++++++++++++ test/data/test.db | Bin 532480 -> 193536 bytes 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/data/database.py b/data/database.py index b3d2eafff..2defc7b56 100644 --- a/data/database.py +++ b/data/database.py @@ -287,7 +287,7 @@ class OAuthAuthorizationCode(BaseModel): application = ForeignKeyField(OAuthApplication) code = CharField(index=True) scope = CharField() - data = CharField(default=random_string_generator()) + data = CharField() # Context for the code, such as the user class OAuthAccessToken(BaseModel): @@ -298,7 +298,7 @@ class OAuthAccessToken(BaseModel): token_type = CharField(default='Bearer') expires_at = DateTimeField() refresh_token = CharField(index=True, null=True) - data = CharField() # What the hell is this field for? + data = TextField() # This is context for which this token was generated, such as the user class NotificationKind(BaseModel): diff --git a/data/model/oauth.py b/data/model/oauth.py index 2e83019e4..037d0c53c 100644 --- a/data/model/oauth.py +++ b/data/model/oauth.py @@ -1,4 +1,5 @@ import logging +import json from datetime import datetime, timedelta from oauth2lib.provider import AuthorizationProvider @@ -6,6 +7,7 @@ from oauth2lib import utils from data.database import (OAuthApplication, OAuthAuthorizationCode, OAuthAccessToken, User, random_string_generator) +from data.model.legacy import get_user from auth import scopes @@ -16,6 +18,9 @@ class DatabaseAuthorizationProvider(AuthorizationProvider): def get_authorized_user(self): raise NotImplementedError('Subclasses must fill in the ability to get the authorized_user.') + def _generate_data_string(self): + return json.dumps({'username': self.get_authorized_user().username}) + def validate_client_id(self, client_id): return self.get_application_for_client_id(client_id) is not None @@ -75,6 +80,7 @@ class DatabaseAuthorizationProvider(AuthorizationProvider): .where(OAuthApplication.client_id == client_id, OAuthAuthorizationCode.code == code, OAuthAuthorizationCode.scope == scope) .get()) + logger.debug('Returning data: %s', found.data) return found.data except OAuthAuthorizationCode.DoesNotExist: return None @@ -94,19 +100,23 @@ class DatabaseAuthorizationProvider(AuthorizationProvider): def persist_authorization_code(self, client_id, code, scope): app = OAuthApplication.get(client_id=client_id) - OAuthAuthorizationCode.create(application=app, code=code, scope=scope) + data = self._generate_data_string() + OAuthAuthorizationCode.create(application=app, code=code, scope=scope, data=data) def persist_token_information(self, client_id, scope, access_token, token_type, expires_in, refresh_token, data): + user = get_user(json.loads(data)['username']) + if not user: + raise RuntimeError('Username must be in the data field') + app = OAuthApplication.get(client_id=client_id) - user = self.get_authorized_user() expires_at = datetime.now() + timedelta(seconds=expires_in) OAuthAccessToken.create(application=app, authorized_user=user, scope=scope, access_token=access_token, token_type=token_type, expires_at=expires_at, refresh_token=refresh_token, data=data) def discard_authorization_code(self, client_id, code): - found = (AuthorizationCode + found = (OAuthAuthorizationCode .select() .join(OAuthApplication) .where(OAuthApplication.client_id == client_id, OAuthAuthorizationCode.code == code) @@ -172,9 +182,10 @@ class DatabaseAuthorizationProvider(AuthorizationProvider): expires_in = self.token_expires_in refresh_token = None # No refresh token for this kind of flow + data = self._generate_data_string() self.persist_token_information(client_id=client_id, scope=scope, access_token=access_token, token_type=token_type, expires_in=expires_in, - refresh_token=refresh_token, data='') + refresh_token=refresh_token, data=data) url = utils.build_url(redirect_uri, params) url += '#access_token=%s&token_type=%s&expires_in=%s' % (access_token, token_type, expires_in) @@ -182,7 +193,8 @@ class DatabaseAuthorizationProvider(AuthorizationProvider): return self._make_response(headers={'Location': url}, status_code=302) def create_application(org, name, application_uri, redirect_uri, **kwargs): - return OAuthApplication.create(organization=org, name=name, application_uri=application_uri, redirect_uri=redirect_uri, **kwargs) + return OAuthApplication.create(organization=org, name=name, application_uri=application_uri, + redirect_uri=redirect_uri, **kwargs) def validate_access_token(access_token): try: diff --git a/endpoints/web.py b/endpoints/web.py index 6b9a3e2e0..a99e132b8 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -331,3 +331,17 @@ def request_authorization_code(): return provider.get_token_response(response_type, client_id, redirect_uri, scope=scope) else: return provider.get_authorization_code(response_type, client_id, redirect_uri, scope=scope) + + +@web.route('/oauth/access_token', methods=['POST']) +@no_cache +def exchange_code_for_token(): + grant_type = request.form.get('grant_type', None) + client_id = request.form.get('client_id', None) + client_secret = request.form.get('client_secret', None) + redirect_uri = request.form.get('redirect_uri', None) + code = request.form.get('code', None) + scope = request.form.get('scope', None) + + provider = FlaskAuthorizationProvider() + return provider.get_token(grant_type, client_id, client_secret, redirect_uri, code, scope=scope) diff --git a/test/data/test.db b/test/data/test.db index 4e24e334a2297efadfb5689091f399fab4b43303..e55773dc0ff62121c315cd06d37f98914f6dca65 100644 GIT binary patch delta 14154 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