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

AWS admin sign-up automation #45

Merged
merged 17 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ Automation has been introduced for creating new config files. New deployers can
Info for parthers: [in the docs](https://github.com/e-mission/e-mission-docs/tree/master/docs/use/start_a_project.md)
Info for developers: [in the docs](https://github.com/e-mission/e-mission-docs/tree/master/docs/dev/future/more_custom_auto_config.md)

Config file submission will begin the process of creating an admin dashboard and associated user pool for the project. In order to log in, users will need to generate an account. This process will be automated, but may occasionally need to be run manually if there are errors in the initial config submission. To generate an account, users will need to install boto3. Please install boto3 in your virtualenv, conda env, or local machine per your preferences:

#Run the following command in terminal:

`pip install boto3`

#Run the email-config.py script, and pass the path to the config file in as an argument:

`python email-config.py -l /path/to/configfile.nrel-op.json`

An email with instructions + admin dashboard link will be sent to all emails listed in the admin access section.

### Reviewing and testing
- contact us by email at openpath@nrel.gov for access to staging apps (Android or IOS)
- also reach out to recieve an OPcode for stage study or stage program
Expand Down
178 changes: 178 additions & 0 deletions email_automation/email-config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import boto3
from botocore.exceptions import ClientError
shankari marked this conversation as resolved.
Show resolved Hide resolved
import json
import os
import logging
import sys
import argparse
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# If you don't have boto3 installed, make sure to `pip install boto3` before running this script.

if __name__ == "__main__":
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-l', '--local',
help = 'Running locally. Provide full path to config file + install boto3 prior to running.' )
group.add_argument('-g', '--github',
help = 'Must be run on GitHub. To run locally, use -l argument.')
args = parser.parse_args()
filepath_raw = sys.argv[2]
filename_raw = filepath_raw.split("/")[-1]
filename = filename_raw.split('.')[0]
pool_name = "nrelopenpath-prod-" + filename
current_path = os.path.dirname(__file__)
maindir = current_path.rsplit("/",1)[0]
config_path = os.path.relpath('../configs/'+ filename_raw, current_path) if args.local else maindir + f'/configs/{filename_raw}'
shankari marked this conversation as resolved.
Show resolved Hide resolved
print("config_path", config_path)

if args.local:
#Set up AWS credentials as environment variables + set variables
ACCESS = os.environ.get("AWS_ACCESS_KEY_ID")
SECRET = os.environ.get("AWS_SECRET_ACCESS_KEY")
TOKEN = os.environ.get("AWS_SESSION_TOKEN")
AWS_REGION = "us-west-2"
welcome = 'welcome-template.txt'
shankari marked this conversation as resolved.
Show resolved Hide resolved

#Set up clients
cognito_client = boto3.client(
'cognito-idp',
aws_access_key_id = ACCESS,
aws_secret_access_key= SECRET,
aws_session_token=TOKEN,
region_name=AWS_REGION
)

sts_client = boto3.client(
'sts',
aws_access_key_id = ACCESS,
aws_secret_access_key= SECRET,
aws_session_token=TOKEN,
region_name=AWS_REGION
)
if args.github:
AWS_REGION = os.environ.get("AWS_REGION")
cognito_client = boto3.client('cognito-idp', region_name=AWS_REGION)
welcome = maindir + '/email_automation/welcome-template.txt'
sts_client = ''
# Functions
def get_userpool_name(pool_name, cognito_client):
response = cognito_client.list_user_pools(MaxResults=60)
is_userpool_exist = False
user_pools = [user_pool["Name"] for user_pool in response["UserPools"]]
is_userpool_exist = True if pool_name in user_pools else False
user_pool_index = user_pools.index(pool_name) if is_userpool_exist else None
pool_id = response["UserPools"][user_pool_index]["Id"]
return is_userpool_exist, pool_id

def user_already_exists(pool_id, email, cognito_client):

try:
response = cognito_client.list_users(UserPoolId=pool_id)
users = response["Users"]
result = False
if str(users).find(email) > 1:
result = True
return result
except ClientError as err:
logger.error(
"Couldn't list users for %s. Here's why: %s: %s",
pool_id,
err.response["Error"]["Code"],
err.response["Error"]["Message"],
)
raise

def get_verified_arn(sts_client):
if args.local:
account_num = sts_client.get_caller_identity()["Account"]
identity_arn = "arn:aws:ses:" + AWS_REGION + ":" + account_num + ":identity/openpath@nrel.gov"
if args.github:
AWS_ACCT_ID = os.environ.get("AWS_ACCT_ID")
identity_arn = "arn:aws:ses:" + AWS_REGION + ":" + AWS_ACCT_ID + ":identity/openpath@nrel.gov"
return identity_arn

def email_extract():
with open (config_path) as config_file:
data = json.load(config_file)
admindash_prefs = data['admin_dashboard']
emails = [i.strip() for i in admindash_prefs['admin_access'].split(",")]
shankari marked this conversation as resolved.
Show resolved Hide resolved
columns_exclude = admindash_prefs['data_trips_columns_exclude']
map_trip_lines_enabled = admindash_prefs['map_trip_lines']
return emails, map_trip_lines_enabled, columns_exclude

def create_account(pool_id, email, cognito_client):
response = cognito_client.admin_create_user(
UserPoolId = pool_id,
Username=email,
UserAttributes=[
{
'Name': 'email',
'Value': email,
},
],
ForceAliasCreation=True,
DesiredDeliveryMediums=[
'EMAIL',
],
)
return response

def format_email(filename, map_trip_lines_enabled, columns_exclude):
with open(welcome, 'r') as f:
html = f.read()
html = html.replace('<filename>', filename)
if map_trip_lines_enabled:
html = html.replace ('<map_trip_lines>', 'Additionally, you can view individual user-origin destination points using the "Map Lines" option from the map page.')
else:
html = html.replace ('<map_trip_lines>', '')
if 'data.start_loc.coordinates' in columns_exclude or 'data.end_loc.coordinates' in columns_exclude:
html = html.replace ('<columns_exclude>', 'Per your requested configuration, your trip table excludes trip start/end coordinates for greater anonymity. Let us know if you need them to be enabled for improved analysis.')
elif columns_exclude == '':
html = html.replace ('<columns_exclude>', 'Since you indicated that you want to map the data to infrastructure updates, your configuration includes trip start/end in the trip table. Let us know if you would like to exclude those for greater anonymity.')
return html


def update_user_pool(pool_id, pool_name, html, identity_arn, cognito_client):
response = cognito_client.update_user_pool(
UserPoolId= pool_id,
AutoVerifiedAttributes=['email'],
EmailConfiguration={
'SourceArn': identity_arn,
'EmailSendingAccount': 'DEVELOPER',
'From': 'openpath@nrel.gov'
},
AdminCreateUserConfig={
'AllowAdminCreateUserOnly': True,
'InviteMessageTemplate': {
'EmailMessage': str(html),
'EmailSubject': f'Welcome to {pool_name} user pool!'
}
},
)
######################################################################
is_userpool_exist, pool_id = get_userpool_name(pool_name, cognito_client)

# Start by checking for the User Pool. If the User Pool does not yet exist, wait until it is set up to add users.
if is_userpool_exist:
#extract email addresses from config file
emails, map_trip_lines_enabled, columns_exclude = email_extract()
#Loop over each email address. Check if they're in the user pool.
for email in emails:
if not user_already_exists(pool_id, email, cognito_client):
#If user not in pool, format the email template for their welcome email, update the user pool, and create an account for them.
print(email + " not in user pool! Creating account...")
html = format_email(filename, map_trip_lines_enabled, columns_exclude)
identity_arn = get_verified_arn(sts_client)
update_user_pool(pool_id, pool_name, html, identity_arn, cognito_client)
response = create_account(pool_id, email, cognito_client)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
print("Account created! Sending welcome email.")
else:
print("Account creation unsuccessful.")
print(response['ResponseMetadata']['HTTPStatusCode'])
else:
print(email + " already in user pool!")
else:
print(pool_name + " does not exist! Try again later.")

32 changes: 32 additions & 0 deletions email_automation/welcome-template.txt
shankari marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<html>
<head></head>
<body>
<h1>Welcome to OpenPATH!</h1>
<p> Thank you for joining the OpenPATH project.<br>
<br>
<strong>Your admin dashboard can be found at:</strong> <br>
<br>
<a href='https://<filename>-openpath.nrel.gov/admin/'>
https://<filename>-openpath.nrel.gov/admin/</a>.<br>
<br>
An account has been created for you with AWS Cognito. Please log in and change your
temporary password:<br>
<br>
Username: {username}
Temporary Password: {####}
<br>
<br>
The login to the admin dashboard uses 2-factor authentication. You will have to install an authenticator app (e.g. Microsoft Authenticator, Google Authenticator) to receive One Time Passcodes (OTP) every time you want to log in. This is a requirement from NREL cyber to ensure that the data access to individual user information is secure.<br>
<br>
For the specified date range, you can download trip, user and trajectory tables as csv and view aggregate origin-destination points in the maps. <map_trip_lines> <br>
<br>
<columns_exclude>

Please let us know if you have any problems with signing in.<br>
<br>
Thank you,<br>
<br>
NREL's OpenPATH Team
</p>
</body>
</html>