//==== SYSCACHE ==== RestOutputStruct::ExitCode WebSocket::EvaluateSyscacheRequest(const QJsonValue in_args, QJsonObject *out){ //syscache only needs a list of sub-commands at the moment (might change later) QStringList in_req; //Parse the input arguments structure if(in_args.isArray()){ in_req = JsonArrayToStringList(in_args.toArray()); } else if(in_args.isObject()){ QStringList keys = in_args.toObject().keys(); for(int i=0; i<keys.length(); i++){ in_req << JsonValueToString(in_args.toObject().value(keys[i])); } }else{ return RestOutputStruct::BADREQUEST; } //Run the Request (should be one value for each in_req) QStringList values = SysCacheClient::parseInputs(in_req); while(values.length() < in_req.length()){ values << "[ERROR]"; } //ensure lists are same length //Format the result for(int i=0; i<values.length(); i++){ if(values[i].contains(SCLISTDELIM)){ //This is an array of values from syscache QStringList vals = values[i].split(SCLISTDELIM); vals.removeAll(""); QJsonArray arr; for(int j=0; j<vals.length(); j++){ arr.append(vals[j]); } out->insert(in_req[i],arr); }else{ out->insert(in_req[i],values[i]); } } //Return Success return RestOutputStruct::OK; }
// === SYSCACHE REQUEST INTERACTION === void WebSocket::EvaluateBackendRequest(QString name, const QJsonValue args, QJsonObject *out){ QJsonObject obj; //output object if(args.isObject()){ //For the moment: all arguments are full syscache DB calls - no special ones QStringList reqs = args.toObject().keys(); if(!reqs.isEmpty()){ if(DEBUG){ qDebug() << "Parsing Inputs:" << reqs; } for(int r=0; r<reqs.length(); r++){ QString req = JsonValueToString(args.toObject().value(reqs[r])); if(DEBUG){ qDebug() << " ["+reqs[r]+"]="+req; } QStringList values; if(name.toLower()=="syscache"){values = SysCacheClient::parseInputs( QStringList() << req ); } else if(name.toLower()=="dispatcher"){values = DispatcherClient::parseInputs( QStringList() << req, AUTHSYSTEM); } values.removeAll(""); //Quick check if a list of outputs was returned if(values.length()==1 && name.toLower()=="syscache"){ values = values[0].split(SCLISTDELIM); //split up the return list (if necessary) values.removeAll(""); } if(DEBUG){ qDebug() << " - Returns:" << values; } if(values.length()<2){ out->insert(req, QJsonValue(values.join("")) ); } else{ //This is an array of outputs QJsonArray arr; for(int i=0; i<values.length(); i++){ arr.append(values[i]); } out->insert(req,arr); } } } //end of special "request" objects }else if(args.isArray()){ QStringList inputs = JsonArrayToStringList(args.toArray()); if(DEBUG){ qDebug() << "Parsing Array inputs:" << inputs; } QStringList values; if(name.toLower()=="syscache"){values = SysCacheClient::parseInputs( inputs ); } else if(name.toLower()=="dispatcher"){values = DispatcherClient::parseInputs( inputs , AUTHSYSTEM); } if(DEBUG){ qDebug() << " - Returns:" << values; } for(int i=0; i<values.length(); i++){ if(name.toLower()=="syscache" && values[i].contains(SCLISTDELIM)){ //This is an array of values from syscache QStringList vals = values[i].split(SCLISTDELIM); vals.removeAll(""); QJsonArray arr; for(int j=0; j<vals.length(); j++){ arr.append(vals[j]); } out->insert(inputs[i],arr); }else{ out->insert(inputs[i],values[i]); } } } //end array of inputs }
QString WebServer::JsonValueToString(QJsonValue val){ //Note: Do not use this on arrays - only use this on single-value values QString out; switch(val.type()){ case QJsonValue::Bool: out = (val.toBool() ? "true": "false"); break; case QJsonValue::Double: out = QString::number(val.toDouble()); break; case QJsonValue::String: out = val.toString(); break; case QJsonValue::Array: out = "\""+JsonArrayToStringList(val.toArray()).join("\" \"")+"\""; default: out.clear(); } return out; }
//==== DISPATCHER ==== RestOutputStruct::ExitCode WebSocket::EvaluateDispatcherRequest(const QJsonValue in_args, QJsonObject *out){ //dispatcher only needs a list of sub-commands at the moment (might change later) if(!AUTHSYSTEM->hasFullAccess(SockAuthToken)){ return RestOutputStruct::FORBIDDEN; //this user does not have permission to queue jobs } QStringList in_req; //Parse the input arguments structure if(in_args.isArray()){ in_req = JsonArrayToStringList(in_args.toArray()); } else if(in_args.isObject()){ QStringList keys = in_args.toObject().keys(); for(int i=0; i<keys.length(); i++){ in_req << JsonValueToString(in_args.toObject().value(keys[i])); } }else{ return RestOutputStruct::BADREQUEST; } //Run the Request (should be one value for each in_req) QStringList values = DispatcherClient::parseInputs(in_req, AUTHSYSTEM); while(values.length() < in_req.length()){ values << "[ERROR]"; } //ensure lists are same length //Format the result for(int i=0; i<values.length(); i++){ out->insert(in_req[i],values[i]); } //Return Success return RestOutputStruct::OK; }
void WebServer::EvaluateRequest(const RestInputStruct &REQ){ RestOutputStruct out; if(REQ.VERB != "GET"){ //Non-supported request (at the moment) - return an error message out.CODE = RestOutputStruct::BADREQUEST; }else{ //GET request //Now check the body of the message and do what it needs QJsonDocument doc = QJsonDocument::fromJson(REQ.Body.toUtf8()); if(doc.isNull()){ qWarning() << "Empty JSON Message Body!!" << REQ.Body.toUtf8(); } //Define the output structures QJsonDocument ret; //return message //parse the message and do something //Objects contain other key/value pairs - this is 99% of cases if(doc.isObject()){ QJsonObject obj; QStringList keys = doc.object().keys(); if(REQ.URI.toLower()=="/syscache"){ QStringList reqs = keys.filter("request",Qt::CaseInsensitive); if(!reqs.isEmpty()){ qDebug() << "Parsing Inputs:" << reqs; for(int r=0; r<reqs.length(); r++){ QString req = JsonValueToString(doc.object().value(reqs[r])); qDebug() << " ["+reqs[r]+"]="+req; QStringList values = SysCacheClient::parseInputs( QStringList() << req ); values.removeAll(""); //Quick check if a list of outputs was returned if(values.length()==1){ values = values[0].split(SCLISTDELIM); //split up the return list (if necessary) values.removeAll(""); } qDebug() << " - Returns:" << values; keys.removeAll(reqs[r]); //this key was already processed if(values.length()<2){ obj.insert(reqs[r],values.join("")); }else{ //This is an array of outputs QJsonArray arr; for(int i=0; i<values.length(); i++){ arr.append(values[i]); } obj.insert(reqs[r],arr); } } } //end of special "request" objects }else{ qDebug() << "Object Variables:" << keys; for(int i=0; i<keys.length(); i++){ qDebug() << keys[i]+"="+JsonValueToString(doc.object().value(keys[i]) ); } } ret.setObject(obj); //Special case for a single syscache input (array of strings) }else if(doc.isArray() && REQ.URI.toLower()=="/syscache"){ QStringList inputs = JsonArrayToStringList(doc.array()); qDebug() << " syscache inputs:" << inputs; QJsonObject obj; QStringList values = SysCacheClient::parseInputs(inputs ); for(int i=0; i<values.length(); i++){ if(values[i].contains(SCLISTDELIM)){ //This is an array of values QStringList vals = values[i].split(SCLISTDELIM); vals.removeAll(""); QJsonArray arr; for(int j=0; j<vals.length(); j++){ arr.append(vals[j]); } obj.insert("Value"+QString::number(i),arr); }else{ obj.insert("Value"+QString::number(i),values[i]); } } ret.setObject(obj); } //Assemble the outputs for this "GET" request out.CODE = RestOutputStruct::OK; out.Body = ret.toJson(); out.Header << "Content-Type: text/json; charset=utf-8"; } //Return any information csock->sendTextMessage(out.assembleMessage()); }
void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ RestOutputStruct out; if(REQ.VERB != "GET"){ //Non-supported request (at the moment) - return an error message out.CODE = RestOutputStruct::BADREQUEST; }else{ //GET request //Now check the body of the message and do what it needs QJsonDocument doc = QJsonDocument::fromJson(REQ.Body.toUtf8()); if(doc.isNull()){ qWarning() << "Empty JSON Message Body!!" << REQ.Body.toUtf8(); } //Define the output structures QJsonObject ret; //return message //Objects contain other key/value pairs - this is 99% of cases if(doc.isObject()){ //First check/set all the various required fields (both in and out) bool good = doc.object().contains("namespace") \ && doc.object().contains("name") \ && doc.object().contains("id") \ && doc.object().contains("args"); //Can add some fallbacks for missing fields here - but not implemented yet //parse the message and do something if(good && (JsonValueToString(doc.object().value("namespace"))=="rpc") ){ //Now fetch the outputs from the appropriate subsection //Note: Each subsection needs to set the "name", "namespace", and "args" output objects QString name = JsonValueToString(doc.object().value("name")).toLower(); QJsonValue args = doc.object().value("args"); if(name.startsWith("auth")){ //Now perform authentication based on type of auth given //Note: This sets/changes the current SockAuthToken AUTHSYSTEM->clearAuth(SockAuthToken); //new auth requested - clear any old token if(DEBUG){ qDebug() << "Authenticate Peer:" << SOCKET->peerAddress().toString(); } bool localhost = (SOCKET->peerAddress() == QHostAddress::LocalHost) || (SOCKET->peerAddress() == QHostAddress::LocalHostIPv6); //Now do the auth if(name=="auth" && args.isObject() ){ //username/password authentication QString user, pass; if(args.toObject().contains("username")){ user = JsonValueToString(args.toObject().value("username")); } if(args.toObject().contains("password")){ pass = JsonValueToString(args.toObject().value("password")); } SockAuthToken = AUTHSYSTEM->LoginUP(localhost, user, pass); }else if(name == "auth_token" && args.isObject()){ SockAuthToken = JsonValueToString(args.toObject().value("token")); }else if(name == "auth_clear"){ return; //don't send a return message after clearing an auth (already done) } //Now check the auth and respond appropriately if(AUTHSYSTEM->checkAuth(SockAuthToken)){ //Good Authentication - return the new token ret.insert("namespace", QJsonValue("rpc")); ret.insert("name", QJsonValue("response")); ret.insert("id", doc.object().value("id")); //use the same ID for the return message QJsonArray array; array.append(SockAuthToken); array.append(AUTHSYSTEM->checkAuthTimeoutSecs(SockAuthToken)); ret.insert("args", array); }else{ SockAuthToken.clear(); //invalid token //Bad Authentication - return error SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized"); } }else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Now provide access to the various subsystems //Pre-set any output fields QJsonObject outargs; ret.insert("namespace", QJsonValue("rpc")); ret.insert("name", QJsonValue("response")); ret.insert("id", doc.object().value("id")); //use the same ID for the return message EvaluateBackendRequest(name, doc.object().value("args"), &outargs); ret.insert("args",outargs); }else{ //Bad/No authentication SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized"); } }else if(good && (JsonValueToString(doc.object().value("namespace"))=="events") ){ if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Pre-set any output fields QJsonObject outargs; ret.insert("namespace", QJsonValue("events")); ret.insert("name", QJsonValue("response")); ret.insert("id", doc.object().value("id")); //use the same ID for the return message //Assemble the list of input events QStringList evlist; if(doc.object().value("args").isObject()){ evlist << JsonValueToString(doc.object().value("args")); } else if(doc.object().value("args").isArray()){ evlist = JsonArrayToStringList(doc.object().value("args").toArray()); } //Now subscribe/unsubscribe to these events if(JsonValueToString(doc.object().value("name"))=="subscribe"){ if(evlist.contains("dispatcher")){ SendAppCafeEvents = true; outargs.insert("subscribe",QJsonValue("dispatcher")); QTimer::singleShot(100, this, SLOT(AppCafeStatusUpdate()) ); } }else if(JsonValueToString(doc.object().value("name"))=="unsubscribe"){ if(evlist.contains("dispatcher")){ SendAppCafeEvents = false; outargs.insert("unsubscribe",QJsonValue("dispatcher")); } }else{ outargs.insert("unknown",QJsonValue("unknown")); } ret.insert("args",outargs); }else{ //Bad/No authentication SetOutputError(&ret, JsonValueToString(doc.object().value("id")), 401, "Unauthorized"); } }else{ //Error in inputs - assemble the return error message QString id = "error"; if(doc.object().contains("id")){ id = JsonValueToString(doc.object().value("id")); } //use the same ID SetOutputError(&ret, id, 400, "Bad Request"); } }else{ //Unknown type of JSON input - nothing to do } //Assemble the outputs for this "GET" request out.CODE = RestOutputStruct::OK; //Assemble the output JSON document/text QJsonDocument retdoc; retdoc.setObject(ret); out.Body = retdoc.toJson(); out.Header << "Content-Type: text/json; charset=utf-8"; } //Return any information SOCKET->sendTextMessage(out.assembleMessage()); }
void WebSocket::EvaluateRequest(const RestInputStruct &REQ){ RestOutputStruct out; out.in_struct = REQ; QHostAddress host; if(SOCKET!=0){ host = SOCKET->peerAddress(); } else if(TSOCKET!=0){ host = TSOCKET->peerAddress(); } if(!REQ.VERB.isEmpty() && REQ.VERB != "GET" && REQ.VERB!="POST" && REQ.VERB!="PUT"){ //Non-supported request (at the moment) - return an error message out.CODE = RestOutputStruct::BADREQUEST; }else if(out.in_struct.name.isEmpty() || out.in_struct.namesp.isEmpty() ){ //Invalid JSON structure validity //Note: id and args are optional at this stage - let the subsystems handle those inputs out.CODE = RestOutputStruct::BADREQUEST; }else{ //First check for a REST authorization (not stand-alone request) if(!out.in_struct.auth.isEmpty()){ AUTHSYSTEM->clearAuth(SockAuthToken); //new auth requested - clear any old token SockAuthToken = AUTHSYSTEM->LoginUP(host, out.in_struct.auth.section(":",0,0), out.in_struct.auth.section(":",1,1)); } //Now check the body of the message and do what it needs if(out.in_struct.namesp.toLower() == "rpc"){ if(out.in_struct.name.startsWith("auth")){ //Now perform authentication based on type of auth given //Note: This sets/changes the current SockAuthToken AUTHSYSTEM->clearAuth(SockAuthToken); //new auth requested - clear any old token if(DEBUG){ qDebug() << "Authenticate Peer:" << SOCKET->peerAddress().toString(); } //Now do the auth if(out.in_struct.name=="auth" && out.in_struct.args.isObject() ){ //username/[password/cert] authentication QString user, pass; if(out.in_struct.args.toObject().contains("username")){ user = JsonValueToString(out.in_struct.args.toObject().value("username")); } if(out.in_struct.args.toObject().contains("password")){ pass = JsonValueToString(out.in_struct.args.toObject().value("password")); } if(!pass.isEmpty()){ //Use the given password SockAuthToken = AUTHSYSTEM->LoginUP(host, user, pass); }else{ //No password - use the current SSL certificates instead QList<QSslCertificate> certs; if(SOCKET!=0){ certs = SOCKET->sslConfiguration().peerCertificateChain(); } else if(TSOCKET!=0){ certs = TSOCKET->peerCertificateChain(); } SockAuthToken = AUTHSYSTEM->LoginUC(host, user, certs); } }else if(out.in_struct.name == "auth_token" && out.in_struct.args.isObject()){ SockAuthToken = JsonValueToString(out.in_struct.args.toObject().value("token")); }else if(out.in_struct.name == "auth_clear"){ return; //don't send a return message after clearing an auth (already done) } //Now check the auth and respond appropriately if(AUTHSYSTEM->checkAuth(SockAuthToken)){ //Good Authentication - return the new token QJsonArray array; array.append(SockAuthToken); array.append(AUTHSYSTEM->checkAuthTimeoutSecs(SockAuthToken)); out.out_args = array; out.CODE = RestOutputStruct::OK; }else{ if(SockAuthToken=="REFUSED"){ out.CODE = RestOutputStruct::FORBIDDEN; } SockAuthToken.clear(); //invalid token //Bad Authentication - return error out.CODE = RestOutputStruct::UNAUTHORIZED; } }else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Now provide access to the various subsystems // First get/set the permissions flag into the input structure out.in_struct.fullaccess = AUTHSYSTEM->hasFullAccess(SockAuthToken); //Pre-set any output fields QJsonObject outargs; out.CODE = EvaluateBackendRequest(out.in_struct, &outargs); out.out_args = outargs; }else{ //Bad/No authentication out.CODE = RestOutputStruct::UNAUTHORIZED; } }else if(out.in_struct.namesp.toLower() == "events"){ if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Pre-set any output fields QJsonObject outargs; //Assemble the list of input events QStringList evlist; if(out.in_struct.args.isObject()){ evlist << JsonValueToString(out.in_struct.args); } else if(out.in_struct.args.isArray()){ evlist = JsonArrayToStringList(out.in_struct.args.toArray()); } //Now subscribe/unsubscribe to these events int sub = -1; //bad input if(out.in_struct.name=="subscribe"){ sub = 1; } else if(out.in_struct.name=="unsubscribe"){ sub = 0; } //qDebug() << "Got Client Event Modification:" << sub << evlist; if(sub>=0 && !evlist.isEmpty() ){ for(int i=0; i<evlist.length(); i++){ EventWatcher::EVENT_TYPE type = EventWatcher::typeFromString(evlist[i]); if(type==EventWatcher::BADEVENT){ continue; } outargs.insert(out.in_struct.name,QJsonValue(evlist[i])); if(sub==1){ ForwardEvents << type; EventUpdate(type); }else{ ForwardEvents.removeAll(type); } } out.out_args = outargs; out.CODE = RestOutputStruct::OK; }else{ //Bad/No authentication out.CODE = RestOutputStruct::BADREQUEST; } }else{ //Bad/No authentication out.CODE = RestOutputStruct::UNAUTHORIZED; } //Other namespace - check whether auth has already been established before continuing }else if( AUTHSYSTEM->checkAuth(SockAuthToken) ){ //validate current Authentication token //Now provide access to the various subsystems // First get/set the permissions flag into the input structure out.in_struct.fullaccess = AUTHSYSTEM->hasFullAccess(SockAuthToken); //Pre-set any output fields QJsonObject outargs; out.CODE = EvaluateBackendRequest(out.in_struct, &outargs); out.out_args = outargs; }else{ //Error in inputs - assemble the return error message out.CODE = RestOutputStruct::UNAUTHORIZED; } //If this is a REST input - go ahead and format the output header if(out.CODE == RestOutputStruct::OK){ out.Header << "Content-Type: text/json; charset=utf-8"; } } //Return any information this->sendReply(out.assembleMessage()); if(out.CODE == RestOutputStruct::FORBIDDEN && SOCKET!=0){ SOCKET->close(QWebSocketProtocol::CloseCodeNormal, "Too Many Authorization Failures - Try again later"); } }