From e49ae5ec967d2c423c07189a94c7671a93246481 Mon Sep 17 00:00:00 2001 From: Stephen Nielson Date: Mon, 18 Sep 2023 14:46:10 -0400 Subject: [PATCH] bug: Refactor oauth2 login pages #6845,#6844 (#6846) * Refactor oauth2 login pages #6845,#6844 Made the login error messages show up - Fixes #6845 Refactored the patient-select,scope-authorize,login pages into twig files that share a twig base of oauth2-base.html.twig. Added PageTemplateRender events that module writers can key into / override if they need to. Moved the MFA script into the config.yaml so we can grab it in the twig file. * Minor code review fixes * Fix copyright issues. * Fix escaping and patient-select --- config/config.yaml | 5 +- oauth2/provider/login.php | 207 ------------------ oauth2/provider/scope-authorize.php | 186 ---------------- oauth2/smart/patient-select.php | 155 ------------- .../AuthorizationController.php | 186 +++++++++++++--- .../SMART/SMARTAuthorizationController.php | 55 ++++- .../oauth2}/ehr-launch-autosubmit.html.twig | 0 templates/oauth2/oauth2-base.html.twig | 25 +++ templates/oauth2/oauth2-login.html.twig | 151 +++++++++++++ templates/oauth2/patient-select.html.twig | 123 +++++++++++ templates/oauth2/scope-authorize.html.twig | 142 ++++++++++++ 11 files changed, 653 insertions(+), 582 deletions(-) delete mode 100644 oauth2/provider/login.php delete mode 100644 oauth2/provider/scope-authorize.php delete mode 100644 oauth2/smart/patient-select.php rename {oauth2/smart => templates/oauth2}/ehr-launch-autosubmit.html.twig (100%) create mode 100644 templates/oauth2/oauth2-base.html.twig create mode 100644 templates/oauth2/oauth2-login.html.twig create mode 100644 templates/oauth2/patient-select.html.twig create mode 100644 templates/oauth2/scope-authorize.html.twig diff --git a/config/config.yaml b/config/config.yaml index be25617a307..892d7c3b91e 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -240,4 +240,7 @@ assets: script: erx_javascript.js checkpwd_validation: basePath: '%webroot%/interface/usergroup/' - script: checkpwd_validation.js \ No newline at end of file + script: checkpwd_validation.js + u2f_api: + basePath: '%webroot%/library/js/' + script: u2f-api.js diff --git a/oauth2/provider/login.php b/oauth2/provider/login.php deleted file mode 100644 index b2dd2275a00..00000000000 --- a/oauth2/provider/login.php +++ /dev/null @@ -1,207 +0,0 @@ - - * @copyright Copyright (c) 2020 Jerry Padgett - * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 - */ - -use OpenEMR\Common\Session\SessionUtil; - -if ($oauthLogin !== true) { - $message = xlt("Error. Not authorized"); - SessionUtil::oauthSessionCookieDestroy(); - echo $message; - exit(); -} - -use OpenEMR\Common\Csrf\CsrfUtils; -use OpenEMR\Core\Header; - -?> - - - <?php echo xlt("OpenEMR Authorization"); ?> - - - - -
-
-
- -

- -

- -

- -
-
- -
-
-
-
-

-
    - " . text($key) . " " . ""; - } - } ?> -
-
-
-
-
-
-
-

