Skip to content

Commit

Permalink
Condense user-list and add enroll- and authentication status views
Browse files Browse the repository at this point in the history
* User list: Make the user-list less wide by moving the secret to the authenticate screen and limiting the PN adress to 20 characters.
* User list: Sort user list in descending order so newest registered users are shown on top
* Add status vieuws: Add views to show status of the authentication and of the enrollment
* Add links to quickly resent push notifications and retstart a authentication
* Show authentication and enrollment timeouts in the UI.
  • Loading branch information
pmeulen committed Aug 4, 2024
1 parent 04e0db6 commit 1e4e408
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 44 deletions.
130 changes: 107 additions & 23 deletions TestServer/TestServerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,12 @@ public function Route(App $app, string $path)
case "/finish-enrollment": // tiqr client posts secret
$this->finish_enrollment($app);
break;
case '/get-enrollment-status': // Use session to check on enrollment status
$this->get_enrollment_status($app, $view);
break;

// Render a QR code
case "/qr": // used from start-enrollment and start_authenticate views
case "/qr": // used from StartEnrollment and StartAuthenticate vieuws
$this->qr($app);
break;

Expand All @@ -268,16 +271,17 @@ public function Route(App $app, string $path)
case "/start-authenticate": // Show authenticate page to user
$this->start_authenticate($app, $view);
break;

// Send push notification
case "/send-push-notification":
$this->send_push_notification($app, $view);
break;

case "/authentication": // tiqr client posts back response
case "/authentication": // tiqr client posts back authentication response
$this->authentication($app);
break;
case '/get-authentication-status': // Use session to check on authentication status
$this->get_authentication_status($app, $view);
break;

// Configuration
case '/show-logs':
$this->show_logs($view);
break;
Expand All @@ -301,13 +305,15 @@ public function Route(App $app, string $path)
}
}

// Start a new enrollment
// user_id: (optional) user_id for the new user. If not specified a new user_id is created
private function start_enrollment(App $app, TestServerView $view)
{
// The session ID is used for communicating enrollment status between this tiqr server and
// the web browser displaying the enrollment interface. It is not used between the tiqr client and
// this server. We do not use it.
$session_id = 'session_id_' . time();
$this->logger->info("Created session $session_id");
// this server.
$session_id = uniqid('session_id_' . time());
$this->logger->info("Created enrollment session_id $session_id");

// The user_id to create. Get it from the request, if it is not there use a test user ID.
$user_id = $app->getGET()['user_id'] ?? 'test-user-' . time();
Expand All @@ -318,18 +324,16 @@ private function start_enrollment(App $app, TestServerView $view)

$user_display_name = $user_id . '\'s display name';

// Create enrollemnt key. The display name we set here is returned in the metadata generated by
// Create enrollment key. The display name we set here is returned in the metadata generated by
// getEnrollmentMetadata.
// Note: we create the user in the userStorage later with a different display name so the displayname in the
// App differs from the user's displayname on the server.
$enrollment_key = $this->tiqrService->startEnrollmentSession($user_id, $user_display_name, $session_id);
$this->logger->info("Started enrollment session $enrollment_key");
$this->logger->info("Started enrollment session with enrollment_key=$enrollment_key");
$metadataUrl = $this->host_url . "/metadata";
$enroll_string = $this->tiqrService->generateEnrollString("$metadataUrl?enrollment_key=$enrollment_key");
$encoded_enroll_string = htmlentities(urlencode($enroll_string));
$image_url = "/qr?code=" . $encoded_enroll_string;

$view->StartEnrollment(htmlentities($enroll_string), $image_url);
$view->StartEnrollment($enroll_string, $user_id, $session_id);
}

// Generate a png image QR code with whatever string is given in the code HTTP request parameter.
Expand Down Expand Up @@ -486,6 +490,22 @@ private function finish_enrollment(App $app)
echo "OK";
}


// Get the status of the enrollment session
// session_id: (required) the applications enrollment session_id
private function get_enrollment_status(App $app, TestServerView $view)
{
$session_id = $app->getGET()['session_id'] ?? '';
if (strlen($session_id) == 0) {
$app::error_exit(404, "get_enrollment_status: 'session_id' request parameter not set");
}

$status = $this->tiqrService->getEnrollmentStatus($session_id); // May throw
$this->logger->info("Enrollment status for session_id $session_id: $status");

$view->ShowEnrollmentStatus($status, $session_id);
}

private function logo(App $app)
{
// Source: https://nl.wikipedia.org/wiki/Bestand:Philips_PM5544.svg
Expand All @@ -512,13 +532,23 @@ function list_users(App $app, TestServerView $view)
}
}
}

// Reverse-Sort users by user ID
usort($users, function ($a, $b) {
return strcmp($b['userId'], $a['userId']);
});

$view->ListUsers($users);
}

// Start authentication for a user
// user_id: (optional) user ID to authenticate. If not set the tiqr client will select the user ID
private function start_authenticate(App $app, TestServerView $view)
{
$session_id = 'session_id_' . time();
$this->logger->info("Created session $session_id");
// Create a unique session ID for the authentication session
// It can later be used to check for the authentication status using getAuthenticatedUser()
$session_id = uniqid('session_id_' . time() );
$this->logger->info("Starting authentication session");

// The user_id to authenticate. Get it from the request, if it is not there use an empty user ID
// Both scenario's are support by tiqr:
Expand All @@ -536,20 +566,27 @@ private function start_authenticate(App $app, TestServerView $view)
}
}


