|
@@ -49,31 +49,40 @@ TEST_F(ApiFixture, KeysCrudAndScopedAuthz) {
|
|
|
auto mk = c.Post("/api/v1/keys",
|
|
auto mk = c.Post("/api/v1/keys",
|
|
|
nlohmann::json{{"label","n8n"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
nlohmann::json{{"label","n8n"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
|
ASSERT_TRUE(mk); ASSERT_EQ(mk->status, 201);
|
|
ASSERT_TRUE(mk); ASSERT_EQ(mk->status, 201);
|
|
|
- std::string scopedKey = nlohmann::json::parse(mk->body)["key"];
|
|
|
|
|
|
|
+ auto mkJson = nlohmann::json::parse(mk->body);
|
|
|
|
|
+ std::string scopedKey = mkJson["key"];
|
|
|
|
|
+ std::string scopedId = mkJson["id"];
|
|
|
ASSERT_FALSE(scopedKey.empty());
|
|
ASSERT_FALSE(scopedKey.empty());
|
|
|
|
|
+ ASSERT_FALSE(scopedId.empty());
|
|
|
|
|
|
|
|
- // list masks the secret
|
|
|
|
|
|
|
+ // list masks the secret but includes id
|
|
|
auto list = c.Get("/api/v1/keys");
|
|
auto list = c.Get("/api/v1/keys");
|
|
|
ASSERT_EQ(list->status, 200);
|
|
ASSERT_EQ(list->status, 200);
|
|
|
auto keysJson = nlohmann::json::parse(list->body);
|
|
auto keysJson = nlohmann::json::parse(list->body);
|
|
|
- for (auto& k : keysJson["keys"]) EXPECT_FALSE(k.contains("key"));
|
|
|
|
|
|
|
+ for (auto& k : keysJson["keys"]) {
|
|
|
|
|
+ EXPECT_FALSE(k.contains("key"));
|
|
|
|
|
+ EXPECT_TRUE(k.contains("id"));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// scoped key can access its project, but not management or another project
|
|
// scoped key can access its project, but not management or another project
|
|
|
EXPECT_EQ(http(scopedKey).Get(("/api/v1/projects/" + project_).c_str())->status, 200);
|
|
EXPECT_EQ(http(scopedKey).Get(("/api/v1/projects/" + project_).c_str())->status, 200);
|
|
|
EXPECT_EQ(http(scopedKey).Get("/api/v1/keys")->status, 403); // not admin
|
|
EXPECT_EQ(http(scopedKey).Get("/api/v1/keys")->status, 403); // not admin
|
|
|
EXPECT_EQ(http(scopedKey).Get("/api/v1/projects/svapitest_other")->status, 403); // not granted
|
|
EXPECT_EQ(http(scopedKey).Get("/api/v1/projects/svapitest_other")->status, 403); // not granted
|
|
|
|
|
|
|
|
- EXPECT_EQ(c.Delete(("/api/v1/keys/" + scopedKey).c_str())->status, 200);
|
|
|
|
|
|
|
+ // delete by id (not by secret)
|
|
|
|
|
+ EXPECT_EQ(c.Delete(("/api/v1/keys/" + scopedId).c_str())->status, 200);
|
|
|
EXPECT_EQ(http(scopedKey).Get(("/api/v1/projects/" + project_).c_str())->status, 401); // revoked
|
|
EXPECT_EQ(http(scopedKey).Get(("/api/v1/projects/" + project_).c_str())->status, 401); // revoked
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TEST_F(ApiFixture, NonAdminCannotCreateProject) {
|
|
TEST_F(ApiFixture, NonAdminCannotCreateProject) {
|
|
|
auto mk = admin().Post("/api/v1/keys",
|
|
auto mk = admin().Post("/api/v1/keys",
|
|
|
nlohmann::json{{"label","x"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
nlohmann::json{{"label","x"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
|
- std::string scoped = nlohmann::json::parse(mk->body)["key"];
|
|
|
|
|
|
|
+ auto mkJson = nlohmann::json::parse(mk->body);
|
|
|
|
|
+ std::string scoped = mkJson["key"];
|
|
|
|
|
+ std::string scopedId = mkJson["id"];
|
|
|
auto r = http(scoped).Post("/api/v1/projects", nlohmann::json{{"name","svapitest_nope"}}.dump(), "application/json");
|
|
auto r = http(scoped).Post("/api/v1/projects", nlohmann::json{{"name","svapitest_nope"}}.dump(), "application/json");
|
|
|
ASSERT_TRUE(r); EXPECT_EQ(r->status, 403);
|
|
ASSERT_TRUE(r); EXPECT_EQ(r->status, 403);
|
|
|
- admin().Delete(("/api/v1/keys/" + scoped).c_str());
|
|
|
|
|
|
|
+ admin().Delete(("/api/v1/keys/" + scopedId).c_str());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
TEST_F(ApiFixture, CollectionCrudUnderProject) {
|
|
TEST_F(ApiFixture, CollectionCrudUnderProject) {
|
|
@@ -237,7 +246,16 @@ TEST_F(ApiFixture, ProjectRecycleAfterDelete) {
|
|
|
|
|
|
|
|
// Fix 5 regression: cannot delete the last admin key
|
|
// Fix 5 regression: cannot delete the last admin key
|
|
|
TEST_F(ApiFixture, CannotDeleteLastAdminKey) {
|
|
TEST_F(ApiFixture, CannotDeleteLastAdminKey) {
|
|
|
- auto r = admin().Delete(("/api/v1/keys/" + adminKey_).c_str());
|
|
|
|
|
|
|
+ // Get the admin key's id from the keys list (find by key_prefix matching adminKey_'s first 8 chars)
|
|
|
|
|
+ auto listRes = admin().Get("/api/v1/keys");
|
|
|
|
|
+ ASSERT_TRUE(listRes); ASSERT_EQ(listRes->status, 200);
|
|
|
|
|
+ auto keysArr = nlohmann::json::parse(listRes->body)["keys"];
|
|
|
|
|
+ std::string prefix8 = adminKey_.substr(0, 8);
|
|
|
|
|
+ std::string adminId;
|
|
|
|
|
+ for (const auto& k : keysArr)
|
|
|
|
|
+ if (k["key_prefix"].get<std::string>() == prefix8) { adminId = k["id"].get<std::string>(); break; }
|
|
|
|
|
+ ASSERT_FALSE(adminId.empty());
|
|
|
|
|
+ auto r = admin().Delete(("/api/v1/keys/" + adminId).c_str());
|
|
|
ASSERT_TRUE(r);
|
|
ASSERT_TRUE(r);
|
|
|
EXPECT_EQ(r->status, 422);
|
|
EXPECT_EQ(r->status, 422);
|
|
|
// Key still works
|
|
// Key still works
|
|
@@ -248,7 +266,16 @@ TEST_F(ApiFixture, CannotDeleteLastAdminKey) {
|
|
|
|
|
|
|
|
// Fix 5 regression: cannot de-admin the last admin key
|
|
// Fix 5 regression: cannot de-admin the last admin key
|
|
|
TEST_F(ApiFixture, CannotDeAdminLastAdminKey) {
|
|
TEST_F(ApiFixture, CannotDeAdminLastAdminKey) {
|
|
|
- auto r = admin().Patch(("/api/v1/keys/" + adminKey_).c_str(),
|
|
|
|
|
|
|
+ // Get the admin key's id from the keys list (find by key_prefix matching adminKey_'s first 8 chars)
|
|
|
|
|
+ auto listRes = admin().Get("/api/v1/keys");
|
|
|
|
|
+ ASSERT_TRUE(listRes); ASSERT_EQ(listRes->status, 200);
|
|
|
|
|
+ auto keysArr = nlohmann::json::parse(listRes->body)["keys"];
|
|
|
|
|
+ std::string prefix8 = adminKey_.substr(0, 8);
|
|
|
|
|
+ std::string adminId;
|
|
|
|
|
+ for (const auto& k : keysArr)
|
|
|
|
|
+ if (k["key_prefix"].get<std::string>() == prefix8) { adminId = k["id"].get<std::string>(); break; }
|
|
|
|
|
+ ASSERT_FALSE(adminId.empty());
|
|
|
|
|
+ auto r = admin().Patch(("/api/v1/keys/" + adminId).c_str(),
|
|
|
nlohmann::json{{"admin", false}}.dump(), "application/json");
|
|
nlohmann::json{{"admin", false}}.dump(), "application/json");
|
|
|
ASSERT_TRUE(r);
|
|
ASSERT_TRUE(r);
|
|
|
EXPECT_EQ(r->status, 422);
|
|
EXPECT_EQ(r->status, 422);
|
|
@@ -295,11 +322,13 @@ TEST_F(ApiFixture, SettingsPutChangesField) {
|
|
|
auto mk = admin().Post("/api/v1/keys",
|
|
auto mk = admin().Post("/api/v1/keys",
|
|
|
nlohmann::json{{"label","scoped"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
nlohmann::json{{"label","scoped"},{"projects",{project_}},{"admin",false}}.dump(), "application/json");
|
|
|
ASSERT_TRUE(mk); ASSERT_EQ(mk->status, 201);
|
|
ASSERT_TRUE(mk); ASSERT_EQ(mk->status, 201);
|
|
|
- std::string scopedKey = nlohmann::json::parse(mk->body)["key"];
|
|
|
|
|
|
|
+ auto mkJson2 = nlohmann::json::parse(mk->body);
|
|
|
|
|
+ std::string scopedKey = mkJson2["key"];
|
|
|
|
|
+ std::string scopedId2 = mkJson2["id"];
|
|
|
auto denied = http(scopedKey).Get("/api/v1/settings");
|
|
auto denied = http(scopedKey).Get("/api/v1/settings");
|
|
|
ASSERT_TRUE(denied);
|
|
ASSERT_TRUE(denied);
|
|
|
EXPECT_EQ(denied->status, 403);
|
|
EXPECT_EQ(denied->status, 403);
|
|
|
- admin().Delete(("/api/v1/keys/" + scopedKey).c_str());
|
|
|
|
|
|
|
+ admin().Delete(("/api/v1/keys/" + scopedId2).c_str());
|
|
|
|
|
|
|
|
// Restore original model so other tests are unaffected
|
|
// Restore original model so other tests are unaffected
|
|
|
admin().Put("/api/v1/settings",
|
|
admin().Put("/api/v1/settings",
|