-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #400 from NEU-DSG/create-post-confirmation-lambda-…
…event-handler Create-post-confirmation-lambda-event-handler
- Loading branch information
Showing
20 changed files
with
2,492 additions
and
930 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
[workspace] | ||
members = ["types", "graphql", "migration"] | ||
members = ["types", "graphql", "migration", "admin-event-handlers"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
name = "admin-event-handlers" | ||
version = "0.1.0" | ||
authors = ["Naomi Trevino <n.trevino@northeastern.edu>"] | ||
edition = "2021" | ||
|
||
# Function Binary definitions | ||
# each lambda function should have its own unique binary | ||
[[bin]] | ||
name = "auth-post-confirmation" | ||
path = "src/auth/post-confirmation/main.rs" | ||
|
||
[dependencies] | ||
aws_lambda_events = { version = "0.12.0", default-features = false, features = ["cognito"] } | ||
|
||
lambda_runtime = "0.9.1" | ||
tokio = { version = "1", features = ["macros"] } | ||
tracing = { version = "0.1", features = ["log"] } | ||
tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] } | ||
anyhow = "1.0" | ||
itertools = "0.10" | ||
futures = "0.3" | ||
dotenv = "0.15" | ||
regex = "1.5" | ||
rayon = "1.4" | ||
lazy_static = "1.4" | ||
base64 = "0.13" | ||
log = "0.4" | ||
pretty_env_logger = "0.4" | ||
serde = {version = "^1.0", features = ["derive"]} | ||
serde_json = "^1.0" | ||
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false } | ||
aws-config = "^1" | ||
aws-sdk-cognitoidentityprovider = "^1" | ||
dailp = {path = "../types"} |
36 changes: 36 additions & 0 deletions
36
admin-event-handlers/src/auth/post-confirmation/cognito_idp_operations.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use aws_config::SdkConfig; | ||
use aws_sdk_cognitoidentityprovider::Client; | ||
use dailp::auth::UserGroup; | ||
|
||
/// A client for conducting Cognito operations. | ||
pub struct CognitoClient { | ||
client: Client, | ||
pool_id: String, | ||
} | ||
|
||
impl CognitoClient { | ||
/// Create a new Cognito IDP Client with the provided configuration. | ||
pub async fn new(config: &SdkConfig, pool_id: String) -> Result<Self, anyhow::Error> { | ||
Ok(Self { | ||
client: Client::new(config), | ||
pool_id, | ||
}) | ||
} | ||
/// Attempts to add a user to a group. | ||
/// Fails if AdminAddUserToGroup fails. | ||
pub async fn add_user_to_group( | ||
self, | ||
email: String, | ||
group: UserGroup, | ||
) -> Result<(), anyhow::Error> { | ||
self.client | ||
.admin_add_user_to_group() | ||
.user_pool_id(self.pool_id) | ||
.username(email) | ||
.group_name(group.to_string()) | ||
.send() | ||
.await | ||
.map_err(|e| anyhow::Error::new(e)) | ||
.map(|_x| ()) | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
admin-event-handlers/src/auth/post-confirmation/google_sheets_operations.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
use anyhow::Result; | ||
use dailp::{auth::UserGroup, SheetResult}; | ||
use serde::{Deserialize, Serialize}; | ||
use std::str::FromStr; | ||
|
||
/// Represents one user's predetermined role. | ||
#[derive(Debug, Serialize, Deserialize, Clone)] | ||
pub struct UserPermission { | ||
/// The user's role | ||
pub role: UserGroup, | ||
/// The user's group | ||
pub email: String, | ||
} | ||
|
||
pub struct SheetInterpretation { | ||
pub sheet: SheetResult, | ||
} | ||
|
||
impl SheetInterpretation { | ||
/// Reads each line of the spreadsheet and encodes any roles defined in it. | ||
pub fn into_permission_list(self) -> Result<Vec<UserPermission>> { | ||
let mut sections: Vec<UserPermission> = Vec::new(); | ||
// First row is headers "Full Name" "Alt name" "DOB" "Role" "email" | ||
for row in self.sheet.values.into_iter().skip(1) { | ||
if row.len() > 4 && !row[3].is_empty() && !row[4].is_empty() { | ||
let role = UserGroup::from_str(&format!("{}s", uppercase_first_letter(&row[3])))?; | ||
sections.push(UserPermission { | ||
role, | ||
email: row[4].clone(), | ||
}) | ||
} | ||
} | ||
Ok(sections) | ||
} | ||
} | ||
|
||
/// Capitalizes the first letter of a string. | ||
fn uppercase_first_letter(s: &str) -> String { | ||
let mut c = s.chars(); | ||
match c.next() { | ||
None => String::new(), | ||
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//! A lambda event handler that adds a user to a group on confirmation if their email appears in a predefined list. | ||
|
||
mod cognito_idp_operations; | ||
mod google_sheets_operations; | ||
|
||
use aws_config::{meta::region::RegionProviderChain, BehaviorVersion, Region}; | ||
use aws_lambda_events::cognito::{ | ||
CognitoEventUserPoolsPostConfirmationRequest as CognitoPostConfirmationRequest, | ||
CognitoEventUserPoolsPostConfirmationResponse as CognitoPostConfirmationResponse, | ||
}; | ||
use cognito_idp_operations::CognitoClient; | ||
use google_sheets_operations::SheetInterpretation; | ||
use itertools::Itertools; | ||
use lambda_runtime::{service_fn, Error, LambdaEvent}; | ||
|
||
/// This is the main body for the lambda function. | ||
/// First gets the email user attribute of the user who caused this function's invocation. | ||
/// Then, retrieves emails and roles about predetermined Editors and Contributors. | ||
/// Finally, adds the user who caused this function's invocation to the appropriate user pool group. | ||
/// If the user is not predetermined to be an Editor or Contributor, the final step is skipped. | ||
/// | ||
/// # Errors: | ||
/// This function errors under any of the following conditions: | ||
/// 1. User attributes either did not exist or did not come with the request. | ||
/// 2. User causing this invocation does not have an attribute named "email" or the attribute exists but has no value. | ||
/// 3. Google Sheets API did not return any data. | ||
/// 4. Multiple users on the permissions list use the same email. | ||
/// 5. This program is unable to access environment variables. | ||
/// 6. AddUserToGroup fails. | ||
async fn function_handler( | ||
event: LambdaEvent<CognitoPostConfirmationRequest>, | ||
) -> Result<CognitoPostConfirmationResponse, Error> { | ||
let user_attributes = event.payload.user_attributes; | ||
if user_attributes.is_empty() { | ||
return Err("No email attribute found in event body.".into()); | ||
} | ||
let user_email_or_none = user_attributes.get("email"); | ||
if user_email_or_none.is_none() { | ||
return Err("Email attribute does not exist or is empty.".into()); | ||
} | ||
let user_email = user_email_or_none.unwrap().clone(); | ||
let user_permission_or_none = SheetInterpretation { | ||
sheet: dailp::SheetResult::from_sheet("1ATTekY411Jz63k6VMDn3ISFu8_f75LYFErCGY-pxVkQ", None) | ||
.await?, | ||
} | ||
.into_permission_list()? | ||
.into_iter() | ||
.filter(move |a| a.email == user_email) | ||
.at_most_one()?; | ||
if user_permission_or_none.is_none() { | ||
// We don't want to error each time a user invoking this function is not in the list. | ||
// Instead, we log that the user is not in the list, then exit successfully. | ||
println!("User does not have preset permissions."); | ||
return Ok(CognitoPostConfirmationResponse {}); | ||
} | ||
let user_permission = user_permission_or_none.unwrap(); | ||
|
||
let region = std::env::var("DAILP_AWS_REGION")?; | ||
let region_provider = RegionProviderChain::first_try(Region::new(region)) | ||
.or_default_provider() | ||
.or_else(Region::new("us-east-1")); | ||
|
||
let config = aws_config::defaults(BehaviorVersion::latest()) | ||
.region(region_provider) | ||
.load() | ||
.await; | ||
let pool_id_or_err = std::env::var("DAILP_USER_POOL"); | ||
if pool_id_or_err.is_err() { | ||
return Err("Unable to access environment variable DAILP_USER_POOL.".into()); | ||
} | ||
let cognito_action = CognitoClient::new(&config, pool_id_or_err?) | ||
.await? | ||
.add_user_to_group(user_permission.email, user_permission.role) | ||
.await; | ||
|
||
if cognito_action.is_err() { | ||
return Err("Failed to add user to group".into()); | ||
} | ||
|
||
Ok(CognitoPostConfirmationResponse {}) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Error> { | ||
dotenv::dotenv().ok(); | ||
pretty_env_logger::init(); | ||
|
||
lambda_runtime::run(service_fn(function_handler)).await | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.