Passed
Branch feature/testserver (9727dc)
by Pieter van der
08:04
created

TestServerController::metadata()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 46
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 46
rs 8.9777
cc 6
nc 12
nop 1
1
<?php
2
3
/*
4
@license New BSD License - See LICENSE file for details.
5
@copyright (C) 2022 SURF BV
6
*/
7
8
namespace TestServer;
9
10
use Tiqr_AutoLoader;
11
use Tiqr_OCRAWrapper;
12
use Tiqr_Service;
13
use Tiqr_UserStorage;
14
15
class TestServerController
16
{
17
    private $tiqrService;
18
    private $userStorage;
19
    private $host_url;
20
21
    /**
22
     * @param $host_url This is the URL by which the tiqr client can reach this server, including http(s):// and port.
23
     * E.g. 'http://my-laptop.local:8000'
24
     * @param $authProtocol This is the app specific url for authentications of the tiqr client, without '://'
0 ignored issues
show
Bug introduced by
The type TestServer\This was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
     * e.g. 'tiqrauth'. This must match what is configured in the tiqr client
26
     * @param $enrollProtocol This is the app specific url for enrolling user accounts in the tiqr client, without '://'
27
     * e.g. 'tiqrenroll'. This must match what is configured in the tiqr client
28
     */
29
    function __construct(string $host_url, string $authProtocol, string $enrollProtocol)
30
    {
31
        $this->host_url = $host_url;
32
        $this->initTiqrLibrary();
33
        $this->tiqrService = $this->createTiqrService($host_url, $authProtocol, $enrollProtocol);
34
        $this->userStorage = $this->createUserStorage();
35
    }
36
37
    /** Initialize the tiqr-server-libphp's autoloader
38
     * @return void
39
     */
40
    private function initTiqrLibrary()
41
    {
42
        // Initialise the tiqr-server-library autoloader
43
        $tiqr_dir = __DIR__ . '/../library/tiqr';
44
        $vendor_dir = __DIR__ . '/../vendor';
45
46
        require_once $tiqr_dir . '/Tiqr/AutoLoader.php';
47
48
        $autoloader = Tiqr_AutoLoader::getInstance([
49
            'tiqr.path' => $tiqr_dir,
50
            'phpqrcode.path' => $vendor_dir . '/kairos/phpqrcode',
51
            'zend.path' => $vendor_dir . '/zendframework/zendframework1/library'
52
        ]);
53
        $autoloader->setIncludePath();
54
    }
55
56
    /**
57
     * @return string Directory path for storing the server's data
58
     */
59
    private function getStorageDir(): string
60
    {
61
        $storage_dir = __DIR__ . '/storage';
62
        if (!is_dir($storage_dir)) {
63
            if (false == mkdir($storage_dir)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
64
                TestServerApp::error_exit(500, "Error creating storage directory: $storage_dir");
65
            }
66
        }
67
68
        return $storage_dir;
69
    }
70
71
    /**
72
     * @return Tiqr_Service
73
     */
74
    private function createTiqrService($host, $authProtocol, $enrollProtocol)
75
    {
76
        // Derive the identifier from the host
77
        $identifier = parse_url($host, PHP_URL_HOST);
78
        $storage_dir = $this->getStorageDir();
0 ignored issues
show
Unused Code introduced by
The assignment to $storage_dir is dead and can be removed.
Loading history...
79
80
        $tiqr_service = new Tiqr_Service(
81
            [
82
                'auth.protocol' => $authProtocol,
83
                'enroll.protocol' => $enrollProtocol,
84
                'ocra.suite' => 'OCRA-1:HOTP-SHA1-6:QH10-S',
85
                'identifier' => $identifier,
86
                'name' => "TestServerController $host",
87
                'logoUrl' => "$host/logoUrl",
88
                'infoUrl' => "$host/infoUrl",
89
90
                // 'phpqrcode.path'
91
                // 'apns.path'
92
                // 'apns.certificate'
93
                'apns.environment' => 'sandbox',
94
95
                'c2dm.username' => 'test_c2dm_username',
96
                'c2dm.password' => 'test_c2dm_password',
97
                'c2dm.application' => 'org.example.authenticator.test',
98
99
                // Session storage, always stored in /tmp/tiqr_state_*
100
                'statestorage' => array(
101
                    'type' => 'file',
102
                ),
103
104
                // Token exchange configuration
105
                'devicestorage' => array(
106
                    'type' => 'dummy',
107
                ),
108
            ]
109
        );
110
111
        return $tiqr_service;
112
    }
113
114
    private function createUserStorage()
115
    {
116
        $storage_dir = $this->getStorageDir();
117
        $options = array(
118
            'type' => 'file',
119
            'path' => $storage_dir,
120
        );
121
        $secretoptions = array(
122
            'type' => 'file',
123
            'path' => $storage_dir,
124
        );
125
        return $userStorage = Tiqr_UserStorage::getStorage(
0 ignored issues
show
Unused Code introduced by
The assignment to $userStorage is dead and can be removed.
Loading history...
126
            'file',
127
            $options,
128
            $secretoptions
129
        );
130
    }
131
132
133
    public function Route(App $app, string $path)
134
    {
135
        $view = new TestServerView();
136
137
        $app::log_info("host_url=$this->host_url");
138
        switch ($path) {
139
            case "/":   // Test server home page
140
                $view->ShowRoot();
141
                break;
142
143
            case "/list-users": // page showing currently enrolled user accounts
144
                $this->list_users($app, $view);
145
                break;
146
147
            // Enrollment
148
            case "/start-enrollment": // Show enroll page to user
149
                $this->start_enrollment($app, $view);
150
                break;
151
            case "/metadata":   // tiqr client gets metadata
152
                $this->metadata($app);
153
                break;
154
            case "/finish-enrollment": // tiqr client posts secret
155
                $this->finish_enrollment($app);
156
                break;
157
158
            // Render a QR code
159
            case "/qr": // used from start-enrollment and start_authenticate views
160
                $this->qr($app);
161
                break;
162
163
            // Serve test logo
164
            case "/logoUrl": // used by tiqr client to download logo, included in metadata
165
                $this->logo($app);
166
                break;
167
            // case "infoUrl": // user in metadata
168
169
            // Authentication
170
            case "/start-authenticate": // Show authenticate page to user
171
                $this->start_authenticate($app, $view);
172
                break;
173
            case "/authentication": // tiqr client posts back response
174
                $this->authentication($app);
175
                break;
176
177
            default:
178
                TestServerApp::error_exit(404, "Unknown route '$path'");
179
        }
180
    }
181
182
    private function start_enrollment(App $app, TestServerView $view)
183
    {
184
        // The session ID is used for communicating enrollment status between this tiqr server and
185
        // the web browser displaying the enrollment interface. It is not used between the tiqr client and
186
        // this server. We do not use it.
187
        $session_id = 'session_id_' . time();
188
        $app::log_info("Created session $session_id");
189
190
        // The user_id to create. Get it from the request, if it is not there use a test user ID.
191
        $user_id = $app->getGET()['user_id'] ?? 'test-user-' . time();
192
193
        if ($this->userStorage->userExists($user_id)) {
194
            $app::log_warning("$user_id already exists");
195
        }
196
197
        $user_display_name = $user_id . '\'s display name';
198
199
        // Create enrollemnt key. The display name we set here is returned in the metadata generated by
200
        // getEnrollmentMetadata.
201
        // Note: we create the user in the userStorage later with a different display name so the displayname in the
202
        //       App differs from the user's displayname on the server.
203
        $enrollment_key = $this->tiqrService->startEnrollmentSession($user_id, $user_display_name, $session_id);
204
        $app::log_info("Started enrollment session $enrollment_key");
205
        $metadataUrl = $this->host_url . "/metadata";
206
        $enroll_string = $this->tiqrService->generateEnrollString($metadataUrl) . "?enrollment_key=$enrollment_key";
207
        $encoded_enroll_string = htmlentities(urlencode($enroll_string));
208
        $image_url = "/qr?code=" . $encoded_enroll_string;
209
210
        $view->StartEnrollment(htmlentities($enroll_string), $image_url);
211
    }
212
213
    // Generate a png image QR code with whatever string is given in the code HTTP request parameter.
214
    private function qr(App $app): void
215
    {
216
        $code = $app->getGET()['code'] ?? '';
217
        if (strlen($code) == 0) {
218
            // http://<server>/qr?code=<the string to encode>
219
            $app::error_exit(404, "qr: 'code' request parameter not set");
220
        }
221
        $this->tiqrService->generateQR($code);
222
    }
223
224
    // In response to scanning the enrollment QR code the tiqr client makes a GET request to the URL in
225
    // the QR code.
226
    // This URL is exactly what we put in the QR code we generated in start_enrollment() :
227
    //   <host_url>/metadata?enrollment_key=<enrollment_key>
228
    private function metadata(App $app)
229
    {
230
        // The enrollment key must be there, it binds the user's browser session to the request comming from
231
        // the user's tiqr client
232
        $enrollment_key = $app->getGET()['enrollment_key'] ?? '';
233
        if (strlen($enrollment_key) == 0) {
234
            // http://<server>/metadata?enrollment_key=<the enrollment key from the QR code>
235
            $app::error_exit(404, "metadata: 'enrollment_key' request parameter not set");
236
        }
237
        // Generate an enrollment secret to add to the metadata URL
238
        $enrollment_secret = $this->tiqrService->getEnrollmentSecret($enrollment_key);
239
        $app::log_info("Created enrollment secret $enrollment_secret for enrollment key $enrollment_key");
240
241
        // -----BEGIN NOTE-----
242
        // The enrollment_secret must be added manually to the enrollment URL.
243
        // I do not see why getEnrollmentMetadata cannot handle this for us en return the enrollment_secret
244
        // to the tiqr client as part of the $enrollment_metadata. That would make the call to getEnrollmentSecret()
245
        // redundant en the gereration of the metadata simpler. The Tiqr client could then return the enrollment_secret
246
        // in it's POST back to the $enrollment_url.
247
        // -----END NOTE-----
248
249
        // Add enrollment_secret to the $enrollment_url
250
        $enrollment_url = $this->host_url . "/finish-enrollment?enrollment_secret=$enrollment_secret";
251
        $authentication_url = $this->host_url . '/authentication';
252
        // Get the enrollment data
253
        $enrollment_metadata = $this->tiqrService->getEnrollmentMetadata($enrollment_key, $authentication_url, $enrollment_url);
254
        if (false == $enrollment_metadata) {
255
            // Happens when calling metadata with the same enrollment key a second time
256
            $app::error_exit(500, 'Error getting enrollment metadata');
257
        }
258
        // Print the generated enrollment data in a more readable form
259
        foreach ($enrollment_metadata as $key1 => $value1) {
260
            if (is_array($value1)) {
261
                foreach ($value1 as $key2 => $value2) {
262
                    $app::log_info("Metadata: $key1/$key2=$value2");
263
                }
264
            }
265
            else {
266
                $app::log_info("Metadata: $key1=$value1");
267
            }
268
        }
269
        // The enrollment metadata must be returned to the client as JSON
270
        $enrollment_metadata_json = json_encode($enrollment_metadata);
271
        $app::log_info("Return: $enrollment_metadata_json");
272
        header("content-type: application/json");
273
        echo $enrollment_metadata_json;
274
    }
275
276
    // After receiving the enrollment metadata the tiqr client generates a secret key and posts
277
    // it to the enrollment URL we specified in the metadata together with the information required for sending
278
    // it push notification and the user's language preference.
279
    private function finish_enrollment(App $app)
280
    {
281
        $enrollment_secret = $app->getGET()['enrollment_secret'] ?? '';
282
        if (strlen($enrollment_secret) == 0) {
283
            // http://<server>/finish_enrollment?enrollment_secret=<the enrollment secret from the metadata>
284
            $app::error_exit(404, "enrollment: 'enrollment_secret' request parameter not set");
285
        }
286
        // Validate the enrollment secret we were sent. In return we get the userid back that we set in
287
        // start_enrollment using startEnrollmentSession
288
        $userid = $this->tiqrService->validateEnrollmentSecret($enrollment_secret);
289
        if (false === $userid) {
290
            $app::error_exit(404, "Invalid enrollment_secret");
291
        }
292
        $app::log_info("userid: $userid");
293
294
        $secret = $app->getPOST()['secret'] ?? '';
295
        if (strlen($secret) == 0) {
296
            $app::error_exit(404, "Missing secret is POST");
297
        }
298
        // This is the hex encoded value of the authentication secret that the tiqr client
299
        // generated
300
        $app::log_info("secret: $secret");
301
302
        $language = $app->getPOST()['language'] ?? '';
303
        if (strlen($language) == 0) {
304
            $app::log_warning("No language in POST");
305
        }
306
        // The iso language code e.g. "nl-NL"
307
        $app::log_info("language: $language");
308
309
        $notificationType = $app->getPOST()['notificationType'] ?? '';
310
        if (strlen($notificationType) == 0) {
311
            $app::log_warning("No notificationType in POST");
312
        }
313
        // The notification message type (APNS, GCM, FCM ...)
314
        $app::log_info("notificationType: $notificationType");
315
316
        $notificationAddress = $app->getPOST()['notificationAddress'] ?? '';
317
        if (strlen($notificationAddress) == 0) {
318
            $app::log_warning("No notificationAddress in POST");
319
        }
320
        // This is the notification address that the Tiqr Client got from the token exchange (e.g. tx.tiqr.org)
321
        $app::log_info("notificationAddress: $notificationAddress");
322
323
        $version = $app->getPOST()['version'] ?? '';
324
        if (strlen($version) == 0) {
325
            $app::log_warning("No version in POST");
326
        }
327
        // ?
328
        $app::log_info("version: $version");
329
330
        $operation = $app->getPOST()['operation'] ?? '';
331
        if (strlen($operation) == 0) {
332
            $app::log_warning("No operation in POST");
333
        }
334
        // Must be "register"
335
        $app::log_info("operation: $operation");
336
        if ($operation != 'register') {
337
            $app::error_exit(404, "Invalid operation: '$operation'. Expected 'register'");
338
        }
339
340
        // Get User-Agent HTTP header
341
        $user_agent = urldecode($_SERVER['HTTP_USER_AGENT'] ?? '');
342
        $app::log_info("User-Agent: $user_agent");
343
344
        // Create the user. Use the display name to store the version the client POSTed and the user-agent it sent
345
        // in this POST request's header.
346
        $this->userStorage->createUser($userid, "$version | $user_agent");
347
        $app::log_info("Created user $userid");
348
349
        // Set the user secret
350
        $this->userStorage->setSecret($userid, $secret);
351
        $app::log_info("Secret for $userid was stored");
352
353
        // Store notification type and the notification address that the client sent us
354
        $this->userStorage->setNotificationType($userid, $notificationType);
355
        $this->userStorage->setNotificationAddress($userid, $notificationAddress);
356
357
        // Finalize the enrollemnt
358
        $this->tiqrService->finalizeEnrollment($enrollment_secret);
359
        $app::log_info("Enrollment was finalized");
360
361
        // Must return "OK" to the tiqr client after a successful enrollment
362
        echo "OK";
363
    }
364
365
    private function logo(App $app)
0 ignored issues
show
Unused Code introduced by
The parameter $app is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

365
    private function logo(/** @scrutinizer ignore-unused */ App $app)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
366
    {
367
        // Source: https://nl.wikipedia.org/wiki/Bestand:Philips_PM5544.svg
368
        $name = __DIR__ . '/Philips_PM5544.jpg';
369
        $fp = fopen($name, 'rb');
370
        header('Content-Type: image/jpeg');
371
        header('Content-Length: ' . filesize($name));
372
        fpassthru($fp);
373
        fclose($fp);
374
    }
375
376
    function list_users(App $app, TestServerView $view)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Unused Code introduced by
The parameter $app is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

376
    function list_users(/** @scrutinizer ignore-unused */ App $app, TestServerView $view)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
377
    {
378
        $storageDir = $this->getStorageDir();
379
        $users = array();
380
        foreach (scandir($storageDir) as $filename) {
381
            if (substr($filename, -5, 5) == '.json') {
382
                $user = json_decode(file_get_contents($storageDir . '/' . $filename), true);
383
                if (($user != NULL) && ($user['secret'])) {
384
                    foreach ($user as $k => $v) {
385
                        $user[$k] = htmlentities($v);
386
                    }
387
                    $users[] = $user;
388
                }
389
            }
390
        }
391
        $view->ListUsers($users);
392
    }
393
394
    private function start_authenticate(App $app, TestServerView $view)
395
    {
396
        $session_id = 'session_id_' . time();
397
        $app::log_info("Created session $session_id");
398
399
        // The user_id to authenticate. Get it from the request, if it is not there use an empty user ID
400
        // Both scenario's are support by tiqr:
401
        // 1. No user-id in the authentication url. This is a login scenario. The tiqr client selects the user id
402
        // 2. Specify the user-is in the authentication url. This is the step-up scenario. The tiqr server specifies
403
        //    the user-id
404
405
        // Get optional user ID
406
        $user_id = $app->getGET()['user_id'] ?? '';
407
        if (strlen($user_id) > 0) {
408
            $app::log_info("Authenticating user '$user_id'");
409
        }
410
411
        if (!$this->userStorage->userExists($user_id)) {
412
            $app::log_warning("'$user_id' is not known on the server");
413
        }
414
415
        // Start authentication session
416
        $session_key = $this->tiqrService->startAuthenticationSession($user_id, $session_id);
417
        $app::log_info('Started authentication session');
418
        $app::log_info("session_key=$session_key");
419
420
        // Get authentication URL for the tiqr client (to put in the QR code)
421
        $authentication_URL = $this->tiqrService->generateAuthURL($session_key);
422
        $app::log_info('Started authentication URL');
423
        $app::log_info("authentication_url=$authentication_URL");
424
425
        $image_url = "/qr?code=" . htmlentities(urlencode($authentication_URL));
0 ignored issues
show
Bug introduced by
It seems like $authentication_URL can also be of type false; however, parameter $string of urlencode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
        $image_url = "/qr?code=" . htmlentities(urlencode(/** @scrutinizer ignore-type */ $authentication_URL));
Loading history...
426
427
        $response = '';
428
        if (strlen($user_id) > 0) {
429
            // Calculate response
430
            $app::log_info("Calculating response for $user_id");
431
            $secret = $this->userStorage->getSecret($user_id);
432
            $app::log_info("secret=$secret");
433
            $exploded = explode('/', $authentication_URL);
0 ignored issues
show
Bug introduced by
It seems like $authentication_URL can also be of type false; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

433
            $exploded = explode('/', /** @scrutinizer ignore-type */ $authentication_URL);
Loading history...
434
            $session_key = $exploded[3]; // hex encoded session
435
            $challenge = $exploded[4];   // 10 digit hex challenge
436
            $app::log_info("challenge=$challenge");
437
            $ocra = new Tiqr_OCRAWrapper('OCRA-1:HOTP-SHA1-6:QH10-S');
438
            $response = $ocra->calculateResponse($secret, $challenge, $session_key);
439
            $app::log_info("response=$response");
440
        }
441
442
        $view->StartAuthenticate(htmlentities($authentication_URL), $image_url, $user_id, $response);
0 ignored issues
show
Bug introduced by
It seems like $authentication_URL can also be of type false; however, parameter $string of htmlentities() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

442
        $view->StartAuthenticate(htmlentities(/** @scrutinizer ignore-type */ $authentication_URL), $image_url, $user_id, $response);
Loading history...
443
    }
444
445
    private function authentication(App $app)
446
    {
447
        // This should be the session key from the authentication URL that we generated
448
        $sessionKey = $app->getPOST()['sessionKey'] ?? '';
449
        if (strlen($sessionKey) == 0) {
450
            $app::error_exit(404, "Missing sessionKey is POST");
451
        }
452
        $app::log_info("sessionKey: $sessionKey");
453
454
        // The userId the client authenticated
455
        $userId = $app->getPOST()['userId'] ?? '';
456
        if (strlen($userId) == 0) {
457
            $app::error_exit(404, "Missing $userId is POST");
458
        }
459
        $app::log_info("userId: $userId");
460
461
        // Get version from POST
462
        $version = $app->getPOST()['version'] ?? '';
463
        if (strlen($version) == 0) {
464
            $app::log_warning("No version in POST");
465
        }
466
        // ?
467
        $app::log_info("version: $version");
468
469
        // Get operation from POST
470
        $operation = $app->getPOST()['operation'] ?? '';
471
        if (strlen($operation) == 0) {
472
            $app::log_warning("No operation in POST");
473
        }
474
        // Must be "login"
475
        $app::log_info("operation: $operation");
476
        if ($operation != 'login') {
477
            $app::error_exit(404, "Invalid operation: '$operation'. Expected 'login'");
478
        }
479
480
        // Get response from POST
481
        $response = $app->getPOST()['response'] ?? '';
482
        if (strlen($response) == 0) {
483
            $app::log_warning("No response in POST");
484
        }
485
        $app::log_info("response: $response");
486
487
        $language = $app->getPOST()['language'] ?? '';
488
        if (strlen($language) == 0) {
489
            $app::log_warning("No language in POST");
490
        }
491
        // The iso language code e.g. "nl-NL"
492
        $app::log_info("language: $language");
493
494
        $notificationType = $app->getPOST()['notificationType'] ?? '';
495
        if (strlen($notificationType) == 0) {
496
            $app::log_warning("No notificationType in POST");
497
        }
498
        // The notification message type (APNS, GCM, FCM ...)
499
        $app::log_info("notificationType: $notificationType");
500
501
        $notificationAddress = $app->getPOST()['notificationAddress'] ?? '';
502
        if (strlen($notificationAddress) == 0) {
503
            $app::log_warning("No notificationAddress in POST");
504
        }
505
        // This is the notification address that the Tiqr Client got from the token exchange (e.g. tx.tiqr.org)
506
        $app::log_info("notificationAddress: $notificationAddress");
507
508
        // Get User-Agent HTTP header
509
        $user_agent = urldecode($_SERVER['HTTP_USER_AGENT'] ?? '');
510
        $app::log_info("User-Agent: $user_agent");
511
512
        $result = 'ERROR'; // Result for the tiqr Client
513
        // 'OK', 'INVALID_CHALLENGE', 'INVALID_REQUEST', 'INVALID_RESPONSE', 'INVALID_USER'
514
515
        if (!$this->userStorage->userExists($userId)) {
516
            $app::log_error("Unknown user: $userId ");
517
            $result = 'INVALID_USER';
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
518
        }
519
520
        // Lookup the secret of the user by ID
521
        $userSecret = $this->userStorage->getSecret($userId);   // Assume this works
522
        $app::log_info("userSercret=$userSecret");
523
524
        $app::log_info("Authenticating user");
525
        $result = $this->tiqrService->authenticate($userId, $userSecret, $sessionKey, $response);
526
        $resultStr = 'ERROR';
527
        switch ($result) {
528
            case Tiqr_Service::AUTH_RESULT_INVALID_REQUEST:
529
                $resultStr = "INVALID_REQUEST";
530
                break;
531
            case Tiqr_Service::AUTH_RESULT_AUTHENTICATED:
532
                $resultStr = "OK";
533
                break;
534
            case Tiqr_Service::AUTH_RESULT_INVALID_RESPONSE:
535
                $resultStr = "INVALID_RESPONSE";
536
                break;
537
            case Tiqr_Service::AUTH_RESULT_INVALID_CHALLENGE:
538
                $resultStr = "INVALID_CHALLENGE";
539
                break;
540
            case Tiqr_Service::AUTH_RESULT_INVALID_USERID:
541
                $resultStr = "INVALID_USER";
542
                break;
543
        }
544
545
        $app::log_info("Returning authentication result '$resultStr'");
546
        echo $resultStr;
547
    }
548
}