From 6e1c35f72174077d9c197ed29b0dde11efdccc39 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Sat, 14 Jan 2023 16:27:07 +0100 Subject: [PATCH] feat: link candidates --- api/src/routes/admin.rs | 23 +++++++--- api/src/test.rs | 1 + core/src/database/mutation/application.rs | 2 +- core/src/database/mutation/candidate.rs | 14 +++++++ core/src/database/query/application.rs | 14 ++++++- core/src/database/query/candidate.rs | 17 +++++++- core/src/services/application_service.rs | 51 +++++++++++++++++++---- core/src/services/candidate_service.rs | 1 + core/src/services/session_service.rs | 6 +-- 9 files changed, 110 insertions(+), 19 deletions(-) diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 0b9d33a..b92a97f 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -85,15 +85,16 @@ pub async fn hello(_session: AdminAuth) -> Result> { #[post("/create", data = "")] pub async fn create_candidate( conn: Connection<'_, Db>, - _session: AdminAuth, + session: AdminAuth, request: Json, ) -> Result, Custom> { let db = conn.into_inner(); let form = request.into_inner(); + let private_key = session.get_private_key(); let plain_text_password = random_12_char_string(); - let application = ApplicationService::create(&db, form.application_id, &plain_text_password, form.personal_id_number.clone()) + let application = ApplicationService::create(&private_key, &db, form.application_id, &plain_text_password, form.personal_id_number.clone()) .await .map_err(to_custom_error)?; @@ -161,10 +162,12 @@ pub async fn get_candidate( let db = conn.into_inner(); let private_key = session.get_private_key(); - let candidate = Query::find_candidate_by_id(db, id) + let application = Query::find_application_by_id(db, id) .await .map_err(|e| to_custom_error(ServiceError::DbError(e)))? .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; + let candidate = ApplicationService::find_related_candidate(db, application).await + .map_err(to_custom_error)?; let details = ApplicationService::decrypt_all_details( private_key, @@ -187,10 +190,13 @@ pub async fn delete_candidate( ) -> Result<(), Custom> { let db = conn.into_inner(); - let candidate = Query::find_candidate_by_id(db, id) + let application = Query::find_application_by_id(db, id) .await .map_err(|e| to_custom_error(ServiceError::DbError(e)))? .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; + let candidate = ApplicationService::find_related_candidate(db, application).await.map_err(to_custom_error)?; + + // TODO CandidateService::delete_candidate(db, candidate) .await @@ -219,12 +225,19 @@ pub async fn reset_candidate_password( #[get("/candidate//portfolio")] pub async fn get_candidate_portfolio( + conn: Connection<'_, Db>, session: AdminAuth, id: i32, ) -> Result, Custom> { + let db = conn.into_inner(); let private_key = session.get_private_key(); - let portfolio = PortfolioService::get_portfolio(id, private_key) + let application = Query::find_application_by_id(db, id) + .await + .map_err(|e| to_custom_error(ServiceError::DbError(e)))? + .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; + + let portfolio = PortfolioService::get_portfolio(application.candidate_id, private_key) .await .map_err(to_custom_error)?; diff --git a/api/src/test.rs b/api/src/test.rs index 98d2ad9..7f38af9 100644 --- a/api/src/test.rs +++ b/api/src/test.rs @@ -43,6 +43,7 @@ pub mod tests { .unwrap(); let application = ApplicationService::create( + &"".to_string(), db, APPLICATION_ID, &CANDIDATE_PASSWORD.to_string(), diff --git a/core/src/database/mutation/application.rs b/core/src/database/mutation/application.rs index e39fcbb..1589c07 100644 --- a/core/src/database/mutation/application.rs +++ b/core/src/database/mutation/application.rs @@ -1,6 +1,6 @@ use ::entity::application; use log::{info, warn}; -use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, EntityTrait, IntoActiveModel}; +use sea_orm::{DbConn, DbErr, Set, ActiveModelTrait, EntityTrait, IntoActiveModel, QueryFilter, ColumnTrait}; use crate::Mutation; diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 9f1be29..2f964a8 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -72,6 +72,20 @@ impl Mutation { Ok(update) } + + pub async fn update_personal_id( + db: &DbConn, + candidate: candidate::Model, + personal_id: &str, + ) -> Result { + let mut candidate = candidate.into_active_model(); + candidate.personal_identification_number = Set(personal_id.to_string()); + + candidate + .update(db) + .await + + } } #[cfg(test)] diff --git a/core/src/database/query/application.rs b/core/src/database/query/application.rs index fa37d62..54974f7 100644 --- a/core/src/database/query/application.rs +++ b/core/src/database/query/application.rs @@ -1,5 +1,5 @@ use entity::{application, candidate}; -use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait}; +use sea_orm::{EntityTrait, DbErr, DbConn, ModelTrait, FromQueryResult, QuerySelect, JoinType, RelationTrait, QueryFilter, ColumnTrait}; #[derive(FromQueryResult, Clone)] pub struct ApplicationCandidateJoin { @@ -52,4 +52,16 @@ impl Query { .all(db) .await } + + pub async fn find_applications_by_candidate_id( + db: &DbConn, + candidate_id: i32, + ) -> Result, DbErr> { + let applications = application::Entity::find() + .filter(application::Column::CandidateId.eq(candidate_id)) + .all(db) + .await?; + + Ok(applications) + } } \ No newline at end of file diff --git a/core/src/database/query/candidate.rs b/core/src/database/query/candidate.rs index c2ebc40..6f32d21 100644 --- a/core/src/database/query/candidate.rs +++ b/core/src/database/query/candidate.rs @@ -6,6 +6,12 @@ use crate::Query; pub const PAGE_SIZE: u64 = 20; +#[derive(FromQueryResult)] +pub struct IdPersonalIdNumberJoin { + pub id: i32, + pub personal_id_number: String, +} + #[derive(FromQueryResult)] pub struct ApplicationId { application: i32, @@ -81,7 +87,16 @@ impl Query { .all(db) .await } - + + pub async fn find_candidate_by_personal_id( + db: &DbConn, + personal_id: &str, + ) -> Result, DbErr> { + Candidate::find() + .filter(candidate::Column::PersonalIdentificationNumber.eq(personal_id)) + .one(db) + .await + } } #[cfg(test)] diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 42a1ad6..969c104 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -18,6 +18,7 @@ impl ApplicationService { /// Encrypted private key /// Public key pub async fn create( + admin_private_key: &String, db: &DbConn, application_id: i32, plain_text_password: &String, @@ -35,7 +36,6 @@ impl ApplicationService { { return Err(ServiceError::UserAlreadyExists); } - let hashed_password = hash_password(plain_text_password.to_string()).await?; let (pubkey, priv_key_plain_text) = crypto::create_identity(); @@ -43,20 +43,20 @@ impl ApplicationService { priv_key_plain_text, plain_text_password.to_string() ).await?; - + let recipients = get_recipients(db, &pubkey).await?; let enc_personal_id_number = EncryptedString::new( &personal_id_number, &recipients, ).await?; - - let candidate = CandidateService::create( + + let candidate = Self::find_or_create_candidate_with_personal_id( + admin_private_key, db, - enc_personal_id_number.clone().to_string() + personal_id_number, + &enc_personal_id_number, ).await?; - println!("candidate: {:?}", candidate); - let application = Mutation::create_application( db, application_id, @@ -70,6 +70,41 @@ impl ApplicationService { Ok(application) } + async fn find_or_create_candidate_with_personal_id( + admin_private_key: &String, + db: &DbConn, + personal_id_number: String, + enc_personal_id_number: &EncryptedString, + ) -> Result { + let candidates = Query::list_candidates_full(db).await?; + let ids_decrypted = futures::future::join_all( + candidates.iter().map(|c| async {( + c.id, + EncryptedString::from(c.personal_identification_number.clone()) + .decrypt(admin_private_key) + .await + .unwrap_or_default(), + )} + )) + .await; + + let found_ids: Vec<&(i32, String)> = ids_decrypted + .iter() + .filter(|(_, id)| id == &personal_id_number) + .collect(); + // TODO: take the candidate id directly from the iterator + if found_ids.iter().any(|(_, personal_id)| personal_id == &personal_id_number) { + let candidate = Query::find_candidate_by_id(db, found_ids[0].0) + .await? + .ok_or(ServiceError::CandidateNotFound)?; + let candidate = Mutation::update_personal_id(db, candidate, &enc_personal_id_number.to_owned().to_string()).await?; + println!("Candidates linked!"); + Ok(candidate) + } else { + CandidateService::create(db, enc_personal_id_number.to_owned().to_string()).await + } + } + fn is_application_id_valid(application_id: i32) -> bool { let s = &application_id.to_string(); if s.len() <= 3 { @@ -368,7 +403,7 @@ mod tests { let secret_message = "trnka".to_string(); - let application = ApplicationService::create(&db, 103100, &plain_text_password, "".to_string()).await.unwrap(); + let application = ApplicationService::create(&"".to_string(), &db, 103100, &plain_text_password, "".to_string()).await.unwrap(); let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, &vec![&application.public_key]) diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 029591c..17baa49 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -141,6 +141,7 @@ pub mod tests { let plain_text_password = "test".to_string(); let application = ApplicationService::create( + &"".to_string(), db, APPLICATION_ID, &plain_text_password, diff --git a/core/src/services/session_service.rs b/core/src/services/session_service.rs index 2d69d48..30321d7 100644 --- a/core/src/services/session_service.rs +++ b/core/src/services/session_service.rs @@ -52,7 +52,7 @@ mod tests { let db = get_memory_sqlite_connection().await; - let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); + let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); assert_eq!(application.id.to_owned(), 103151); assert_ne!(application.password.to_owned(), SECRET.to_string()); @@ -66,7 +66,7 @@ mod tests { async fn test_candidate_session_correct_password() { let db = &get_memory_sqlite_connection().await; - let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); + let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); // correct password let session = ApplicationService::new_session( @@ -89,7 +89,7 @@ mod tests { async fn test_candidate_session_incorrect_password() { let db = &get_memory_sqlite_connection().await; - let application = ApplicationService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); + let application = ApplicationService::create(&"".to_string(), &db, 103151, &SECRET.to_string(), "".to_string()).await.unwrap(); // incorrect password assert!(ApplicationService::new_session(