feat!: lock candidate details for application entities. who can't decrypt it

This commit is contained in:
Sebastian Pravda 2023-01-14 17:33:40 +01:00
parent fc176348d0
commit 85c6a47232
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
9 changed files with 48 additions and 24 deletions

View file

@ -166,13 +166,12 @@ pub async fn get_candidate(
.await .await
.map_err(|e| to_custom_error(ServiceError::DbError(e)))? .map_err(|e| to_custom_error(ServiceError::DbError(e)))?
.ok_or(to_custom_error(ServiceError::CandidateNotFound))?; .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( let details = ApplicationService::decrypt_all_details(
private_key, private_key,
db, db,
candidate &application,
false,
) )
.await .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;
@ -194,7 +193,7 @@ pub async fn delete_candidate(
.await .await
.map_err(|e| to_custom_error(ServiceError::DbError(e)))? .map_err(|e| to_custom_error(ServiceError::DbError(e)))?
.ok_or(to_custom_error(ServiceError::CandidateNotFound))?; .ok_or(to_custom_error(ServiceError::CandidateNotFound))?;
let candidate = ApplicationService::find_related_candidate(db, application).await.map_err(to_custom_error)?; let candidate = ApplicationService::find_related_candidate(db, &application).await.map_err(to_custom_error)?;
// TODO // TODO

View file

@ -77,7 +77,7 @@ pub async fn whoami(conn: Connection<'_, Db>, session: ApplicationAuth) -> Resul
let private_key = session.get_private_key(); let private_key = session.get_private_key();
let application: entity::application::Model = session.into(); let application: entity::application::Model = session.into();
let candidate = ApplicationService::find_related_candidate(&db, application.clone()).await.map_err(to_custom_error)?; // TODO let candidate = ApplicationService::find_related_candidate(&db, &application).await.map_err(to_custom_error)?; // TODO
println!("candidate: {:?}", candidate); println!("candidate: {:?}", candidate);
let response = NewCandidateResponse::from_encrypted(&private_key, candidate).await let response = NewCandidateResponse::from_encrypted(&private_key, candidate).await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;
@ -95,9 +95,9 @@ pub async fn post_details(
let db = conn.into_inner(); let db = conn.into_inner();
let form = details.into_inner(); let form = details.into_inner();
let application: application::Model = session.into(); let application: application::Model = session.into();
let candidate = ApplicationService::find_related_candidate(&db, application.clone()).await.map_err(to_custom_error)?; // TODO let candidate = ApplicationService::find_related_candidate(&db, &application).await.map_err(to_custom_error)?; // TODO
let _candidate_parent = ApplicationService::add_all_details(db, &application.public_key, candidate, &form) let _candidate_parent = ApplicationService::add_all_details(db, &application, candidate, &form)
.await .await
.map_err(to_custom_error)?; .map_err(to_custom_error)?;
@ -112,9 +112,13 @@ pub async fn get_details(
let db = conn.into_inner(); let db = conn.into_inner();
let private_key = session.get_private_key(); let private_key = session.get_private_key();
let application: entity::application::Model = session.into(); let application: entity::application::Model = session.into();
let candidate = ApplicationService::find_related_candidate(&db, application.clone()).await.map_err(to_custom_error)?; // TODO
let details = ApplicationService::decrypt_all_details(private_key, db, candidate) let details = ApplicationService::decrypt_all_details(
private_key,
db,
&application,
true
)
.await .await
.map(|x| Json(x)) .map(|x| Json(x))
.map_err(to_custom_error); .map_err(to_custom_error);
@ -218,7 +222,7 @@ pub async fn submit_portfolio(
let db = conn.into_inner(); let db = conn.into_inner();
let application: entity::application::Model = session.into(); let application: entity::application::Model = session.into();
let candidate = ApplicationService::find_related_candidate(&db, application.clone()).await.map_err(to_custom_error)?; // TODO let candidate = ApplicationService::find_related_candidate(&db, &application).await.map_err(to_custom_error)?; // TODO
let submit = PortfolioService::submit(&application.public_key, &candidate, &db).await; let submit = PortfolioService::submit(&application.public_key, &candidate, &db).await;

View file

@ -44,11 +44,12 @@ impl Mutation {
pub async fn update_candidate_details( pub async fn update_candidate_details(
db: &DbConn, db: &DbConn,
user: candidate::Model, candidate: candidate::Model,
enc_candidate: EncryptedCandidateDetails, enc_candidate: EncryptedCandidateDetails,
encrypted_by_id: i32,
) -> Result<candidate::Model, sea_orm::DbErr> { ) -> Result<candidate::Model, sea_orm::DbErr> {
let application = user.id; let application = candidate.id;
let mut candidate: candidate::ActiveModel = user.into(); let mut candidate: candidate::ActiveModel = candidate.into();
candidate.name = Set(enc_candidate.name.map(|e| e.into())); candidate.name = Set(enc_candidate.name.map(|e| e.into()));
candidate.surname = Set(enc_candidate.surname.map(|e| e.into())); candidate.surname = Set(enc_candidate.surname.map(|e| e.into()));
@ -63,6 +64,7 @@ impl Mutation {
candidate.school_name = Set(enc_candidate.school_name.map(|e| e.into())); candidate.school_name = Set(enc_candidate.school_name.map(|e| e.into()));
candidate.health_insurance = Set(enc_candidate.health_insurance.map(|e| e.into())); candidate.health_insurance = Set(enc_candidate.health_insurance.map(|e| e.into()));
candidate.study = Set(enc_candidate.study.map(|e| e.into())); candidate.study = Set(enc_candidate.study.map(|e| e.into()));
candidate.encrypted_by_id = Set(Some(encrypted_by_id));
candidate.updated_at = Set(chrono::offset::Local::now().naive_local()); candidate.updated_at = Set(chrono::offset::Local::now().naive_local());
@ -128,7 +130,7 @@ mod tests {
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()], vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
).await.unwrap(); ).await.unwrap();
let candidate = Mutation::update_candidate_details(&db, candidate, encrypted_details.candidate).await.unwrap(); let candidate = Mutation::update_candidate_details(&db, candidate, encrypted_details.candidate, 1).await.unwrap();
let candidate = Query::find_candidate_by_id(&db, candidate.id) let candidate = Query::find_candidate_by_id(&db, candidate.id)
.await .await

View file

@ -27,7 +27,7 @@ impl Query {
pub async fn find_related_candidate( pub async fn find_related_candidate(
db: &DbConn, db: &DbConn,
application: application::Model, application: &application::Model,
) -> Result<Option<candidate::Model>, DbErr> { ) -> Result<Option<candidate::Model>, DbErr> {
application application
.find_related(candidate::Entity) .find_related(candidate::Entity)

View file

@ -18,6 +18,8 @@ pub enum ServiceError {
UserAlreadyExists, UserAlreadyExists,
#[error("Candidate not found")] #[error("Candidate not found")]
CandidateNotFound, CandidateNotFound,
#[error("Resource is locked")]
Locked,
#[error("Parrent not found")] #[error("Parrent not found")]
ParentNotFound, ParentNotFound,
#[error("Database error")] #[error("Database error")]
@ -69,7 +71,7 @@ pub enum ServiceError {
impl ServiceError { impl ServiceError {
pub fn code(&self) -> u16 { pub fn code(&self) -> u16 {
match self { match self {
// 40X // 4XX
ServiceError::InvalidApplicationId => 400, ServiceError::InvalidApplicationId => 400,
ServiceError::ParentOverflow => 400, ServiceError::ParentOverflow => 400,
ServiceError::Unauthorized => 401, ServiceError::Unauthorized => 401,
@ -79,6 +81,7 @@ impl ServiceError {
ServiceError::CandidateNotFound => 404, ServiceError::CandidateNotFound => 404,
ServiceError::IncompletePortfolio => 406, ServiceError::IncompletePortfolio => 406,
ServiceError::UserAlreadyExists => 409, ServiceError::UserAlreadyExists => 409,
ServiceError::Locked => 423,
// 500 // 500
ServiceError::ParentNotFound => 500, ServiceError::ParentNotFound => 500,
ServiceError::DbError(_) => 500, ServiceError::DbError(_) => 500,

View file

@ -160,7 +160,7 @@ impl ApplicationService {
pub async fn find_related_candidate( pub async fn find_related_candidate(
db: &DbConn, db: &DbConn,
application: application::Model, application: &application::Model,
) -> Result<candidate::Model, ServiceError> { ) -> Result<candidate::Model, ServiceError> {
let candidate = Query::find_related_candidate(db, application).await?; let candidate = Query::find_related_candidate(db, application).await?;
if let Some(candidate) = candidate { if let Some(candidate) = candidate {
@ -172,13 +172,13 @@ impl ApplicationService {
pub async fn add_all_details( pub async fn add_all_details(
db: &DbConn, db: &DbConn,
public_key: &String, application: &application::Model,
candidate: candidate::Model, candidate: candidate::Model,
form: &ApplicationDetails, form: &ApplicationDetails,
) -> Result<(candidate::Model, Vec<parent::Model>), ServiceError> { ) -> Result<(candidate::Model, Vec<parent::Model>), ServiceError> {
let recipients = get_recipients(db, public_key).await?; let recipients = get_recipients(db, &application.public_key).await?;
let candidate = CandidateService::add_candidate_details(db, candidate, &form.candidate, &recipients).await?; let candidate = CandidateService::add_candidate_details(db, candidate, &form.candidate, &recipients, application.id).await?;
let parents = ParentService::add_parents_details(db, &candidate, &form.parents, &recipients).await?; let parents = ParentService::add_parents_details(db, &candidate, &form.parents, &recipients).await?;
Ok( Ok(
( (
@ -191,8 +191,15 @@ impl ApplicationService {
pub async fn decrypt_all_details( pub async fn decrypt_all_details(
private_key: String, private_key: String,
db: &DbConn, db: &DbConn,
candidate: candidate::Model, application: &application::Model,
restrict_access: bool,
) -> Result<ApplicationDetails, ServiceError> { ) -> Result<ApplicationDetails, ServiceError> {
let candidate = ApplicationService::find_related_candidate(db, application).await?;
if restrict_access && candidate.encrypted_by_id.is_some() && candidate.encrypted_by_id != Some(application.id) {
return Err(ServiceError::Locked)
}
let parents = Query::find_candidate_parents(db, &candidate).await?; let parents = Query::find_candidate_parents(db, &candidate).await?;
let enc_details = EncryptedApplicationDetails::from((&candidate, parents)); let enc_details = EncryptedApplicationDetails::from((&candidate, parents));

View file

@ -48,9 +48,15 @@ impl CandidateService {
candidate: candidate::Model, candidate: candidate::Model,
details: &CandidateDetails, details: &CandidateDetails,
recipients: &Vec<String>, recipients: &Vec<String>,
encrypted_by: i32,
) -> Result<entity::candidate::Model, ServiceError> { ) -> Result<entity::candidate::Model, ServiceError> {
let enc_details = EncryptedCandidateDetails::new(&details, recipients).await?; let enc_details = EncryptedCandidateDetails::new(&details, recipients).await?;
let model = Mutation::update_candidate_details(db, candidate, enc_details).await?; let model = Mutation::update_candidate_details(
db,
candidate,
enc_details,
encrypted_by
).await?;
Ok(model) Ok(model)
} }
@ -148,11 +154,11 @@ pub mod tests {
"0000001111".to_string() "0000001111".to_string()
).await.unwrap(); ).await.unwrap();
let candidate= ApplicationService::find_related_candidate(db, application.to_owned()).await.unwrap(); let candidate= ApplicationService::find_related_candidate(db, &application).await.unwrap();
let form = APPLICATION_DETAILS.lock().unwrap().clone(); let form = APPLICATION_DETAILS.lock().unwrap().clone();
let (candidate, parents) = ApplicationService::add_all_details(&db, &application.public_key, candidate, &form) let (candidate, parents) = ApplicationService::add_all_details(&db, &application, candidate, &form)
.await .await
.unwrap(); .unwrap();

View file

@ -21,6 +21,7 @@ pub struct Model {
pub personal_identification_number: String, pub personal_identification_number: String,
pub school_name: Option<String>, pub school_name: Option<String>,
pub health_insurance: Option<String>, pub health_insurance: Option<String>,
pub encrypted_by_id: Option<i32>,
pub created_at: DateTime, pub created_at: DateTime,
pub updated_at: DateTime, pub updated_at: DateTime,
} }

View file

@ -32,6 +32,7 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string().not_null()) .col(ColumnDef::new(Candidate::PersonalIdentificationNumber).string().not_null())
.col(ColumnDef::new(Candidate::SchoolName).string()) .col(ColumnDef::new(Candidate::SchoolName).string())
.col(ColumnDef::new(Candidate::HealthInsurance).string()) .col(ColumnDef::new(Candidate::HealthInsurance).string())
.col(ColumnDef::new(Candidate::EncryptedById).integer())
.col(ColumnDef::new(Candidate::CreatedAt).date_time().not_null()) .col(ColumnDef::new(Candidate::CreatedAt).date_time().not_null())
.col(ColumnDef::new(Candidate::UpdatedAt).date_time().not_null()) .col(ColumnDef::new(Candidate::UpdatedAt).date_time().not_null())
.to_owned(), .to_owned(),
@ -64,6 +65,7 @@ pub enum Candidate {
PersonalIdentificationNumber, PersonalIdentificationNumber,
SchoolName, SchoolName,
HealthInsurance, HealthInsurance,
EncryptedById,
CreatedAt, CreatedAt,
UpdatedAt, UpdatedAt,
} }