File: logic.cpp

package info (click to toggle)
bibledit 5.1.023-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 252,116 kB
  • sloc: xml: 915,984; ansic: 231,234; cpp: 96,957; javascript: 46,996; sh: 5,022; makefile: 512; php: 69
file content (274 lines) | stat: -rw-r--r-- 8,520 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
/*
Copyright (©) 2003-2025 Teus Benschop.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


#include <session/logic.h>
#include <database/sqlite.h>
#include <database/users.h>
#include <database/login.h>
#include <webserver/request.h>
#include <filter/url.h>
#include <filter/string.h>
#include <filter/roles.h>
#include <config/globals.h>
#include <user/logic.h>


// The username and password for a demo installation, and for a disconnected client installation
std::string session_admin_credentials ()
{
  return "admin";
}


/*

The PHP persistent storage of sessions normally works well.
On shared hosts, the hosting provider may influence how PHP sessions work.
In such a case, the session mechanism does not always work as desired.
This may result in frequently being locked out.
A possible solution would be to work around this behaviour.

Bibledit then found another solution:
It had its own persistent session storage mechanism.
This mechanism was independent of any hosting provider or any PHP session mechanism.

That mechanism made the following situation possible, although not likely:
* One user logs in.
* Another user, with exactly the same signature, is automatically logged in too.
The above is undesired.
The behaviour can be prevented when one of the users sets another user agent.

To improve the persistent login mechanism, the following has been done:
1. Bibledit generates a cookie with unique session identifier and other parameters.
2. When the user logs in, the browser will send this new cookie.
3. Bibledit stores the cookie details as it receives the cookie during login.
4. Next time when the user requests any page page, the browser will again send this cookie.
   Bibledit checks the details from the submitted cookie with its database.
   If the details match, the user is logged in.
It sets the cookie to expire after a certain time.

*/


Session_Logic::Session_Logic (Webserver_Request& webserver_request):
m_webserver_request (webserver_request)
{
  m_touch_enabled = false;
  open ();
}


// Call this when logging in.
void Session_Logic::open ()
{
  if (open_access ()) 
    return;
  if (client_access ()) 
    return;

  // Work around a weird bug where the user_agent's size is 140735294083184 leading to a crash.
  if (m_webserver_request.user_agent.size () > 10'000) 
    return;

  // Discard empty cookies right-away.
  // Don't regard this as something that triggers the brute force attach mitigation mechanism.
  std::string cookie = m_webserver_request.session_identifier;
  if (cookie.empty ()) {
    set_username (std::string());
    m_logged_in = false;
    return;
  }
  
  bool daily;
  std::string username_from_cookie = Database_Login::getUsername (cookie, daily);
  if (!username_from_cookie.empty ()) {
    set_username (username_from_cookie);
    m_logged_in = true;
    if (daily) m_webserver_request.resend_cookie = true;
    m_touch_enabled = Database_Login::getTouchEnabled (cookie);
  } else {
    set_username (std::string());
    m_logged_in = false;
  }
}


void Session_Logic::set_username (const std::string& name)
{
  m_username = name;
}


bool Session_Logic::open_access ()
{
  // Open access if it is flagged as such.
  if (config_globals_open_installation) {
    set_username (session_admin_credentials ());
    m_level = Filter_Roles::admin ();
    m_logged_in = true;
    return true;
  }
  return false;
}


// Attempts to log into the system.
// Records whether the user logged in from a touch-enabled device.
// Returns boolean success.
bool Session_Logic::attempt_login (std::string user_or_email, const std::string& password,
                                   bool touch_enabled_in, bool skip_checks)
{
  // Brute force attack mitigation.
  if (!user_logic_login_failure_check_okay ()) {
    return false;
  }

  Database_Users database_users = Database_Users ();
  bool login_okay = false;

  // Match username and email.
  if (database_users.matchUserPassword (user_or_email, password)) {
    login_okay = true;
  }

  // Match password and email.
  if (database_users.matchEmailPassword (user_or_email, password)) {
    login_okay = true;
    // Fetch username that belongs to the email address that was used to login.
    user_or_email = database_users.getEmailToUser (user_or_email);
  }
  
  // The routine can skip the checks, as in the case of a confirmation link.
  if (skip_checks) {
    login_okay = true;
    // When skipping checks, the email is passed, so fetch the username from that.
    user_or_email = database_users.getEmailToUser (user_or_email);
    if (user_or_email.empty()) login_okay = false;
  }
  
  if (login_okay) {
    open ();
    set_username (user_or_email);
    m_logged_in = true;
    std::string cookie = m_webserver_request.session_identifier;
    Database_Login::setTokens (user_or_email, "", "", "", cookie, touch_enabled_in);
    get_level (true);
    return true;
  } else {
    user_logic_login_failure_register ();
  }
  
  return false;
}


// Returns true if the user is logged in.
bool Session_Logic::get_logged_in ()
{
  // The logged-in status is stored in the object, so that if it is requested twice,
  // the session system is queried only once. It has been seen on some sites that if the php session
  // system was queried more than once, it did not behave consistently.
  // Buffering the status in the object resolved this.
  // After the session system was dropped, the above comment is no longer relevant.
  // The information this comment contains remains relevant for the future.
  if (open_access ()) 
    return true;
  return m_logged_in;
}


const std::string& Session_Logic::get_username () const
{
  return m_username;
}


bool Session_Logic::get_touch_enabled ()
{
  // Deal with the global variable for touch-enabled.
  // The variable, if zero, does nothing.
  // Else it either sets or clears the touch-enabled state.
  // This 'doing nothing' is needed for a situation where a server has clients
  // with and without a touch-screen, so one global variable does not affect a local state.
  // The global variable is for a client application, where there's only one user per app.
  if (config_globals_touch_enabled > 0) 
    m_touch_enabled = true;
  if (config_globals_touch_enabled < 0)
    m_touch_enabled = false;
  // Give the result, either set globally, or else through prior reading from the database.
  return m_touch_enabled;
}


// Returns the current level of the session as an integer.
int Session_Logic::get_level (bool force)
{
  if (open_access ()) 
    return m_level;
  if ((m_level == 0) || force) {
    if (m_logged_in) {
      Database_Users database = Database_Users();
      m_level = database.get_level (m_username);
    } else {
      m_level = Filter_Roles::guest ();
    }
  }
  return m_level;
}


void Session_Logic::logout ()
{
  const std::string cookie = m_webserver_request.session_identifier;
  Database_Login::removeTokens (m_username, cookie);
  set_username (std::string());
  m_level = Filter_Roles::guest();
}


bool Session_Logic::client_access ()
{
  // If client mode is prepared, 
  // log in as the first username in the user database,
  // or as the admin in case no user has been set up yet.
  if (config_globals_client_prepared) {
    Database_Users database_users;
    std::vector <std::string> users = database_users.get_users ();
    std::string user;
    if (users.empty ()) {
      user = session_admin_credentials ();
      m_level = Filter_Roles::admin ();
    } else {
      user = users.at(0);
      m_level = database_users.get_level (user);
    }
    set_username (user);
    m_logged_in = true;
    return true;
  }
  return false;
}


void Session_Logic::switch_user (std::string new_user)
{
  std::string cookie = m_webserver_request.session_identifier;
  Database_Login::removeTokens (new_user, cookie);
  Database_Login::renameTokens (m_username, new_user, cookie);
}