-
    - $value) { - $key_n = explode('_', $key); - if (stripos($_SESSION['scopes'], $key_n[0]) === false) { - continue; - } - if ((int)$value === 1) { - $value = 'True'; - } - $key = ucwords(str_replace("_", " ", $key)); - echo "
  • " . text($key) . ": " . text($value) . "
  • "; - } - } ?> -
-
-
-
-
- -
- - -
- " type="email" name="email"> -
-
- " type="text" name="username" value=""> -
-
- -
- - - - -
- -
-
- " type="text" name="mfa_token"> -
-
- -
- - - -
-
- -
-
    -
  • -
  • -
-
-
- - - -
- - -
- -
-
- -
-
- -
- - -
-
-
- -
- -
- -
- - - - - - -
-
- - -
- -
-
-
-
-
- - - - - diff --git a/oauth2/provider/scope-authorize.php b/oauth2/provider/scope-authorize.php deleted file mode 100644 index d948ba25693..00000000000 --- a/oauth2/provider/scope-authorize.php +++ /dev/null @@ -1,186 +0,0 @@ - - * @copyright Copyright (c) 2020 Jerry Padgett - * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 - */ - -use OpenEMR\Common\Auth\UuidUserAccount; -use OpenEMR\Common\Auth\OpenIDConnect\Repositories\ScopeRepository; -use OpenEMR\Common\Session\SessionUtil; -use OpenEMR\Common\Csrf\CsrfUtils; -use OpenEMR\Core\Header; - -$oauthLogin = $oauthLogin ?? null; -$offline_requested = $offline_requested ?? false; -$scopes = $scopes ?? []; -$scopeString = $scopeString ?? ""; -$offline_access_date = $offline_access_date ?? ""; -$userRole = $userRole ?? UuidUserAccount::USER_ROLE_PATIENT; -$clientName = $clientName ?? ""; - -if ($oauthLogin !== true) { - $message = xlt("Error. Not authorized"); - SessionUtil::oauthSessionCookieDestroy(); - echo $message; - exit(); -} - -$scopesByResource = []; -$otherScopes = []; -$scopeDescriptions = []; -$hiddenScopes = []; -$scopeRepository = new ScopeRepository(); -foreach ($scopes as $scope) { - // if there are any other scopes we want hidden we can put it here. - if (in_array($scope, ['openid'])) { - $hiddenScopes[] = $scope; - } else if (in_array($scope, $scopeRepository->fhirRequiredSmartScopes())) { - $otherScopes[$scope] = $scopeRepository->lookupDescriptionForScope($scope, $userRole == UuidUserAccount::USER_ROLE_PATIENT); - continue; - } - - $parts = explode("/", $scope); - $context = reset($parts); - $resourcePerm = $parts[1] ?? ""; - $resourcePermParts = explode(".", $resourcePerm); - $resource = $resourcePermParts[0] ?? ""; - $permission = $resourcePermParts[1] ?? ""; - - if (!empty($resource)) { - $scopesByResource[$resource] = $scopesByResource[$resource] ?? ['permissions' => []]; - - $scopesByResource[$resource]['permissions'][$scope] = $scopeRepository->lookupDescriptionForScope($scope, $userRole == UuidUserAccount::USER_ROLE_PATIENT); - } -} -// sort by the resource -ksort($scopesByResource); - -?> - - - <?php echo xlt("OpenEMR Authorization"); ?> - - - - - -
-
-
-
-

-
-
-
-
-
-
-
-
-
- $scopeCollection) : ?> - - -
-
-
- $description) : ?> - - - - - -
-
-
-
-
-
-
-
-
-

