Test Failed
Push — master ( 6c4ac8...017701 )
by Andreas
21:16
created

midcom_services_auth_backend   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Test Coverage

Coverage 43.94%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 64
dl 0
loc 170
ccs 29
cts 66
cp 0.4394
rs 10
c 1
b 0
f 0
wmc 27

6 Methods

Rating   Name   Duplication   Size   Complexity  
A authenticate() 0 19 5
A get_user() 0 21 6
B check_for_active_login_session() 0 26 7
A logout() 0 7 2
A check_timestamp() 0 15 4
A login() 0 13 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
     * @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
                $this->_user_cache[$id] = new midcom_core_user($param);
82
            } catch (midcom_error $e) {
83
                // Keep it silent while missing user object can mess here
84 114
                $this->_user_cache[$id] = null;
85 4
            }
86
        }
87 4
88
        return $this->_user_cache[$id];
89
    }
90
91 189
    /**
92
     * Checks for a running login session.
93
     */
94
    public function check_for_active_login_session(Request $request) : ?midcom_core_user
95
    {
96
        if (!$data = $this->read_session($request)) {
97 1
            return null;
98
        }
99 1
100 1
        if (   midcom::get()->config->get('auth_check_client_ip')
101
            && $data['clientip'] != $request->getClientIp()) {
102
            debug_add("The session had mismatching client IP.", MIDCOM_LOG_INFO);
103
            debug_add("Expected {$data['clientip']}, got {$request->getClientIp()}.");
104
            return null;
105
        }
106
107
        if (!$user = $this->get_user($data['userid'])) {
108
            debug_add("The user ID {$data['userid']} is invalid, could not load the user from the database, assuming tampered session.",
109
            MIDCOM_LOG_ERROR);
110
            $this->delete_session();
111
            return null;
112
        }
113
114
        if (   !$this->check_timestamp($data['timestamp'], $user)
115
            || !$this->authenticate($user->username, '', true)) {
116
            $this->logout($user);
117
            return null;
118
        }
119
        return $user;
120
    }
121
122
    private function check_timestamp($timestamp, midcom_core_user $user) : bool
123
    {
124
        $timeout = midcom::get()->config->get('auth_login_session_timeout', 0);
125
        if ($timeout > 0 && time() - $timeout > $timestamp) {
126
            debug_add("The session has timed out.", MIDCOM_LOG_INFO);
127
            return false;
128
        }
129
130
        if ($timestamp < time() - midcom::get()->config->get('auth_login_session_update_interval')) {
131
            // Update the timestamp if previous timestamp is older than specified interval
132
            $this->update_session();
133
            $person = $user->get_storage()->__object;
134
            $person->set_parameter('midcom', 'online', time());
135
        }
136
        return true;
137
    }
138
139
    /**
140
     * Does the actual Midgard authentication.
141
     */
142
    public function authenticate(string $username, string $password, bool $trusted = false) : ?midcom_core_user
143
    {
144
        if (empty($username)) {
145 64
            debug_add("Failed to authenticate: Username is empty.", MIDCOM_LOG_ERROR);
146
            return null;
147 64
        }
148
        if (!$trusted && empty($password)) {
149
            debug_add("Failed to authenticate: Password is empty.", MIDCOM_LOG_ERROR);
150
            return null;
151 64
        }
152
153
        $user = midcom_connection::login($username, $password, $trusted);
154
155
        if (!$user) {
156 64
            debug_add("Failed to authenticate the given user: " . midcom_connection::get_error_string(), MIDCOM_LOG_INFO);
157
            return null;
158 64
        }
159
160
        return $this->get_user($user->person);
161
    }
162
163 64
    /**
164
     * Creates a login session using the given credentials. It assumes that
165
     * no login has concluded earlier
166
     */
167
    public function login(string $username, string $password, string $clientip = null, bool $trusted = false) : ?midcom_core_user
168
    {
169
        $user = $this->authenticate($username, $password, $trusted);
170 64
        if (!$user) {
171
            return null;
172 64
        }
173 64
174
        if ($this->create_session($clientip, $user)) {
175
            $person = $user->get_storage()->__object;
176
            $person->set_parameter('midcom', 'online', time());
177 64
            return $user;
178 64
        }
179 64
        return null;
180 64
    }
181
182
    /**
183
     * Deletes login information and session
184
     */
185
    public function logout(midcom_core_user $user)
186
    {
187
        if ($person = $user->get_storage()) {
188 1
            $person->__object->delete_parameters(['domain' => 'midcom', 'name' => 'online']);
189
        }
190 1
        $this->delete_session();
191 1
        midcom_connection::logout();
192
    }
193
}
194