6
6
use \BNETDocs \Libraries \User ;
7
7
8
8
use \CarlBennett \MVC \Libraries \Common ;
9
+ use \CarlBennett \MVC \Libraries \DatabaseDriver ;
9
10
11
+ use \DateTime ;
12
+ use \DateTimeZone ;
13
+ use \InvalidArgumentException ;
10
14
use \PDO ;
11
15
use \PDOException ;
12
- use \UnexpectedValueException ;
13
16
14
17
/**
15
18
* Authentication
16
19
* The class that handles authenticating and verifying a client.
17
20
*/
18
21
class Authentication {
19
22
20
- const CACHE_KEY = 'bnetdocs-auth-%s ' ;
21
- const COOKIE_NAME = 'sid ' ;
22
- const TTL = 2592000 ; // 1 month
23
+ const COOKIE_NAME = 'sid ' ;
24
+ const DATE_SQL = 'Y-m-d H:i:s ' ;
25
+ const MAX_USER_AGENT = 255 ;
26
+ const TTL = 2592000 ; // 1 month
23
27
24
28
/**
25
29
* @var string $key
@@ -47,8 +51,26 @@ private function __construct() {}
47
51
*
48
52
* @return bool Indicates if the operation succeeded.
49
53
*/
50
- protected static function discard ( $ key ) {
51
- return Common::$ cache ->delete ( sprintf ( self ::CACHE_KEY , $ key ));
54
+ protected static function discard (string $ key ) {
55
+ if (!isset (Common::$ database )) {
56
+ Common::$ database = DatabaseDriver::getDatabaseObject ();
57
+ }
58
+
59
+ try {
60
+ $ stmt = Common::$ database ->prepare ('
61
+ DELETE FROM `user_sessions` WHERE `id` = :id LIMIT 1;
62
+ ' );
63
+
64
+ $ stmt ->bindParam (':id ' , $ key , PDO ::PARAM_STR );
65
+
66
+ $ r = $ stmt ->execute ();
67
+ $ stmt ->closeCursor ();
68
+
69
+ } catch (PDOException $ e ) {
70
+ throw new QueryException ('Cannot delete user session key ' , $ e );
71
+ } finally {
72
+ return $ r ;
73
+ }
52
74
}
53
75
54
76
/**
@@ -59,13 +81,13 @@ protected static function discard( $key ) {
59
81
*
60
82
* @return array The fingerprint details.
61
83
*/
62
- protected static function getFingerprint ( User &$ user ) {
84
+ protected static function getFingerprint (User &$ user ) {
63
85
$ fingerprint = array ();
64
86
65
- $ fingerprint ['ip_address ' ] = getenv ( 'REMOTE_ADDR ' );
66
-
67
- $ fingerprint ['user_id ' ] = (
68
- isset ( self :: $ user ) ? self ::$ user -> getId () : null
87
+ $ fingerprint ['ip_address ' ] = getenv ('REMOTE_ADDR ' );
88
+ $ fingerprint [ ' user_id ' ] = ( is_null ( $ user ) ? null : $ user -> getId ());
89
+ $ fingerprint ['user_agent ' ] = substr (
90
+ getenv ( ' HTTP_USER_AGENT ' ), 0 , self ::MAX_USER_AGENT
69
91
);
70
92
71
93
return $ fingerprint ;
@@ -79,9 +101,12 @@ protected static function getFingerprint( User &$user ) {
79
101
*
80
102
* @return string The unique string.
81
103
*/
82
- protected static function getUniqueKey ( User &$ user ) {
104
+ protected static function getUniqueKey (User &$ user ) {
105
+ if (!$ user instanceof User) {
106
+ throw new InvalidArgumentException ('$user is not instance of User ' );
107
+ }
83
108
return hash ( 'sha1 ' ,
84
- mt_rand () . getenv ( 'REMOTE_ADDR ' ) .
109
+ mt_rand () . getenv ('REMOTE_ADDR ' ) .
85
110
$ user ->getId () . $ user ->getEmail () . $ user ->getUsername () .
86
111
$ user ->getPasswordHash () . $ user ->getPasswordSalt () .
87
112
Common::$ config ->bnetdocs ->user_password_pepper
@@ -97,16 +122,16 @@ protected static function getUniqueKey( User &$user ) {
97
122
*
98
123
* @return bool Indicates if the browser cookie was sent.
99
124
*/
100
- public static function login ( User &$ user ) {
101
- if ( !$ user instanceof User ) {
102
- throw new UnexpectedValueException ( '$user is not instance of User ' );
125
+ public static function login (User &$ user ) {
126
+ if (!$ user instanceof User) {
127
+ throw new InvalidArgumentException ( '$user is not instance of User ' );
103
128
}
104
129
105
- self ::$ key = self ::getUniqueKey ( $ user );
130
+ self ::$ key = self ::getUniqueKey ($ user );
106
131
self ::$ user = $ user ;
107
132
108
- $ fingerprint = self ::getFingerprint ( $ user );
109
- self ::store ( self ::$ key , $ fingerprint );
133
+ $ fingerprint = self ::getFingerprint ($ user );
134
+ self ::store (self ::$ key , $ fingerprint );
110
135
111
136
// 'domain' is an empty string to only allow this specific http host to
112
137
// authenticate, excluding any subdomains. If we were to specify our
@@ -130,7 +155,7 @@ public static function login( User &$user ) {
130
155
* @return bool Indicates if the browser cookie was sent.
131
156
*/
132
157
public static function logout () {
133
- self ::discard ( self ::$ key );
158
+ self ::discard (self ::$ key );
134
159
135
160
self ::$ key = '' ;
136
161
self ::$ user = null ;
@@ -156,31 +181,92 @@ public static function logout() {
156
181
*
157
182
* @param string $key The secret key, typically from the client.
158
183
*
159
- * @return string The fingerprint details, or false if not found.
184
+ * @return array The fingerprint details, or false if not found.
160
185
*/
161
- protected static function lookup ( $ key ) {
162
- $ fingerprint = Common::$ cache ->get ( sprintf ( self ::CACHE_KEY , $ key ));
163
-
164
- if ( $ fingerprint !== false ) {
165
- $ fingerprint = unserialize ( $ fingerprint );
186
+ protected static function lookup (string $ key ) {
187
+ if (!isset (Common::$ database )) {
188
+ Common::$ database = DatabaseDriver::getDatabaseObject ();
166
189
}
167
190
168
- return $ fingerprint ;
191
+ $ fingerprint = false ;
192
+
193
+ try {
194
+ $ stmt = Common::$ database ->prepare ('
195
+ SELECT `user_id`, `ip_address`, `user_agent`
196
+ FROM `user_sessions` WHERE `id` = :id LIMIT 1;
197
+ ' );
198
+
199
+ $ stmt ->bindParam (':id ' , $ key , PDO ::PARAM_STR );
200
+
201
+ $ r = $ stmt ->execute ();
202
+
203
+ if ($ r ) {
204
+ $ fingerprint = $ stmt ->fetch (PDO ::FETCH_ASSOC );
205
+ }
206
+
207
+ $ stmt ->closeCursor ();
208
+
209
+ } catch (PDOException $ e ) {
210
+ throw new QueryException ('Cannot lookup user session key ' , $ e );
211
+ } finally {
212
+ return $ fingerprint ;
213
+ }
169
214
}
170
215
171
216
/**
172
217
* store()
173
218
* Stores authentication info server-side for lookup later.
174
219
*
175
- * @param string $key The secret key.
176
- * @param string $value The fingerprint details.
220
+ * @param string $key The secret key.
221
+ * @param array $fingerprint The fingerprint details.
177
222
*
178
223
* @return bool Indicates if the operation succeeded.
179
224
*/
180
- protected static function store ( $ key , &$ fingerprint ) {
181
- return Common::$ cache ->set (
182
- sprintf ( self ::CACHE_KEY , $ key ), serialize ( $ fingerprint ), self ::TTL
225
+ protected static function store (string $ key , array &$ fingerprint ) {
226
+ if (!isset (Common::$ database )) {
227
+ Common::$ database = DatabaseDriver::getDatabaseObject ();
228
+ }
229
+
230
+ $ tz = new DateTimeZone ('Etc/UTC ' );
231
+
232
+ $ user_id = $ fingerprint ['user_id ' ];
233
+ $ ip_address = $ fingerprint ['ip_address ' ];
234
+ $ user_agent = $ fingerprint ['user_agent ' ];
235
+ $ created_dt = new DateTime ('now ' , $ tz );
236
+ $ created_str = $ created_dt ->format (self ::DATE_SQL );
237
+ $ expires_dt = new DateTime (
238
+ '@ ' . ($ created_dt ->getTimestamp () + self ::TTL ), $ tz
183
239
);
240
+ $ expires_str = $ expires_dt ->format (self ::DATE_SQL );
241
+
242
+ $ r = false ;
243
+
244
+ try {
245
+ $ stmt = Common::$ database ->prepare ('
246
+ INSERT INTO `user_sessions` (
247
+ `id`, `user_id`, `ip_address`, `user_agent`,
248
+ `created_datetime`, `expires_datetime`
249
+ ) VALUES (
250
+ :id, :user_id, :ip_address, :user_agent,
251
+ :created_dt, :expires_dt
252
+ );
253
+ ' );
254
+
255
+ $ stmt ->bindParam (':id ' , $ key , PDO ::PARAM_STR );
256
+ $ stmt ->bindParam (':user_id ' , $ user_id , PDO ::PARAM_INT );
257
+ $ stmt ->bindParam (':ip_address ' , $ ip_address , PDO ::PARAM_STR );
258
+ $ stmt ->bindParam (':user_agent ' , $ user_agent , PDO ::PARAM_STR );
259
+ $ stmt ->bindParam (':created_dt ' , $ created_str , PDO ::PARAM_STR );
260
+ $ stmt ->bindParam (':expires_dt ' , $ expires_str , PDO ::PARAM_STR );
261
+
262
+ $ r = $ stmt ->execute ();
263
+ $ stmt ->closeCursor ();
264
+
265
+ } catch (PDOException $ e ) {
266
+ throw new QueryException ('Cannot store user session key ' , $ e );
267
+ } finally {
268
+ return $ r ;
269
+ }
184
270
}
185
271
186
272
/**
@@ -192,33 +278,39 @@ protected static function store( $key, &$fingerprint ) {
192
278
public static function verify () {
193
279
// get client's lookup key
194
280
self ::$ key = (
195
- isset ( $ _COOKIE [self ::COOKIE_NAME ] ) ? $ _COOKIE [self ::COOKIE_NAME ] : ''
281
+ isset ($ _COOKIE [self ::COOKIE_NAME ]) ? $ _COOKIE [self ::COOKIE_NAME ] : ''
196
282
);
197
283
198
284
// no user yet
199
285
self ::$ user = null ;
200
286
201
287
// return if cookie is empty or not set
202
- if ( empty ( self ::$ key )) { return false ; }
288
+ if (empty (self ::$ key )) { return false ; }
203
289
204
290
// lookup key in our store
205
- $ lookup = self ::lookup ( self ::$ key );
291
+ $ lookup = self ::lookup (self ::$ key );
206
292
207
293
// logout and return if we could not verify their info
208
- if ( !$ lookup ) {
294
+ if (!$ lookup ) {
209
295
self ::logout ();
210
296
return false ;
211
297
}
212
298
213
299
// logout and return if their fingerprint ip address does not match
214
- if ( $ lookup ['ip_address ' ] !== getenv ( 'REMOTE_ADDR ' )) {
300
+ if ($ lookup ['ip_address ' ] !== getenv ('REMOTE_ADDR ' )) {
301
+ self ::logout ();
302
+ return false ;
303
+ }
304
+
305
+ // logout and return if their fingerprint user agent does not match
306
+ if ($ lookup ['user_agent ' ] !== getenv ('HTTP_USER_AGENT ' )) {
215
307
self ::logout ();
216
308
return false ;
217
309
}
218
310
219
311
// verified info, let's get the user object
220
- if ( isset ( $ lookup ['user_id ' ] )) {
221
- self ::$ user = new User ( $ lookup ['user_id ' ] );
312
+ if (isset ($ lookup ['user_id ' ])) {
313
+ self ::$ user = new User ($ lookup ['user_id ' ]);
222
314
}
223
315
224
316
return true ;
0 commit comments