-
    - $value) { - $key_n = explode('_', $key); - if (stripos($scopeString, $key_n[0]) === false) { - continue; - } - if ((int)$value === 1) { - $value = 'True'; - } - if ($key == 'fhirUser') { - echo "
  • " . xlt("Permission to retrieve information about the current logged-in user") - . " " . text($userAccount['firstname'] ?? '') . ' ' . text($userAccount['lastname']) . "
  • "; - } else { - $key = ucwords(str_replace("_", " ", $key)); - echo "
  • " . text($key) . ": " . text($value) . "
  • "; - } - } - } ?> -
-
-
-
-
- -
-
-
-

- -

-

:

-

- -
-
-
- - - -
-
-
-
- -
-
- - -
-
-
-
-
-
- - \ No newline at end of file diff --git a/oauth2/smart/patient-select.php b/oauth2/smart/patient-select.php deleted file mode 100644 index bd7b22082e2..00000000000 --- a/oauth2/smart/patient-select.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @copyright Copyright (c) 2021 Stephen Nielson - * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 - */ - -use OpenEMR\Common\Csrf\CsrfUtils; -use OpenEMR\Common\Session\SessionUtil; -use OpenEMR\Core\Header; - -if ($oauthLogin !== true) { - $message = xlt("Error. Not authorized"); - SessionUtil::oauthSessionCookieDestroy(); - echo $message; - exit(); -} - -// make sure we have our patients set -$errorMessage = $errorMessage ?? ""; -$patients = $patients ?? []; -$redirect = $redirect ?? ""; -$searchAction = $searchAction ?? ""; -$fname = $searchParams['fname'] ?? ""; -$mname = $searchParams['mname'] ?? ""; -$lname = $searchParams['lname'] ?? ""; -$hasMore = $hasMore ?? false; - -?> - - - <?php echo xlt("OpenEMR Authorization"); ?> - - - -
-
-
-

-
-
-
- -

- - - -

- -
- " - value="" /> - " - value="" /> - " - value="" /> - " /> -
- -

- -
-
-
-

- - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - -
- -

-
- -
- - -
-
- - - - diff --git a/src/RestControllers/AuthorizationController.php b/src/RestControllers/AuthorizationController.php index 0f72abe0545..f9bf51bfe6a 100644 --- a/src/RestControllers/AuthorizationController.php +++ b/src/RestControllers/AuthorizationController.php @@ -63,6 +63,7 @@ use OpenEMR\Common\Utils\HttpUtils; use OpenEMR\Common\Utils\RandomGenUtils; use OpenEMR\Common\Uuid\UuidRegistry; +use OpenEMR\Events\Core\TemplatePageEvent; use OpenEMR\FHIR\Config\ServerConfig; use OpenEMR\FHIR\SMART\SmartLaunchController; use OpenEMR\FHIR\SMART\SMARTLaunchToken; @@ -77,6 +78,7 @@ use Psr\Log\LoggerInterface; use RestConfig; use RuntimeException; +use Twig\Environment; class AuthorizationController { @@ -122,6 +124,11 @@ class AuthorizationController */ private $restConfig; + /** + * @var Environment + */ + private $twig; + public function __construct($providerForm = true) { if (is_callable([RestConfig::class, 'GetInstance'])) { @@ -153,20 +160,26 @@ public function __construct($providerForm = true) private function getSmartAuthController(): SMARTAuthorizationController { if (!isset($this->smartAuthController)) { - $twigContainer = new TwigContainer(__DIR__ . "/../../oauth2/", $GLOBALS['kernel']); - $twig = $twigContainer->getTwig(); - $this->smartAuthController = new SMARTAuthorizationController( $this->logger, $this->authBaseFullUrl, $this->authBaseFullUrl . self::ENDPOINT_SCOPE_AUTHORIZE_CONFIRM, __DIR__ . "/../../oauth2/", - $twig + $this->getTwig() ); } return $this->smartAuthController; } + private function getTwig(): Environment + { + if (!isset($this->twig)) { + $twigContainer = new TwigContainer(__DIR__ . "/../../oauth2/", $GLOBALS['kernel']); + $this->twig = $twigContainer->getTwig(); + } + return $this->twig; + } + private function configKeyPairs(): void { $response = $this->createServerResponse(); @@ -671,13 +684,40 @@ public function userLogin(): void { $response = $this->createServerResponse(); - $patientRoleSupport = (!empty($GLOBALS['rest_portal_api']) || !empty($GLOBALS['rest_fhir_api'])); + $clientId = $_SESSION['client_id'] ?? null; + $twig = $this->getTwig(); + if (empty($clientId)) { + // why are we logging in... we need to terminate + $this->logger->errorLogCaller("application client_id was missing when it shouldn't have been"); + echo $twig->render("error/general_http_error.html.twig", ['statusCode' => 500]); + die(); + } + $clientService = new ClientRepository(); + $client = $clientService->getClientEntity($clientId); + + $patientRoleSupport = (!empty($GLOBALS['rest_portal_api']) || !empty($GLOBALS['rest_fhir_api'])); + $loginTwigVars = [ + 'authorize' => null + ,'mfaRequired' => false + ,'redirect' => $this->authBaseUrl . "/login" + ,'isTOTP' => false + ,'isU2F' => false + ,'u2fRequests' => '' + ,'appId' => '' + ,'enforce_signin_email' => $GLOBALS['enforce_signin_email'] === '1' ?? false + ,'user' => [ + 'email' => $_POST['email'] ?? '' + ,'username' => $_POST['username'] ?? '' + ,'password' => $_POST['password'] ?? '' + ] + ,'patientRoleSupport' => $patientRoleSupport + ,'invalid' => '' + ,'client' => $client + ]; if (empty($_POST['username']) && empty($_POST['password'])) { $this->logger->debug("AuthorizationController->userLogin() presenting blank login form"); - $oauthLogin = true; - $redirect = $this->authBaseUrl . "/login"; - require_once(__DIR__ . "/../../oauth2/provider/login.php"); + $this->renderTwigPage('oauth2/authorize/login', 'oauth2/oauth2-login.html.twig', $loginTwigVars); exit(); } $continueLogin = false; @@ -687,9 +727,8 @@ public function userLogin(): void CsrfUtils::csrfNotVerified(false, true, false); unset($_POST['username'], $_POST['password']); $invalid = "Sorry. Invalid CSRF!"; // todo: display error - $oauthLogin = true; - $redirect = $this->authBaseUrl . "/login"; - require_once(__DIR__ . "/../../oauth2/provider/login.php"); + $loginTwigVars['invalid'] = $invalid; + $this->renderTwigPage('oauth2/authorize/login', 'oauth2/oauth2-login.html.twig', $loginTwigVars); exit(); } else { $this->logger->debug("AuthorizationController->userLogin() verifying login information"); @@ -700,10 +739,9 @@ public function userLogin(): void if (!$continueLogin) { $this->logger->debug("AuthorizationController->userLogin() login invalid, presenting login form"); - $invalid = "Sorry, Invalid!"; // todo: display error - $oauthLogin = true; - $redirect = $this->authBaseUrl . "/login"; - require_once(__DIR__ . "/../../oauth2/provider/login.php"); + $invalid = xl("Sorry, verify the information you have entered is correct"); // todo: display error + $loginTwigVars['invalid'] = $invalid; + $this->renderTwigPage('oauth2/authorize/login', 'oauth2/oauth2-login.html.twig', $loginTwigVars); exit(); } else { $this->logger->debug("AuthorizationController->userLogin() login valid, continuing oauth process"); @@ -714,27 +752,23 @@ public function userLogin(): void $mfaToken = $mfa->tokenFromRequest($_POST['mfa_type'] ?? null); $mfaType = $mfa->getType(); $TOTP = MfaUtils::TOTP; - $U2F = MfaUtils::U2F; + $loginTwigVars['isTOTP'] = in_array($TOTP, $mfaType); if ($_POST['user_role'] === 'api' && $mfa->isMfaRequired() && is_null($mfaToken)) { - $oauthLogin = true; - $mfaRequired = true; - $redirect = $this->authBaseUrl . "/login"; + $loginTwigVars['mfaRequired'] = true; if (in_array(MfaUtils::U2F, $mfaType)) { - $appId = $mfa->getAppId(); - $requests = $mfa->getU2fRequests(); + $loginTwigVars['appId'] = $mfa->getAppId() ?? ''; + $loginTwigVars['u2fRequests'] = $mfa->getU2fRequests() ?? ''; } - require_once(__DIR__ . "/../../oauth2/provider/login.php"); + $this->renderTwigPage('oauth2/authorize/login', 'oauth2/oauth2-login.html.twig', $loginTwigVars); exit(); } //Check the validity of the authentication token if ($_POST['user_role'] === 'api' && $mfa->isMfaRequired() && !is_null($mfaToken)) { if (!$mfaToken || !$mfa->check($mfaToken, $_POST['mfa_type'])) { $invalid = "Sorry, Invalid code!"; - $oauthLogin = true; - $mfaRequired = true; - $mfaType = $mfa->getType(); - $redirect = $this->authBaseUrl . "/login"; - require_once(__DIR__ . "/../../oauth2/provider/login.php"); + $loginTwigVars['mfaRequired'] = true; + $loginTwigVars['invalid'] = $invalid; + $this->renderTwigPage('oauth2/authorize/login', 'oauth2/oauth2-login.html.twig', $loginTwigVars); exit(); } } @@ -744,9 +778,7 @@ public function userLogin(): void $user = new UserEntity(); $user->setIdentifier($_SESSION['user_id']); $_SESSION['claims'] = $user->getClaims(); - $oauthLogin = true; // need to redirect to patient select if we have a launch context && this isn't a patient login - $authorize = 'authorize'; // if we need to authorize any smart context as part of our OAUTH handler we do that here // otherwise we send on to our scope authorization confirm. @@ -762,6 +794,24 @@ public function userLogin(): void exit; } + private function renderTwigPage($pageName, $template, $templateVars) + { + $twig = $this->getTwig(); + $templatePageEvent = new TemplatePageEvent($pageName, [], $template, $templateVars); + $dispatcher = $GLOBALS['kernel']->getEventDispatcher(); + $updatedTemplatePageEvent = $dispatcher->dispatch($templatePageEvent); + $template = $updatedTemplatePageEvent->getTwigTemplate(); + $vars = $updatedTemplatePageEvent->getTwigVariables(); + // TODO: @adunsulag do we want to catch exceptions here? + try { + echo $twig->render($template, $vars); + } catch (\Exception $e) { + $this->logger->errorLogCaller("caught exception rendering template", ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); + echo $twig->render("error/general_http_error.html.twig", ['statusCode' => 500]); + die(); + } + } + public function scopeAuthorizeConfirm() { // TODO: @adunsulag if there are no scopes or claims here we probably want to show an error... @@ -799,7 +849,83 @@ public function scopeAuthorizeConfirm() $uuidToUser = new UuidUserAccount($_SESSION['user_id']); $userRole = $uuidToUser->getUserRole(); $userAccount = $uuidToUser->getUserAccount(); - require_once(__DIR__ . "/../../oauth2/provider/scope-authorize.php"); + + + $oauthLogin = $oauthLogin ?? null; + $offline_requested = $offline_requested ?? false; + $scopes = $scopes ?? []; + $scopeString = $scopeString ?? ""; + $offline_access_date = $offline_access_date ?? ""; + $userRole = $userRole ?? UuidUserAccount::USER_ROLE_PATIENT; + $clientName = $clientName ?? ""; + + if ($oauthLogin !== true) { + $message = xlt("Error. Not authorized"); + SessionUtil::oauthSessionCookieDestroy(); + echo $message; + exit(); + } + + $scopesByResource = []; + $otherScopes = []; + $scopeDescriptions = []; + $hiddenScopes = []; + $scopeRepository = new ScopeRepository(); + foreach ($scopes as $scope) { + // if there are any other scopes we want hidden we can put it here. + if (in_array($scope, ['openid'])) { + $hiddenScopes[] = $scope; + } else if (in_array($scope, $scopeRepository->fhirRequiredSmartScopes())) { + $otherScopes[$scope] = $scopeRepository->lookupDescriptionForScope($scope, $userRole == UuidUserAccount::USER_ROLE_PATIENT); + continue; + } + + $parts = explode("/", $scope); + $context = reset($parts); + $resourcePerm = $parts[1] ?? ""; + $resourcePermParts = explode(".", $resourcePerm); + $resource = $resourcePermParts[0] ?? ""; + $permission = $resourcePermParts[1] ?? ""; + + if (!empty($resource)) { + $scopesByResource[$resource] = $scopesByResource[$resource] ?? ['permissions' => []]; + + $scopesByResource[$resource]['permissions'][$scope] = $scopeRepository->lookupDescriptionForScope($scope, $userRole == UuidUserAccount::USER_ROLE_PATIENT); + } + } +// sort by the resource + ksort($scopesByResource); + + $updatedClaims = []; + foreach ($claims as $key => $value) { + $key_n = explode('_', $key); + if (stripos($scopeString, $key_n[0]) === false) { + continue; + } + if ((int)$value === 1) { + $value = 'True'; + } + $updatedKey = $key; + $updatedClaims[$key] = $value; + if ($key != 'fhirUser') { + $updatedKey = ucwords(str_replace("_", " ", $key)); + } + $scopeDescriptions[$updatedKey] = $value; + } + + $twigVars = [ + 'redirect' => $redirect + ,'client' => $client + ,'otherScopes' => $otherScopes + ,'scopesByResource' => $scopesByResource + ,'hiddenScopes' => $hiddenScopes + ,'claims' => $updatedClaims + ,'userAccount' => $userAccount + ,'offlineRequested' => true == $offline_requested + ,'offline_access_date' => $offline_access_date ?? "" + ]; + $this->renderTwigPage('oauth2/authorize/scopes-authorize', "oauth2/scope-authorize.html.twig", $twigVars); + die(); } /** diff --git a/src/RestControllers/SMART/SMARTAuthorizationController.php b/src/RestControllers/SMART/SMARTAuthorizationController.php index 20ad3b432b6..2b5f9ace47c 100644 --- a/src/RestControllers/SMART/SMARTAuthorizationController.php +++ b/src/RestControllers/SMART/SMARTAuthorizationController.php @@ -18,6 +18,7 @@ use OpenEMR\Common\Auth\OpenIDConnect\Repositories\ClientRepository; use OpenEMR\Common\Csrf\CsrfUtils; use OpenEMR\Common\Session\SessionUtil; +use OpenEMR\Events\Core\TemplatePageEvent; use OpenEMR\FHIR\SMART\SmartLaunchController; use OpenEMR\Services\PatientService; use Psr\Log\LoggerInterface; @@ -140,7 +141,7 @@ public function ehrLaunchAutoSubmit() $data = [ 'endpoint' => $endpoint ]; - echo $this->twig->render("smart/ehr-launch-autosubmit.html.twig", $data); + $this->renderTwigPage('oauth2/authorize/ehr-launch-auto-submit', "oauth2/ehr-launch-autosubmit.html.twig", $data); } /** @@ -249,7 +250,20 @@ public function patientSelect() $hasMore = count($patients) > self::PATIENT_SEARCH_MAX_RESULTS; $patients = $hasMore ? array_slice($patients, 0, self::PATIENT_SEARCH_MAX_RESULTS) : $patients; - require_once($this->oauthTemplateDir . "smart/patient-select.php"); + $this->renderTwigPage( + 'oauth2/authorize/patient-select', + "oauth2/patient-select.html.twig", + [ + 'patients' => $patients + , 'hasMore' => $hasMore + , 'errorMessage' => $errorMessage + , 'searchAction' => $searchAction + , 'fname' => $searchParams['fname'] ?? '' + , 'lname' => $searchParams['lname'] ?? '' + , 'mname' => $searchParams['mname'] ?? '' + , 'redirect' => $redirect + ] + ); } catch (AccessDeniedException $error) { // make sure to grab the redirect uri before the session is destroyed $redirectUri = $this->getClientRedirectURI(); @@ -262,7 +276,42 @@ public function patientSelect() // error occurred, no patients found just display the screen with an error message $error_message = "There was a server error in loading patients. Contact your system administrator for assistance"; $this->logger->error("AuthorizationController->patientSelect() Exception thrown", ['exception' => $error->getMessage()]); - require_once($this->oauthTemplateDir . "smart/patient-select.php"); + echo $this->twig->render( + "smart/patient-select.html.twig", + [ + 'patients' => $patients + , 'hasMore' => $hasMore + , 'errorMessage' => $errorMessage + , 'searchAction' => $searchAction + , 'fname' => $searchParams['fname'] ?? '' + , 'lname' => $searchParams['lname'] ?? '' + , 'mname' => $searchParams['mname'] ?? '' + , 'redirect' => $redirect + ] + ); + } + } + + private function getTwig() + { + return $this->twig; + } + + private function renderTwigPage($pageName, $template, $templateVars) + { + $twig = $this->getTwig(); + $templatePageEvent = new TemplatePageEvent($pageName, [], $template, $templateVars); + $dispatcher = $GLOBALS['kernel']->getEventDispatcher(); + $updatedTemplatePageEvent = $dispatcher->dispatch($templatePageEvent); + $template = $updatedTemplatePageEvent->getTwigTemplate(); + $vars = $updatedTemplatePageEvent->getTwigVariables(); + // TODO: @adunsulag do we want to catch exceptions here? + try { + echo $twig->render($template, $vars); + } catch (\Exception $e) { + $this->logger->errorLogCaller("caught exception rendering template", ['message' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); + echo $twig->render("error/general_http_error.html.twig", ['statusCode' => 500]); + die(); } } diff --git a/oauth2/smart/ehr-launch-autosubmit.html.twig b/templates/oauth2/ehr-launch-autosubmit.html.twig similarity index 100% rename from oauth2/smart/ehr-launch-autosubmit.html.twig rename to templates/oauth2/ehr-launch-autosubmit.html.twig diff --git a/templates/oauth2/oauth2-base.html.twig b/templates/oauth2/oauth2-base.html.twig new file mode 100644 index 00000000000..071050240f4 --- /dev/null +++ b/templates/oauth2/oauth2-base.html.twig @@ -0,0 +1,25 @@ +{# +# Base OAuth2 template file. +# +# @package OpenEMR +# @link http://www.open-emr.org +# @author Jerry Padgett +# @author Stephen Nielson +# @copyright Copyright (c) 2020 Jerry Padgett +# @copyright Copyright (c) 2023 Discover and Change, Inc. +# @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 +#} + + + {% block title %}{% endblock %} + {{ setupHeader(['u2f_api']) }} + + {% block scripts %} + {% endblock %} + + +{% block preContent %}{% endblock %} +{% block content %}{% endblock %} +{% block postContent %}{% endblock %} + + diff --git a/templates/oauth2/oauth2-login.html.twig b/templates/oauth2/oauth2-login.html.twig new file mode 100644 index 00000000000..015c8ee420d --- /dev/null +++ b/templates/oauth2/oauth2-login.html.twig @@ -0,0 +1,151 @@ + +{# + # Authorization Server Member + # + # @package OpenEMR + # @link http://www.open-emr.org + # @author Jerry Padgett + # @author Stephen Nielson + # @copyright Copyright (c) 2020 Jerry Padgett + # @copyright Copyright (c) 2023 Discover and Change, Inc. + # @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 +#} +{% extends "oauth2/oauth2-base.html.twig" %} +{% block title %}{{ "OpenEMR Authorization"|xlt }}{% endblock %} +{% block content %} +
+
+
+

{% if mfaRequired %}{{'MFA Verification'|xlt }}{% else %}{{ "Sign In"|xlt }}{% endif %}

+
+
+
+ {% if invalid is not empty %} +
+

{{ invalid|text }}

+
+ {% endif %} + + {% if not mfaRequired %} + + {% if enforce_signin_email %} +
+ +
+ {% endif %} +
+ +
+
+ +
+ {% endif %} + + {% if mfaRequired %} + {% if isTOTP %} +
+ {{'Provide TOTP code'|xlt }} +
+
+ +
+
+ +
+ {% endif %} + + {% if isU2F %} +
+
+ {{ 'Insert U2F Key'|xlt }} +
+
    +
  • {{ 'Insert your key into a USB port and click the Authenticate button below.'|xlt }}
  • +
  • {{ 'Then press the flashing button on your key within 1 minute.'|xlt }}
  • +
+
+
+ + + +
+ {% endif %} + + {% if enforce_signin_email %} +
+ +
+ {% endif %} +
+ +
+
+ +
+ + {% endif %} +
+
+
+
+ {% if mfaRequired is empty %} + + {% if patientRoleSupport is not empty %} + + {% endif %} + {% endif %} +
+
+ + +
+
+
+
+
+
+ +{% endblock %} diff --git a/templates/oauth2/patient-select.html.twig b/templates/oauth2/patient-select.html.twig new file mode 100644 index 00000000000..99cba66a4ae --- /dev/null +++ b/templates/oauth2/patient-select.html.twig @@ -0,0 +1,123 @@ +{# + # patient-select.html.twig + # @package openemr + # @link http://www.open-emr.org + # @author Stephen Nielson + # @copyright Copyright (c) 2021 Stephen Nielson + # @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 +#} +{% extends "oauth2/oauth2-base.html.twig" %} +{% block title %}{{ "OpenEMR Authorization"|xlt }}{% endblock %} +{% block content %} +
+
+
+

{{ "Patient Selection"|xlt }}

+
+ {% if patients|length < 1 %} +
+
+ {% if errorMessage is not empty %}

{{ errorMessage|text }}

{% endif %} +

{{ "No patients to select"|xlt }}

+
+
+ {% else %} +
+
+
+ + + + +
+ {% if hasMore %} +

{{ "Too many search results found. Displaying a limited set of patients. Narrow your search results through the filters above."|xlt }}

+ {% endif %} +
+
+
+

+ + + + + + + + + + + + + {% for patient in patients %} + + + + + + + + {% endfor %} + +
{{ "Name"|xlt }}{{ "DOB"|xlt }}{{ "Sex"|xlt }}{{ "Email"|xlt }}
+ {{ patient.fname|text }}{% if patient.mname %} {{ patient.mname|text }}{% endif%} {{ patient.lname|text }} + + {{ patient.DOB|text }} + + {{ patient.sex|text }} + + {{ patient.email|text }} + + +
+

+
+ {% endif %} +
+ + +
+
+ + +{% endblock %} diff --git a/templates/oauth2/scope-authorize.html.twig b/templates/oauth2/scope-authorize.html.twig new file mode 100644 index 00000000000..7982955c40b --- /dev/null +++ b/templates/oauth2/scope-authorize.html.twig @@ -0,0 +1,142 @@ +{# + # Handles the display of the scope authorization for the oauth2 form. + # + # @package OpenEMR + # @link http://www.open-emr.org + # @author Jerry Padgett + # @author Stephen Nielson + # @copyright Copyright (c) 2020 Jerry Padgett + # @copyright Copyright (c) 2023 Discover and Change, Inc. + # @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 +#} +{% extends "oauth2/oauth2-base.html.twig" %} + {% block title %}{{ "OpenEMR Authorization"|xlt }}{% endblock %} + {% block content %} + {# include the original contents page #} +
+ {% block form_header %}{% endblock %} +
+
+
+ {% block clientHeader %} +

{{ "Authorizing for Application"|xlt }} {{ client.name|text }}

+ {% endblock %} +
+
+ {% block scopesContainer %} + {% block resourceScopesContainer %} +
+
+
+
{{ "Grant this application access to do the following"|xlt }}
+
+ {% block resourcePermissions %} +
{{ "Resource Permissions"|xlt }}
+
+ {% for resource,scopeCollection in scopesByResource %} + + {% endfor %} +
+ {% endblock %} + {% block otherPermissions %} +
{{ "Other Permissions"|xlt }}
+
+ {% for scope,description in otherScopes %} + + {% endfor %} + {% for scope in hiddenScopes %} + + {% endfor %} +
+ {% endblock %} +
+
+
+ {% endblock %} + {% block claimsContainer %} +
+
+
+
{{ "Identity Information Requested"|xlt }}
+
+

{{ "This application is requesting access to the following information about you"|xlt }}

+
    + {% for key,value in claims %} + {% if key == 'fhirUser' %} +
  • + {{ "Permission to retrieve information about the current logged-in user"|xlt }} + {{ userAccount.firstName|default("")|text }} {{ userAccount.lastName|default("")|text }} +
  • + {% else %} +
  • {{ key|text }}: {{ value|text }}
  • + {% endif %} + {% endfor %} +
+
+
+
+ {% endblock %} + {% endblock %} +
+ {% block offlineFooter %} + {% if offlineRequested %} +
+
+
+

+ {{ "This application has requested offline access to your data. This permission will allow the data you authorize below to be accessed for an extended period of time"|xlt }} +

+

{{ "Offline access end date"|xlt }} {{ offline_access_date|text}}

+

{{ "If you do not want to allow this application to have offline access to your data, uncheck the Offline Access permission"|xlt }}

+ +
+
+
+ {% endif %} +
+ {% endblock %} + + {% block csrf %} + + {% endblock %} + + {% block form_controls %} +
+
+
+ {% block form_buttons %}{% endblock %} + +
+
+ + +
+
+
+ {% endblock %} + {% block form_footer %}{% endblock %} +
+
+
+ {% endblock %}