Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1c71326
Check if self.base_attributes exists
balloob Dec 12, 2017
6241072
Implement resend confirmation code mothod
caaatisgood Dec 14, 2017
16c2543
Remove redundant spaces
caaatisgood Dec 14, 2017
ab5443b
Add documentation of resend confirmation code
caaatisgood Dec 14, 2017
8002887
Add whitespace after comma
caaatisgood Dec 15, 2017
374cf6d
Use inline if-else statement
caaatisgood Dec 15, 2017
e79330b
Issue #87: Allow passing in filter argument to get_users method
Jan 29, 2018
7c658ba
Use python-jose instead of python-jose-cryptodome
dotlambda Apr 9, 2018
ae17d17
Pip10; Compatibility fix in setup.py
Apr 16, 2018
e9d9d00
Merge pull request #98 from dotlambda/patch-1
bjinwright Apr 25, 2018
819be80
Merge branch 'develop' into patch-1
bjinwright Apr 25, 2018
db16ea5
Merge pull request #82 from balloob/patch-1
bjinwright Apr 25, 2018
437ac2e
Merge branch 'develop' into patch-1
bjinwright Apr 25, 2018
103e14c
Merge pull request #83 from imrobinized/patch-1
bjinwright Apr 25, 2018
6e2f087
Merge branch 'develop' into develop
bjinwright Apr 25, 2018
991bf6c
Merge pull request #101 from OliverHofkens/develop
bjinwright Apr 25, 2018
e37774e
Add support for custom validation in pre-sign up trigger
May 11, 2018
f2f6126
removed the python-jose-cryptodome>=1.3.2
brianjinwright May 19, 2018
3ce3023
added warrant-lite
brianjinwright May 19, 2018
d376523
removed AWSSRP in favor of WarrantLite
brianjinwright May 19, 2018
1b0e627
Merged in remote develop branch
brianjinwright May 19, 2018
c914b42
Merge pull request #111 from takeahsiaor/feature/optional-validation-…
bjinwright May 19, 2018
9474350
Merge branch 'develop' into list-user-filters
bjinwright May 19, 2018
241e79d
removed aws_srp.py
brianjinwright May 19, 2018
639be4d
Merge branch 'develop' into list-user-filters
bjinwright May 19, 2018
65ff784
Merge pull request #88 from Canuteson/list-user-filters
bjinwright May 19, 2018
67ae2ae
fixed setup.py imports in pip; replaced pycrypto with pycryptodome
Zuiluj Oct 7, 2019
0ccedb3
removed vscode 'git changes' syntax and removed custom functino parse…
Zuiluj Oct 7, 2019
3693d07
fixed extras_require values
Zuiluj Oct 8, 2019
56f5fac
added warrant-lite to requirements.txt
Zuiluj Oct 8, 2019
84d1c42
signature_version will automatically be UNSIGNED if no access_key and…
Zuiluj Oct 22, 2019
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
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Makes working with AWS Cognito easier for Python developers.
- [Confirm Forgot Password](#confirm-forgot-password)
- [Change Password](#change-password)
- [Confirm Sign Up](#confirm-sign-up)
- [Resend Confirmation Code](#resend-confirmation-code)
- [Update Profile](#update-profile)
- [Send Verification](#send-verification)
- [Get User Object](#get-user-object)
Expand Down Expand Up @@ -264,6 +265,18 @@ u = Cognito('your-user-pool-id','your-client-id')
u.confirm_sign_up('users-conf-code',username='bob')
```

#### Resend Confirmation Code

Resend the confirmation for registration

```python
from warrant import Cognito

u = Cognito('your-user-pool-id','your-client-id')

u.resend_confirmation_code(username='bob')
```

##### Arguments

- **confirmation_code:** Confirmation code sent via text or email
Expand Down Expand Up @@ -444,21 +457,11 @@ This is the preferred method of user authentication with AWS Cognito.
The process involves a series of authentication challenges and responses, which if successful,
results in a final response that contains ID, access and refresh tokens.

### Using AWSSRP
The `AWSSRP` class takes a username, password, cognito user pool id, cognito app id, an optional
client secret (if app client is configured with client secret), an optional pool_region or `boto3` client.
Afterwards, the `authenticate_user` class method is used for SRP authentication.
### Using AWSSRP (Now WarrantLite)
The `AWSSRP` code has moved to [Warrant-Lite](https://github.com/capless/warrant-lite). Some projects don't need all of the features that Warrant has so we decided to make a separate.


```python
import boto3
from warrant.aws_srp import AWSSRP

client = boto3.client('cognito-idp')
aws = AWSSRP(username='username', password='password', pool_id='user_pool_id',
client_id='client_id', client=client)
tokens = aws.authenticate_user()
```

## Projects Using Warrant

Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
boto3>=1.4.3
envs>=0.3.0
python-jose-cryptodome>=1.3.2
requests>=2.13.0
pycryptodome>=3.9.0
warrant-lite>=1.0.4
11 changes: 8 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import os

from setuptools import setup, find_packages
from pip.req import parse_requirements

# pip >= 10
try:
from pip._internal.req import parse_requirements
# pip <= 9.0.3
except ImportError:
from pip.req import parse_requirements

install_reqs = parse_requirements('requirements.txt', session=False)
test_reqs = parse_requirements('requirements_test.txt', session=False)


version = '0.6.1'

README="""Python class to integrate Boto3's Cognito client so it is easy to login users. With SRP support."""
Expand Down
69 changes: 49 additions & 20 deletions warrant/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import datetime
import re
import requests
from botocore.config import Config
from botocore import UNSIGNED

from envs import env
from jose import jwt, JWTError
from warrant_lite import WarrantLite

from .aws_srp import AWSSRP
from .exceptions import TokenVerificationException


Expand Down Expand Up @@ -137,9 +139,9 @@ def __init__(
self, user_pool_id, client_id,user_pool_region=None,
username=None, id_token=None, refresh_token=None,
access_token=None, client_secret=None,
access_key=None, secret_key=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
Expand All @@ -152,7 +154,7 @@ def __init__(

self.user_pool_id = user_pool_id
self.client_id = client_id
self.user_pool_region = self.user_pool_id.split('_')[0]
self.user_pool_region = self.user_pool_id.split('_')[0]
self.username = username
self.id_token = id_token
self.access_token = access_token
Expand All @@ -163,13 +165,18 @@ def __init__(
self.base_attributes = None

boto3_client_kwargs = {}
boto3_client_kwargs['service_name'] = 'cognito-idp'
if access_key and secret_key:
boto3_client_kwargs['aws_access_key_id'] = access_key
boto3_client_kwargs['aws_secret_access_key'] = secret_key
if user_pool_region:
boto3_client_kwargs['region_name'] = user_pool_region

# if access_key and secret_key is not provided, make all calls UNSIGNED
if not access_key and not secret_key:
boto3_client_kwargs['config'] = Config(signature_version=UNSIGNED)

self.client = boto3.client('cognito-idp', **boto3_client_kwargs)
self.client = boto3.client(**boto3_client_kwargs)

def get_keys(self):

Expand Down Expand Up @@ -242,7 +249,7 @@ def switch_session(self,session):
"""
self.client = session.client('cognito-idp')

def check_token(self, renew=True):
def check_token(self, renew=True):
"""
Checks the exp attribute of the access_token and either refreshes
the tokens by calling the renew_access_tokens method or does nothing
Expand All @@ -268,14 +275,14 @@ def add_base_attributes(self, **kwargs):
def add_custom_attributes(self, **kwargs):
custom_key = 'custom'
custom_attributes = {}

for old_key, value in kwargs.items():
new_key = custom_key + ':' + old_key
custom_attributes[new_key] = value

self.custom_attributes = custom_attributes

def register(self, username, password, attr_map=None):
def register(self, username, password, attr_map=None, validation_data=None):
"""
Register the user. Other base attributes from AWS Cognito User Pools
are address, birthdate, email, family_name (last name), gender,
Expand All @@ -285,6 +292,7 @@ def register(self, username, password, attr_map=None):
:param username: User Pool username
:param password: User Pool password
:param attr_map: Attribute map to Cognito's attributes
:param validation_data: Validation data dict for custom validation in pre-sign up trigger
:return response: Response from Cognito

Example response::
Expand All @@ -297,7 +305,10 @@ def register(self, username, password, attr_map=None):
}
}
"""
attributes = self.base_attributes.copy()
if self.base_attributes is None:
attributes = {}
else:
attributes = self.base_attributes.copy()
if self.custom_attributes:
attributes.update(self.custom_attributes)
cognito_attributes = dict_to_cognito(attributes, attr_map)
Expand All @@ -307,6 +318,10 @@ def register(self, username, password, attr_map=None):
'Password': password,
'UserAttributes': cognito_attributes
}
if validation_data:
cognito_validation_data = dict_to_cognito(validation_data)
params.update({'ValidationData': cognito_validation_data})

self._add_secret_hash(params, 'SecretHash')
response = self.client.sign_up(**params)

Expand All @@ -330,22 +345,30 @@ def admin_confirm_sign_up(self, username=None):
Username=username,
)

def confirm_sign_up(self,confirmation_code,username=None):
def confirm_sign_up(self, confirmation_code, username=None):
"""
Using the confirmation code that is either sent via email or text
message.
:param confirmation_code: Confirmation code sent via text or email
:param username: User's username
:return:
"""
if not username:
username = self.username
params = {'ClientId': self.client_id,
'Username': username,
'Username': self.username if username is None else username,
'ConfirmationCode': confirmation_code}
self._add_secret_hash(params, 'SecretHash')
self.client.confirm_sign_up(**params)

def resend_confirmation_code(self, username=None):
"""
Resend the confirmation for registration
:param username: User's username
"""
params = {'ClientId': self.client_id,
'Username': self.username if username is None else username}
self._add_secret_hash(params, 'SecretHash')
self.client.resend_confirmation_code(**params)

def admin_authenticate(self, password):
"""
Authenticate the user using admin super privileges
Expand All @@ -370,13 +393,14 @@ def admin_authenticate(self, password):
self.verify_token(tokens['AuthenticationResult']['AccessToken'], 'access_token','access')
self.token_type = tokens['AuthenticationResult']['TokenType']

def authenticate(self, password):
def authenticate(self, password):
"""
Authenticate the user using the SRP protocol
:param password: The user's passsword
:return:
"""
aws = AWSSRP(username=self.username, password=password, pool_id=self.user_pool_id,

aws = WarrantLite(username=self.username, password=password, pool_id=self.user_pool_id,
client_id=self.client_id, client=self.client,
client_secret=self.client_secret)
tokens = aws.authenticate_user()
Expand All @@ -391,7 +415,7 @@ def new_password_challenge(self, password, new_password):
:param password: The user's current passsword
:param password: The user's new passsword
"""
aws = AWSSRP(username=self.username, password=password, pool_id=self.user_pool_id,
aws = WarrantLite(username=self.username, password=password, pool_id=self.user_pool_id,
client_id=self.client_id, client=self.client,
client_secret=self.client_secret)
tokens = aws.set_new_password_challenge(new_password)
Expand Down Expand Up @@ -459,14 +483,16 @@ def get_user(self, attr_map=None):
attribute_list=user.get('UserAttributes'),
metadata=user_metadata,attr_map=attr_map)

def get_users(self, attr_map=None):
def get_users(self, attr_map=None, attr_filter=None):
"""
Returns all users for a user pool. Returns instances of the
self.user_class.
:param attr_map:
:return:
"""
kwargs = {"UserPoolId":self.user_pool_id}
if attr_filter is not None:
kwargs['Filter'] = "%s = \"%s\"" % (attr_filter['Name'], attr_filter['Value'])

response = self.client.list_users(**kwargs)
return [self.get_user_obj(user.get('Username'),
Expand Down Expand Up @@ -543,10 +569,13 @@ def validate_verification(self, confirmation_code, attribute='email'):
Code=confirmation_code
)

def renew_access_token(self):
def renew_access_token(self): # BUG: stems from check_token - NotAuthorizedException
"""
Sets a new access token on the User using the refresh token.
"""

# Potential fix: try to make the client request unsigned

auth_params = {'REFRESH_TOKEN': self.refresh_token}
self._add_secret_hash(auth_params, 'SECRET_HASH')
refresh_response = self.client.initiate_auth(
Expand Down Expand Up @@ -624,7 +653,7 @@ def _add_secret_hash(self, parameters, key):
to a parameters dictionary at a specified key
"""
if self.client_secret is not None:
secret_hash = AWSSRP.get_secret_hash(self.username, self.client_id,
secret_hash = WarrantLite.get_secret_hash(self.username, self.client_id,
self.client_secret)
parameters[key] = secret_hash

Expand Down
Loading