Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[server] Add admin db user remove #857 #875

Merged
merged 2 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions agdb_server/openapi/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,46 @@
]
}
},
"/api/v1/admin/db/user/remove": {
"post": {
"tags": [
"crate::routes::admin::db::user"
],
"operationId": "remove",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RemoveDbUser"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "user removed"
},
"401": {
"description": "unauthorized"
},
"403": {
"description": "cannot remove last admin user"
},
"464": {
"description": "user not found"
},
"466": {
"description": "db not found"
}
},
"security": [
{
"Token": []
}
]
}
},
"/api/v1/admin/shutdown": {
"get": {
"tags": [
Expand Down
1 change: 1 addition & 0 deletions agdb_server/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use utoipa::OpenApi;
crate::routes::admin::db::remove,
crate::routes::admin::db::user::add,
crate::routes::admin::db::user::list,
crate::routes::admin::db::user::remove,
crate::routes::admin::user::change_password,
crate::routes::admin::user::create,
crate::routes::admin::user::list,
Expand Down
3 changes: 2 additions & 1 deletion agdb_server/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ pub(crate) fn app(config: Config, shutdown_sender: Sender<()>, db_pool: DbPool)
};

let admin_db_user_router_v1 = Router::new()
.route("/add", routing::post(routes::admin::db::user::add))
.route("/list", routing::get(routes::admin::db::user::list))
.route("/add", routing::post(routes::admin::db::user::add));
.route("/remove", routing::post(routes::admin::db::user::remove));

let admin_db_router_v1 = Router::new()
.route("/list", routing::get(routes::admin::db::list))
Expand Down
26 changes: 26 additions & 0 deletions agdb_server/src/db_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl DbPool {
&QueryBuilder::select()
.ids(
QueryBuilder::search()
.depth_first()
.from("dbs")
.limit(1)
.where_()
Expand All @@ -243,6 +244,7 @@ impl DbPool {
.db()?
.exec(
&QueryBuilder::search()
.depth_first()
.from("dbs")
.limit(1)
.where_()
Expand Down Expand Up @@ -324,6 +326,7 @@ impl DbPool {
&QueryBuilder::select()
.ids(
QueryBuilder::search()
.depth_first()
.from(user)
.limit(1)
.where_()
Expand Down Expand Up @@ -351,6 +354,7 @@ impl DbPool {
&QueryBuilder::select()
.ids(
QueryBuilder::search()
.depth_first()
.from("users")
.limit(1)
.where_()
Expand All @@ -376,6 +380,7 @@ impl DbPool {
.db()?
.exec(
&QueryBuilder::search()
.depth_first()
.from("users")
.limit(1)
.where_()
Expand All @@ -399,6 +404,7 @@ impl DbPool {
.db()?
.exec(
&QueryBuilder::search()
.depth_first()
.from("users")
.limit(1)
.where_()
Expand Down Expand Up @@ -434,6 +440,26 @@ impl DbPool {
.ids())
}

pub(crate) fn db_user_id(&self, db: DbId, name: &str) -> ServerResult<DbId> {
Ok(self
.db()?
.exec(
&QueryBuilder::search()
.depth_first()
.to(db)
.where_()
.distance(CountComparison::Equal(2))
.and()
.key("name")
.value(Comparison::Equal(name.into()))
.query(),
)?
.elements
.get(0)
.ok_or(ErrorCode::UserNotFound)?
.id)
}

pub(crate) fn db_users(&self, db: DbId) -> ServerResult<Vec<(String, String)>> {
let mut users = vec![];

Expand Down
31 changes: 31 additions & 0 deletions agdb_server/src/routes/admin/db/user.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::db_pool::DbPool;
use crate::routes::db::user::DbUser;
use crate::routes::db::user::RemoveDbUser;
use crate::routes::db::ServerDatabaseName;
use crate::server_error::ServerResponse;
use crate::user_id::AdminId;
Expand Down Expand Up @@ -62,3 +63,33 @@ pub(crate) async fn list(

Ok((StatusCode::OK, Json(users)))
}

#[utoipa::path(post,
path = "/api/v1/admin/db/user/remove",
request_body = RemoveDbUser,
security(("Token" = [])),
responses(
(status = 204, description = "user removed"),
(status = 401, description = "unauthorized"),
(status = 403, description = "cannot remove last admin user"),
(status = 464, description = "user not found"),
(status = 466, description = "db not found"),
)
)]
pub(crate) async fn remove(
_admin: AdminId,
State(db_pool): State<DbPool>,
Json(request): Json<RemoveDbUser>,
) -> ServerResponse {
let db = db_pool.find_db_id(&request.database)?;
let db_user = db_pool.db_user_id(db, &request.user)?;
let admins = db_pool.db_admins(db)?;

if admins == vec![db_user] {
return Ok(StatusCode::FORBIDDEN);
}

db_pool.remove_db_user(db, db_user)?;

Ok(StatusCode::NO_CONTENT)
}
4 changes: 1 addition & 3 deletions agdb_server/src/routes/db/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,9 @@ pub(crate) async fn remove(
Json(request): Json<RemoveDbUser>,
) -> ServerResponse {
let db = db_pool.find_db_id(&request.database)?;
let db_user = db_pool.find_user_id(&request.user)?;
let db_user = db_pool.db_user_id(db, &request.user)?;
let admins = db_pool.db_admins(db)?;

println!("{:?} == {:?}", admins, vec![db_user]);

if (!admins.contains(&user.0) && user.0 != db_user) || admins == vec![db_user] {
return Ok(StatusCode::FORBIDDEN);
}
Expand Down
1 change: 1 addition & 0 deletions agdb_server/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub const ADMIN_DB_LIST_URI: &str = "/admin/db/list";
pub const ADMIN_DB_REMOVE_URI: &str = "/admin/db/remove";
pub const ADMIN_DB_USER_ADD_URI: &str = "/admin/db/user/add";
pub const ADMIN_DB_USER_LIST_URI: &str = "/admin/db/user/list";
pub const ADMIN_DB_USER_REMOVE_URI: &str = "/admin/db/user/remove";
pub const ADMIN_USER_LIST_URI: &str = "/admin/user/list";
pub const ADMIN_CHANGE_PASSWORD_URI: &str = "/admin/user/change_password";
pub const DB_REMOVE_URI: &str = "/db/remove";
Expand Down
162 changes: 162 additions & 0 deletions agdb_server/tests/routes/admin_db_user_remove_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use crate::AddUser;
use crate::DbWithRole;
use crate::TestServer;
use crate::ADMIN_DB_USER_REMOVE_URI;
use crate::DB_LIST_URI;
use crate::DB_USER_ADD_URI;
use crate::NO_TOKEN;
use serde::Serialize;

#[derive(Serialize)]
struct RemoveUser {
database: String,
user: String,
}

#[tokio::test]
async fn remove() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let user = server.init_user().await?;
let reader = server.init_user().await?;
let db = server.init_db("memory", &user).await?;
let role = AddUser {
database: &db,
user: &reader.name,
role: "read",
};
let (_, list) = server
.get::<Vec<DbWithRole>>(DB_LIST_URI, &reader.token)
.await?;
assert_eq!(list?, vec![]);
assert_eq!(
server.post(DB_USER_ADD_URI, &role, &user.token).await?.0,
201
);
let (_, list) = server
.get::<Vec<DbWithRole>>(DB_LIST_URI, &reader.token)
.await?;
let expected = vec![DbWithRole {
name: db.clone(),
db_type: "memory".to_string(),
role: "read".to_string(),
}];
assert_eq!(list?, expected);
let rem = RemoveUser {
database: db,
user: reader.name,
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
204
);
let (_, list) = server
.get::<Vec<DbWithRole>>(DB_LIST_URI, &reader.token)
.await?;
assert_eq!(list?, vec![]);
Ok(())
}

#[tokio::test]
async fn remove_last_admin() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let user = server.init_user().await?;
let db = server.init_db("memory", &user).await?;
let rem = RemoveUser {
database: db.clone(),
user: user.name,
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
403
);
let (_, list) = server
.get::<Vec<DbWithRole>>(DB_LIST_URI, &user.token)
.await?;
let expected = vec![DbWithRole {
name: db,
db_type: "memory".to_string(),
role: "admin".to_string(),
}];
assert_eq!(list?, expected);
Ok(())
}

#[tokio::test]
async fn db_not_found() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let user = server.init_user().await?;
let rem = RemoveUser {
database: format!("{}/db_not_found", user.name),
user: String::new(),
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
466
);
Ok(())
}

#[tokio::test]
async fn user_not_found() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let user = server.init_user().await?;
let db = server.init_db("memory", &user).await?;
let rem = RemoveUser {
database: db,
user: "user_not_found".to_string(),
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
464
);
Ok(())
}

#[tokio::test]
async fn db_user_not_found() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let user = server.init_user().await?;
let other = server.init_user().await?;
let db = server.init_db("memory", &user).await?;
let rem = RemoveUser {
database: db,
user: other.name,
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
464
);
Ok(())
}

#[tokio::test]
async fn no_token() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let rem = RemoveUser {
database: String::new(),
user: String::new(),
};
assert_eq!(
server
.post(ADMIN_DB_USER_REMOVE_URI, &rem, NO_TOKEN)
.await?
.0,
401
);
Ok(())
}
3 changes: 2 additions & 1 deletion agdb_server/tests/routes/db_list_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async fn list() -> anyhow::Result<()> {
let user = server.init_user().await?;
let db1 = server.init_db("memory", &user).await?;
let db2 = server.init_db("memory", &user).await?;
let expected = vec![
let mut expected = vec![
DbWithRole {
name: db1.clone(),
db_type: "memory".to_string(),
Expand All @@ -21,6 +21,7 @@ async fn list() -> anyhow::Result<()> {
role: "admin".to_string(),
},
];
expected.sort();
let (status, list) = server
.get::<Vec<DbWithRole>>(DB_LIST_URI, &user.token)
.await?;
Expand Down
3 changes: 2 additions & 1 deletion agdb_server/tests/routes/db_user_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async fn list_users() -> anyhow::Result<()> {
.await?;
let mut list = list?;
list.sort();
let expected = vec![
let mut expected = vec![
DbUser {
database: db.clone(),
user: user.name,
Expand All @@ -44,6 +44,7 @@ async fn list_users() -> anyhow::Result<()> {
role: "read".to_string(),
},
];
expected.sort();
assert_eq!(list, expected);
Ok(())
}
Expand Down
Loading