Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: python
python:
- "2.7"
- "3.6"
# command to install dependencies
install:
- "pip install -r requirements.txt"
Expand Down
22 changes: 17 additions & 5 deletions cdu/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
ALLOWED_HOSTS = []

AUTHENTICATION_BACKENDS = [
'django_warrant.backend.CognitoBackend',
'django.contrib.auth.backends.ModelBackend'
'django_warrant.backend.CognitoNoModelBackend'
]

COGNITO_TEST_USERNAME = env('COGNITO_TEST_USERNAME')
Expand All @@ -39,14 +38,25 @@

COGNITO_USER_POOL_ID = env('COGNITO_USER_POOL_ID')

COGNITO_ADMIN_GROUP = env('COGNITO_ADMIN_GROUP','Admins')

COGNITO_APP_ID = env('COGNITO_APP_ID')

COGNITO_CLIENT_SECRET = env('COGNITO_CLIENT_SECRET')

COGNITO_ATTR_MAPPING = env(
'COGNITO_ATTR_MAPPING',
{
'email': 'email',
'given_name': 'first_name',
'family_name': 'last_name',
'name':'name',
'username':'username',
'address':'address',
'gender':'gender',
'preferred_username':'preferred_username',
'phone_number':'phone_number',
'phone_number_verified':'phone_number_verified',
'custom:api_key': 'api_key',
'custom:api_key_id': 'api_key_id'
},
Expand Down Expand Up @@ -75,7 +85,7 @@
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_warrant.middleware.CognitoAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Expand Down Expand Up @@ -132,8 +142,8 @@
},
]

LOGIN_REDIRECT_URL = '/accounts/profile'

LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/'
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/

Expand All @@ -154,3 +164,5 @@
STATIC_URL = '/static/'

CRISPY_TEMPLATE_PACK = 'bootstrap3'

# AUTH_USER_MODEL = 'django_warrant.UserObj'
2 changes: 1 addition & 1 deletion cdu/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('django_warrant.urls',namespace='dw'))
url(r'^', include('django_warrant.urls',namespace='dw'))
]
3 changes: 2 additions & 1 deletion django_warrant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ def add_user_tokens(sender, user, **kwargs):
"""
Add Cognito tokens to the session upon login
"""
if user.backend == 'django_warrant.backend.CognitoBackend':
if user.backend in ['django_warrant.backend.CognitoBackend',
'django_warrant.backend.CognitoNoModelBackend']:
request = kwargs['request']
request.session['ACCESS_TOKEN'] = user.access_token
request.session['ID_TOKEN'] = user.id_token
Expand Down
151 changes: 101 additions & 50 deletions django_warrant/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,20 @@
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.utils.six import iteritems
from django.utils.crypto import salted_hmac
from warrant_lite import WarrantLite

from warrant import Cognito
from django_warrant.models import UserObj
from .utils import cognito_to_dict


class CognitoUser(Cognito):
user_class = get_user_model()
# Mapping of Cognito User attribute name to Django User attribute name
COGNITO_ATTR_MAPPING = getattr(settings, 'COGNITO_ATTR_MAPPING',
{
'email': 'email',
'given_name': 'first_name',
'family_name': 'last_name',
}
)

def get_user_obj(self,username=None,attribute_list=[],metadata={},attr_map={}):
user_attrs = cognito_to_dict(attribute_list,CognitoUser.COGNITO_ATTR_MAPPING)
django_fields = [f.name for f in CognitoUser.user_class._meta.get_fields()]
extra_attrs = {}
for k, v in user_attrs.items():
if k not in django_fields:
extra_attrs.update({k: user_attrs.pop(k, None)})
if getattr(settings, 'COGNITO_CREATE_UNKNOWN_USERS', True):
user, created = CognitoUser.user_class.objects.update_or_create(
username=username,
defaults=user_attrs)
else:
try:
user = CognitoUser.user_class.objects.get(username=username)
for k, v in iteritems(user_attrs):
setattr(user, k, v)
user.save()
except CognitoUser.user_class.DoesNotExist:
user = None
if user:
for k, v in extra_attrs.items():
setattr(user, k, v)
return user


class AbstractCognitoBackend(ModelBackend):
__metaclass__ = abc.ABCMeta

UNAUTHORIZED_ERROR_CODE = 'NotAuthorizedException'

USER_NOT_FOUND_ERROR_CODE = 'UserNotFoundException'

COGNITO_USER_CLASS = CognitoUser

