Passed
Push — master ( d9761c...9b08f6 )
by Andreas
10:15
created

midcom_services_auth_backend   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 44.62%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 27
eloc 62
c 2
b 0
f 0
dl 0
loc 165
ccs 29
cts 65
cp 0.4462
rs 10

6 Methods

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