From ce403701eb61c2117736344d808ab661bb888b6b Mon Sep 17 00:00:00 2001 From: Nikolai Kristiansen Date: Wed, 25 Dec 2013 17:24:33 +0100 Subject: [PATCH] Adds DjangoAuth backend and Accesstoken model, misc fixes --- example.py | 13 ++++++++++-- pydusken/__init__.py | 30 +++++++++++++++++++++------- pydusken/authentication.py | 41 ++++++++++++++++++++++++++++---------- pydusken/members.py | 22 +++++++++++++++----- pydusken/models.py | 3 +++ 5 files changed, 85 insertions(+), 24 deletions(-) diff --git a/example.py b/example.py index d2c303c..99acdc2 100644 --- a/example.py +++ b/example.py @@ -3,12 +3,18 @@ import json import pydusken +# oauth +client_id = "d802768a8afd9dec2660" +client_secret = "3962467fe60a94727812053c8a4f44533f4ef610" + # credentials username = "admin" password = "admin" # setup client -client = pydusken.DuskenApi(username=username, password=password) +client = pydusken.DuskenApi(client_id, client_secret) + +client.authenticate(username, password) # get member pprint.pprint(client.members.get('1')) @@ -17,4 +23,7 @@ pprint.pprint(client.members.get('1')) pprint.pprint(client.members.get_groups('1')) # update member password to the same password as you have -pprint.pprint(client.members.update('1', password='admin')) +pprint.pprint(client.members.update('1', address='admin')) + +# filter members +pprint.pprint(client.members.filter(email="nikolai@studentersamfundet.no")) diff --git a/pydusken/__init__.py b/pydusken/__init__.py index 604ba05..2a2b9e3 100644 --- a/pydusken/__init__.py +++ b/pydusken/__init__.py @@ -1,3 +1,4 @@ +import logging import json import requests from urllib import quote_plus @@ -5,30 +6,45 @@ from .members import Members #from .membership import Memberships #from .groups import Groups +class DuskenBaseModel: + pass + class DuskenApi(object): def __init__(self, client_id, client_secret, base_url="http://127.0.0.1:8000/api/v1"): - self._base_url = base_url + self.base_url = base_url self._client_id = client_id self._client_secret = client_secret - #self.members = Members(username, self._api_key, self._base_url) + self.members = Members(self) #self.memberships = Memberships(username, self._apikey, self._base_url) #self.groups = Groups(username, self._apikey, self._base_url) - def authenticate(self, username, password): - return self.get_auth_token(username, password, scope='write') + def set_access_token(self, access_token): + self.access_token = access_token - def get_auth_token(self, username, password, scope='read'): + def is_authenticated(self): + return hasattr(self, 'access_token') and self.access_token + + def authenticate(self, username='', password=''): + try: + self.access_token = self.get_auth_token(username, password, scope='write')['access_token'] + except requests.exceptions.HTTPError as e: + logging.error("Could not authenticate with API. Correct client_id, client_secret, username or password?. {}".format(e)) + return None + + return self.access_token + + def get_auth_token(self, username, password, scope='read', grant_type='password'): resp = requests.post( - "{}/oauth2/access_token/".format(self._base_url), + "{}/oauth2/access_token/".format(self.base_url), data=dict( username=username, password=password, client_id = self._client_id, client_secret = self._client_secret, scope=scope, - grant_type='password')) + grant_type=grant_type)) resp.raise_for_status() diff --git a/pydusken/authentication.py b/pydusken/authentication.py index c59c2ce..94b3286 100644 --- a/pydusken/authentication.py +++ b/pydusken/authentication.py @@ -1,6 +1,8 @@ from django.conf import settings from django.contrib.auth.models import User from requests.exceptions import HTTPError +from django.core.validators import validate_email +from django.core.exceptions import ValidationError from . import * @@ -14,21 +16,42 @@ class DuskenBackend(object): self._api = DuskenApi(client_id=settings.DUSKEN_CLIENT_ID, client_secret=settings.DUSKEN_CLIENT_SECRET) - def authenticate(self, username=None, password=None): - # Check the username/password and return a User. + def _is_using_email(self, username): try: - access_token = self._api.authenticate(username=username, password=password) + validate_email(username) + return True + except ValidationError as e: + return False + + def authenticate(self, username=None, password=None): + # Check the username/password and return a User. Username can be an email + is_using_email = self._is_using_email(username) + try: + if is_using_email: + access_token = self._api.authenticate(email=username, password=password) + else: + access_token = self._api.authenticate(username=username, password=password) + + if not access_token: + return None + try: - user = User.objects.get(username=username) + if is_using_email: + user = User.objects.get(email=username) + else: + user = User.objects.get(username=username) if not user.duskenaccesstoken: - d = DuskenAccessToken(access_token=access_token['access_token'], user=user) + d = DuskenAccessToken(access_token=access_token, user=user) d.save() else: - if user.duskenaccesstoken.access_token != access_token['access_token']: - user.duskenaccesstoken.access_token = access_token=access_token['access_token'] + # refresh access_token + if user.duskenaccesstoken.access_token != access_token: + user.duskenaccesstoken.access_token = access_token=access_token user.save() + # if local user exist, sync email (for password reset) except User.DoesNotExist: # if user does not exist, create a local user + # TODO ...with synced data user = User(username=username) user.set_unusable_password() user.save() @@ -37,10 +60,8 @@ class DuskenBackend(object): return user except HTTPError as e: + # Did not authenticate pass - # TODO - # if local user exist, sync email - # return user with access_token return None def get_user(self, user_id): diff --git a/pydusken/members.py b/pydusken/members.py index 458a1ab..b3d59dc 100644 --- a/pydusken/members.py +++ b/pydusken/members.py @@ -4,9 +4,12 @@ import requests class Members(object): __module__ = 'pydusken' + def __init__(self, api): + self._api = api + def __request_auth_headers(self): return { - 'Authorization': "ApiKey {}:{}".format(self._username, self._api_key) + 'Authorization': "Oauth {}".format(self._api.access_token) } @@ -29,23 +32,32 @@ class Members(object): if offset: params['offset'] = offset - resp = self.__GET("{0}/member/".format(self._base_url), params=params) + resp = self.__GET("{0}/member/".format(self._api.base_url), params=params) resp.raise_for_status() return json.loads(resp.content) def get(self, member_id, memberships=None, groups=None): - resp = self.__GET("{0}/member/{1}/".format(self._base_url, member_id), params=dict(memberships=memberships, groups=groups)) + resp = self.__GET("{0}/member/{1}/".format(self._api.base_url, member_id), params=dict(memberships=memberships, groups=groups)) resp.raise_for_status() return json.loads(resp.content) + def filter(self, username=None, first_name=None, last_name=None, email=None): + resp = self.__GET("{0}/member/".format(self._api.base_url), params=dict(username=username, first_name=first_name, last_name=last_name, email=email)) + resp.raise_for_status() + return json.loads(resp.content) + + def get_username_from_email(self, email): + m = self.filter(email=email) + return m.username + def get_groups(self, member_id): - resp = self.__GET("{0}/member/{1}/groups/".format(self._base_url, member_id)) + resp = self.__GET("{0}/member/{1}/groups/".format(self._api.base_url, member_id)) resp.raise_for_status() return json.loads(resp.content) def update(self, member_id, password=None): #TODO more attributes - resp = self.__PATCH("{0}/member/{1}/".format(self._base_url, member_id), data=json.dumps(dict(password=password)), headers={'content-type': 'application/json'}) + resp = self.__PATCH("{0}/member/{1}/".format(self._api.base_url, member_id), data=json.dumps(dict(password=password)), headers={'content-type': 'application/json'}) resp.raise_for_status() # Response body is empty return True diff --git a/pydusken/models.py b/pydusken/models.py index ed69582..77bb6bb 100644 --- a/pydusken/models.py +++ b/pydusken/models.py @@ -4,3 +4,6 @@ from django.contrib.auth.models import User class DuskenAccessToken(models.Model): access_token = models.CharField(max_length=255) user = models.OneToOneField(User) + + def __unicode__(self): + return u"{} {}".format(self.user, self.access_token)