//======================= // PRIVATE //======================= void WebSocket::EvaluateREST(QString msg){ //Parse the message into it's elements and proceed to the main data evaluation RestInputStruct IN(msg); //NOTE: All the REST functionality is disabled for the moment, until we decide to turn it on again at a later time (just need websockets right now - not full REST) if(DEBUG){ qDebug() << "New REST Message:"; qDebug() << " VERB:" << IN.VERB << "URI:" << IN.URI; qDebug() << " HEADERS:" << IN.Header; qDebug() << " BODY:" << IN.Body; } //Now check for the REST-specific verbs/actions if(IN.VERB == "OPTIONS" || IN.VERB == "HEAD"){ RestOutputStruct out; out.CODE = RestOutputStruct::OK; if(IN.VERB=="HEAD"){ }else{ //OPTIONS out.Header << "Allow: HEAD, GET"; out.Header << "Hosts: /syscache"; } out.Header << "Accept: text/json"; out.Header << "Content-Type: text/json; charset=utf-8"; SOCKET->sendTextMessage(out.assembleMessage()); }else{ EvaluateRequest(IN); } }
//======================= // PRIVATE //======================= void WebServer::EvaluateREST(QString msg){ //Parse the message into it's elements and proceed to the main data evaluation RestInputStruct IN(msg); if(DEBUG){ qDebug() << "New REST Message:"; qDebug() << " VERB:" << IN.VERB << "URI:" << IN.URI; qDebug() << " HEADERS:" << IN.Header; qDebug() << " BODY:" << IN.Body; } //Now check for the REST-specific verbs/actions if(IN.VERB == "OPTIONS" || IN.VERB == "HEAD"){ RestOutputStruct out; out.CODE = RestOutputStruct::OK; if(IN.VERB=="HEAD"){ }else{ //OPTIONS out.Header << "Allow: HEAD, GET"; out.Header << "Hosts: /syscache"; } out.Header << "Accept: text/json"; out.Header << "Content-Type: text/json; charset=utf-8"; csock->sendTextMessage(out.assembleMessage()); }else{ EvaluateRequest(IN); } }
// ====================== // PUBLIC SLOTS // ====================== void WebSocket::EventUpdate(EventWatcher::EVENT_TYPE evtype, QJsonValue msg){ if(msg.isNull()){ msg = EVENTS->lastEvent(evtype); } //qDebug() << "Socket Status Update:" << msg; if(!ForwardEvents.contains(evtype)){ return; } RestOutputStruct out; out.CODE = RestOutputStruct::OK; out.in_struct.namesp = "events"; out.out_args = msg; out.Header << "Content-Type: text/json; charset=utf-8"; //REST header info out.in_struct.name = EventWatcher::typeToString(evtype); //Now send the message back through the socket this->sendReply(out.assembleMessage()); }
void WebSocket::EvaluateREST(QString msg){ //Parse the message into it's elements and proceed to the main data evaluation RestInputStruct IN(msg); //NOTE: All the REST functionality is disabled for the moment, until we decide to turn it on again at a later time (just need websockets right now - not full REST) if(DEBUG){ qDebug() << "New REST Message:"; qDebug() << " VERB:" << IN.VERB << "URI:" << IN.URI; qDebug() << " HEADERS:" << IN.Header; qDebug() << " BODY:" << IN.Body; //qDebug() << " Auth:" << IN.auth; qDebug() << "JSON Values:"; qDebug() << " - Name:" << IN.name; qDebug() << " - Namespace:" << IN.namesp; qDebug() << " - ID:" << IN.id; qDebug() << " - Has Args:" << !IN.args.isNull(); } //Now check for the REST-specific verbs/actions if(IN.VERB == "OPTIONS" || IN.VERB == "HEAD"){ RestOutputStruct out; out.in_struct = IN; out.CODE = RestOutputStruct::OK; if(IN.VERB=="HEAD"){ }else{ //OPTIONS out.Header << "Allow: HEAD, GET"; out.Header << "Hosts: /syscache"; } out.Header << "Accept: text/json"; out.Header << "Content-Type: text/json; charset=utf-8"; this->sendReply(out.assembleMessage()); }else{ //EvaluateRequest(IN); if(IN.name.startsWith("auth") ){ //Keep auth system requests in order EvaluateRequest(IN); }else{ QtConcurrent::run(this, &WebSocket::EvaluateRequest, IN); } } }
// ====================== // PUBLIC SLOTS // ====================== void WebSocket::AppCafeStatusUpdate(QString msg){ if(!msg.isEmpty()){ lastDispatchEvent = msg; } else{ msg = lastDispatchEvent; } //qDebug() << "Socket Status Update:" << msg; if(!SendAppCafeEvents){ return; } //don't report events on this socket RestOutputStruct out; //Define the output structures QJsonObject ret; //return message //Pre-set any output fields QJsonObject outargs; ret.insert("namespace", QJsonValue("events")); ret.insert("name", QJsonValue("event")); ret.insert("id", QJsonValue("")); outargs.insert("name", "dispatcher"); outargs.insert("args",QJsonValue(msg)); ret.insert("args",outargs); 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"; SOCKET->sendTextMessage(out.assembleMessage()); }
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"); } }