Skip to content
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
23 changes: 19 additions & 4 deletions controllers/api_videos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,22 @@ drogon::Task<drogon::HttpResponsePtr> videos::getVideo([[maybe_unused]] HttpRequ

drogon::Task<drogon::HttpResponsePtr> videos::patchVideo([[maybe_unused]] HttpRequestPtr req, std::string id) {
auto jsonPtr = req->getJsonObject();
if (!jsonPtr) {
if (!jsonPtr || !jsonPtr->isObject() || jsonPtr->empty()) {
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setStatusCode(drogon::HttpStatusCode::k400BadRequest);
resp->setBody("Invalid JSON format");
resp->setBody("Request body must be a non-empty JSON object");
co_return resp;
}

for (const auto& key : jsonPtr->getMemberNames()) {
if ((key != "title" && key != "description") || !(*jsonPtr)[key].isString()) {
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setStatusCode(drogon::HttpStatusCode::k400BadRequest);
resp->setBody("Only string fields 'title' and 'description' can be updated");
co_return resp;
}
}

drogon::orm::CoroMapper<drogon_model::playbacq::Videos> mapper(drogon::app().getDbClient());
try {
auto video = co_await mapper.findByPrimaryKey(id);
Expand All @@ -373,7 +383,12 @@ drogon::Task<drogon::HttpResponsePtr> videos::patchVideo([[maybe_unused]] HttpRe
resp->setBody("You are not the owner of this video");
co_return resp;
}
video.updateByJson(*jsonPtr);
if (jsonPtr->isMember("title")) {
video.setTitle((*jsonPtr)["title"].asString());
}
if (jsonPtr->isMember("description")) {
video.setDescription((*jsonPtr)["description"].asString());
}
co_await mapper.update(video);

auto resp = drogon::HttpResponse::newHttpJsonResponse(video.toJson());
Expand Down Expand Up @@ -929,4 +944,4 @@ drogon::Task<drogon::HttpResponsePtr> videos::getThumbnails(HttpRequestPtr req,
co_return resp;
}
co_return co_await getVideoThumbnails(req, id, filename);
}
}
2 changes: 2 additions & 0 deletions docs/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ paths:
application/json:
schema:
type: object
minProperties: 1
additionalProperties: false
properties:
title:
type: string
Expand Down
63 changes: 63 additions & 0 deletions test/controllers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,69 @@ DROGON_TEST(EditVideoTest)
CHECK(deleteVideo(videoId) == true);
}

DROGON_TEST(EditVideoMassAssignmentTest)
{
std::optional<std::string> videoIdOpt = postVideo("変更されないタイトル", "変更されない説明");
REQUIRE(videoIdOpt.has_value());
const std::string videoId = videoIdOpt.value();

auto beforeResp = sendSyncRequest(drogon::Get, "/api/videos/" + videoId);
REQUIRE(beforeResp != nullptr);
REQUIRE(beforeResp->getStatusCode() == drogon::k200OK);
auto beforeJsonPtr = beforeResp->getJsonObject();
REQUIRE(beforeJsonPtr != nullptr);
const Json::Value beforeJson = *beforeJsonPtr;

const std::vector<std::pair<std::string, Json::Value>> protectedFields = {
{"video_id", "attacker-video"},
{"user_id", "otheruser"},
{"thumbnail_url", "https://example.com/example.png"},
{"video_url", "https://example.com/watch/attacker-video"},
{"created_at", "2008-04-23 00:31:07"},
{"view_count", 999999999},
{"duration", 8},
{"like_count", 999999999},
{"status", 2},
{"is_external", 1},
{"type", "youtube"},
};

for (const auto& [field, value] : protectedFields) {
Json::Value body;
body["title"] = "攻撃者が変更したタイトル";
body[field] = value;
auto resp = sendSyncRequest(drogon::Patch, "/api/videos/" + videoId, body);
REQUIRE(resp != nullptr);
CHECK(resp->getStatusCode() == drogon::k400BadRequest);
}

const std::vector<Json::Value> invalidBodies = {
Json::Value(Json::objectValue),
Json::Value(Json::arrayValue),
Json::Value(42),
};
for (const auto& body : invalidBodies) {
auto resp = sendSyncRequest(drogon::Patch, "/api/videos/" + videoId, body);
REQUIRE(resp != nullptr);
CHECK(resp->getStatusCode() == drogon::k400BadRequest);
}

Json::Value nonStringTitle;
nonStringTitle["title"] = 42;
auto invalidTypeResp = sendSyncRequest(drogon::Patch, "/api/videos/" + videoId, nonStringTitle);
REQUIRE(invalidTypeResp != nullptr);
CHECK(invalidTypeResp->getStatusCode() == drogon::k400BadRequest);

auto afterResp = sendSyncRequest(drogon::Get, "/api/videos/" + videoId);
REQUIRE(afterResp != nullptr);
REQUIRE(afterResp->getStatusCode() == drogon::k200OK);
auto afterJson = afterResp->getJsonObject();
REQUIRE(afterJson != nullptr);
CHECK(*afterJson == beforeJson);

CHECK(deleteVideo(videoId) == true);
}

DROGON_TEST(SearchTest)
{
// 動画を投稿
Expand Down
Loading