MythTV  master
mythsession.cpp
Go to the documentation of this file.
1 
2 #include "mythsession.h"
3 
4 #include "mythdbcon.h"
5 #include "mythlogging.h"
6 #include "mythcorecontext.h"
7 #include "mythdate.h"
8 
9 #include <QCryptographicHash>
10 #include <QUuid>
11 
15 bool MythUserSession::IsValid(void) const
16 {
17  if (m_userId == 0)
18  return false;
19 
20  if (m_sessionToken.isEmpty() || m_sessionToken.length() != 40)
21  return false;
22 
24  return false;
25 
27  return false;
28 
29  // NOTE: Check client string as well?
30 
31  return true;
32 }
33 
37 bool MythUserSession::CheckPermission(const QString &/*context*/,
38  uint /*permission*/)
39 {
41  {
42  // TODO: Connect to master and do checking there
43  return false;
44  }
45 
46  Update();
47 
48  // TODO: Implement perms checking here
49 
50  return false;
51 }
52 
57 {
58  if (m_userId == 0)
59  return false;
60 
61  if (m_sessionToken.isEmpty())
62  {
63  QByteArray randBytes = QUuid::createUuid().toByteArray();
64  m_sessionToken = QCryptographicHash::hash(randBytes,
65  QCryptographicHash::Sha1).toHex();
66  }
67 
68  if (m_sessionCreated.isNull())
70  if (m_sessionLastActive.isNull())
72  if (m_sessionExpires.isNull())
73  m_sessionExpires = MythDate::current().addDays(1);
74 
76  query.prepare("REPLACE INTO user_sessions SET "
77  "sessionToken = :SESSION_TOKEN, "
78  "userid = :USERID, "
79  "client = :CLIENT, "
80  "created = :CREATED, "
81  "lastactive = :LASTACTIVE, "
82  "expires = :EXPIRES");
83  query.bindValue(":SESSION_TOKEN", m_sessionToken);
84  query.bindValue(":USERID", m_userId);
85  query.bindValue(":CLIENT", m_sessionClient);
86  query.bindValue(":CREATED", m_sessionCreated);
87  query.bindValue(":LASTACTIVE", m_sessionLastActive);
88  // For now, fixed duration of 1 day, this should be different for the
89  // WebFrontend (external client) vs the Frontend (local client). It should
90  // perhaps be configurable. The expiry date is extended every time the
91  // the session is validated, for the WebFrontend this is every http
92  // connection for other clients this has yet to be defined
93  query.bindValue(":EXPIRES", m_sessionExpires);
94 
95  return query.exec();
96 }
97 
102 {
104  m_sessionExpires = MythDate::current().addDays(1);
105  return Save();
106 }
107 
112 {
114  LoadSessions();
115 }
116 
121 {
122  m_sessionList.clear();
123 
124  MSqlQuery query(MSqlQuery::InitCon());
125  query.prepare("SELECT s.sessiontoken, s.created, s.lastactive, s.expires, "
126  " s.client, u.userid, u.username "
127  "FROM user_sessions s, users u");
128 
129  if (!query.exec())
130  MythDB::DBError("Error loading user sessions from database", query);
131 
132  while (query.next())
133  {
134  MythUserSession session;
135 
136  session.m_sessionToken = query.value(0).toString();
137  session.m_sessionCreated = query.value(1).toDateTime();
138  session.m_sessionLastActive = query.value(2).toDateTime();
139  session.m_sessionExpires = query.value(3).toDateTime();
140  session.m_sessionClient = query.value(4).toString();
141  session.m_userId = query.value(5).toUInt();
142  session.m_name = query.value(6).toString();
143 
144  m_sessionList.insert(session.m_sessionToken, session);
145  }
146 }
147 
151 bool MythSessionManager::IsValidUser(const QString& username)
152 {
153  if (username.isEmpty())
154  return false;
155 
157  {
158  // TODO: Connect to master and do checking there
159  return false;
160  }
161 
162  MSqlQuery query(MSqlQuery::InitCon());
163  query.prepare("SELECT userid FROM users WHERE username = :USERNAME");
164  query.bindValue(":USERNAME", username);
165 
166  if (!query.exec())
167  MythDB::DBError("Error finding user", query);
168 
169  return query.next();
170 }
171 
176 {
178  {
179  // TODO: Connect to master and do checking there
180  return MythUserSession();
181  }
182 
183  if (IsValidSession(sessionToken))
184  return m_sessionList[sessionToken];
185 
186  return MythUserSession();
187 }
188 
193  const QString &client)
194 {
196  {
197  // TODO: Connect to master and do checking there
198  return MythUserSession();
199  }
200 
201  QMap<QString, MythUserSession>::iterator it;
202  for (it = m_sessionList.begin(); it != m_sessionList.end(); ++it)
203  {
204  if (((*it).m_name == username) &&
205  ((*it).m_sessionClient == client))
206  {
207  if ((*it).IsValid())
208  {
209  (*it).Update();
210  return *it;
211  }
212 
213  DestroyUserSession((*it).m_sessionToken);
214  break;
215  }
216  }
217 
218  return MythUserSession();
219 }
220 
224 QString MythSessionManager::GetPasswordDigest(const QString& username)
225 {
226  MSqlQuery query(MSqlQuery::InitCon());
227  query.prepare("SELECT password_digest FROM users WHERE username = :USERNAME");
228  query.bindValue(":USERNAME", username);
229 
230  if (!query.exec())
231  MythDB::DBError("Error finding user", query);
232 
233  if (query.next())
234  return query.value(0).toString();
235 
236  return QString();
237 }
238 
242 bool MythSessionManager::IsValidSession(const QString& sessionToken)
243 {
245  {
246  // TODO: Connect to master and do checking there
247  return false;
248  }
249 
250  if (m_sessionList.contains(sessionToken))
251  {
252  MythUserSession session = m_sessionList[sessionToken];
253  if (session.IsValid())
254  {
255  // Accessing a session automatically extends it
256  UpdateSession(sessionToken);
257  return true;
258  }
259 
260  DestroyUserSession(sessionToken);
261  }
262 
263  return false;
264 }
265 
269 void MythSessionManager::UpdateSession(const QString& sessionToken)
270 {
271  if (m_sessionList.contains(sessionToken))
272  {
273  MythUserSession session = m_sessionList[sessionToken];
274  session.Update(); // Update the database
275  m_sessionList[sessionToken] = session; // Update the cache
276  }
277 }
278 
283  const QByteArray &digest,
284  const QString &client)
285 {
286  if (username.isEmpty() || digest.isEmpty() || digest.length() < 32 ||
287  digest.length() > 32)
288  return MythUserSession();
289 
291  {
292  // TODO: Connect to master and do checking there
293  return MythUserSession();
294  }
295 
296  MSqlQuery query(MSqlQuery::InitCon());
297  query.prepare("SELECT userid, username FROM users WHERE "
298  "username = :USERNAME AND password_digest = :PWDIGEST");
299 
300  query.bindValue(":USERNAME", username);
301  query.bindValue(":PWDIGEST", QString(digest));
302 
303  if (!query.exec())
304  return MythUserSession();
305 
306  if (query.size() > 1)
307  {
308  LOG(VB_GENERAL, LOG_CRIT, "LoginUser: Warning, multiple matching user records found.");
309  return MythUserSession();
310  }
311 
312  if (query.next())
313  {
314  // Having verified the password, check if there is an existing session
315  // open that we should re-use. This is necessary for unencrypted HTTP
316  // Auth where the session token isn't stored on the client and we must
317  // re-auth on every request
318  MythUserSession session = GetSession(username, client);
319 
320  if (session.IsValid())
321  return session;
322 
323  // No pre-existing session, so create a new one
324  uint userId = query.value(0).toUInt();
325  QString userName = query.value(1).toString();
326 
327  return CreateUserSession(userId, userName, client);
328  }
329 
330  LOG(VB_GENERAL, LOG_WARNING, QString("LoginUser: Failed login attempt for "
331  "user %1").arg(username));
332 
333  return MythUserSession();
334 }
335 
340  const QString &password,
341  const QString &client)
342 {
343  QByteArray digest = CreateDigest(username, password);
344 
345  return LoginUser(username, digest, client);
346 }
347 
352  const QString &userName,
353  const QString &client)
354 {
355  MythUserSession session;
356 
357  session.m_userId = userId;
358  session.m_name = userName;
359 
360  QString clientIdentifier = client;
361  if (clientIdentifier.isEmpty())
362  {
363  QString type = "Master";
365  type = "Slave";
366 
367  clientIdentifier = QString("%1_%2").arg(type)
368  .arg(gCoreContext->GetHostName());
369  }
370 
371  session.m_sessionClient = clientIdentifier;
372 
375  session.m_sessionExpires = MythDate::current().addDays(1);
376 
377  if (session.Save()) // Sets the session token
378  {
379  m_sessionList.insert(session.m_sessionToken, session);
380  }
381 
382  return session;
383 }
384 
388 void MythSessionManager::DestroyUserSession(const QString &sessionToken)
389 {
390  if (sessionToken.isEmpty())
391  return;
392 
393  MSqlQuery query(MSqlQuery::InitCon());
394  query.prepare("DELETE FROM user_sessions WHERE "
395  "sessionToken = :SESSION_TOKEN");
396  query.bindValue(":SESSION_TOKEN", sessionToken);
397 
398  if (!query.exec())
399  {
400  MythDB::DBError("Error deleting user session from database", query);
401  }
402 
403  if (m_sessionList.contains(sessionToken))
404  m_sessionList.remove(sessionToken);
405 }
406 
410 bool MythSessionManager::AddDigestUser(const QString& username,
411  const QString& password,
412  const QString& adminPassword)
413 {
414  bool bResult;
415 
416  if (adminPassword.isEmpty())
417  {
418  LOG(VB_GENERAL, LOG_ERR, QString("Admin password is missing."));
419  return false;
420  }
421 
422  if (IsValidUser(username))
423  {
424  LOG(VB_GENERAL, LOG_ERR, QString("Tried to add an existing user: %1.")
425  .arg(username));
426  return false;
427  }
428 
429  if (CreateDigest("admin", adminPassword) != GetPasswordDigest("admin"))
430  {
431  LOG(VB_GENERAL, LOG_ERR, QString("Incorrect password for user: %1.")
432  .arg("admin"));
433  return false;
434  }
435 
436  MSqlQuery insert(MSqlQuery::InitCon());
437  insert.prepare("INSERT INTO users SET "
438  "username = :USER_NAME, "
439  "password_digest = :PASSWORD_DIGEST");
440  insert.bindValue(":USER_NAME", username);
441  insert.bindValue(":PASSWORD_DIGEST", CreateDigest(username, password));
442 
443  bResult = insert.exec();
444 
445  if (!bResult)
446  MythDB::DBError("Error adding digest user to database", insert);
447 
448  return bResult;
449 }
450 
454 bool MythSessionManager::RemoveDigestUser(const QString& username,
455  const QString& password)
456 {
457  bool bResult;
458 
459  if (!IsValidUser(username))
460  {
461  LOG(VB_GENERAL, LOG_ERR, QString("Tried to remove a non-existing "
462  "user: %1.").arg(username));
463  return false;
464  }
465 
466  if (username == "admin")
467  {
468  LOG(VB_GENERAL, LOG_ERR, QString("Tried to remove user: %1 (not "
469  "permitted.)").arg("admin"));
470  return false;
471  }
472 
473  if (CreateDigest(username, password) != GetPasswordDigest(username))
474  {
475  LOG(VB_GENERAL, LOG_ERR, QString("Incorrect password for user: %1.")
476  .arg(username));
477  return false;
478  }
479 
480  MSqlQuery deleteQuery(MSqlQuery::InitCon());
481  deleteQuery.prepare("DELETE FROM users WHERE " "username = :USER_NAME ");
482  deleteQuery.bindValue(":USER_NAME", username);
483 
484  bResult = deleteQuery.exec();
485 
486  if (!bResult)
487  MythDB::DBError("Error removing digest user from database",
488  deleteQuery);
489 
490  return bResult;
491 }
492 
496 bool MythSessionManager::ChangeDigestUserPassword(const QString& username,
497  const QString& oldPassword,
498  const QString& newPassword)
499 {
500  bool bResult;
501 
502  if (newPassword.isEmpty())
503  {
504  LOG(VB_GENERAL, LOG_ERR, QString("New password is missing."));
505  return false;
506  }
507 
508  if (!IsValidUser(username))
509  {
510  LOG(VB_GENERAL, LOG_ERR, QString("Attempted to update non-existing"
511  " user: %1.").arg(username));
512  return false;
513  }
514 
515  QByteArray oldPasswordDigest = CreateDigest(username, oldPassword);
516 
517  if (oldPasswordDigest != GetPasswordDigest(username))
518  {
519  LOG(VB_GENERAL, LOG_ERR, QString("Incorrect old password for "
520  "user: %1.").arg(username));
521  return false;
522  }
523 
524  MSqlQuery update(MSqlQuery::InitCon());
525  update.prepare("UPDATE users SET "
526  "password_digest = :NEW_PASSWORD_DIGEST WHERE "
527  "username = :USER_NAME AND "
528  "password_digest = :OLD_PASSWORD_DIGEST");
529  update.bindValue(":NEW_PASSWORD_DIGEST", CreateDigest(username,
530  newPassword));
531  update.bindValue(":USER_NAME", username);
532  update.bindValue(":OLD_PASSWORD_DIGEST", oldPasswordDigest);
533 
534  bResult = update.exec();
535 
536  if (!bResult)
537  MythDB::DBError("Error updating digest user in database", update);
538 
539  return bResult;
540 }
541 
545 QByteArray MythSessionManager::CreateDigest(const QString &username,
546  const QString &password)
547 {
548  // The realm is a constant, it's not private because it's included in the
549  // plain text sent with an HTTP WWW-Authenticate header.
550  QString plainText = QString("%1:MythTV:%2").arg(username).arg(password);
551  QByteArray digest = QCryptographicHash::hash(plainText.toLatin1(),
552  QCryptographicHash::Md5).toHex();
553  return digest;
554 }
555 
560  const QString& username,
561  const QString& password,
562  const QString& newPassword,
563  const QString& adminPassword)
564 {
565  bool returnCode = false;
566 
567  if (username.isEmpty() || password.isEmpty())
568  LOG(VB_GENERAL, LOG_ERR, QString("Username and password required."));
569  else if (action == DIGEST_USER_ADD)
570  returnCode = AddDigestUser(username, password, adminPassword);
571  else if (action == DIGEST_USER_REMOVE)
572  returnCode = RemoveDigestUser(username, password);
573  else if (action == DIGEST_USER_CHANGE_PW)
574  returnCode = ChangeDigestUserPassword(username, password, newPassword);
575  else
576  LOG(VB_GENERAL, LOG_ERR, QString("Unknown action."));
577 
578  return returnCode;
579 }
bool next(void)
Wrap QSqlQuery::next() so we can display the query results.
Definition: mythdbcon.cpp:782
void bindValue(const QString &placeholder, const QVariant &val)
Add a single binding.
Definition: mythdbcon.cpp:863
bool ManageDigestUser(DigestUserActions action, const QString &username, const QString &password, const QString &newPassword, const QString &adminPassword)
Manage digest user entries.
bool IsValidSession(const QString &sessionToken)
Check if the session token is valid.
bool IsValid(void) const
Check if this session object appears properly constructed, it DOES NOT validate whether it is a valid...
Definition: mythsession.cpp:15
QDateTime m_sessionLastActive
Definition: mythsession.h:75
QString m_sessionClient
Definition: mythsession.h:77
QSqlQuery wrapper that fetches a DB connection from the connection pool.
Definition: mythdbcon.h:125
int size(void) const
Definition: mythdbcon.h:203
MythUserSession LoginUser(const QString &username, const QByteArray &digest, const QString &client="")
Login user by digest.
void DestroyUserSession(const QString &sessionToken)
Removes user session from the database and cache.
void LoadSessions(void)
Load the values from the sessions table on startup.
unsigned int uint
Definition: compat.h:140
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QVariant value(int i) const
Definition: mythdbcon.h:198
bool IsValidUser(const QString &username)
Check if the given user exists but not whether there is a valid session open for them!
bool CheckPermission(const QString &context, uint permission)
Check if the user has the given permission in a context.
Definition: mythsession.cpp:37
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
QString m_name
Definition: mythsession.h:71
static MSqlQueryInfo InitCon(ConnectionReuse=kNormalConnection)
Only use this in combination with MSqlQuery constructor.
Definition: mythdbcon.cpp:535
static QByteArray CreateDigest(const QString &username, const QString &password)
Generate a digest string.
MythUserSession GetSession(const QString &sessionToken)
Load the session details and return.
bool Save(void)
Save the session to the database.
Definition: mythsession.cpp:56
QString GetPasswordDigest(const QString &username)
Load the password digest for comparison in the HTTP Auth code.
QDateTime m_sessionCreated
Definition: mythsession.h:74
bool AddDigestUser(const QString &username, const QString &password, const QString &adminPassword)
DigestUserActions
Definition: mythsession.h:10
QString m_sessionToken
Definition: mythsession.h:73
bool RemoveDigestUser(const QString &username, const QString &password)
bool prepare(const QString &query)
QSqlQuery::prepare() is not thread safe in Qt <= 3.3.2.
Definition: mythdbcon.cpp:807
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QMap< QString, MythUserSession > m_sessionList
Definition: mythsession.h:221
bool ChangeDigestUserPassword(const QString &username, const QString &oldPassword, const QString &newPassword)
MythUserSession CreateUserSession(uint userId, const QString &username, const QString &client)
Add new user session to the database and cache.
bool exec(void)
Wrap QSqlQuery::exec() so we can display SQL.
Definition: mythdbcon.cpp:603
static void DBError(const QString &where, const MSqlQuery &query)
Definition: mythdb.cpp:179
bool IsMasterBackend(void)
is this the actual MBE process
void UpdateSession(const QString &sessionToken)
Update the session timestamps.
QString GetHostName(void)
QDateTime m_sessionExpires
Definition: mythsession.h:76
bool Update(void)
Update session expiry and access times.