Skip to content

Commit

Permalink
[server] Scope db names per user #865 (#866)
Browse files Browse the repository at this point in the history
* update tests

* fix tests
  • Loading branch information
michaelvlach authored Dec 14, 2023
1 parent 94eb2c9 commit 0a3f3f7
Show file tree
Hide file tree
Showing 18 changed files with 469 additions and 287 deletions.
3 changes: 3 additions & 0 deletions agdb_server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub(crate) struct ConfigImpl {
pub(crate) host: String,
pub(crate) port: u16,
pub(crate) admin: String,
pub(crate) data_dir: String,
}

pub(crate) fn new() -> ServerResult<Config> {
Expand All @@ -23,7 +24,9 @@ pub(crate) fn new() -> ServerResult<Config> {
host: "127.0.0.1".to_string(),
port: 3000,
admin: "admin".to_string(),
data_dir: "agdb_server_data".to_string(),
};

std::fs::write(CONFIG_FILE, serde_yaml::to_string(&config)?)?;

Ok(Config::new(config))
Expand Down
35 changes: 21 additions & 14 deletions agdb_server/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,17 @@ impl DbPool {
Ok(db_pool)
}

pub(crate) fn add_db(&self, user: DbId, database: Database) -> ServerResult {
let db = ServerDb::new(&format!("{}:{}", database.db_type, database.name)).map_err(
|mut e| {
e.status = ErrorCode::DbInvalid.into();
e.description = format!("{}: {}", ErrorCode::DbInvalid.as_str(), e.description);
e
},
)?;
pub(crate) fn add_db(&self, user: DbId, database: Database, config: &Config) -> ServerResult {
let db_path = Path::new(&config.data_dir).join(&database.name);
let user_dir = db_path.parent().ok_or(ErrorCode::DbInvalid)?;
std::fs::create_dir_all(user_dir)?;
let path = db_path.to_str().ok_or(ErrorCode::DbInvalid)?.to_string();

let db = ServerDb::new(&format!("{}:{}", database.db_type, path)).map_err(|mut e| {
e.status = ErrorCode::DbInvalid.into();
e.description = format!("{}: {}", ErrorCode::DbInvalid.as_str(), e.description);
e
})?;
self.get_pool_mut()?.insert(database.name.clone(), db);

self.db_mut()?.transaction_mut(|t| {
Expand Down Expand Up @@ -168,16 +171,20 @@ impl DbPool {
Ok(())
}

pub(crate) fn delete_db(&self, db: Database) -> ServerResult {
let filename = self.remove_db(db)?.get()?.filename().to_string();
let path = Path::new(&filename);
pub(crate) fn delete_db(&self, db: Database, config: &Config) -> ServerResult {
let path = Path::new(&config.data_dir).join(&db.name);
self.remove_db(db)?;

if path.exists() {
std::fs::remove_file(&filename)?;
let main_file_name = path
.file_name()
.ok_or(ErrorCode::DbInvalid)?
.to_string_lossy();
std::fs::remove_file(&path)?;
let dot_file = path
.parent()
.unwrap_or(Path::new("./"))
.join(format!(".{filename}"));
.ok_or(ErrorCode::DbInvalid)?
.join(format!(".{main_file_name}"));
std::fs::remove_file(dot_file)?;
}

Expand Down
14 changes: 12 additions & 2 deletions agdb_server/src/routes/db.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub(crate) mod user;

use crate::config::Config;
use crate::db::Database;
use crate::db::DbPool;
use crate::error_code::ErrorCode;
Expand Down Expand Up @@ -70,8 +71,16 @@ impl From<Database> for ServerDatabase {
pub(crate) async fn add(
user: UserId,
State(db_pool): State<DbPool>,
State(config): State<Config>,
Json(request): Json<ServerDatabase>,
) -> ServerResponse {
let (db_user, _db) = request.name.split_once('/').ok_or(ErrorCode::DbInvalid)?;
let db_user_id = db_pool.find_user_id(db_user)?;

if db_user_id != user.0 {
return Err(ErrorCode::DbInvalid.into());
}

if db_pool.find_db_id(&request.name).is_ok() {
return Err(ErrorCode::DbExists.into());
}
Expand All @@ -82,7 +91,7 @@ pub(crate) async fn add(
db_type: request.db_type.to_string(),
};

db_pool.add_db(user.0, db)?;
db_pool.add_db(user.0, db, &config)?;

Ok(StatusCode::CREATED)
}
Expand All @@ -101,6 +110,7 @@ pub(crate) async fn add(
pub(crate) async fn delete(
user: UserId,
State(db_pool): State<DbPool>,
State(config): State<Config>,
Json(request): Json<ServerDatabaseName>,
) -> ServerResponse {
let db = db_pool.find_user_db(user.0, &request.name)?;
Expand All @@ -109,7 +119,7 @@ pub(crate) async fn delete(
return Ok(StatusCode::FORBIDDEN);
}

db_pool.delete_db(db)?;
db_pool.delete_db(db, &config)?;

Ok(StatusCode::NO_CONTENT)
}
Expand Down
37 changes: 29 additions & 8 deletions agdb_server/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub const NO_TOKEN: &Option<String> = &None;

const BINARY: &str = "agdb_server";
const CONFIG_FILE: &str = "agdb_server.yaml";
const SERVER_DATA_DIR: &str = "agdb_server_data";
const PROTOCOL: &str = "http";
const HOST: &str = "127.0.0.1";
const DEFAULT_PORT: u16 = 3000;
Expand Down Expand Up @@ -84,13 +85,19 @@ pub struct ChangePassword<'a> {

pub struct TestServerImpl {
pub dir: String,
pub data_dir: String,
pub port: u16,
pub process: Child,
pub admin: String,
pub admin_password: String,
pub admin_token: Option<String>,
}

pub struct ServerUser {
pub name: String,
pub token: Option<String>,
}

pub struct TestServer {
server: &'static TestServerImpl,
client: Client,
Expand Down Expand Up @@ -122,12 +129,14 @@ impl TestServer {
self.server.post(&self.client, uri, json, token).await
}

pub async fn init_user(&self) -> anyhow::Result<(String, Option<String>)> {
pub async fn init_user(&self) -> anyhow::Result<ServerUser> {
self.server.init_user(&self.client).await
}

pub async fn init_db(&self, db_type: &str, token: &Option<String>) -> anyhow::Result<String> {
self.server.init_db(&self.client, db_type, token).await
pub async fn init_db(&self, db_type: &str, server_user: &ServerUser) -> anyhow::Result<String> {
self.server
.init_db(&self.client, db_type, server_user)
.await
}
}

Expand Down Expand Up @@ -175,6 +184,7 @@ impl TestServerImpl {
pub async fn init() -> anyhow::Result<Self> {
let port = PORT.fetch_add(1, Ordering::Relaxed);
let dir = format!("{BINARY}.{port}.test");
let data_dir = format!("{dir}/{SERVER_DATA_DIR}");

Self::remove_dir_if_exists(&dir)?;
std::fs::create_dir(&dir)?;
Expand All @@ -184,6 +194,7 @@ impl TestServerImpl {
config.insert("host", HOST.into());
config.insert("port", port.into());
config.insert("admin", ADMIN.into());
config.insert("data_dir", SERVER_DATA_DIR.into());

let file = std::fs::File::options()
.create_new(true)
Expand Down Expand Up @@ -220,6 +231,7 @@ impl TestServerImpl {
let admin_token = Some(response.text().await?);
let server = Self {
dir,
data_dir,
port,
process,
admin: ADMIN.to_string(),
Expand Down Expand Up @@ -256,7 +268,7 @@ impl TestServerImpl {
Ok((status, response.json().await.map_err(|e| anyhow!(e))))
}

pub async fn init_user(&self, client: &Client) -> anyhow::Result<(String, Option<String>)> {
pub async fn init_user(&self, client: &Client) -> anyhow::Result<ServerUser> {
let name = format!("db_user{}", COUNTER.fetch_add(1, Ordering::Relaxed));
let user = User {
name: &name,
Expand All @@ -270,21 +282,30 @@ impl TestServerImpl {
);
let response = self.post(client, USER_LOGIN_URI, &user, &None).await?;
assert_eq!(response.0, 200);
Ok((name, Some(response.1)))
Ok(ServerUser {
name,
token: Some(response.1),
})
}

pub async fn init_db(
&self,
client: &Client,
db_type: &str,
user_token: &Option<String>,
server_user: &ServerUser,
) -> anyhow::Result<String> {
let name = format!("db{}", COUNTER.fetch_add(1, Ordering::Relaxed));
let name = format!(
"{}/db{}",
server_user.name,
COUNTER.fetch_add(1, Ordering::Relaxed)
);
let db = Db {
name: name.clone(),
db_type: db_type.to_string(),
};
let (status, _) = self.post(client, DB_ADD_URI, &db, user_token).await?;
let (status, _) = self
.post(client, DB_ADD_URI, &db, &server_user.token)
.await?;
assert_eq!(status, 201);
Ok(name)
}
Expand Down
4 changes: 2 additions & 2 deletions agdb_server/tests/routes/admin_db_list_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ async fn db_list() -> anyhow::Result<()> {
.await?;
assert_eq!(status, 200);
assert!(list?.is_empty());
let (_, user1) = server.init_user().await?;
let (_, user2) = server.init_user().await?;
let user1 = server.init_user().await?;
let user2 = server.init_user().await?;
let db1 = server.init_db("memory", &user1).await?;
let db2 = server.init_db("memory", &user2).await?;
let (status, list) = server
Expand Down
41 changes: 31 additions & 10 deletions agdb_server/tests/routes/admin_db_remove_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,59 @@ use crate::NO_TOKEN;
use serde::Serialize;

#[derive(Serialize)]
struct DbName<'a> {
name: &'a str,
struct DbName {
name: String,
}

#[tokio::test]
async fn remove() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let (_, token) = server.init_user().await?;
let db = server.init_db("mapped", &token).await?;
let (status, list) = server.get::<Vec<Db>>(DB_LIST_URI, &token).await?;
let user = server.init_user().await?;
let db = server.init_db("mapped", &user).await?;
let (status, list) = server.get::<Vec<Db>>(DB_LIST_URI, &user.token).await?;
assert_eq!(status, 200);
assert!(list?.contains(&Db {
name: db.clone(),
db_type: "mapped".to_string(),
}));
let rem = DbName { name: &db };
let rem = DbName { name: db.clone() };
assert_eq!(
server
.post(ADMIN_DB_REMOVE_URI, &rem, &server.admin_token)
.await?
.0,
204
);
let (status, list) = server.get::<Vec<Db>>(DB_LIST_URI, &token).await?;
let (status, list) = server.get::<Vec<Db>>(DB_LIST_URI, &user.token).await?;
assert_eq!(status, 200);
assert!(list?.is_empty());
assert!(Path::new(&server.dir).join(db).exists());
assert!(Path::new(&server.data_dir).join(db).exists());
Ok(())
}

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

#[tokio::test]
async fn user_not_found() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let db = DbName {
name: "missing_user/some_db".to_string(),
};
assert_eq!(
server
.post(ADMIN_DB_REMOVE_URI, &db, &server.admin_token)
Expand All @@ -55,7 +74,9 @@ async fn db_not_found() -> anyhow::Result<()> {
#[tokio::test]
async fn no_admin_token() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let db = DbName { name: "some_db" };
let db = DbName {
name: "user/some_db".to_string(),
};
assert_eq!(
server.post(ADMIN_DB_REMOVE_URI, &db, NO_TOKEN).await?.0,
401
Expand Down
8 changes: 4 additions & 4 deletions agdb_server/tests/routes/admin_user_change_password_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use crate::USER_LOGIN_URI;
#[tokio::test]
async fn change_password() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let (name, _) = server.init_user().await?;
let user = server.init_user().await?;
let user = User {
name: &name,
name: &user.name,
password: "password456",
};
let admin = &server.admin_token;
Expand All @@ -27,9 +27,9 @@ async fn change_password() -> anyhow::Result<()> {
#[tokio::test]
async fn password_too_short() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let (name, _) = server.init_user().await?;
let user = server.init_user().await?;
let user = User {
name: &name,
name: &user.name,
password: "pswd",
};
let admin = &server.admin_token;
Expand Down
4 changes: 2 additions & 2 deletions agdb_server/tests/routes/admin_user_create_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ async fn password_too_short() -> anyhow::Result<()> {
#[tokio::test]
async fn user_already_exists() -> anyhow::Result<()> {
let server = TestServer::new().await?;
let (name, _) = server.init_user().await?;
let user = server.init_user().await?;
let user = User {
name: &name,
name: &user.name,
password: "password123",
};
assert_eq!(
Expand Down
8 changes: 4 additions & 4 deletions agdb_server/tests/routes/admin_user_list_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ async fn user_list() -> anyhow::Result<()> {
assert!(list?.contains(&UserStatus {
name: "admin".to_string()
}));
let (name1, _) = server.init_user().await?;
let (name2, _) = server.init_user().await?;
let user1 = server.init_user().await?;
let user2 = server.init_user().await?;
let (status, list) = server
.get::<Vec<UserStatus>>(ADMIN_USER_LIST_URI, &server.admin_token)
.await?;
Expand All @@ -23,8 +23,8 @@ async fn user_list() -> anyhow::Result<()> {
assert!(list.contains(&UserStatus {
name: "admin".to_string(),
}));
assert!(list.contains(&UserStatus { name: name1 }));
assert!(list.contains(&UserStatus { name: name2 }));
assert!(list.contains(&UserStatus { name: user1.name }));
assert!(list.contains(&UserStatus { name: user2.name }));
Ok(())
}

Expand Down
Loading

0 comments on commit 0a3f3f7

Please sign in to comment.