Skip to content

Commit

Permalink
Add update views, refactored the backend and Cognito class
Browse files Browse the repository at this point in the history
  • Loading branch information
bjinwright committed Feb 28, 2017
1 parent 75a10a8 commit 6f1f994
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 136 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ var/
*.egg-info/
.installed.cfg
*.egg

manage.py
db.sqlite3
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down
4 changes: 4 additions & 0 deletions cdu/demo/templates/logout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% extends 'base.html' %}
{% block main_content %}
<h1>Logged Out</h1>
{% endblock %}
2 changes: 1 addition & 1 deletion cdu/demo/templates/user_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<p>Email: {{ request.user.email }}</p>
<p>First Name: {{ request.user.first_name }}</p>
<p>Last Name: {{ request.user.last_name }}</p>
<p>ID Token: {{ request.session.ID_TOKEN }}</p>
<p>ID Token: {{ request.session.ID_TOKEN}}</p>
<p>Access Token: {{ request.session.ACCESS_TOKEN }}</p>
<p>Refresh Token: {{ request.session.REFRESH_TOKEN }}</p>
{% endblock %}
12 changes: 9 additions & 3 deletions cdu/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
ALLOWED_HOSTS = []

AUTHENTICATION_BACKENDS = [
'cognito.django.backend.CognitoUserPoolAuthBackend'
'cognito.django.backend.CognitoBackend'
]

COGNITO_TEST_USERNAME = env('COGNITO_TEST_USERNAME')

COGNITO_TEST_PASSWORD = env('COGNITO_TEST_PASSWORD')
Expand All @@ -39,6 +40,8 @@

COGNITO_APP_ID = env('COGNITO_APP_ID')

COGNITO_ATTR_MAPPING = env('COGNITO_ATTR_MAPPING',{},var_type='dict')

# Application definition

INSTALLED_APPS = [
Expand All @@ -49,7 +52,8 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'cdu.demo',
'crispy_forms,'
'cognito.django',
'crispy_forms',
'django_extensions'
]

Expand Down Expand Up @@ -81,6 +85,8 @@
},
]

SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer'

WSGI_APPLICATION = 'cdu.wsgi.application'


Expand Down Expand Up @@ -134,4 +140,4 @@

STATIC_URL = '/static/'

CRISPY_TEMPLATE_PACK = 'bootstrap3'
CRISPY_TEMPLATE_PACK = 'bootstrap3'
3 changes: 2 additions & 1 deletion cdu/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from demo.views import UserView

urlpatterns = [
url(r'^login/$', auth_views.login, {'template_name': 'login.html'}, name='login'),
url(r'^accounts/login/$', auth_views.login, {'template_name': 'login.html'}, name='login'),
url(r'^accounts/logout/$', auth_views.logout, {'template_name': 'logout.html'}, name='logout'),
url(r'^user_info/$', UserView.as_view(), name='user_view'),
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('cognito.django.urls'))
Expand Down
114 changes: 63 additions & 51 deletions cognito/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import boto3
import ast
import json
import base64


def attribute_dict(attributes):
Expand All @@ -10,60 +12,51 @@ def attribute_dict(attributes):
"""
return [{'Name': key, 'Value': value} for key, value in attributes.items()]

def decode_jwt(token):
"""Decode base64, padding being optional.
class UserObj(object):
:param data: Base64 data as an ASCII byte string
:returns: The decoded byte string.
def __init__(self, username, attribute_list, metadata={}):
"""
:param username:
:param attribute_list:
:param metadata: Dictionary of User metadata
"""
self.username = username
self.pk = username
for a in attribute_list:
name = a.get('Name')
value = a.get('Value')
if value in ['true','false']:
value = ast.literal_eval(value.capitalize())
setattr(self, name, value)
for key, value in metadata.items():
setattr(self, key.lower(), value)
"""
header,payload,signature = token.split('.')
missing_padding = len(payload) % 4
if missing_padding != 0:
payload += b'='* (4 - missing_padding)
return json.loads(base64.decodestring(payload))


class Cognito(object):

user_class = dict

def __init__(
self, user_pool_id, client_id,
username=None, password=None,
username=None,
id_token=None,refresh_token=None,
access_token=None,expires_datetime=None,
access_token=None,secret_hash=None,
access_key=None, secret_key=None,
):
"""
:param user_pool_id: Cognito User Pool ID
:param client_id: Cognito User Pool Application client ID
:param username: User Pool username
:param password: User Pool password
:param id_token: ID Token returned by authentication
:param refresh_token: Refresh Token returned by authentication
:param access_token: Access Token returned by authentication
:param expires_datetime: Datetime object created by
the authenticate method
:param access_key: AWS IAM access key
:param secret_key: AWS IAM secret key
"""

self.user_pool_id = user_pool_id
self.client_id = client_id
self.username = username
self.password = password
self.id_token = id_token
self.access_token = access_token
self.refresh_token = refresh_token
self.secret_hash = secret_hash
self.token_type = None
self.expires_in = None
self.expires_datetime = expires_datetime

if access_key and secret_key:
self.client = boto3.client('cognito-idp',
aws_access_key_id=access_key,
Expand All @@ -72,6 +65,10 @@ def __init__(
else:
self.client = boto3.client('cognito-idp')

def get_user_obj(self,username=None,attribute_list=[],metadata={}):
return self.user_class(username=username,attribute_list=attribute_list,
metadata=metadata)

def switch_session(self,session):
"""
Primarily used for unit testing so we can take advantage of the
Expand All @@ -81,15 +78,26 @@ def switch_session(self,session):
"""
self.client = session.client('cognito-idp')


def check_token(self):
"""
Checks the self.expires_datetime attribute and either refreshes
Checks the exp attribute of the access_token and either refreshes
the tokens by calling the renew_access_tokens method or does nothing
:return: bool
:return: None
"""
if not self.access_token:
raise AttributeError('Access Token Required to Check Token')
now = datetime.datetime.now()
if now > self.expires_datetime:
dec_access_token = decode_jwt(self.access_token)

if now > datetime.datetime.fromtimestamp(dec_access_token['exp']):
self.renew_access_token()
return True
return False

def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)

def register(self, username, password, **kwargs):
"""
Expand Down Expand Up @@ -142,27 +150,28 @@ def confirm_sign_up(self,confirmation_code,username=None):
ConfirmationCode=confirmation_code
)

