Remoto - VFS: VFS_sessionManager.cpp Source File
Remoto - VFS
VFS_sessionManager.cpp
Go to the documentation of this file.
1 
2 #include <QRandomGenerator>
3 #include <QUuid>
4 
5 #include "VFS.h"
6 #include "VFS_base/VFS_icons.h"
8 #include "VFS_session.h"
9 #include "utilities/rutils.h"
10 
11 #include "VFS_sessionManager.h"
12 
61 VFS_sessionManager::VFS_sessionManager(QString cwd, QString userdata, qint64 ttl, QString tokendata, QString developerGroup, double pruneProbability)
62 : VFS_node()
63 , _cwd(cwd)
64 , _userdata(userdata)
65 , _tokendata(tokendata)
66 , _ttl(ttl)
67 , _developerGroup(developerGroup)
68 , _pruneProbability(pruneProbability)
69 , _pruner(_tokendata)
70 {
71  _pruneProbability = qBound(0.0,_pruneProbability,1.0);
72 
73  _sessions = new VFS_node();
74  append("sessions",_sessions);
75 
77 }
78 
80 {
81 }
82 
83 
90 {
91  return VFS_icons::get("users");
92 }
93 
94 
105 {
106  if (r->_path == "preferences.rfm")
107  return this;
108  else if (r->_path == "preferencesBase.rfm")
109  return this;
110  else if (r->_path == "tokenauth")
111  return this;
112  else
113  return VFS_node::find(r);
114 }
115 
116 
126 {
127  if (r->_path == "preferences.rfm")
128  {
129  VFS_session *s = r->_session;
130 
131  QJsonObject p;
132  if (s && s->isMemberOf(_developerGroup))
133  p = rutils::jsonResource( ":/skeleton/preferencesDeveloper.rfm" );
134  else
135  p = rutils::jsonResource( ":/skeleton/preferences.rfm" );
136 
137  r->_data.setObject(p);
138  r->_success = true;
139  }
140  else if (r->_path == "preferencesBase.rfm")
141  {
142  QJsonObject p = rutils::jsonResource( ":/skeleton/preferencesBase.rfm" );
143 
144  r->_data.setObject(p);
145  r->_success = true;
146  }
147  else if (r->_path == "tokenauth")
148  {
149  QJsonObject o = r->_data.object();
150  QString token = o["authtoken"].toString("");
151 
152  if (token.isEmpty() || token.contains("/")) //disallow empty tokens and those that contain a subpath. Single file names only.
153  {
154  r->_reason = "No valid authtoken provided.";
155  r->_success = false;
156  return;
157  }
158 
159  pruneTokens();
160 
161  VFS_request *rr = r->getCallback(this);
162  rr->_path = rr->_initialPath = _tokendata+"/"+token;
163  rr->_metadata["credentials"] = r->_data.object(); //tuck that away for later
164  rr->_data.setObject(QJsonObject()); //clear the data, because if it fails we don't want it going anywhere bad
165  rr->_metadata["suppressError"] = true; //if the token isn't found, we don't want an error message
166  issueRequest(rr);
167  }
168  else
169  VFS_node::read(r);
170 }
171 
172 
182 {
184  {
185  //VFS_websocket_client *c = dynamic_cast<VFS_websocket_client *>(r->_origin);
186  //printf("client: %p\n",c);
187 
188  //create and append the session
189  VFS_node *client = r->_origin;
190  QDateTime exp = (_ttl > 0 ) ? QDateTime::currentDateTimeUtc().addSecs( _ttl-1 ) : QDateTime(); //set the timeout to one second less than the expiration
191 
192  QJsonObject o = r->_data.object();
193 
194  //if an expiration is set in the credentials, use it
195  if (o.contains("expire"))
196  exp.setSecsSinceEpoch( o["expire"].toInt()*60 );
197 
198  QString user = o["username"].toString();
199  //quint16 uidnumber = o["uidnumber"].toInt( -1 );
200  QString type = o["type"].toString();
201  QString authpath = o["authpath"].toString();
202  QString home = _userdata+"/"+user;
203  QString address = o["address"].toString();
204  QJsonObject parameters = o["parameters"].toObject();
205  QString token = o["authtoken"].toString("");
206 
207  if (!_tokendata.isEmpty() && token.isEmpty()) //if we're doing authtokens but one is not present, we need to generate one.
208  token = generateAuthtoken();
209 
210  if (!token.isEmpty())
211  token = _tokendata+"/"+token;
212 
213  VFS_session *session = new VFS_session(client, this, user, type, home, authpath, exp, address, parameters, token);
214  // the session is appended or deleted in receiveResponse()
215  // the session is appended later to prevent subscriptions during initialization
216 
217 // //append last to prevent subscriptions during initialization
218 // QString sessionName = _sessions->uniqueChildName(user);
219 // if (!_sessions->append(sessionName,session))
220 // { VFS::WARN( r->_reason = QString("Unable to append user '%1'. Is the session name valid?").arg(user) );
221 // r->_success = false;
222 // delete session;
223 // return;
224 // }
225 
226  //issue the initialization request, which will issueResponse with rr
227  VFS_request *rr = r->getCallback(this);
228  session->initializeUser(rr);
229 
230  return;
231  }
232 
233  VFS::WARN( r->_reason = QString("%1::write() bad?").arg(className()), 0, r->_user );
234  r->_success = false;
235 }
236 
243 {
244  //printf("VFS_sessionManager receiveResponse: %d %s\n",r->_requestType,qUtf8Printable(r->_initialPath));
245 
246  switch(r->_requestType)
247  {
248  case VFS_request::create: {
249  // get the session
250  VFS_session *session = r->_session;
251  if (!session)
252  { //printf("No session in return from session initialization. Gonna crash now.\n");
253  break;
254  }
255 
256  // see if the session was successfully created
257  if (!r->_success)
258  {
259  VFS::WARN( r->_reason = QString("Could not create a session for '%1'. Is the home directory writable?").arg(r->_user) );
260  delete session;
261  break;
262  }
263 
264  // append to the vfs
265  QString sessionName = _sessions->uniqueChildName(r->_user);
266  if (!_sessions->append(sessionName,session))
267  { VFS::WARN( r->_reason = QString("Unable to append session '%1'. Is the session name valid?").arg(sessionName) );
268  r->_success = false;
269  delete session;
270  break;
271  }
272 
273  // add authtoken data
274  // if the sessionManager has a _tokendata path, then tokens are enabled.
275  // Include the token value and expiration so a browser can update its cookie.
276  if (!_tokendata.isEmpty())
277  {
278  QJsonObject o = r->_data.object();
279  //if (d.contains("authkey"))
280  {
281  QJsonObject authtoken;
282  authtoken["token"] = session->authtoken();
283  authtoken["expire"] = session->secondsToExpire();
284  o["authtoken"] = authtoken;
285  }
286  r->_data.setObject(o);
287  }
288  }
289  break;
290 
291  case VFS_request::read: {
292  //if this was a token auth check
293  if (r->_initialPath.startsWith(_tokendata))
294  {
295  //printf("TOKEN READ: %s\n",qUtf8Printable(r->toJson(0,true,true)));
296 
297  QJsonObject o = r->_data.object();
298  QJsonObject credentials = r->_metadata["credentials"].toObject();
299  QString token = credentials["authtoken"].toString();
300 
301  credentials.remove("authtoken"); // must remove here to prevent a loop
302  credentials.remove("expire");
303  r->_metadata.remove("credentials"); // return to the pre-authtoken metadata values
304 
305  if (!token.isEmpty() && r->_success) // successfully read the token, but it may be expired
306  {
307  int now = (qint32) (QDateTime::currentDateTimeUtc().toSecsSinceEpoch()/60);
308  int expire = o["expire"].toInt(0); // if there is no expire field, zero will be used, which will cause expiration
309  QString user = o["username"].toString(""); // a token data response without a username is invalid
310  QString authpath = o["authpath"].toString(""); // an empty authpath is invalid
311 
312  /*
313  if (user != r->_user)
314  {
315  r->_success = false;
316  r->_reason = QString("Token '%1' is not for '%2'.").arg(token).arg(r->_user);
317 
318  if (user.isEmpty())
319  pruneTokens();
320  }
321  */
322 
323  if (user.isEmpty())
324  {
325  r->_success = false;
326  r->_reason = QString("Invalid session data: missing user.");
327  }
328  else if (authpath.isEmpty())
329  {
330  r->_success = false;
331  r->_reason = QString("Invalid session data: missing authpath.");
332  }
333  else if (expire < now)
334  {
335  r->_success = false;
336  r->_reason = QString("Authtoken '%1' has expired.").arg(token);
337 
338  //it's expired, but we will let the pruner decide to do garbage collection
339  pruneTokens();
340  }
341  else
342  {
343  //valid authtoken!
344  credentials["username"] = user;
345  credentials["authtoken"] = token;
346  credentials["expire"] = expire; // we will create a session with this expiration
347  credentials["authpath"] = authpath;
348  }
349  }
350  else
351  {
352  r->_reason = QString("Invalid token: '%1'").arg(token);
353  r->_success = false;
354  }
355 
356  r->_data.setObject(credentials); // return the credentials
357  }
358  }
359  break;
360 
361  default: break;
362  }
363 
365 }
366 
367 
374 {
375  //QMutexLocker l(&_lock);
376 
377  return _cwd;
378 }
379 
380 
387 {
388  //return "session details here like registered types, connected users, etc.";
389  return "";
390 }
391 
398 {
399  QUuid id = QUuid::createUuid();
400  return id.toString(QUuid::WithoutBraces);
401 }
402 
411 {
412  if ( !(_pruneProbability > 0.0 && QRandomGenerator::global()->bounded(1.0) < _pruneProbability ) )
413  return;
414 
415  if (_pruner.running())
416  return;
417 
418  _pruner.run();
419 }
420 
421 
422 
429 : VFS_node()
430 , _running(false)
431 , _path(path)
432 {
433 }
434 
435 
443 {
444  stop();
445 }
446 
447 
456 {
457  if (_running) return false;
458 
459  VFS::LOG("Pruning tokens on '"+_path+"'...",8,"pruner");
460 
462  issueRequest(ls);
463 
464  _running = true;
465 
466  return true;
467 }
468 
469 
475 {
476  _running = false;
477  _pathList.clear();
478 }
479 
480 
487 {
488  return _running;
489 }
490 
491 
498 {
499  if (!r->_success)
500  {
501  VFS::ERROR("Prune failed on '"+r->_initialPath+"'",0,"pruner");
502  stop();
503  }
504  else
505  {
506  switch(r->_requestType)
507  {
508  case VFS_request::ls: {
509  //printf("Pruner LS: %s\n",qUtf8Printable(r->toJson(0,true,true)));
510  QJsonObject o = r->_data.object();
511  _pathList = o.keys();
512 
513  peel();
514  }
515  break;
516 
517  case VFS_request::read: {
518  //printf("Pruner Read: %s\n",qUtf8Printable(r->toJson(0,true,true)));
519  QJsonObject o = r->_data.object();
520  if (!checkEntry(o))
521  peel();
522  else
524  }
525  break;
526 
527  case VFS_request::rm: {
528  //printf("Pruner RM: %s\n",qUtf8Printable(r->toJson(0,true,true)));
529  peel();
530  }
531  break;
532 
533  default: break;
534  }
535  }
536 
538 }
539 
545 {
546  if (_pathList.length())
547  {
548  QString p = _path+"/"+_pathList.takeFirst();
549  VFS::LOG( QString(" Pruner checking: %1\n").arg(qUtf8Printable(p)),8,"pruner");
551  r->_metadata["cache"] = false; //don't blow out our cache by reading all tokens
552  issueRequest(r);
553  }
554  else
555  stop();
556 }
557 
570 {
571  if ( o["username"].toString("").isEmpty() ) return false;
572  if ( o["authpath"].toString("").isEmpty() ) return false;
573 
574  int now = (qint32) (QDateTime::currentDateTimeUtc().toSecsSinceEpoch()/60);
575 
576  if ( o["expire"].toInt(0) > now ) return false;
577 
578  return true;
579 }
580 
587 {
588  VFS::WARN( QString(" Pruner pruning: %1\n").arg(qUtf8Printable(p)),8,"pruner");
589  VFS_request *r = createRequest(VFS_request::rm,p,"pruner");
590  r->_metadata["cache"] = false; //don't blow out our cache reading tokens
591  issueRequest(r);
592 }
static char * get(QString which="")
Fetch an icon from the library.
Definition: VFS_icons.cpp:34
VFS_node is the base class from which all other VFS_node classes derive.
Definition: VFS_node.h:143
virtual VFS_node * append(QString name, VFS_node *node, bool containerCheck=true, QString user="server")
Append a VFS_node as a child of this node.
Definition: VFS_node.cpp:1566
virtual VFS_request * createRequest(VFS_request::requestType type, QString path, QString user="unknown", QJsonDocument data=QJsonDocument(), QJsonObject metadata=QJsonObject(), bool dontDelete=false)
Create a new VFS_request with this object as _origin.
Definition: VFS_node.cpp:1913
virtual void read(VFS_request *r)
Return the data contents of this node, or if it's a container call ls()
Definition: VFS_node.cpp:724
Q_INVOKABLE VFS_node()
The VFS_node constructor will add its instance to the VFS_node::__allNodes global node registry,...
Definition: VFS_node.cpp:515
QString uniqueChildName(QString name)
Generate a unique child name.
Definition: VFS_node.cpp:2054
virtual void issueRequest(VFS_request *t)
A convenience function.
Definition: VFS_node.cpp:1933
VFS_node * find(QString path)
Find a node by string path.
Definition: VFS_node.cpp:1100
virtual void ls(VFS_request *r)
List the contents of this node.
Definition: VFS_node.cpp:692
QString className()
Return the class name of a node.
Definition: VFS_node.cpp:2039
friend class VFS_session
Definition: VFS_node.h:146
virtual void receiveResponse(VFS_request *t)
Once a VFS_request has been completed, a response will be issued back to its _origin.
Definition: VFS_node.cpp:1870
The base class for all requests between nodes.
Definition: VFS_node.h:54
@ read
read full contents (4)
Definition: VFS_node.h:68
@ ls
list children of a node (1)
Definition: VFS_node.h:65
@ create
create a new file/path (2)
Definition: VFS_node.h:66
@ rm
delete an existing file/path (3)
Definition: VFS_node.h:67
VFS_node * _origin
the origin of the request
Definition: VFS_node.h:89
requestType _requestType
the action this request is performing or requesting
Definition: VFS_node.h:87
QString _initialPath
the target path when the request was made (relative to the responder)
Definition: VFS_node.h:93
QString _user
who initiated this request, mostly for logging
Definition: VFS_node.h:106
VFS_session * _session
The session associated with this request. This is an optional value, and care should be taken to chec...
Definition: VFS_node.h:105
QString _reason
if something (probably bad) happened, this is the reason
Definition: VFS_node.h:108
QString _path
the target path remnant... the remaining path element once the request has found its target
Definition: VFS_node.h:95
bool _success
if the request was successfully completed
Definition: VFS_node.h:107
QJsonDocument _data
the request payload
Definition: VFS_node.h:102
virtual VFS_request * getCallback(VFS_node *receiver)
Create and chain a VFS_request for a receiver.
Definition: VFS_node.cpp:316
QJsonObject _metadata
the request payload
Definition: VFS_node.h:101
The VFS_session object represents a single session.
Definition: VFS_session.h:14
virtual void initializeUser(VFS_request *r)
Check and create session files for a user.
qint64 secondsToExpire()
Given the current time, calculate the number of seconds until this session will expire.
bool isMemberOf(QString group)
Check if this session's group list includes the specified group.
QString authtoken()
Return the file component of the _authtoken path.
static void initializeTypes()
Initialize the VFS_session::_sessionTypes member.
VFS_sessionTokenPruner _pruner
Our pruner.
QString _cwd
The VFS path of this node.
virtual void receiveResponse(VFS_request *t)
VFS_sessionManager::receiveResponse.
Q_INVOKABLE VFS_sessionManager(QString cwd, QString userdata, qint64 ttl, QString tokendata="", QString developerGroup="", double pruneProbability=0.1)
Create a session node, whose job is to return session data for a user.
qint64 _ttl
Number of seconds for sessions to live once created (time to live)
virtual void read(VFS_request *r)
Read from the sessionManager.
QString _tokendata
A VFS path to store session tokens.
virtual VFS_node * find(VFS_request *r)
Find a node using a VFS_request.
VFS_node * _sessions
A VFS_node to hold current sessions.
double _pruneProbability
The probability that a prune will happen.
virtual void write(VFS_request *r)
Write to the session manager to create a session.
QString _userdata
A VFS path to store user data.
QString _developerGroup
Name of the group used for development mode. Members of this group will have additional preferences a...
QString generateAuthtoken()
Generate a new unique authtoken for this session.
virtual QByteArray icon()
Return the user icon from VFS_icons.
virtual QString getCWD()
Return the manager's _cwd value.
virtual QString reportDetails()
Return information about any current sessions.
void pruneTokens()
Run a session token prune on a separate thread.
VFS_sessionTokenPruner(QString path)
Create a VFS_sessionTokenPruner.
void pruneEntry(QString p)
Request to remove a token entry.
virtual void receiveResponse(VFS_request *t)
Receive a response on a pruner request.
QString _path
The VFS path to prune.
QStringList _pathList
The list of paths remaining to check.
bool checkEntry(QJsonObject e)
Check a token entry for validity.
bool running()
Return the _running state of the pruner.
void stop()
Stop the prune operation if it is running.
virtual ~VFS_sessionTokenPruner()
Destroy the pruner.
void peel()
Remove a path from the stack or stop processing if empty.
bool run()
Attempt to run the prune operation.
bool _running
The running state of the pruner.
static void LOG(QString message, int level=0, QString user="server")
Send a message to the VFS::_messages VFS_stream.
Definition: VFS.cpp:209
static void ERROR(QString message, int level=0, QString user="server")
Send a message to the VFS::_errors VFS_stream.
Definition: VFS.cpp:307
static void WARN(QString message, int level=0, QString user="server")
Send a message to the VFS::_warnings VFS_stream.
Definition: VFS.cpp:258
getter path
a getter DOCME
QJsonObject jsonResource(QString resource, bool *ok=nullptr)
Fetch the contents of a Qt resource as a QJsonObject.
Definition: rutils.cpp:90