@abc.abstractmethod
def authenticate(self, username=None, password=None):
Expand All @@ -65,24 +29,58 @@ def authenticate(self, username=None, password=None):
:param password: Cognito password
:return: returns User instance of AUTH_USER_MODEL or None
"""
cognito_user = CognitoUser(
settings.COGNITO_USER_POOL_ID,
settings.COGNITO_APP_ID,
access_key=getattr(settings, 'AWS_ACCESS_KEY_ID', None),
secret_key=getattr(settings, 'AWS_SECRET_ACCESS_KEY', None),
username=username)
wl = WarrantLite(username=username, password=password,
pool_id=settings.COGNITO_USER_POOL_ID,
client_id=settings.COGNITO_APP_ID,
client_secret=settings.COGNITO_CLIENT_SECRET)

try:
cognito_user.authenticate(password)
tokens = wl.authenticate_user()
except (Boto3Error, ClientError) as e:
return self.handle_error_response(e)
user = cognito_user.get_user()

access_token = tokens['AuthenticationResult']['AccessToken']
refresh_token = tokens['AuthenticationResult']['RefreshToken']
id_token = tokens['AuthenticationResult']['IdToken']
wl.verify_token(access_token, 'access_token', 'access')
wl.verify_token(id_token, 'id_token', 'id')

cognito_user = wl.client.get_user(
AccessToken=access_token
)
user = self.get_user_obj(username,cognito_user)
if user:
user.access_token = cognito_user.access_token
user.id_token = cognito_user.id_token
user.refresh_token = cognito_user.refresh_token
user.access_token = access_token
user.id_token = id_token
user.refresh_token = refresh_token

return user

def get_user_obj(self,username,cognito_user):
user_attrs = cognito_to_dict(cognito_user.get('UserAttributes'),
settings.COGNITO_ATTR_MAPPING or {
'email':'email',
'given_name':'first_name',
'family_name':'last_name'
})
User = get_user_model()
django_fields = [f.name for f in User._meta.get_fields()]
extra_attrs = {}
new_user_attrs = user_attrs.copy()

for k, v in user_attrs.items():
if k not in django_fields:
extra_attrs.update({k: new_user_attrs.pop(k, None)})
user_attrs = new_user_attrs
try:
u = User.objects.get(username=username)
except User.DoesNotExist:
u = None
if u:
for k, v in extra_attrs.items():
setattr(u, k, v)
return u

def handle_error_response(self, error):
error_code = error.response['Error']['Code']
if error_code in [
Expand All @@ -107,3 +105,56 @@ def authenticate(self, request, username=None, password=None):
request.session['REFRESH_TOKEN'] = user.refresh_token
request.session.save()
return user


class CognitoNoModelBackend(ModelBackend):

def authenticate(self, request, username=None, password=None):
wl = WarrantLite(username=username, password=password,
pool_id=settings.COGNITO_USER_POOL_ID,
client_id=settings.COGNITO_APP_ID,
client_secret=settings.COGNITO_CLIENT_SECRET)

try:
tokens = wl.authenticate_user()
except (Boto3Error, ClientError) as e:
return self.handle_error_response(e)

access_token = tokens['AuthenticationResult']['AccessToken']
refresh_token = tokens['AuthenticationResult']['RefreshToken']
id_token = tokens['AuthenticationResult']['IdToken']
wl.verify_token(access_token, 'access_token', 'access')
wl.verify_token(id_token, 'id_token', 'id')

user = UserObj(wl.client.get_user(
AccessToken=access_token
),access_token=access_token,is_authenticated=True)
user.refresh_token = refresh_token
user.access_token = access_token
user.id_token = id_token
user.session_auth_hash = get_session_auth_hash(password)
if user:
request.session['ACCESS_TOKEN'] = user.access_token
request.session['ID_TOKEN'] = user.id_token
request.session['REFRESH_TOKEN'] = user.refresh_token
request.session.save()
return user

def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None

def get_user(self,user_id):
pass


def get_session_auth_hash(password):
"""
Return an HMAC of the password field.
"""
key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
return salted_hmac(key_salt, password).hexdigest()
42 changes: 41 additions & 1 deletion django_warrant/forms.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _


class ProfileForm(forms.Form):
first_name = forms.CharField(max_length=200,required=True)
last_name = forms.CharField(max_length=200,required=True)
email = forms.EmailField(required=True)
phone_number = forms.CharField(max_length=30,required=True)
phone_number = forms.CharField(max_length=30,required=True,help_text='ex. +14325551212')
gender = forms.ChoiceField(choices=(('female','Female'),('male','Male')),required=True)
address = forms.CharField(max_length=200,required=True)
preferred_username = forms.CharField(max_length=200,required=True)


class AdminProfileForm(ProfileForm):
api_key = forms.CharField(max_length=200, required=False)
api_key_id = forms.CharField(max_length=200, required=False)

Expand All @@ -19,3 +24,38 @@ class APIKeySubscriptionForm(forms.Form):
def __init__(self, plans=[], users_plans=[], *args, **kwargs):
self.base_fields['plan'].choices = [(p.get('id'),p.get('name')) for p in plans if not p.get('id') in users_plans]
super(APIKeySubscriptionForm, self).__init__(*args, **kwargs)


class ForgotPasswordForm(forms.Form):
username = forms.CharField(max_length=200,required=True)


class PasswordForm(forms.Form):
password = forms.CharField(widget=forms.PasswordInput, required=True, max_length=200)
confirm_password = forms.CharField(widget=forms.PasswordInput, required=True, max_length=200)

def clean_confirm_password(self):
password = self.cleaned_data['password']
confirm_password = self.cleaned_data['confirm_password']
if password != confirm_password:
raise ValidationError(_('The passwords entered do not match.'))
return confirm_password


class VerificationCodeForm(forms.Form):
username = forms.CharField(max_length=200,required=True)
verification_code = forms.CharField(max_length=6)


class ConfirmForgotPasswordForm(VerificationCodeForm,PasswordForm):
pass


class RegistrationForm(ProfileForm,PasswordForm,ForgotPasswordForm):
pass


class UpdatePasswordForm(PasswordForm):
previous_password = forms.CharField(max_length=200,
widget=forms.PasswordInput,
required=True)
21 changes: 21 additions & 0 deletions django_warrant/middleware.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from django_warrant.models import get_user as gu


class APIKeyMiddleware(object):
"""
Expand All @@ -22,3 +26,20 @@ def process_request(request):
request.api_key = request.META['HTTP_AUTHORIZATION_ID']

return None


def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = gu(request)
return request._cached_user


class CognitoAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE%s setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
request.user = get_user(request)
Loading