Passed
Push — master ( c4a4cd...90d26e )
by Andreas
18:42
created

midcom_services_auth_backend::get_user()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.9295

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 13
nop 1
dl 0
loc 24
ccs 11
cts 15
cp 0.7332
crap 7.9295
rs 8.8333
c 0
b 0
f 0
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
     * @param Request $request The request object
38
     * @return ?array clientip, userid and timeout if the login session was successfully loaded
39
     */
40
    abstract public function read_session(Request $request) : ?array;
41
42
    /**
43
     * This is called immediately after a new login
44
     * The authentication driver has to ensure that the login identifier stays
45
     * available during subsequent requests.
46
     *
47
     * @param string $clientip
48
     * @param midcom_core_user $user
49
     * @return boolean Indicating success
50
     */
51
    abstract public function create_session(?string $clientip, midcom_core_user $user) : bool;
52
53
    /**
54
     * This should delete the currently active login session,
55
     * which has been loaded by a previous call to read_session or created during
56
     * create_session.
57
     *
58
     * You should throw midcom_error if anything goes wrong here.
59
     */
60
    abstract public function delete_session();
61
62
    /**
63
     * Refresh the session's timestamp here
64
     */
65
    abstract public function update_session();
66
67
    /**
68
     * Load a user from the database and returns an object instance.
69
     *
70
     * @param mixed $id A valid identifier for a MidgardPerson: An existing midgard_person class
71
     *     or subclass thereof, a Person ID or GUID or a midcom_core_user identifier.
72
     */
73 174
    public function get_user($id) : ?midcom_core_user
74
    {
75 174
        $param = $id;
76
77 174
        if (isset($param->id)) {
78 3
            $id = $param->id;
79 174
        } elseif (!is_string($id) && !is_int($id)) {
80
            debug_add('The passed argument was an object of an unsupported type: ' . gettype($param), MIDCOM_LOG_WARN);
81
            debug_print_r('Complete object dump:', $param);
82
            return null;
83
        }
84 174
        if (!array_key_exists($id, $this->_user_cache)) {
85
            try {
86 111
                if (is_a($param, midcom_db_person::class)) {
87
                    $param = $param->__object;
88
                }
89 111
                $this->_user_cache[$id] = new midcom_core_user($param);
90 4
            } catch (midcom_error $e) {
91
                // Keep it silent while missing user object can mess here
92 4
                $this->_user_cache[$id] = null;
93
            }
94
        }
95
96 174
        return $this->_user_cache[$id];
97
    }
98
99
    /**
100
     * Checks for a running login session.
101
     */
102 1
    public function check_for_active_login_session(Request $request) : ?midcom_core_user
103
    {
104 1
        if (!$data = $this->read_session($request)) {
105 1
            return null;
106
        }
107
108
        if (   midcom::get()->config->get('auth_check_client_ip')
109
            && $data['clientip'] != $request->getClientIp()) {
110
            debug_add("The session had mismatching client IP.", MIDCOM_LOG_INFO);
111
            debug_add("Expected {$data['clientip']}, got {$request->getClientIp()}.");
112
            return null;
113
        }
114
115
        if (!$user = $this->get_user($data['userid'])) {
116
            debug_add("The user ID {$data['userid']} is invalid, could not load the user from the database, assuming tampered session.",
117
            MIDCOM_LOG_ERROR);
118
            $this->delete_session();
119
            return null;
120
        }
121
122
        if (   !$this->check_timestamp($data['timestamp'], $user)
123
            || !$this->authenticate($user->username, '', true)) {
124
            $this->logout($user);
125
            return null;
126
        }
127
        return $user;
128
    }
129
130
    private function check_timestamp($timestamp, midcom_core_user $user) : bool
131
    {
132
        $timeout = midcom::get()->config->get('auth_login_session_timeout', 0);
133
        if ($timeout > 0 && time() - $timeout > $timestamp) {
134
            debug_add("The session has timed out.", MIDCOM_LOG_INFO);
135
            return false;
136
        }
137
138
        if ($timestamp < time() - midcom::get()->config->get('auth_login_session_update_interval')) {
139
            // Update the timestamp if previous timestamp is older than specified interval
140
            $this->update_session();
141
            $person = $user->get_storage()->__object;
142
            $person->set_parameter('midcom', 'online', time());
143
        }
144
        return true;
145
    }
146
147
    /**
148
     * Does the actual Midgard authentication.
149
     */
150 62
    public function authenticate(string $username, string $password, bool $trusted = false) : ?midcom_core_user
151
    {
152 62
        if (empty($username)) {
153
            debug_add("Failed to authenticate: Username is empty.", MIDCOM_LOG_ERROR);
154
            return null;
155
        }
156 62
        if (!$trusted && empty($password)) {
157
            debug_add("Failed to authenticate: Password is empty.", MIDCOM_LOG_ERROR);
158
            return null;
159
        }
160
161 62
        $user = midcom_connection::login($username, $password, $trusted);
162
163 62
        if (!$user) {
164
            debug_add("Failed to authenticate the given user: ". midcom_connection::get_error_string(),
165
                    MIDCOM_LOG_INFO);
166
            return null;
167
        }
168
169 62
        return $this->get_user($user->person);
170
    }
171
172
    /**
173
     * Creates a login session using the given credentials. It assumes that
174
     * no login has concluded earlier
175
     */
176 62
    public function login(string $username, string $password, string $clientip = null, bool $trusted = false) : ?midcom_core_user
177
    {
178 62
        $user = $this->authenticate($username, $password, $trusted);
179 62
        if (!$user) {
180
            return null;
181
        }
182
183 62
        if ($this->create_session($clientip, $user)) {
184 62
            $person = $user->get_storage()->__object;
185 62
            $person->set_parameter('midcom', 'online', time());
186 62
            return $user;
187
        }
188
        return null;
189
    }
190
191
    /**
192
     * Deletes login information and session
193
     */
194 1
    public function logout(midcom_core_user $user)
195
    {
196 1
        if ($person = $user->get_storage()) {
197 1
            $person->__object->delete_parameters(['domain' => 'midcom', 'name' => 'online']);
198
        }
199 1
        $this->delete_session();
200 1
        midcom_connection::logout();
201 1
    }
202
}
203