From 7f6987a4b4950e77597fd1a4fd5d8c60e6209f07 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 2 Feb 2014 17:34:07 +0900 Subject: [PATCH] Implement new RPC authorization using --rpc-secret option --- doc/manual-src/en/aria2c.rst | 104 +++++++++++++++++++++++------------ doc/xmlrpc/aria2rpc | 85 ++++++++++++++++------------ src/OptionHandlerFactory.cc | 8 +++ src/RpcMethod.cc | 25 +++++++++ src/RpcMethod.h | 2 + src/RpcMethodImpl.h | 8 +++ src/ValueBase.cc | 10 ++++ src/ValueBase.h | 10 +++- src/prefs.cc | 2 + src/prefs.h | 2 + src/usage_text.h | 12 ++-- test/RpcMethodTest.cc | 43 +++++++++++++++ 12 files changed, 232 insertions(+), 79 deletions(-) diff --git a/doc/manual-src/en/aria2c.rst b/doc/manual-src/en/aria2c.rst index 4ac5cda3..f45d6d17 100644 --- a/doc/manual-src/en/aria2c.rst +++ b/doc/manual-src/en/aria2c.rst @@ -926,9 +926,9 @@ RPC Options .. option:: --enable-rpc[=true|false] - Enable JSON-RPC/XML-RPC server. It is strongly recommended to set username - and password using :option:`--rpc-user` and :option:`--rpc-passwd` - option. See also :option:`--rpc-listen-port` option. Default: ``false`` + Enable JSON-RPC/XML-RPC server. It is strongly recommended to set + secret authorization token using :option:`--rpc-secret` option. See + also :option:`--rpc-listen-port` option. Default: ``false`` .. option:: --pause[=true|false] @@ -1004,6 +1004,11 @@ RPC Options :func:`aria2.addTorrent` or :func:`aria2.addMetalink` will not be saved by :option:`--save-session` option. Default: ``false`` +.. option:: --rpc-secret= + + Set RPC secret authorization token. Read :ref:`rpc_auth` to know + how this option value is used. + .. option:: --rpc-secure[=true|false] RPC transport will be encrypted by SSL/TLS. The RPC clients must @@ -2021,13 +2026,40 @@ GID <--gid>` option. When querying download by GID, you can specify the prefix of GID as long as it is a unique prefix among others. +.. _rpc_auth: + +RPC authorization secret token +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As of 1.18.4, in addition to HTTP basic authorization, aria2 provides +RPC method-level authorization. In the future release, HTTP basic +authorization will be removed and RPC method-level authorization will +become mandatory. + +To use RPC method-level authorization, user has to specify RPC secret +authorization token using :option:`--rpc-secret` option. For each RPC +method call, the caller has to include the token prefixed with +``token:``. If :option:`--rpc-secret` option is not used and first +parameter in the RPC method is a String and starts with ``token:``, it +is removed from the parameter before being processed. + +For example, if RPC secret authorization token is ``$$secret$$``, to +call `aria2.addUri` RPC method would look like this:: + + aria2.addUri("token::$$secret$$", ["http://example.org/file"]) + +The `system.multicall` RPC method is treated specially. Since XML-RPC +specification only allows one array as a paremter for this method, we +don't specify token in its call. Instead, each nested method call has +to provide token as 1st parameter as described above. + Methods ~~~~~~~ All code examples come from Python2.7 interpreter. +For *secret* parameter, see :ref:`rpc_auth`. - -.. function:: aria2.addUri(uris[, options[, position]]) +.. function:: aria2.addUri([secret], uris[, options[, position]]) This method adds new HTTP(S)/FTP/BitTorrent Magnet URI. *uris* is of type array and its element is URI which is of type string. For @@ -2075,7 +2107,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.addUri(['http://example.org/file'], {}, 0) 'ca3d829cee549a4d' -.. function:: aria2.addTorrent(torrent[, uris[, options[, position]]]) +.. function:: aria2.addTorrent([secret], torrent[, uris[, options[, position]]]) This method adds BitTorrent download by uploading ".torrent" file. If you want to add BitTorrent Magnet URI, use :func:`aria2.addUri` @@ -2125,7 +2157,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.addTorrent(xmlrpclib.Binary(open('file.torrent').read())) '2089b05ecca3d829' -.. function:: aria2.addMetalink(metalink[, options[, position]]) +.. function:: aria2.addMetalink([secret], metalink[, options[, position]]) This method adds Metalink download by uploading ".metalink" file. *metalink* is of type base64 which contains Base64-encoded @@ -2170,7 +2202,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.addMetalink(xmlrpclib.Binary(open('file.meta4').read())) ['2089b05ecca3d829'] -.. function:: aria2.remove(gid) +.. function:: aria2.remove([secret], gid) This method removes the download denoted by *gid*. *gid* is of type string. If specified download is in progress, it is stopped at @@ -2200,14 +2232,14 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.remove('2089b05ecca3d829') '2089b05ecca3d829' -.. function:: aria2.forceRemove(gid) +.. function:: aria2.forceRemove([secret], gid) This method removes the download denoted by *gid*. This method behaves just like :func:`aria2.remove` except that this method removes download without any action which takes time such as contacting BitTorrent tracker. -.. function:: aria2.pause(gid) +.. function:: aria2.pause([secret], gid) This method pauses the download denoted by *gid*. *gid* is of type string. The status of paused download becomes ``paused``. If the @@ -2216,36 +2248,36 @@ All code examples come from Python2.7 interpreter. started. To change status to ``waiting``, use :func:`aria2.unpause` method. This method returns GID of paused download. -.. function:: aria2.pauseAll() +.. function:: aria2.pauseAll([secret]) This method is equal to calling :func:`aria2.pause` for every active/waiting download. This methods returns ``OK`` for success. -.. function:: aria2.forcePause(pid) +.. function:: aria2.forcePause([secret], pid) This method pauses the download denoted by *gid*. This method behaves just like :func:`aria2.pause` except that this method pauses download without any action which takes time such as contacting BitTorrent tracker. -.. function:: aria2.forcePauseAll() +.. function:: aria2.forcePauseAll([secret]) This method is equal to calling :func:`aria2.forcePause` for every active/waiting download. This methods returns ``OK`` for success. -.. function:: aria2.unpause(gid) +.. function:: aria2.unpause([secret], gid) This method changes the status of the download denoted by *gid* from ``paused`` to ``waiting``. This makes the download eligible to restart. *gid* is of type string. This method returns GID of unpaused download. -.. function:: aria2.unpauseAll() +.. function:: aria2.unpauseAll([secret]) This method is equal to calling :func:`aria2.unpause` for every active/waiting download. This methods returns ``OK`` for success. -.. function:: aria2.tellStatus(gid[, keys]) +.. function:: aria2.tellStatus([secret], gid[, keys]) This method returns download progress of the download denoted by *gid*. *gid* is of type string. *keys* is array of string. If it is @@ -2443,7 +2475,7 @@ All code examples come from Python2.7 interpreter. >>> pprint(r) {'completedLength': '34896138', 'gid': '2089b05ecca3d829', 'totalLength': '34896138'} -.. function:: aria2.getUris(gid) +.. function:: aria2.getUris([secret], gid) This method returns URIs used in the download denoted by *gid*. *gid* is of type string. The response is of type array and its element is of @@ -2481,7 +2513,7 @@ All code examples come from Python2.7 interpreter. >>> pprint(r) [{'status': 'used', 'uri': 'http://example.org/file'}] -.. function:: aria2.getFiles(gid) +.. function:: aria2.getFiles([secret], gid) This method returns file list of the download denoted by *gid*. *gid* is of type string. The response is of type array and its element is of @@ -2553,7 +2585,7 @@ All code examples come from Python2.7 interpreter. 'uris': [{'status': 'used', 'uri': 'http://example.org/file'}]}] -.. function:: aria2.getPeers(gid) +.. function:: aria2.getPeers([secret], gid) This method returns peer list of the download denoted by *gid*. *gid* is of type string. This method is for BitTorrent only. The response @@ -2648,7 +2680,7 @@ All code examples come from Python2.7 interpreter. 'seeder': 'false, 'uploadSpeed': '6890'}] -.. function:: aria2.getServers(gid) +.. function:: aria2.getServers([secret], gid) This method returns currently connected HTTP(S)/FTP servers of the download denoted by *gid*. *gid* is of type string. The response is of type array and its element is of type struct and it contains @@ -2701,14 +2733,14 @@ All code examples come from Python2.7 interpreter. 'downloadSpeed': '20285', 'uri': 'http://example.org/file'}]}] -.. function:: aria2.tellActive([keys]) +.. function:: aria2.tellActive([secret], [keys]) This method returns the list of active downloads. The response is of type array and its element is the same struct returned by :func:`aria2.tellStatus` method. For *keys* parameter, please refer to :func:`aria2.tellStatus` method. -.. function:: aria2.tellWaiting(offset, num, [keys]) +.. function:: aria2.tellWaiting([secret], offset, num, [keys]) This method returns the list of waiting download, including paused downloads. *offset* is of type integer and specifies the offset from @@ -2732,7 +2764,7 @@ All code examples come from Python2.7 interpreter. The response is of type array and its element is the same struct returned by :func:`aria2.tellStatus` method. -.. function:: aria2.tellStopped(offset, num, [keys]) +.. function:: aria2.tellStopped([secret], offset, num, [keys]) This method returns the list of stopped download. *offset* is of type integer and specifies the offset from the oldest download. *num* is of @@ -2745,7 +2777,7 @@ All code examples come from Python2.7 interpreter. The response is of type array and its element is the same struct returned by :func:`aria2.tellStatus` method. -.. function:: aria2.changePosition(gid, pos, how) +.. function:: aria2.changePosition([secret], gid, pos, how) This method changes the position of the download denoted by *gid*. *pos* is of type integer. *how* is of type string. If *how* is @@ -2789,7 +2821,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.changePosition('2089b05ecca3d829', 0, 'POS_SET') 0 -.. function:: aria2.changeUri(gid, fileIndex, delUris, addUris[, position]) +.. function:: aria2.changeUri([secret], gid, fileIndex, delUris, addUris[, position]) This method removes URIs in *delUris* from and appends URIs in *addUris* to download denoted by *gid*. *delUris* and *addUris* are @@ -2837,7 +2869,7 @@ All code examples come from Python2.7 interpreter. ['http://example.org/file']) [0, 1] -.. function:: aria2.getOption(gid) +.. function:: aria2.getOption([secret], gid) This method returns options of the download denoted by *gid*. The response is of type struct. Its key is the name of option. The value @@ -2882,7 +2914,7 @@ All code examples come from Python2.7 interpreter. 'async-dns': 'true', .... -.. function:: aria2.changeOption(gid, options) +.. function:: aria2.changeOption([secret], gid, options) This method changes options of the download denoted by *gid* dynamically. *gid* is of type string. *options* is of type struct. @@ -2933,7 +2965,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.changeOption('2089b05ecca3d829', {'max-download-limit':'20K'}) 'OK' -.. function:: aria2.getGlobalOption() +.. function:: aria2.getGlobalOption([secret]) This method returns global options. The response is of type struct. Its key is the name of option. The value type is string. @@ -2943,7 +2975,7 @@ All code examples come from Python2.7 interpreter. for the options of newly added download, the response contains keys returned by :func:`aria2.getOption` method. -.. function:: aria2.changeGlobalOption(options) +.. function:: aria2.changeGlobalOption([secret], options) This method changes global options dynamically. *options* is of type struct. @@ -2974,7 +3006,7 @@ All code examples come from Python2.7 interpreter. value. Note that log file is always opened in append mode. This method returns ``OK`` for success. -.. function:: aria2.getGlobalStat() +.. function:: aria2.getGlobalStat([secret]) This method returns global statistics such as overall download and upload speed. The response is of type struct and contains following @@ -3026,12 +3058,12 @@ All code examples come from Python2.7 interpreter. 'numWaiting': '0', 'uploadSpeed': '0'} -.. function:: aria2.purgeDownloadResult() +.. function:: aria2.purgeDownloadResult([secret]) This method purges completed/error/removed downloads to free memory. This method returns ``OK``. -.. function:: aria2.removeDownloadResult(gid) +.. function:: aria2.removeDownloadResult([secret], gid) This method removes completed/error/removed download denoted by *gid* from memory. This method returns ``OK`` for success. @@ -3061,7 +3093,7 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.removeDownloadResult('2089b05ecca3d829') 'OK' -.. function:: aria2.getVersion() +.. function:: aria2.getVersion([secret]) This method returns version of the program and the list of enabled features. The response is of type struct and contains following keys. @@ -3111,7 +3143,7 @@ All code examples come from Python2.7 interpreter. 'XML-RPC'], 'version': '1.11.0'} -.. function:: aria2.getSessionInfo() +.. function:: aria2.getSessionInfo([secret]) This method returns session information. The response is of type struct and contains following key. @@ -3140,11 +3172,11 @@ All code examples come from Python2.7 interpreter. >>> s.aria2.getSessionInfo() {'sessionId': 'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'} -.. function:: aria2.shutdown() +.. function:: aria2.shutdown([secret]) This method shutdowns aria2. This method returns ``OK``. -.. function:: aria2.forceShutdown() +.. function:: aria2.forceShutdown([secret]) This method shutdowns :func:`aria2. This method behaves like aria2.shutdown` except that any actions which takes time such as contacting BitTorrent diff --git a/doc/xmlrpc/aria2rpc b/doc/xmlrpc/aria2rpc index f8a89d02..646d5df7 100755 --- a/doc/xmlrpc/aria2rpc +++ b/doc/xmlrpc/aria2rpc @@ -239,6 +239,8 @@ OptionParser.new do |opt| opt.on("--user USERNAME", "XML-RPC username"){|val| options["user"]=val } opt.on("--passwd PASSWORD", "XML-RPC password"){|val| options["passwd"]=val } + opt.on("--secret SECRET", "XML-RPC secret authorization token"){|val| options["secret"]=val } + opt.banner=< options["server"], :port => options["port"], @@ -311,79 +314,89 @@ options.delete("server") options.delete("port") options.delete("user") options.delete("passwd") +options.delete("secret") + +def client_call client, secret, method, *params + if secret.nil? + client.call(method, *params) + else + client.call(method, secret, *params) + end +end if command == "addUri" then - result=client.call("aria2."+command, resources, options) + result=client_call(client, secret, "aria2."+command, resources, options) elsif command == "addTorrent" then torrentData=IO.read(resources[0]) - result=client.call("aria2."+command, + result=client_call(client, secret, "aria2."+command, XMLRPC::Base64.new(torrentData), resources[1..-1], options) elsif command == "addMetalink" then metalinkData=IO.read(resources[0]) - result=client.call("aria2."+command, + result=client_call(client, secret, "aria2."+command, XMLRPC::Base64.new(metalinkData), options) elsif command == "tellStatus" then - result=client.call("aria2."+command, resources[0], resources[1..-1]) + result=client_call(client, secret, "aria2."+command, + resources[0], resources[1..-1]) elsif command == "tellActive" then - result=client.call("aria2."+command, resources[0..-1]) + result=client_call(client, secret, "aria2."+command, resources[0..-1]) elsif command == "tellWaiting" then - result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(), - resources[2..-1]) + result=client_call(client, secret, "aria2."+command, resources[0].to_i(), + resources[1].to_i(), resources[2..-1]) elsif command == "tellStopped" then - result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(), - resources[2..-1]) + result=client_call(client, secret, "aria2."+command, resources[0].to_i(), + resources[1].to_i(), resources[2..-1]) elsif command == "getOption" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "getGlobalOption" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "pause" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "pauseAll" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "forcePause" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "forcePauseAll" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "unpause" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "unpauseAll" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "remove" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "forceRemove" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "changePosition" then - result=client.call("aria2."+command, resources[0], resources[1].to_i(), - resources[2]) + result=client_call(client, secret, "aria2."+command, resources[0], + resources[1].to_i(), resources[2]) elsif command == "getFiles" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "getUris" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "getPeers" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "getServers" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "purgeDownloadResult" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "removeDownloadResult" then - result=client.call("aria2."+command, resources[0]) + result=client_call(client, secret, "aria2."+command, resources[0]) elsif command == "changeOption" then - result=client.call("aria2."+command, resources[0], options) + result=client_call(client, secret, "aria2."+command, resources[0], options) elsif command == "changeGlobalOption" then - result=client.call("aria2."+command, options) + result=client_call(client, secret, "aria2."+command, options) elsif command == "getVersion" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "getSessionInfo" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "shutdown" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "forceShutdown" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "getGlobalStat" then - result=client.call("aria2."+command) + result=client_call(client, secret, "aria2."+command) elsif command == "appendUri" then - result=client.call("aria2.changeUri", resources[0], resources[1].to_i(), [], - resources[2..-1]) + result=client_call(client, secret, "aria2.changeUri", resources[0], + resources[1].to_i(), [], resources[2..-1]) else puts "Command not recognized" exit 1 diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc index 33b04778..e709b67b 100644 --- a/src/OptionHandlerFactory.cc +++ b/src/OptionHandlerFactory.cc @@ -865,6 +865,14 @@ std::vector OptionHandlerFactory::createOptionHandlers() op->setChangeGlobalOption(true); handlers.push_back(op); } + { + OptionHandler* op(new DefaultOptionHandler + (PREF_RPC_SECRET, + TEXT_RPC_SECRET)); + op->addTag(TAG_RPC); + op->setEraseAfterParse(true); + handlers.push_back(op); + } { OptionHandler* op(new BooleanOptionHandler (PREF_RPC_SECURE, diff --git a/src/RpcMethod.cc b/src/RpcMethod.cc index a9b6f8d9..e7b2750f 100644 --- a/src/RpcMethod.cc +++ b/src/RpcMethod.cc @@ -48,6 +48,7 @@ #include "fmt.h" #include "DlAbortEx.h" #include "a2functional.h" +#include "util.h" namespace aria2 { @@ -68,9 +69,33 @@ std::unique_ptr RpcMethod::createErrorResponse return std::move(params); } +void RpcMethod::authorize(RpcRequest& req, DownloadEngine* e) +{ + std::string token; + // We always treat first parameter as token if it is string and + // starts with "token:" and remove it from parameter list, so that + // we don't have to add conditionals to all RPCMethod + // implementations. + if(req.params && !req.params->empty()) { + auto t = downcast(req.params->get(0)); + if(t) { + if(util::startsWith(t->s(), "token:")) { + token = t->s().substr(6); + req.params->pop_front(); + } + } + } + if(e && e->getOption()->defined(PREF_RPC_SECRET)) { + if(token != e->getOption()->get(PREF_RPC_SECRET)) { + throw DL_ABORT_EX("Unauthorized"); + } + } +} + RpcResponse RpcMethod::execute(RpcRequest req, DownloadEngine* e) { try { + authorize(req, e); auto r = process(req, e); return RpcResponse(0, std::move(r), std::move(req.id)); } catch(RecoverableException& ex) { diff --git a/src/RpcMethod.h b/src/RpcMethod.h index a710aab4..f5fa2024 100644 --- a/src/RpcMethod.h +++ b/src/RpcMethod.h @@ -93,6 +93,8 @@ public: virtual ~RpcMethod(); + virtual void authorize(RpcRequest& req, DownloadEngine* e); + // Do work to fulfill RpcRequest req and returns its result as // RpcResponse. This method delegates to process() method. RpcResponse execute(RpcRequest req, DownloadEngine* e); diff --git a/src/RpcMethodImpl.h b/src/RpcMethodImpl.h index 5671d3f9..b20087ae 100644 --- a/src/RpcMethodImpl.h +++ b/src/RpcMethodImpl.h @@ -572,6 +572,14 @@ protected: virtual std::unique_ptr process (const RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE; public: + virtual void authorize(RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE + { + // Batch calls (e.g., system.multicall) authorizes only nested + // methods. This is because XML-RPC system.multicall only accpets + // methods array and there is no room for us to insert token + // parameter. + } + static const char* getMethodName() { return "system.multicall"; diff --git a/src/ValueBase.cc b/src/ValueBase.cc index d8a3afdf..108bac45 100644 --- a/src/ValueBase.cc +++ b/src/ValueBase.cc @@ -151,6 +151,16 @@ void List::set(size_t index, std::unique_ptr v) list_[index] = std::move(v); } +void List::pop_front() +{ + list_.pop_front(); +} + +void List::pop_back() +{ + list_.pop_back(); +} + void List::append(std::unique_ptr v) { list_.push_back(std::move(v)); diff --git a/src/ValueBase.h b/src/ValueBase.h index e65699c1..bf6a8c1c 100644 --- a/src/ValueBase.h +++ b/src/ValueBase.h @@ -38,7 +38,7 @@ #include "common.h" #include -#include +#include #include #include @@ -170,7 +170,7 @@ private: class List:public ValueBase { public: - typedef std::vector> ValueType; + typedef std::deque> ValueType; List(); @@ -196,6 +196,12 @@ public: // Returns the const reference of the object at the given index. ValueBase* operator[](size_t index) const; + // Pops the value in the front of the list. + void pop_front(); + + // Pops the value in the back of the list. + void pop_back(); + // Returns a read/write iterator that points to the first object in // list. ValueType::iterator begin(); diff --git a/src/prefs.cc b/src/prefs.cc index 48632e5b..b22286ff 100644 --- a/src/prefs.cc +++ b/src/prefs.cc @@ -359,6 +359,8 @@ PrefPtr PREF_GID = makePref("gid"); // values: 1*digit PrefPtr PREF_SAVE_SESSION_INTERVAL = makePref("save-session-interval"); PrefPtr PREF_ENABLE_COLOR = makePref("enable-color"); +// value: string +PrefPtr PREF_RPC_SECRET = makePref("rpc-secret"); /** * FTP related preferences diff --git a/src/prefs.h b/src/prefs.h index a6b30630..76be3ee2 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -296,6 +296,8 @@ extern PrefPtr PREF_GID; extern PrefPtr PREF_SAVE_SESSION_INTERVAL; // value: true |false extern PrefPtr PREF_ENABLE_COLOR; +// value: string +extern PrefPtr PREF_RPC_SECRET; /** * FTP related preferences diff --git a/src/usage_text.h b/src/usage_text.h index 914af5ce..b1c7fc7c 100644 --- a/src/usage_text.h +++ b/src/usage_text.h @@ -770,11 +770,11 @@ " option is useful when the system does not have\n" \ " /etc/resolv.conf and user does not have the\n" \ " permission to create it.") -#define TEXT_ENABLE_RPC \ - _(" --enable-rpc[=true|false] Enable JSON-RPC/XML-RPC server.\n" \ - " It is strongly recommended to set username and\n" \ - " password using --rpc-user and --rpc-passwd\n" \ - " option. See also --rpc-listen-port option.") +#define TEXT_ENABLE_RPC \ + _(" --enable-rpc[=true|false] Enable JSON-RPC/XML-RPC server.\n" \ + " It is strongly recommended to set secret\n" \ + " authorization token using --rpc-secret option.\n" \ + " See also --rpc-listen-port option.") #define TEXT_RPC_MAX_REQUEST_SIZE \ _(" --rpc-max-request-size=SIZE Set max size of JSON-RPC/XML-RPC request. If aria2\n" \ " detects the request is more than SIZE bytes, it\n" \ @@ -960,3 +960,5 @@ " when aria2 exits.") #define TEXT_ENABLE_COLOR \ _(" --enable-color[=true|false] Enable color output for a terminal.") +#define TEXT_RPC_SECRET \ + _(" --rpc-secret=TOKEN Set RPC secret authorization token.") diff --git a/test/RpcMethodTest.cc b/test/RpcMethodTest.cc index 15a94b45..07ccddfa 100644 --- a/test/RpcMethodTest.cc +++ b/test/RpcMethodTest.cc @@ -33,6 +33,7 @@ namespace rpc { class RpcMethodTest:public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RpcMethodTest); + CPPUNIT_TEST(testAuthorize); CPPUNIT_TEST(testAddUri); CPPUNIT_TEST(testAddUri_withoutUri); CPPUNIT_TEST(testAddUri_notUri); @@ -96,6 +97,7 @@ public: (std::vector>{}, 1, option_.get())); } + void testAuthorize(); void testAddUri(); void testAddUri_withoutUri(); void testAddUri_notUri(); @@ -159,6 +161,47 @@ RpcRequest createReq(std::string methodName) } } // namespace +void RpcMethodTest::testAuthorize() +{ + // Select RPC method which takes non-string parameter to make sure + // that token: prefixed parameter is stripped before the call. + TellActiveRpcMethod m; + // no secret token set and no token: prefixed parameter is given + { + auto req = createReq(TellActiveRpcMethod::getMethodName()); + auto res = m.execute(std::move(req), e_.get()); + CPPUNIT_ASSERT_EQUAL(0, res.code); + } + // no secret token set and token: prefixed parameter is given + { + auto req = createReq(GetVersionRpcMethod::getMethodName()); + req.params->append("token:foo"); + auto res = m.execute(std::move(req), e_.get()); + CPPUNIT_ASSERT_EQUAL(0, res.code); + } + e_->getOption()->put(PREF_RPC_SECRET, "foo"); + // secret token set and no token: prefixed parameter is given + { + auto req = createReq(GetVersionRpcMethod::getMethodName()); + auto res = m.execute(std::move(req), e_.get()); + CPPUNIT_ASSERT_EQUAL(1, res.code); + } + // secret token set and token: prefixed parameter is given + { + auto req = createReq(GetVersionRpcMethod::getMethodName()); + req.params->append("token:foo"); + auto res = m.execute(std::move(req), e_.get()); + CPPUNIT_ASSERT_EQUAL(0, res.code); + } + // secret token set and bad token: prefixed parameter is given + { + auto req = createReq(GetVersionRpcMethod::getMethodName()); + req.params->append("token:foo2"); + auto res = m.execute(std::move(req), e_.get()); + CPPUNIT_ASSERT_EQUAL(1, res.code); + } +} + void RpcMethodTest::testAddUri() { AddUriRpcMethod m;