From 76f222e1c4afe0b1c4666393473031a6b59b0a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20Berthaud-M=C3=BCller?= Date: Sun, 2 May 2021 15:56:42 +0200 Subject: [PATCH] change zone member management --- src/main.rs | 3 +- src/models/users.rs | 91 ++++++++++++++++++++++++++++----------------- src/routes/users.rs | 21 ----------- src/routes/zones.rs | 55 +++++++++++++++++++++++++-- 4 files changed, 111 insertions(+), 59 deletions(-) diff --git a/src/main.rs b/src/main.rs index c9cc829..7dff5a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ async fn rocket() -> rocket::Rocket { .attach(DbConn::fairing()) .mount("/api/v1", routes![ get_zone_records, - create_user_zone, + get_zones, + add_member_to_zone, create_auth_token, create_user ]) diff --git a/src/models/users.rs b/src/models/users.rs index 71def04..2c9d334 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -21,7 +21,6 @@ use crate::schema::*; use crate::DbConn; use crate::config::Config; use crate::models::errors::{ErrorResponse, make_500}; -use crate::models::dns::AbsoluteName; const BEARER: &str = "Bearer "; @@ -38,7 +37,6 @@ pub enum Role { } // TODO: Store Uuid instead of string?? -// TODO: Store role as Role and not String. #[derive(Debug, Queryable, Identifiable, Insertable)] #[table_name = "user"] pub struct User { @@ -63,9 +61,10 @@ pub struct UserZone { pub zone_id: String, } -#[derive(Debug, Queryable, Identifiable, Insertable)] +#[derive(Debug, Serialize, Queryable, Identifiable, Insertable)] #[table_name = "zone"] pub struct Zone { + #[serde(skip)] pub id: String, pub name: String, } @@ -79,8 +78,8 @@ pub struct CreateUserRequest { } #[derive(Debug, Deserialize)] -pub struct CreateUserZoneRequest { - pub zone: AbsoluteName, +pub struct AddZoneMemberRequest { + pub id: String, } // pub struct LdapUserAssociation { @@ -145,36 +144,16 @@ impl UserInfo { Ok(res_zone) } - pub fn add_zone(&self, conn: &diesel::SqliteConnection, request: CreateUserZoneRequest) -> Result { + pub fn get_zones(&self, conn: &diesel::SqliteConnection) -> Result, UserError> { use crate::schema::user_zone::dsl::*; use crate::schema::zone::dsl::*; - let zone_name = request.zone.to_utf8(); - let current_zone: Zone = zone.filter(name.eq(zone_name)) - .get_result(conn) - .map_err(|e| match e { - DieselError::NotFound => UserError::ZoneNotFound, - other => UserError::DbError(other) - })?; + let res: Vec<(Zone, UserZone)> = zone.inner_join(user_zone) + .filter(user_id.eq(&self.id)) + .get_results(conn) + .map_err(UserError::DbError)?; - let new_user_zone = UserZone { - zone_id: current_zone.id.clone(), - user_id: self.id.clone() - }; - - let res = diesel::insert_into(user_zone) - .values(new_user_zone) - .execute(conn); - - match res { - // If user has already access to the zone, safely ignore the conflit - // TODO: use 'on conflict do nothing' in postgres when we get there - Err(DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => (), - Err(e) => return Err(e.into()), - Ok(_) => () - }; - - Ok(current_zone) + Ok(res.into_iter().map(|(z, _)| z).collect()) } } @@ -211,8 +190,8 @@ impl<'r> FromRequest<'r> for UserInfo { let user_id = token_data.sub; - conn.run(|c| { - match LocalUser::get_user_by_uuid(c, user_id) { + conn.run(move |c| { + match LocalUser::get_user_by_uuid(c, &user_id) { Err(UserError::NotFound) => ErrorResponse::from(UserError::NotFound).into(), Err(e) => ErrorResponse::from(e).into(), Ok(d) => Outcome::Success(d), @@ -318,7 +297,7 @@ impl LocalUser { }) } - pub fn get_user_by_uuid(conn: &diesel::SqliteConnection, request_user_id: String) -> Result { + pub fn get_user_by_uuid(conn: &diesel::SqliteConnection, request_user_id: &str) -> Result { use crate::schema::localuser::dsl::*; use crate::schema::user::dsl::*; @@ -365,3 +344,47 @@ impl AuthClaims { encode(&Header::default(), &self, &EncodingKey::from_secret(secret.as_ref())) } } + +// NOTE: Should probably not be implemented here +// also, "UserError" seems like a misleading name +impl Zone { + pub fn get_all(conn: &diesel::SqliteConnection) -> Result, UserError> { + use crate::schema::zone::dsl::*; + + zone.get_results(conn) + .map_err(UserError::DbError) + } + + pub fn get_by_name(conn: &diesel::SqliteConnection, zone_name: &str) -> Result { + use crate::schema::zone::dsl::*; + + zone.filter(name.eq(zone_name)) + .get_result(conn) + .map_err(|e| match e { + DieselError::NotFound => UserError::ZoneNotFound, + other => UserError::DbError(other) + }) + } + + pub fn add_member(&self, conn: &diesel::SqliteConnection, new_member: &UserInfo) -> Result<(), UserError> { + use crate::schema::user_zone::dsl::*; + + let new_user_zone = UserZone { + zone_id: self.id.clone(), + user_id: new_member.id.clone() + }; + + let res = diesel::insert_into(user_zone) + .values(new_user_zone) + .execute(conn); + + match res { + // If user has already access to the zone, safely ignore the conflit + // TODO: use 'on conflict do nothing' in postgres when we get there + Err(DieselError::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => (), + Err(e) => return Err(e.into()), + Ok(_) => () + }; + Ok(()) + } +} diff --git a/src/routes/users.rs b/src/routes/users.rs index 9626531..e6967b3 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -6,10 +6,8 @@ use crate::config::Config; use crate::DbConn; use crate::models::errors::{ErrorResponse, make_500}; use crate::models::users::{ - UserInfo, LocalUser, CreateUserRequest, - CreateUserZoneRequest, AuthClaims, AuthTokenRequest, AuthTokenResponse @@ -45,22 +43,3 @@ pub async fn create_user<'r>(conn: DbConn, user_request: Json .status(Status::Created) .ok() } - -#[post("/users//zones", data = "")] -pub async fn create_user_zone<'r>( - conn: DbConn, - user_info: Result, - user_id: String, - user_zone_request: Json -) -> Result, ErrorResponse>{ - user_info?.check_admin()?; - - conn.run(move |c| { - let user_info = LocalUser::get_user_by_uuid(&c, user_id)?; - user_info.add_zone(&c, user_zone_request.into_inner()) - }).await?; - - Response::build() - .status(Status::Created) - .ok() -} diff --git a/src/routes/zones.rs b/src/routes/zones.rs index c872d72..c893b5d 100644 --- a/src/routes/zones.rs +++ b/src/routes/zones.rs @@ -1,3 +1,4 @@ +use rocket::Response; use rocket::http::Status; use rocket_contrib::json::Json; @@ -8,7 +9,7 @@ use trust_dns_client::rr::{DNSClass, RecordType}; use crate::{DbConn, models::dns}; use crate::models::errors::{ErrorResponse, make_500}; -use crate::models::users::UserInfo; +use crate::models::users::{LocalUser, UserInfo, Zone, AddZoneMemberRequest}; #[get("/zones//records")] @@ -24,7 +25,7 @@ pub async fn get_zone_records( if !user_info.is_admin() { let zone_name = zone.clone().to_string(); conn.run(move |c| { - dbg!(user_info.get_zone(c, &zone_name)) + user_info.get_zone(c, &zone_name) }).await?; } @@ -33,10 +34,12 @@ pub async fn get_zone_records( query.await.map_err(make_500)? }; + // TODO: Better error handling (ex. not authorized should be 500) if response.response_code() != ResponseCode::NoError { + println!("Querrying of zone {} failed with code {}", *zone, response.response_code()); return ErrorResponse::new( Status::NotFound, - format!("zone {} could not be found", *zone) + format!("Zone {} could not be found", *zone) ).err() } @@ -51,3 +54,49 @@ pub async fn get_zone_records( Ok(Json(records)) } + +// TODO: the post version of that +#[get("/zones")] +pub async fn get_zones( + conn: DbConn, + user_info: Result, +) -> Result>, ErrorResponse> { + let user_info = user_info?; + + let zones = conn.run(move |c| { + if user_info.is_admin() { + Zone::get_all(c) + } else { + user_info.get_zones(c) + } + }).await?; + + Ok(Json(zones)) +} + + +#[post("/zones//members", data = "")] +pub async fn add_member_to_zone<'r>( + conn: DbConn, + zone: dns::AbsoluteName, + user_info: Result, + zone_member_request: Json +) -> Result, ErrorResponse> { + let user_info = user_info?; + let zone_name = zone.to_utf8(); + + conn.run(move |c| { + let zone = if user_info.is_admin() { + Zone::get_by_name(c, &zone_name) + } else { + user_info.get_zone(c, &zone_name) + }?; + + let new_member = LocalUser::get_user_by_uuid(c, &zone_member_request.id)?; + zone.add_member(&c, &new_member) + }).await?; + + Response::build() + .status(Status::Created) // TODO: change this? + .ok() +}