Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SSO to retrieve credentials for calling Cognito related queries #41

Merged
merged 5 commits into from
Oct 26, 2023
Merged
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
7 changes: 4 additions & 3 deletions .devcontainer/docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ services:
- ../../backend.env
volumes:
- ../../:/portal
- ~/.aws:/.aws
- ~/.aws:/root/.aws
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- /var/run/docker.sock:/var/run/docker.sock
- ~/.gitconfig:/.gitconfig
command: /bin/sh -c "while sleep 1000; do :; done"
network_mode: host
environment:
AWS_SHARED_CREDENTIALS_FILE: /.aws/credentials
AWS_CONFIG_FILE: /.aws/config
AWS_SHARED_CREDENTIALS_FILE: /root/.aws/credentials
AWS_CONFIG_FILE: /root/.aws/config
AWS_PROFILE: cpac-webmaster
1 change: 0 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from starlette.middleware.base import _StreamingResponse
from typing import Awaitable, Callable


app = FastAPI()
auth = Cognito(
region=COGNITO_REGION,
Expand Down
2 changes: 1 addition & 1 deletion controllers/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def post(self, item: PostItem, current_user: CognitoClaims = Depends(get_c
helpers=item.helpers,
num_additional_chairs=item.num_additional_chairs,
signer_email=current_user.email, # TODO assert that emails are verified
signer_name=current_user.username,
signer_name=current_user.username, # TODO signer_name should be the user's name, not username
artist_phone_number=item.artist_phone_number # TODO this should be stored in AWS
)
except NoApproverException:
Expand Down
2 changes: 1 addition & 1 deletion controllers/me.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def patch(self, current_user: CognitoClaims = Depends(get_current_user)) -

async def post(self, item: PostItem, current_user: CognitoClaims = Depends(get_current_user)) -> Response: # type: ignore[no-any-unimported]
try:
ret: bool = await MeManager().create_user(current_user.sub, str(item.vendor_type))
ret: bool = await MeManager().create_user(current_user.sub, current_user.username, str(item.vendor_type))
except DuplicateKeyError:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
Expand Down
3 changes: 2 additions & 1 deletion database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .users import UsersDB
from .contracts import ContractsDB
from .contracts import ContractsDB
from .cognito import CognitoIdentityProviderWrapper
49 changes: 49 additions & 0 deletions database/cognito.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# https://docs.aws.amazon.com/code-library/latest/ug/python_3_cognito-identity-provider_code_examples.html

from botocore.exceptions import ClientError
import logging
import boto3
from config.env import COGNITO_USERPOOL_ID
from utilities.types import JSONDict
from distutils.util import strtobool


class CognitoUser:

def __init__(self, cognito_response_json: JSONDict) -> None:
assert cognito_response_json.get("UserAttributes")
for attribute_dict in cognito_response_json['UserAttributes']:
assert "Name" in attribute_dict
assert attribute_dict.get("Value")
match attribute_dict['Name']:
case 'sub':
self.sub: str = attribute_dict['Value']
case 'email_verified':
self.email_verified: bool = bool(strtobool(attribute_dict['Value']))
case 'email':
self.email: str = attribute_dict['Value']
assert self.sub is not None
assert self.email_verified is not None
assert self.email is not None


class CognitoIdentityProviderWrapper:
"""Encapsulates Amazon Cognito actions"""
def __init__(self) -> None:
self.cognito_idp_client = boto3.client('cognito-idp')

def get_user(self, username: str) -> CognitoUser:
"""
Gets a user in Cognito by it's username.

:return: user
"""
try:
response: JSONDict = self.cognito_idp_client.admin_get_user(UserPoolId=COGNITO_USERPOOL_ID, Username=username)
assert type(response) is dict
return CognitoUser(response)
except ClientError as err:
logging.error(
"Couldn't list users for %s. Here's why: %s: %s", COGNITO_USERPOOL_ID,
err.response['Error']['Code'], err.response['Error']['Message'])
raise err
8 changes: 4 additions & 4 deletions database/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
class UsersDB(BaseDB):

@classmethod
def _new_user(cls, _id: str, vendor_type: str) -> JSONDict:
def _new_user(cls, _id: str, username: str, vendor_type: str) -> JSONDict:
return {
"_id": _id,
"username": username,
"contracts": [],
"group": Groups.CUSTOMER,
"vendor_type": vendor_type,
Expand All @@ -26,9 +27,8 @@ async def get_user(cls, uuid: str) -> Optional[MongoMappingType]:
return result

@classmethod
async def create_user(cls, uuid: str, vendor_type: str) -> bool:
query = cls._new_user(uuid, vendor_type)
# TODO catch error if user already exists
async def create_user(cls, uuid: str, username: str, vendor_type: str) -> bool:
query = cls._new_user(uuid, username, vendor_type)
ret: pymongo.results.InsertOneResult = await cls.get_collection().insert_one(query)
return ret.acknowledged

Expand Down
14 changes: 9 additions & 5 deletions managers/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from database import UsersDB
from utilities import NoApproverException
from typing import List
from database import CognitoIdentityProviderWrapper


class ContractManager():
Expand All @@ -28,11 +29,14 @@ async def create_contract(
approver = await UsersDB.get_random_artist_reviewer()
if approver is None:
raise NoApproverException()
# TODO cannot do this to get approver. Need to grab from cognito DB
# approver_email = approver.get("email")
# approver_name = approver.get('name')
approver_email = "[email protected]"
approver_name = "test"

# Get the email and username from CognitoDB
assert "username" in approver
approver_name = approver["username"]
approver_cognito_data = CognitoIdentityProviderWrapper().get_user(approver_name)
approver_email = approver_cognito_data.email

# TODO the approver_name should be the user's name, not username
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#46


assert type(approver_email) is str, "Expected a string for approver email"
assert type(approver_name) is str, "Expected a string for approver name"
Expand Down
4 changes: 2 additions & 2 deletions managers/me.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

class MeManager():

async def create_user(self, user_id: str, vendor_type: str) -> bool:
return await UsersDB.create_user(user_id, vendor_type)
async def create_user(self, user_id: str, username: str, vendor_type: str) -> bool:
return await UsersDB.create_user(user_id, username, vendor_type)
6 changes: 6 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ ignore_missing_imports = True
ignore_missing_imports = True

[mypy-motor.motor_asyncio]
ignore_missing_imports = True

[mypy-botocore.exceptions]
ignore_missing_imports = True

[mypy-boto3]
ignore_missing_imports = True
8 changes: 6 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ anyio==3.7.1
asyncio==3.4.3
attrs==23.1.0
blinker==1.6.2
boto3==1.28.48
botocore==1.31.48
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
Expand All @@ -23,6 +25,7 @@ httpcore==0.17.3
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
jmespath==1.0.1
jsonschema==4.18.4
jsonschema-specifications==2023.7.1
MarkupSafe==2.1.3
Expand All @@ -48,10 +51,11 @@ referencing==0.30.0
requests==2.31.0
rpds-py==0.9.2
rsa==4.9
s3transfer==0.6.2
six==1.16.0
sniffio==1.3.0
tomli==2.0.1
types-jsonschema==4.17.0.10
typing_extensions==4.7.1
urllib3==2.0.4
Werkzeug==2.3.6
urllib3==1.26.16
Werkzeug==2.3.6