use super::*;
const NOTES_TOML: &str = r#"
[resource]
name = "note"
scope = "organization"
[permissions]
list = "role:admin"
read = "role:admin"
create = "role:admin"
update = "role:admin"
delete = "role:admin"
[fields.title]
type = "string"
required = true
"#;
const SECRET_TOML: &str = r#"
[resource]
name = "secret"
scope = "global"
[permissions]
list = "private"
read = "private"
create = "private"
update = "private"
delete = "private"
[fields.title]
type = "string"
"#;
fn main_toml(db_url: &str, extra: &str) -> String {
format!("[server]\nbase_path = \"/api\"\n\n[database]\nurl = \"{db_url}\"\n\n{extra}")
}
#[ntex::test]
async fn auth_disabled_lets_anybody_do_anything_except_reach_private() {
let db = TempDatabase::create("auth_off").await;
let root = temp_dir("auth_off");
write_files(
&root,
&[
(
"main.toml",
&main_toml(&db.url, "[auth]\nenabled = false\n"),
),
("resources/notes.toml", NOTES_TOML),
("resources/secrets.toml", SECRET_TOML),
],
);
let state = load_state(&root).await;
let app = init_http_app!(state);
let created = test::call_service(
&app,
req_json("POST", "/api/note", json!({ "title": "hello" })),
)
.await;
assert_eq!(
created.status(),
201,
"anonymous create on a role:admin resource"
);
let note = read_json(created).await;
let id = note["id"].as_str().unwrap().to_string();
assert_eq!(
note["organization_id"].as_str(),
Some(apiplant_core::SOLO_ORGANIZATION_ID),
"rows land in the solo organisation"
);
let listed =
test::call_service(&app, test::TestRequest::get().uri("/api/note").to_request()).await;
assert_eq!(listed.status(), 200);
let updated = test::call_service(
&app,
req_json(
"PATCH",
&format!("/api/note/{id}"),
json!({ "title": "again" }),
),
)
.await;
assert_eq!(updated.status(), 200);
let deleted = test::call_service(
&app,
test::TestRequest::delete()
.uri(&format!("/api/note/{id}"))
.to_request(),
)
.await;
assert_eq!(deleted.status(), 204);
let secret = test::call_service(
&app,
test::TestRequest::get().uri("/api/secret").to_request(),
)
.await;
assert_eq!(secret.status(), 404, "private stays private with auth off");
db.cleanup().await;
}
#[ntex::test]
async fn auth_disabled_mounts_no_auth_endpoints_and_creates_no_account_tables() {
let db = TempDatabase::create("auth_off_routes").await;
let root = temp_dir("auth_off_routes");
write_files(
&root,
&[(
"main.toml",
&main_toml(&db.url, "[auth]\nenabled = false\n"),
)],
);
let app_config = App::load(&root).unwrap();
for absent in [
"user",
"membership",
"membership_role",
"api_key",
"oauth_connection",
"invitation",
"auth_token",
] {
assert!(
!app_config.resources.contains_key(absent),
"`{absent}` should not exist in an app with no accounts"
);
}
assert!(
app_config.resources.contains_key("organization"),
"the tenant is not an auth table and stays"
);
let state = load_state(&root).await;
let app = init_http_app!(state);
for path in ["/api/auth/login", "/api/auth/register"] {
let response = test::call_service(
&app,
req_json(
"POST",
path,
json!({ "email": "a@example.com", "password": "pw" }),
),
)
.await;
assert!(
response.status().is_client_error(),
"{path} should not sign anybody in: {}",
response.status()
);
}
db.cleanup().await;
}
#[ntex::test]
async fn auth_disabled_refuses_a_resource_that_references_an_account() {
let root = temp_dir("auth_off_refs");
write_files(
&root,
&[
(
"main.toml",
"[database]\nurl = \"postgres://postgres@127.0.0.1:5432/unused\"\n\n[auth]\nenabled = false\n",
),
(
"resources/posts.toml",
"[resource]\nname = \"post\"\nscope = \"global\"\n\n[fields.author_id]\ntype = \"reference\"\nreferences = \"user\"\n",
),
],
);
let error = App::load(&root).unwrap_err().to_string();
assert!(error.contains("author_id"), "{error}");
assert!(error.contains("user"), "{error}");
assert!(error.contains("[auth] enabled = false"), "{error}");
}
#[ntex::test]
async fn organizations_disabled_put_everybody_in_one_tenant() {
let db = TempDatabase::create("orgs_off").await;
let root = temp_dir("orgs_off");
write_files(
&root,
&[
(
"main.toml",
&main_toml(&db.url, "[organization]\nenabled = false\n"),
),
("resources/notes.toml", NOTES_TOML),
],
);
let state = load_state(&root).await;
let app = init_http_app!(state);
let register = |email: &'static str| {
req_json(
"POST",
"/api/auth/register",
json!({ "email": email, "password": "pw" }),
)
};
let alice = read_json(test::call_service(&app, register("alice@example.com")).await).await;
let alice_token = alice["token"].as_str().unwrap().to_string();
let bob = read_json(test::call_service(&app, register("bob@example.com")).await).await;
let bob_token = bob["token"].as_str().unwrap().to_string();
let created = test::call_service(
&app,
bearer(
test::TestRequest::post()
.uri("/api/note")
.header(CONTENT_TYPE, "application/json")
.set_payload(json!({ "title": "shared" }).to_string()),
&alice_token,
)
.to_request(),
)
.await;
assert_eq!(
created.status(),
201,
"no membership needed with one tenant"
);
let note = read_json(created).await;
assert_eq!(
note["organization_id"].as_str(),
Some(apiplant_core::SOLO_ORGANIZATION_ID)
);
let listed = read_json(
test::call_service(
&app,
bearer(test::TestRequest::get().uri("/api/note"), &bob_token)
.header("x-organization", Uuid::new_v4().to_string())
.to_request(),
)
.await,
)
.await;
let rows = listed["data"]
.as_array()
.unwrap_or_else(|| listed.as_array().expect("a list response"));
assert_eq!(rows.len(), 1, "one tenant, one set of rows: {listed}");
db.cleanup().await;
}
#[ntex::test]
async fn auth_disabled_forces_organizations_off() {
let root = temp_dir("orgs_derived");
write_files(
&root,
&[(
"main.toml",
"[database]\nurl = \"postgres://postgres@127.0.0.1:5432/unused\"\n\n[auth]\nenabled = false\n\n[organization]\nenabled = true\n",
)],
);
let app = App::load(&root).unwrap();
assert!(!app.config.auth_enabled());
assert!(
!app.config.organizations_enabled(),
"tenancy cannot outlive the accounts it is made of"
);
}