mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-09-23 22:34:42 -04:00
Implement JSON API HTTP Basic authentication
jsonapi-generator is now capable of generating API for headers outside retroshare/ directory jsonapi-generator do a bit of methods parameter sanity check JsonApiServer is now integrated in the rsinit hell like other services Add *::exportGPGKeyPairToString to a bunch of classes in cascade RsControl is now capable of calling back a function when retroshare is almost completely stopped, this is useful when running retroshare toghether with externally managed runloop such as QCoreApplication Expose a bunch of methods through JSON API retroshare-nogui remove some dead code and fix stopping from the RetroShare API
This commit is contained in:
parent
ac9350d375
commit
eb77f921ec
32 changed files with 816 additions and 398 deletions
|
@ -55,7 +55,7 @@ extern RsGxsChannels* rsGxsChannels;
|
|||
.Calling the JSON API with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
curl -u $API_USER --data @paramethers.json http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.JSON API call result
|
||||
|
@ -76,7 +76,7 @@ least in two different ways.
|
|||
.Calling the JSON API with GET method with curl on the terminal
|
||||
[source,bash]
|
||||
--------------------------------------------------------------------------------
|
||||
curl --get --data-urlencode jsonData@paramethers.json \
|
||||
curl -u $API_USER --get --data-urlencode jsonData@paramethers.json \
|
||||
http://127.0.0.1:9092/rsGxsChannels/createGroup
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@ -85,12 +85,53 @@ equivalent to the following.
|
|||
|
||||
.Calling the JSON API with GET method and pre-encoded data with curl on the terminal
|
||||
--------------------------------------------------------------------------------
|
||||
curl http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D
|
||||
curl -u $API_USER http://127.0.0.1:9092/rsGxsChannels/createGroup?jsonData=%7B%0A%20%20%20%20%22group%22%3A%7B%0A%20%20%20%20%20%20%20%20%22mMeta%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupName%22%3A%22JSON%20test%20group%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mGroupFlags%22%3A4%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22mSignFlags%22%3A520%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%22mDescription%22%3A%22JSON%20test%20group%20description%22%0A%20%20%20%20%7D%2C%0A%20%20%20%20%22caller_data%22%3A%22Here%20can%20go%20any%20kind%20of%20JSON%20data%20%28even%20objects%29%20that%20the%20caller%20want%20to%20get%20back%20together%20with%20the%20response%22%0A%7D
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Note that using +GET+ method +?jsonData=+ and then the JSON data URL encoded are
|
||||
added after the path in the HTTP request.
|
||||
|
||||
|
||||
== JSON API authentication
|
||||
|
||||
Most of JSON API methods require authentication as they give access to
|
||||
RetroShare user data, and we don't want any application running on the system
|
||||
eventually by other users be able to access private data indiscriminately.
|
||||
JSON API support HTTP Basic as authentication scheme, this is enough as JSON API
|
||||
server is intented for usage on the same system (127.0.0.1) not over an
|
||||
untrusted network.
|
||||
If you need to use JSON API over an untrusted network consider using a reverse
|
||||
proxy with HTTPS such as NGINX in front of JSON API server.
|
||||
If RetroShare login has been effectuated through the JSON API you can use your
|
||||
location SSLID as username and your PGP password as credential for the JSON API,
|
||||
but we suggests you use specific meaningful and human readable credentials for
|
||||
each JSON API client so the human user can have better control over which client
|
||||
can access the JSON API.
|
||||
|
||||
.NewToken.json
|
||||
[source,json]
|
||||
--------------------------------------------------------------------------------
|
||||
{
|
||||
"token": "myNewUser:myNewPassword"
|
||||
}
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An authenticated client can authorize new tokens like this
|
||||
--------------------------------------------------------------------------------
|
||||
curl -u $API_USER --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/authorizeToken
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
.An unauthenticated JSON API client can request access with
|
||||
--------------------------------------------------------------------------------
|
||||
curl --data @NewToken.json http://127.0.0.1:9092/jsonApiServer/requestNewTokenAutorization
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
When an unauthenticated client request his token to be authorized, JSON API
|
||||
server will try to ask confirmation to the human user if possible through
|
||||
+mNewAccessRequestCallback+, if it is not possible or the user didn't authorized
|
||||
the token +false+ is returned.
|
||||
|
||||
|
||||
== Offer new RetroShare services through JSON API
|
||||
|
||||
To offer a retroshare service through the JSON API, first of all one need find
|
||||
|
@ -231,6 +272,17 @@ data: {"results":[{"size":668,"hash":"e8845280912ebf3779e400000000000000000000",
|
|||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
By default JSON API methods requires client authentication and their wrappers
|
||||
are automatically generated by +json-api-generator+.
|
||||
In some cases methods need do be accessible without authentication such as
|
||||
+rsLoginHelper/getLocations+ so in the doxygen documentaion they have the custom
|
||||
command +@jsonapi{RS_VERSION,unauthenticated}+.
|
||||
Other methods such as +/rsControl/rsGlobalShutDown+ need special care so they
|
||||
are marked with the custom doxygen command +@jsonapi{RS_VERSION,manualwrapper}+
|
||||
and their wrappers are not automatically generated but written manually into
|
||||
+JsonApiServer::JsonApiServer(...)+.
|
||||
|
||||
|
||||
== A bit of history
|
||||
|
||||
=== First writings about this
|
||||
|
|
|
@ -61,5 +61,5 @@ $%outputParamsSerialization%$
|
|||
session->yield(message.str());
|
||||
$%sessionDelayedClose%$
|
||||
} );
|
||||
});
|
||||
}, $%requiresAuth%$);
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ DOXYFILE_ENCODING = UTF-8
|
|||
PROJECT_NAME = "libretroshare"
|
||||
|
||||
ALIASES += jsonapi{1}="\xmlonly<jsonapi minversion=\"\1\"/>\endxmlonly"
|
||||
ALIASES += jsonapi{2}="\xmlonly<jsonapi minversion=\"\1\" access=\"\2\"/>\endxmlonly"
|
||||
|
||||
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
|
||||
# documentation from any documented member that it re-implements.
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include <QVariant>
|
||||
|
||||
struct MethodParam
|
||||
{
|
||||
|
@ -40,7 +42,10 @@ struct MethodParam
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
if(argc != 3)
|
||||
qFatal("Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH");
|
||||
{
|
||||
qDebug() << "Usage: jsonapi-generator SOURCE_PATH OUTPUT_PATH";
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
QString sourcePath(argv[1]);
|
||||
QString outputPath(argv[2]);
|
||||
|
@ -59,6 +64,20 @@ int main(int argc, char *argv[])
|
|||
qFatal(QString("Can't open: " + cppApiIncludesFilePath).toLatin1().data());
|
||||
QSet<QString> cppApiIncludesSet;
|
||||
|
||||
auto fatalError = [&](
|
||||
std::initializer_list<QVariant> errors, int ernum = EINVAL )
|
||||
{
|
||||
QString errorMsg;
|
||||
for(const QVariant& error: errors)
|
||||
errorMsg += error.toString() + " ";
|
||||
errorMsg.chop(1);
|
||||
QByteArray cppError(QString("#error "+errorMsg+"\n").toLocal8Bit());
|
||||
wrappersDefFile.write(cppError);
|
||||
cppApiIncludesFile.write(cppError);
|
||||
qDebug() << errorMsg;
|
||||
return ernum;
|
||||
};
|
||||
|
||||
QDirIterator it(doxPrefix, QStringList() << "*8h.xml", QDir::Files);
|
||||
while(it.hasNext())
|
||||
{
|
||||
|
@ -74,9 +93,10 @@ int main(int argc, char *argv[])
|
|||
continue;
|
||||
}
|
||||
|
||||
QFileInfo hfi(hFile);
|
||||
QString headerFileName(hfi.fileName());
|
||||
headerFileName.replace(QString("_8h.xml"), QString(".h"));
|
||||
QFileInfo headerFileInfo(hDoc.elementsByTagName("location").at(0)
|
||||
.attributes().namedItem("file").nodeValue());
|
||||
QString headerRelPath = headerFileInfo.dir().dirName() + "/" +
|
||||
headerFileInfo.fileName();
|
||||
|
||||
QDomNodeList sectiondefs = hDoc.elementsByTagName("memberdef");
|
||||
for(int j = 0; j < sectiondefs.size(); ++j)
|
||||
|
@ -112,6 +132,7 @@ int main(int argc, char *argv[])
|
|||
QDomNode member = members.item(i);
|
||||
QString refid(member.attributes().namedItem("refid").nodeValue());
|
||||
QString methodName(member.firstChildElement("name").toElement().text());
|
||||
bool requiresAuth = true;
|
||||
QString defFilePath(doxPrefix + refid.split('_')[0] + ".xml");
|
||||
|
||||
qDebug() << "Looking for" << typeName << methodName << "into"
|
||||
|
@ -134,10 +155,21 @@ int main(int argc, char *argv[])
|
|||
QDomElement tmpMBD = memberdefs.item(k).toElement();
|
||||
QString tmpId = tmpMBD.attributes().namedItem("id").nodeValue();
|
||||
QString tmpKind = tmpMBD.attributes().namedItem("kind").nodeValue();
|
||||
bool hasJsonApi = !tmpMBD.elementsByTagName("jsonapi").isEmpty();
|
||||
if( tmpId == refid && tmpKind == "function" && hasJsonApi )
|
||||
QDomNodeList tmpJsonApiTagList(tmpMBD.elementsByTagName("jsonapi"));
|
||||
|
||||
if( tmpJsonApiTagList.count() && tmpId == refid &&
|
||||
tmpKind == "function")
|
||||
{
|
||||
memberdef = tmpMBD;
|
||||
QDomElement tmpJsonApiTag = tmpJsonApiTagList.item(0).toElement();
|
||||
QString tmpAccessValue;
|
||||
if(tmpJsonApiTag.hasAttribute("access"))
|
||||
tmpAccessValue = tmpJsonApiTag.attributeNode("access").nodeValue();
|
||||
|
||||
requiresAuth = "unauthenticated" != tmpAccessValue;
|
||||
|
||||
if("manualwrapper" != tmpAccessValue)
|
||||
memberdef = tmpMBD;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +221,7 @@ int main(int argc, char *argv[])
|
|||
pType.replace(QString("&"), QString());
|
||||
pType.replace(QString(" "), QString());
|
||||
}
|
||||
|
||||
paramsMap.insert(tmpParam.name, tmpParam);
|
||||
orderedParamNames.push_back(tmpParam.name);
|
||||
}
|
||||
|
@ -211,6 +244,16 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
|
||||
// Params sanity check
|
||||
for( const MethodParam& pm : paramsMap)
|
||||
if( !(pm.isMultiCallback || pm.isSingleCallback
|
||||
|| pm.in || pm.out) )
|
||||
return fatalError(
|
||||
{ "Parameter:", pm.name, "of:", apiPath,
|
||||
"declared in:", headerRelPath,
|
||||
"miss doxygen parameter direction attribute!",
|
||||
defFile.fileName() });
|
||||
|
||||
QString functionCall("\t\t");
|
||||
if(retvalType != "void")
|
||||
{
|
||||
|
@ -325,7 +368,6 @@ int main(int argc, char *argv[])
|
|||
substitutionsMap.insert("inputParamsDeserialization", inputParamsDeserialization);
|
||||
substitutionsMap.insert("outputParamsSerialization", outputParamsSerialization);
|
||||
substitutionsMap.insert("instanceName", instanceName);
|
||||
substitutionsMap.insert("headerFileName", headerFileName);
|
||||
substitutionsMap.insert("functionCall", functionCall);
|
||||
substitutionsMap.insert("apiPath", apiPath);
|
||||
substitutionsMap.insert("sessionEarlyClose", sessionEarlyClose);
|
||||
|
@ -334,6 +376,7 @@ int main(int argc, char *argv[])
|
|||
substitutionsMap.insert("callbackName", callbackName);
|
||||
substitutionsMap.insert("callbackParams", callbackParams);
|
||||
substitutionsMap.insert("callbackParamsSerialization", callbackParamsSerialization);
|
||||
substitutionsMap.insert("requiresAuth", requiresAuth ? "true" : "false");
|
||||
|
||||
QString templFilePath(sourcePath);
|
||||
if(hasMultiCallback || hasSingleCallback)
|
||||
|
@ -351,7 +394,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
wrappersDefFile.write(wrapperDef.toLocal8Bit());
|
||||
|
||||
cppApiIncludesSet.insert("#include \"retroshare/" + headerFileName + "\"\n");
|
||||
cppApiIncludesSet.insert("#include \"" + headerRelPath + "\"\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,5 +44,5 @@ $%outputParamsSerialization%$
|
|||
// return them to the API caller
|
||||
DEFAULT_API_CALL_JSON_RETURN(rb::OK);
|
||||
} );
|
||||
});
|
||||
}, $%requiresAuth%$);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue