//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2018 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Dan Larkin-York //////////////////////////////////////////////////////////////////////////////// #include "RestTasksHandler.h" #include "ApplicationFeatures/ApplicationServer.h" #include "Basics/VelocyPackHelper.h" #include "Cluster/ServerState.h" #include "Rest/HttpRequest.h" #include "V8/JavaScriptSecurityContext.h" #include "V8/v8-globals.h" #include "V8/v8-vpack.h" #include "V8Server/V8DealerFeature.h" #include "VocBase/Methods/Tasks.h" #include #include using namespace arangodb::basics; using namespace arangodb::rest; namespace arangodb { RestTasksHandler::RestTasksHandler(GeneralRequest* request, GeneralResponse* response) : RestVocbaseBaseHandler(request, response) {} RestStatus RestTasksHandler::execute() { auto const type = _request->requestType(); switch (type) { case rest::RequestType::POST: { registerTask(false); break; } case rest::RequestType::PUT: { registerTask(true); break; } case rest::RequestType::DELETE_REQ: { deleteTask(); break; } case rest::RequestType::GET: { getTasks(); break; } default: { generateError(rest::ResponseCode::METHOD_NOT_ALLOWED, TRI_ERROR_HTTP_METHOD_NOT_ALLOWED); } } return RestStatus::DONE; } /// @brief returns the short id of the server which should handle this request uint32_t RestTasksHandler::forwardingTarget() { rest::RequestType const type = _request->requestType(); if (type != rest::RequestType::POST && type != rest::RequestType::PUT && type != rest::RequestType::GET && type != rest::RequestType::DELETE_REQ) { return 0; } std::vector const& suffixes = _request->suffixes(); if (suffixes.size() < 1) { return 0; } uint64_t tick = arangodb::basics::StringUtils::uint64(suffixes[0]); uint32_t sourceServer = TRI_ExtractServerIdFromTick(tick); return (sourceServer == ServerState::instance()->getShortId()) ? 0 : sourceServer; } void RestTasksHandler::getTasks() { std::vector const& suffixes = _request->decodedSuffixes(); if (suffixes.size() > 1) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "superfluous parameter, expecting /_api/tasks[/]"); return; } std::shared_ptr builder; if (suffixes.size() == 1) { // get a single task builder = Task::registeredTask(suffixes[0]); } else { // get all tasks builder = Task::registeredTasks(); } if (builder == nullptr) { generateError(TRI_ERROR_TASK_NOT_FOUND); return; } generateResult(rest::ResponseCode::OK, builder->slice()); } void RestTasksHandler::registerTask(bool byId) { bool parseSuccess = false; VPackSlice body = this->parseVPackBody(parseSuccess); if (!parseSuccess) { // error message generated in parseVPackBody return; } std::vector const& suffixes = _request->decodedSuffixes(); // job id std::string id; if (byId) { if ((suffixes.size() != 1) || suffixes[0].empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "expected PUT /_api/tasks/"); return; } else { id = suffixes[0]; } } ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr) { if (exec->databaseAuthLevel() != auth::Level::RW) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN, "registering a task needs db RW permissions"); return; } } // job id if (id.empty()) { id = VelocyPackHelper::getStringValue(body, "id", std::to_string(TRI_NewServerSpecificTick())); } // job name std::string name = VelocyPackHelper::getStringValue(body, "name", "user-defined task"); bool isSystem = VelocyPackHelper::getBooleanValue(body, "isSystem", false); // offset in seconds into period or from now on if no period double offset = VelocyPackHelper::getNumericValue(body, "offset", 0.0); // period in seconds & count double period = 0.0; auto periodSlice = body.get("period"); if (periodSlice.isNumber()) { period = VelocyPackHelper::getNumericValue(body, "period", 0.0); if (period <= 0.0) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "task period must be specified and positive"); return; } } std::string runAsUser = VelocyPackHelper::getStringValue(body, "runAsUser", ""); // only the superroot is allowed to run tasks as an arbitrary user TRI_ASSERT(exec == ExecContext::CURRENT); if (exec != nullptr) { if (runAsUser.empty()) { // execute task as the same user runAsUser = exec->user(); } else if (exec->user() != runAsUser) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN, "cannot run task as a different user"); return; } } // extract the command std::string command; auto cmdSlice = body.get("command"); if (!cmdSlice.isString()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "command must be specified"); return; } try { JavaScriptSecurityContext securityContext = JavaScriptSecurityContext::createRestrictedContext(); V8ContextGuard guard(&_vocbase, securityContext); v8::Isolate* isolate = guard.isolate(); v8::HandleScope scope(isolate); v8::Handle bv8 = TRI_VPackToV8(isolate, body).As(); if (bv8->Get(TRI_V8_ASCII_STRING(isolate, "command"))->IsFunction()) { // need to add ( and ) around function because call will otherwise break command = "(" + cmdSlice.copyString() + ")(params)"; } else { command = cmdSlice.copyString(); } if (!Task::tryCompile(isolate, command)) { generateError(rest::ResponseCode::BAD, TRI_ERROR_BAD_PARAMETER, "cannot compile command"); return; } } catch (arangodb::basics::Exception const& ex) { generateError(Result{ex.code(), ex.what()}); return; } catch (std::exception const& ex) { generateError(Result{TRI_ERROR_INTERNAL, ex.what()}); return; } catch (...) { generateError(TRI_ERROR_INTERNAL); return; } // extract the parameters auto parameters = std::make_shared(body.get("params")); command = "(function (params) { " + command + " } )(params);"; int res; std::shared_ptr task = Task::createTask(id, name, &_vocbase, command, isSystem, res); if (res != TRI_ERROR_NO_ERROR) { generateError(res); return; } // set the user this will run as if (!runAsUser.empty()) { task->setUser(runAsUser); } // set execution parameters task->setParameter(parameters); if (period > 0.0) { // create a new periodic task task->setPeriod(offset, period); } else { // create a run-once timer task task->setOffset(offset); } // get the VelocyPack representation of the task std::shared_ptr builder = task->toVelocyPack(); if (builder == nullptr) { generateError(TRI_ERROR_INTERNAL); return; } task->start(); generateResult(rest::ResponseCode::OK, builder->slice()); } void RestTasksHandler::deleteTask() { std::vector const& suffixes = _request->decodedSuffixes(); if ((suffixes.size() != 1) || suffixes[0].empty()) { generateError(rest::ResponseCode::BAD, TRI_ERROR_HTTP_SUPERFLUOUS_SUFFICES, "bad parameter, expecting /_api/tasks/"); return; } ExecContext const* exec = ExecContext::CURRENT; if (exec != nullptr) { if (exec->databaseAuthLevel() != auth::Level::RW) { generateError(rest::ResponseCode::FORBIDDEN, TRI_ERROR_FORBIDDEN, "unregister task needs db RW permissions"); return; } } int res = Task::unregisterTask(suffixes[0], true); if (res != TRI_ERROR_NO_ERROR) { generateError(res); return; } generateOk(rest::ResponseCode::OK, velocypack::Slice()); } } // namespace arangodb