// Start authentication session
// Start authentication session. This will return a session_key that is communicated to through the Tiqr client
// embedded in a uri that is sent to the client either using a QR code or a push notification or by opening the
// uri on the device where the tiqr client (App) is running.
// Note that the $session_key != the $auth_session_id:
// - session_key: used between tiqr client and tiqr server library
// - auth_session_id: used between tiqr server library and the application
$session_key = $this->tiqrService->startAuthenticationSession($user_id, $session_id);
$this->logger->info('Started authentication session');
$this->logger->info("session_key=$session_key");
$this->logger->info("session_key=$session_key session_id=$session_id");

// Get authentication URL for the tiqr client (to put in the QR code)
$authentication_URL = $this->tiqrService->generateAuthURL($session_key);
$this->logger->info('Started authentication URL');
$this->logger->info("authentication_url=$authentication_URL");

$image_url = "/qr?code=" . htmlentities(urlencode($authentication_URL));

// Authentication can be started for any user (i.e. user_id == '') or for a specific user, in which case
// user_id is set to the user to request authentication for. If we know the user_id we lookup the user to get
// its secret, get the challenge from the authenticationURL and calculate the response so we can show these in the
// UI for testing purposes.
$response = '';
$secret = '';
if (strlen($user_id) > 0) {
// Calculate response
$this->logger->info("Calculating response for $user_id");
Expand All @@ -568,15 +605,21 @@ private function start_authenticate(App $app, TestServerView $view)
$challenge = $exploded[4]; // 10 digit hex challenge
}
$this->logger->info("challenge=$challenge");
$response=\OCRA::generateOCRA('OCRA-1:HOTP-SHA1-6:QH10-S', $secret, '', $challenge, '', $session_key, '');
// Assume the default OCRA suite is used
$response=\OCRA::generateOCRA(Tiqr_Service::DEFAULT_OCRA_SUITE, $secret, '', $challenge, '', $session_key, '');
$this->logger->info("response=$response");
}

$view->StartAuthenticate(htmlentities($authentication_URL), $image_url, $user_id, $response, $session_key);
// $user_id, $response and $secret will only be set when when authenticating a specific user
$view->StartAuthenticate($authentication_URL, $user_id, $response, $session_key, $secret, $session_id);
}


// user_id: (required) The user to send the push notification to
// session_key: (required) The session_key of the authentication session
// session_id: (optional) The application's authentication session ID, used for the check authentication state option
private function send_push_notification(App $app, TestServerView $view) {
// Required to get
$user_id = $app->getGET()['user_id'] ?? '';
if (strlen($user_id) == 0) {
$app::error_exit(404, "Missing user_id in POST");
Expand All @@ -589,6 +632,9 @@ private function send_push_notification(App $app, TestServerView $view) {
}
$this->logger->info("session_key = $session_key");

// Optional session ID.
$session_id = $app->getGET()['session_id'] ?? '';

// Get Notification address and type from userid
$notificationType=$this->userStorage->getNotificationType($user_id);
$this->logger->info("notificationType = $notificationType");
Expand All @@ -615,7 +661,7 @@ private function send_push_notification(App $app, TestServerView $view) {
$this->tiqrService->sendAuthNotification($session_key, $notificationType, $deviceNotificationAddress);
$this->logger->info("Push notification sent");

$view->PushResult("Sent $notificationType to $deviceNotificationAddress");
$view->PushResult("Sent $notificationType to $deviceNotificationAddress", $session_key, $user_id, $session_id);
}


Expand Down Expand Up @@ -765,6 +811,44 @@ private function authentication(App $app)
}


// session_id: (required) The application session_id to check the authentication status for
// user_id: (optional) The user_id to check the authentication status for
private function get_authentication_status(App $app, TestServerView $view)
{
$session_id
= $app->getGET()['session_id'] ?? '';
if (strlen($session_id) == 0) {
$app::error_exit(404, "Missing session_id in GET");
}
$this->logger->info("session_id = $session_id");

// User ID is optional, if set it is the user_id we expect to be authenticated
$user_id = $app->getGET()['user_id'] ?? '';
$this->logger->info("expected user_id = $user_id");

// Returns NULL when not authenticated, returns userid when authenticated
$status = $this->tiqrService->getAuthenticatedUser($session_id);
$statusMsg = '';
if ($status === NULL) {
$statusMsg = 'User not authenticated';
$this->logger->info($statusMsg);
}
else {
$user_id = $status; // $status holds the ID of the authenticated user
$statusMsg = "User $status was authenticated.";
$this->logger->info($statusMsg);
// Check if the user ID from the session matches the user ID from the GET request
// If the user_id was provided in the get request these are expected to match
if (strlen($user_id)>0 && $status != $user_id) {
$this->logger->warning("User ID from session ($status) does not match user ID from GET ($user_id)");
$statusMsg .= " Note: the provided user ID ('$status') does not match the authenticated user ID";
}
}

$view->ShowAuthenticationStatus($statusMsg, $session_id, $user_id);
}


private function show_logs(TestServerView $view)
{
$logFile = $this->getStorageDir() . '/' . $this->current_user . '.log';
Expand Down
Loading

0 comments on commit 1e4e408

Please sign in to comment.