From d32997e9927d1b8182213b6c96b6a7fe59513a04 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Mon, 25 Dec 2023 17:02:33 +0100 Subject: [PATCH 1/2] add admin user remove --- agdb_server/openapi/schema.json | 45 +++++++ agdb_server/src/api.rs | 1 + agdb_server/src/app.rs | 4 + agdb_server/src/db_pool.rs | 39 ++++++ agdb_server/src/routes/admin/user.rs | 24 ++++ .../tests/routes/admin_user_remove_test.rs | 123 ++++++++++++++++++ agdb_server/tests/routes/mod.rs | 1 + .../src/composables/graph/class/edge.ts | 16 +++ 8 files changed, 253 insertions(+) create mode 100644 agdb_server/tests/routes/admin_user_remove_test.rs diff --git a/agdb_server/openapi/schema.json b/agdb_server/openapi/schema.json index 0623d14c9..8f642d208 100644 --- a/agdb_server/openapi/schema.json +++ b/agdb_server/openapi/schema.json @@ -773,6 +773,51 @@ ] } }, + "/api/v1/admin/user/{username}/remove": { + "post": { + "tags": [ + "crate::routes::admin::user" + ], + "operationId": "remove", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "user name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "user removed", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserStatus" + } + } + } + } + }, + "401": { + "description": "unauthorized" + }, + "404": { + "description": "user not found" + } + }, + "security": [ + { + "Token": [] + } + ] + } + }, "/api/v1/db/admin/{owner}/{db}/restore": { "post": { "tags": [ diff --git a/agdb_server/src/api.rs b/agdb_server/src/api.rs index 4ed2cb4e7..08f089405 100644 --- a/agdb_server/src/api.rs +++ b/agdb_server/src/api.rs @@ -24,6 +24,7 @@ use utoipa::OpenApi; crate::routes::admin::user::change_password, crate::routes::admin::user::add, crate::routes::admin::user::list, + crate::routes::admin::user::remove, crate::routes::admin::shutdown, crate::routes::user::login, crate::routes::user::change_password, diff --git a/agdb_server/src/app.rs b/agdb_server/src/app.rs index 52876a642..99c892c7f 100644 --- a/agdb_server/src/app.rs +++ b/agdb_server/src/app.rs @@ -31,6 +31,10 @@ pub(crate) fn app(config: Config, shutdown_sender: Sender<()>, db_pool: DbPool) "/admin/user/:user/change_password", routing::put(routes::admin::user::change_password), ) + .route( + "/admin/user/:user/remove", + routing::post(routes::admin::user::remove), + ) .route("/admin/db/list", routing::get(routes::admin::db::list)) .route( "/admin/db/:user/:db/add", diff --git a/agdb_server/src/db_pool.rs b/agdb_server/src/db_pool.rs index 155afcc8c..17555188d 100644 --- a/agdb_server/src/db_pool.rs +++ b/agdb_server/src/db_pool.rs @@ -716,6 +716,27 @@ impl DbPool { Ok(self.get_pool_mut()?.remove(&db_name).unwrap()) } + pub(crate) fn remove_user(&self, username: &str, config: &Config) -> ServerResult { + let user_id = self.find_user_id(username)?; + let dbs = self.find_user_databases(user_id)?; + for db in dbs.iter() { + self.get_pool_mut()?.remove(&db.name); + } + let mut ids = dbs + .into_iter() + .map(|db| db.db_id.unwrap()) + .collect::>(); + ids.push(user_id); + self.db_mut()? + .exec_mut(&QueryBuilder::remove().ids(ids).query())?; + let user_dir = Path::new(&config.data_dir).join(username); + if user_dir.exists() { + std::fs::remove_dir_all(user_dir)?; + } + + Ok(()) + } + pub(crate) fn rename_db( &self, owner: &str, @@ -907,6 +928,24 @@ impl DbPool { .try_into()?) } + fn find_user_databases(&self, user: DbId) -> ServerResult> { + Ok(self + .db()? + .exec( + &QueryBuilder::select() + .ids( + QueryBuilder::search() + .depth_first() + .from(user) + .where_() + .distance(CountComparison::Equal(2)) + .query(), + ) + .query(), + )? + .try_into()?) + } + fn find_user_db_id(&self, user: DbId, db: &str) -> ServerResult { let db_id_query = self.find_user_db_id_query(user, db); Ok(self diff --git a/agdb_server/src/routes/admin/user.rs b/agdb_server/src/routes/admin/user.rs index 8e894fca3..3a924f69b 100644 --- a/agdb_server/src/routes/admin/user.rs +++ b/agdb_server/src/routes/admin/user.rs @@ -1,3 +1,4 @@ +use crate::config::Config; use crate::db_pool::DbPool; use crate::db_pool::ServerUser; use crate::error_code::ErrorCode; @@ -104,3 +105,26 @@ pub(crate) async fn list( .collect(); Ok((StatusCode::OK, Json(users))) } + +#[utoipa::path(post, + path = "/api/v1/admin/user/{username}/remove", + security(("Token" = [])), + params( + ("username" = String, Path, description = "user name"), + ), + responses( + (status = 204, description = "user removed", body = Vec), + (status = 401, description = "unauthorized"), + (status = 404, description = "user not found"), + ) +)] +pub(crate) async fn remove( + _admin: AdminId, + State(db_pool): State, + State(config): State, + Path(username): Path, +) -> ServerResponse { + db_pool.remove_user(&username, &config)?; + + Ok(StatusCode::NO_CONTENT) +} diff --git a/agdb_server/tests/routes/admin_user_remove_test.rs b/agdb_server/tests/routes/admin_user_remove_test.rs new file mode 100644 index 000000000..91c8bac51 --- /dev/null +++ b/agdb_server/tests/routes/admin_user_remove_test.rs @@ -0,0 +1,123 @@ +use crate::Db; +use crate::TestServer; +use crate::DB_LIST_URI; +use crate::NO_TOKEN; +use std::path::Path; + +#[tokio::test] +async fn remove() -> anyhow::Result<()> { + let server = TestServer::new().await?; + let user = server.init_user().await?; + + assert_eq!( + server + .post::<()>( + &format!("/admin/user/{}/remove", user.name), + &None, + &server.admin_token + ) + .await? + .0, + 204 + ); + + Ok(()) +} + +#[tokio::test] +async fn remove_with_other() -> anyhow::Result<()> { + let server = TestServer::new().await?; + let user = server.init_user().await?; + let other = server.init_user().await?; + let db = server.init_db("mapped", &user).await?; + + assert_eq!( + server + .put::<()>( + &format!("/admin/db/{db}/user/{}/add?db_role=write", other.name), + &None, + &server.admin_token + ) + .await?, + 201 + ); + + let list = server.get::>(DB_LIST_URI, &other.token).await?.1; + assert_eq!( + list?, + vec![Db { + name: db, + db_type: "mapped".to_string(), + role: "write".to_string(), + size: 2600, + backup: 0, + }] + ); + + assert!(Path::new(&server.data_dir).join(&user.name).exists()); + + assert_eq!( + server + .post::<()>( + &format!("/admin/user/{}/remove", user.name), + &None, + &server.admin_token + ) + .await? + .0, + 204 + ); + + assert!(!Path::new(&server.data_dir).join(user.name).exists()); + + let list = server.get::>(DB_LIST_URI, &other.token).await?.1; + assert_eq!(list?, vec![]); + + Ok(()) +} + +#[tokio::test] +async fn user_not_found() -> anyhow::Result<()> { + let server = TestServer::new().await?; + + assert_eq!( + server + .post::<()>("/admin/user/not_found/remove", &None, &server.admin_token) + .await? + .0, + 404 + ); + + Ok(()) +} + +#[tokio::test] +async fn non_admin() -> anyhow::Result<()> { + let server = TestServer::new().await?; + let user = server.init_user().await?; + + assert_eq!( + server + .post::<()>("/admin/user/not_found/remove", &None, &user.token) + .await? + .0, + 401 + ); + + Ok(()) +} + +#[tokio::test] +async fn no_token() -> anyhow::Result<()> { + let server = TestServer::new().await?; + + assert_eq!( + server + .post::<()>("/admin/user/not_found/remove", &None, NO_TOKEN) + .await? + .0, + 401 + ); + + Ok(()) +} diff --git a/agdb_server/tests/routes/mod.rs b/agdb_server/tests/routes/mod.rs index b90f80f67..a003281ec 100644 --- a/agdb_server/tests/routes/mod.rs +++ b/agdb_server/tests/routes/mod.rs @@ -13,6 +13,7 @@ mod admin_db_user_remove_test; mod admin_user_add_test; mod admin_user_change_password_test; mod admin_user_list_test; +mod admin_user_remove_test; mod db_add_test; mod db_backup_restore_test; mod db_copy_test; diff --git a/agdb_studio/src/composables/graph/class/edge.ts b/agdb_studio/src/composables/graph/class/edge.ts index ad394d56d..18c517265 100644 --- a/agdb_studio/src/composables/graph/class/edge.ts +++ b/agdb_studio/src/composables/graph/class/edge.ts @@ -7,6 +7,22 @@ export type EdgeOptions = { values: { [key: string]: string }; }; +class Property { + +} + +class InsertNodes { + public count: number; + public values: [DataType, DataType] | Property[][]; + public aliases: string[]; + + constructor(count: number, values: [DataType, DataType] | Property[][], aliases: string[]) { + this.count = count; + this.values = values; + this.aliases = aliases; + } +} + export default class Edge { private id: number; private from: Node | undefined; From 0bcbfae719f5c9673ec3c8ab9f837da53e0147e0 Mon Sep 17 00:00:00 2001 From: Michael Vlach Date: Mon, 25 Dec 2023 17:04:53 +0100 Subject: [PATCH 2/2] remove temp code --- agdb_studio/src/composables/graph/class/edge.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/agdb_studio/src/composables/graph/class/edge.ts b/agdb_studio/src/composables/graph/class/edge.ts index 18c517265..ad394d56d 100644 --- a/agdb_studio/src/composables/graph/class/edge.ts +++ b/agdb_studio/src/composables/graph/class/edge.ts @@ -7,22 +7,6 @@ export type EdgeOptions = { values: { [key: string]: string }; }; -class Property { - -} - -class InsertNodes { - public count: number; - public values: [DataType, DataType] | Property[][]; - public aliases: string[]; - - constructor(count: number, values: [DataType, DataType] | Property[][], aliases: string[]) { - this.count = count; - this.values = values; - this.aliases = aliases; - } -} - export default class Edge { private id: number; private from: Node | undefined;