1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package midcom.services |
4
|
|
|
* @author The Midgard Project, http://www.midgard-project.org |
5
|
|
|
* @copyright The Midgard Project, http://www.midgard-project.org |
6
|
|
|
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
use Symfony\Component\HttpFoundation\Request; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Authentication backend, responsible for validating user/password pairs and |
13
|
|
|
* mapping them to a given user as well as the "sessioning" part, e.g. the transition |
14
|
|
|
* of the authentication credentials over several requests. |
15
|
|
|
* |
16
|
|
|
* Configuration, if necessary, should be done using the MidCOM configuration |
17
|
|
|
* system, prefixing all values with 'auth_backend_$name_', e.g. |
18
|
|
|
* 'auth_backend_cookie_timeout'. |
19
|
|
|
* |
20
|
|
|
* @package midcom.services |
21
|
|
|
*/ |
22
|
|
|
abstract class midcom_services_auth_backend |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* Internal cache of all loaded users, indexed by their identifiers. |
26
|
|
|
* |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private $_user_cache = []; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* This function, always called first in the order of execution, should check |
33
|
|
|
* whether we have a usable login session. It has to use the login session management |
34
|
|
|
* system to load a login session. At the end of the successful execution of this |
35
|
|
|
* function, you have to populate the $session and $user members accordingly. |
36
|
|
|
* |
37
|
|
|
* @return ?array clientip, userid and timeout if the login session was successfully loaded |
38
|
|
|
*/ |
39
|
|
|
abstract public function read_session(Request $request) : ?array; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* This is called immediately after a new login |
43
|
|
|
* The authentication driver has to ensure that the login identifier stays |
44
|
|
|
* available during subsequent requests. |
45
|
|
|
*/ |
46
|
|
|
abstract public function create_session(?string $clientip, midcom_core_user $user) : bool; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* This should delete the currently active login session, |
50
|
|
|
* which has been loaded by a previous call to read_session or created during |
51
|
|
|
* create_session. |
52
|
|
|
* |
53
|
|
|
* You should throw midcom_error if anything goes wrong here. |
54
|
|
|
*/ |
55
|
|
|
abstract public function delete_session(); |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Refresh the session's timestamp here |
59
|
|
|
*/ |
60
|
|
|
abstract public function update_session(); |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Load a user from the database and returns an object instance. |
64
|
|
|
* |
65
|
|
|
* @param mixed $id A valid identifier for a MidgardPerson: An existing midgard_person class |
66
|
|
|
* or subclass thereof, a Person ID or GUID or a midcom_core_user identifier. |
67
|
|
|
*/ |
68
|
189 |
|
public function get_user($id) : ?midcom_core_user |
69
|
|
|
{ |
70
|
189 |
|
$param = $id; |
71
|
|
|
|
72
|
189 |
|
if (isset($param->id)) { |
73
|
|
|
$id = $param->id; |
74
|
189 |
|
} elseif (!is_string($id) && !is_int($id)) { |
75
|
|
|
debug_add('The passed argument was an object of an unsupported type: ' . gettype($param), MIDCOM_LOG_WARN); |
76
|
|
|
debug_print_r('Complete object dump:', $param); |
77
|
|
|
return null; |
78
|
|
|
} |
79
|
189 |
|
if (!array_key_exists($id, $this->_user_cache)) { |
80
|
|
|
try { |
81
|
114 |
|
if ($param instanceof midcom_db_person) { |
82
|
|
|
$param = $param->__object; |
83
|
|
|
} |
84
|
114 |
|
$this->_user_cache[$id] = new midcom_core_user($param); |
85
|
4 |
|
} catch (midcom_error $e) { |
86
|
|
|
// Keep it silent while missing user object can mess here |
87
|
4 |
|
$this->_user_cache[$id] = null; |
88
|
|
|
} |
89
|
|
|
} |
90
|
|
|
|
91
|
189 |
|
return $this->_user_cache[$id]; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Checks for a running login session. |
96
|
|
|
*/ |
97
|
1 |
|
public function check_for_active_login_session(Request $request) : ?midcom_core_user |
98
|
|
|
{ |
99
|
1 |
|
if (!$data = $this->read_session($request)) { |
100
|
1 |
|
return null; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
if ( midcom::get()->config->get('auth_check_client_ip') |
104
|
|
|
&& $data['clientip'] != $request->getClientIp()) { |
105
|
|
|
debug_add("The session had mismatching client IP.", MIDCOM_LOG_INFO); |
106
|
|
|
debug_add("Expected {$data['clientip']}, got {$request->getClientIp()}."); |
107
|
|
|
return null; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
if (!$user = $this->get_user($data['userid'])) { |
111
|
|
|
debug_add("The user ID {$data['userid']} is invalid, could not load the user from the database, assuming tampered session.", |
112
|
|
|
MIDCOM_LOG_ERROR); |
113
|
|
|
$this->delete_session(); |
114
|
|
|
return null; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
if ( !$this->check_timestamp($data['timestamp'], $user) |
118
|
|
|
|| !$this->authenticate($user->username, '', true)) { |
119
|
|
|
$this->logout($user); |
120
|
|
|
return null; |
121
|
|
|
} |
122
|
|
|
return $user; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
private function check_timestamp($timestamp, midcom_core_user $user) : bool |
126
|
|
|
{ |
127
|
|
|
$timeout = midcom::get()->config->get('auth_login_session_timeout', 0); |
128
|
|
|
if ($timeout > 0 && time() - $timeout > $timestamp) { |
129
|
|
|
debug_add("The session has timed out.", MIDCOM_LOG_INFO); |
130
|
|
|
return false; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
if ($timestamp < time() - midcom::get()->config->get('auth_login_session_update_interval')) { |
134
|
|
|
// Update the timestamp if previous timestamp is older than specified interval |
135
|
|
|
$this->update_session(); |
136
|
|
|
$person = $user->get_storage()->__object; |
137
|
|
|
$person->set_parameter('midcom', 'online', time()); |
138
|
|
|
} |
139
|
|
|
return true; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Does the actual Midgard authentication. |
144
|
|
|
*/ |
145
|
64 |
|
public function authenticate(string $username, string $password, bool $trusted = false) : ?midcom_core_user |
146
|
|
|
{ |
147
|
64 |
|
if (empty($username)) { |
148
|
|
|
debug_add("Failed to authenticate: Username is empty.", MIDCOM_LOG_ERROR); |
149
|
|
|
return null; |
150
|
|
|
} |
151
|
64 |
|
if (!$trusted && empty($password)) { |
152
|
|
|
debug_add("Failed to authenticate: Password is empty.", MIDCOM_LOG_ERROR); |
153
|
|
|
return null; |
154
|
|
|
} |
155
|
|
|
|
156
|
64 |
|
$user = midcom_connection::login($username, $password, $trusted); |
157
|
|
|
|
158
|
64 |
|
if (!$user) { |
159
|
|
|
debug_add("Failed to authenticate the given user: " . midcom_connection::get_error_string(), MIDCOM_LOG_INFO); |
160
|
|
|
return null; |
161
|
|
|
} |
162
|
|
|
|
163
|
64 |
|
return $this->get_user($user->person); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Creates a login session using the given credentials. It assumes that |
168
|
|
|
* no login has concluded earlier |
169
|
|
|
*/ |
170
|
64 |
|
public function login(string $username, string $password, string $clientip = null, bool $trusted = false) : ?midcom_core_user |
171
|
|
|
{ |
172
|
64 |
|
$user = $this->authenticate($username, $password, $trusted); |
173
|
64 |
|
if (!$user) { |
174
|
|
|
return null; |
175
|
|
|
} |
176
|
|
|
|
177
|
64 |
|
if ($this->create_session($clientip, $user)) { |
178
|
64 |
|
$person = $user->get_storage()->__object; |
179
|
64 |
|
$person->set_parameter('midcom', 'online', time()); |
180
|
64 |
|
return $user; |
181
|
|
|
} |
182
|
|
|
return null; |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Deletes login information and session |
187
|
|
|
*/ |
188
|
1 |
|
public function logout(midcom_core_user $user) |
189
|
|
|
{ |
190
|
1 |
|
if ($person = $user->get_storage()) { |
191
|
1 |
|
$person->__object->delete_parameters(['domain' => 'midcom', 'name' => 'online']); |
192
|
|
|
} |
193
|
1 |
|
$this->delete_session(); |
194
|
1 |
|
midcom_connection::logout(); |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
|