def authenticate(self):
def authenticate(self, password):
"""
Authenticate the user.
:param user_pool_id: User Pool Id found in Cognito User Pool
:param client_id: App Client ID found in the Apps section of the Cognito User Pool
:return:
"""

auth_params = {
'USERNAME': self.username,
'PASSWORD': password
}
auth_params['']
tokens = self.client.admin_initiate_auth(
UserPoolId=self.user_pool_id,
ClientId=self.client_id,
# AuthFlow='USER_SRP_AUTH'|'REFRESH_TOKEN_AUTH'|'REFRESH_TOKEN'|'CUSTOM_AUTH'|'ADMIN_NO_SRP_AUTH',
AuthFlow='ADMIN_NO_SRP_AUTH',
AuthParameters={
'USERNAME': self.username,
'PASSWORD': self.password
},
AuthParameters=auth_params,
)

self.expires_in = tokens['AuthenticationResult']['ExpiresIn']
self.expires_datetime = datetime.datetime.now() + datetime.timedelta(seconds=self.expires_in)


self.id_token = tokens['AuthenticationResult']['IdToken']
self.refresh_token = tokens['AuthenticationResult']['RefreshToken']
self.access_token = tokens['AuthenticationResult']['AccessToken']
Expand All @@ -178,8 +187,7 @@ def logout(self):
self.client.global_sign_out(
AccessToken=self.access_token
)
self.expires_in = None
self.expires_datetime = None

self.id_token = None
self.refresh_token = None
self.access_token = None
Expand All @@ -197,16 +205,20 @@ def update_profile(self, attrs):
)

def get_user(self):
# self.check_token()
user = self.client.get_user(
AccessToken=self.access_token
)
user_metadata = {
'username': user.get('Username'),
'expires_in': self.expires_in,
'expires_datetime': self.expires_datetime
'id_token': self.id_token,
'access_token': self.access_token,
'refresh_token': self.refresh_token
}

return UserObj(self.username, user.get('UserAttributes'), metadata=user_metadata)
return self.get_user_obj(username=self.username,
attribute_list=user.get('UserAttributes'),
metadata=user_metadata)

def admin_get_user(self):
"""
Expand All @@ -220,11 +232,14 @@ def admin_get_user(self):
user_metadata = {
'user_status':user.get('UserStatus'),
'username':user.get('Username'),
'expires_in':self.expires_in,
'expires_datetime':self.expires_datetime
'id_token': self.id_token,
'access_token': self.access_token,
'refresh_token': self.refresh_token
}
return self.get_user_obj(username=self.username,
atttribute_list=user.get('UserAttributes'),
metadata=user_metadata)

return UserObj(self.username, user.get('UserAttributes'), metadata=user_metadata)

def send_verification(self, attribute='email'):
"""
Expand All @@ -241,7 +256,7 @@ def validate_verification(self, confirmation_code, attribute='email'):
"""
Verifies the specified user attributes in the user pool.
:param confirmation_code: Code sent to user upon intiating verification
:param attribute: Attribute to confirm
:param attribute: Attribute to confirm
"""
self.check_token()
return self.client.verify_user_attribute(
Expand All @@ -268,10 +283,7 @@ def renew_access_token(self):
{
'access_token': refresh_response['AuthenticationResult']['AccessToken'],
'id_token': refresh_response['AuthenticationResult']['IdToken'],
'token_type': refresh_response['AuthenticationResult']['TokenType'],
'expires_in': refresh_response['AuthenticationResult']['ExpiresIn'],
'expires_datetime':datetime.datetime.now() + datetime.timedelta(
seconds=refresh_response['AuthenticationResult']['ExpiresIn'])
'token_type': refresh_response['AuthenticationResult']['TokenType']
}
)

Expand All @@ -286,9 +298,9 @@ def initiate_forgot_password(self):

def confirm_forgot_password(self, confirmation_code, password):
"""
Allows a user to enter a code provided when they reset their password
Allows a user to enter a code provided when they reset their password
to update their password.
:param confirmation_code: The confirmation code sent by a user's request
:param confirmation_code: The confirmation code sent by a user's request
to retrieve a forgotten password
:param password: New password
"""
Expand Down
Loading

0 comments on commit 6f1f994

Please sign in to comment.