SSOServer::generateSessionId()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
namespace Zefy\SimpleSSO;
4
5
use Zefy\SimpleSSO\Exceptions\SSOServerException;
6
use Zefy\SimpleSSO\Interfaces\SSOServerInterface;
7
8
/**
9
 * Class SSOServer. This class is only a skeleton.
10
 * First of all, you need to implement abstract functions.
11
 *
12
 * @package Zefy\SimpleSSO
13
 */
14
abstract class SSOServer implements SSOServerInterface
15
{
16
    /**
17
     * @var mixed
18
     */
19
    protected $brokerId;
20
21
    /**
22
     * Attach user's session to broker's session.
23
     *
24
     * @param string|null $broker Broker's name/id.
25
     * @param string|null $token Token sent from broker.
26
     * @param string|null $checksum Calculated broker+token checksum.
27
     *
28
     * @return string or redirect
29
     */
30
    public function attach(?string $broker, ?string $token, ?string $checksum)
31
    {
32
        try {
33
            if (!$broker) {
34
                $this->fail('No broker id specified.', true);
35
            }
36
37
            if (!$token) {
38
                $this->fail('No token specified.', true);
39
            }
40
41
            if (!$checksum || $checksum != $this->generateAttachChecksum($broker, $token)) {
42
                $this->fail('Invalid checksum.', true);
43
            }
44
45
            $this->startUserSession();
46
            $sessionId = $this->generateSessionId($broker, $token);
47
48
            $this->saveBrokerSessionData($sessionId, $this->getSessionData('id'));
49
        } catch (SSOServerException $e) {
50
            return $this->redirect(null, ['sso_error' => $e->getMessage()]);
51
        }
52
53
        $this->attachSuccess();
54
    }
55
56
    /**
57
     * @param null|string $username
58
     * @param null|string $password
59
     *
60
     * @return string
61
     */
62
    public function login(?string $username, ?string $password)
63
    {
64
        try {
65
            $this->startBrokerSession();
66
67
            if (!$username || !$password) {
68
                $this->fail('No username and/or password provided.');
69
            }
70
71
            if (!$this->authenticate($username, $password)) {
72
                $this->fail('User authentication failed.');
73
            }
74
        } catch (SSOServerException $e) {
75
            return $this->returnJson(['error' => $e->getMessage()]);
76
        }
77
78
        $this->setSessionData('sso_user', $username);
79
80
        return $this->userInfo();
81
    }
82
83
    /**
84
     * Logging user out.
85
     *
86
     * @return string
87
     */
88
    public function logout()
89
    {
90
        try {
91
            $this->startBrokerSession();
92
            $this->setSessionData('sso_user', null);
93
        } catch (SSOServerException $e) {
94
            return $this->returnJson(['error' => $e->getMessage()]);
95
        }
96
97
        return $this->returnJson(['success' => 'User has been successfully logged out.']);
98
    }
99
100
    /**
101
     * Returning user info for the broker.
102
     *
103
     * @return string
104
     */
105
    public function userInfo()
106
    {
107
        try {
108
            $this->startBrokerSession();
109
110
            $username = $this->getSessionData('sso_user');
111
112
            if (!$username) {
113
                $this->fail('User not authenticated. Session ID: ' . $this->getSessionData('id'));
114
            }
115
116
            if (!$user = $this->getUserInfo($username)) {
117
                $this->fail('User not found.');
118
            }
119
        } catch (SSOServerException $e) {
120
            return $this->returnJson(['error' => $e->getMessage()]);
121
        }
122
123
        return $this->returnUserInfo($user);
124
    }
125
126
    /**
127
     * Resume broker session if saved session id exist.
128
     *
129
     * @throws SSOServerException
130
     *
131
     * @return void
132
     */
133
    protected function startBrokerSession()
134
    {
135
        if (isset($this->brokerId)) {
136
            return;
137
        }
138
139
        $sessionId = $this->getBrokerSessionId();
140
141
        if (!$sessionId) {
142
            $this->fail('Missing session key from broker.');
143
        }
144
145
        $savedSessionId = $this->getBrokerSessionData($sessionId);
146
147
        if (!$savedSessionId) {
148
            $this->fail('There is no saved session data associated with the broker session id.');
149
        }
150
151
        $this->startSession($savedSessionId);
152
153
        $this->brokerId = $this->validateBrokerSessionId($sessionId);
154
    }
155
156
    /**
157
     * Check if broker session is valid.
158
     *
159
     * @param string $sessionId Session id from the broker.
160
     *
161
     * @throws SSOServerException
162
     *
163
     * @return string
164
     */
165
    protected function validateBrokerSessionId(string $sessionId)
166
    {
167
        $matches = null;
168
169
        if (!preg_match('/^SSO-(\w*+)-(\w*+)-([a-z0-9]*+)$/', $this->getBrokerSessionId(), $matches)) {
170
            $this->fail('Invalid session id');
171
        }
172
173
        if ($this->generateSessionId($matches[1], $matches[2]) != $sessionId) {
174
            $this->fail('Checksum failed: Client IP address may have changed');
175
        }
176
177
        return $matches[1];
178
    }
179
180
    /**
181
     * Generate session id from session token.
182
     *
183
     * @param string $brokerId
184
     * @param string $token
185
     *
186
     * @throws SSOServerException
187
     *
188
     * @return string
189
     */
190
    protected function generateSessionId(string $brokerId, string $token)
191
    {
192
        $broker = $this->getBrokerInfo($brokerId);
193
194
        if (!$broker) {
195
            $this->fail('Provided broker does not exist.');
196
        }
197
198
        return 'SSO-' . $brokerId . '-' . $token . '-' . hash('sha256', 'session' . $token . $broker['secret']);
199
    }
200
201
    /**
202
     * Generate session id from session token.
203
     *
204
     * @param string $brokerId
205
     * @param string $token
206
     *
207
     * @throws SSOServerException
208
     *
209
     * @return string
210
     */
211
    protected function generateAttachChecksum($brokerId, $token)
212
    {
213
        $broker = $this->getBrokerInfo($brokerId);
214
215
        if (!$broker) {
216
            $this->fail('Provided broker does not exist.');
217
        }
218
219
        return hash('sha256', 'attach' . $token . $broker['secret']);
220
    }
221
222
    /**
223
     * Do things if attaching was successful.
224
     *
225
     * @return void
226
     */
227
    protected function attachSuccess()
228
    {
229
        $this->redirect();
230
    }
231
232
    /**
233
     * If something failed, throw an Exception or redirect.
234
     *
235
     * @param null|string $message
236
     * @param bool $isRedirect
237
     * @param null|string $url
238
     *
239
     * @throws SSOServerException
240
     *
241
     * @return void
242
     */
243
    protected function fail(?string $message, bool $isRedirect = false, ?string $url = null)
244
    {
245
        if (!$isRedirect) {
246
            throw new SSOServerException($message);
247
        }
248
249
        $this->redirect($url, ['sso_error' => $message]);
250
    }
251
252
    /**
253
     * Redirect to provided URL with query string.
254
     *
255
     * If $url is null, redirect to url which given in 'return_url'.
256
     *
257
     * @param string|null $url URL to be redirected.
258
     * @param array $parameters HTTP query string.
259
     * @param int $httpResponseCode HTTP response code for redirection.
260
     *
261
     * @return mixed
262
     */
263
    abstract protected function redirect(?string $url = null, array $parameters = [], int $httpResponseCode = 307);
264
265
    /**
266
     * Returning json response for the broker.
267
     *
268
     * @param null|array $response Response array which will be encoded to json.
269
     * @param int $httpResponseCode HTTP response code.
270
     *
271
     * @return string
272
     */
273
    abstract protected function returnJson(?array $response = null, int $httpResponseCode = 204);
274
275
    /**
276
     * Authenticate using user credentials
277
     *
278
     * @param string $username
279
     * @param string $password
280
     *
281
     * @return bool|array
282
     */
283
    abstract protected function authenticate(string $username, string $password);
284
285
    /**
286
     * Get the secret key and other info of a broker
287
     *
288
     * @param string $brokerId
289
     *
290
     * @return null|array
291
     */
292
    abstract protected function getBrokerInfo(string $brokerId);
293
294
    /**
295
     * Get the information about a user
296
     *
297
     * @param string $username
298
     *
299
     * @return array|object|null
300
     */
301
    abstract protected function getUserInfo(string $username);
302
303
    /**
304
     * Returning user info for broker. Should return json or something like that.
305
     *
306
     * @param array|object $user Can be user object or array.
307
     *
308
     * @return mixed
309
     */
310
    abstract protected function returnUserInfo($user);
311
312
    /**
313
     * Return session id sent from broker.
314
     *
315
     * @return null|string
316
     */
317
    abstract protected function getBrokerSessionId();
318
319
    /**
320
     * Start new session when user visits server.
321
     *
322
     * @return void
323
     */
324
    abstract protected function startUserSession();
325
326
    /**
327
     * Set session data
328
     *
329
     * @param string $key
330
     * @param null|string $value
331
     *
332
     * @return void
333
     */
334
    abstract protected function setSessionData(string $key, ?string $value = null);
335
336
    /**
337
     * Get data saved in session.
338
     *
339
     * @param string $key
340
     *
341
     * @return null|string
342
     */
343
    abstract protected function getSessionData(string $key);
344
345
    /**
346
     * Start new session with specific session id.
347
     *
348
     * @param $sessionId
349
     *
350
     * @return void
351
     */
352
    abstract protected function startSession(string $sessionId);
353
354
    /**
355
     * Save broker session data to cache.
356
     *
357
     * @param string $brokerSessionId
358
     * @param string $sessionData
359
     *
360
     * @return void
361
     */
362
    abstract protected function saveBrokerSessionData(string $brokerSessionId, string $sessionData);
363
364
    /**
365
     * Get broker session data from cache.
366
     *
367
     * @param string $brokerSessionId
368
     *
369
     * @return null|string
370
     */
371
    abstract protected function getBrokerSessionData(string $brokerSessionId);
372
}
373