You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
574 lines
22 KiB
574 lines
22 KiB
2 years ago
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Useful globals class for Rest
|
||
|
*
|
||
|
* @package OpenEMR
|
||
|
* @link http://www.open-emr.org
|
||
|
* @author Jerry Padgett <sjpadgett@gmail.com>
|
||
|
* @author Brady Miller <brady.g.miller@gmail.com>
|
||
|
* @copyright Copyright (c) 2018-2020 Jerry Padgett <sjpadgett@gmail.com>
|
||
|
* @copyright Copyright (c) 2019 Brady Miller <brady.g.miller@gmail.com>
|
||
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
|
||
|
*/
|
||
|
|
||
|
require_once __DIR__ . '/vendor/autoload.php';
|
||
|
|
||
|
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||
|
use League\OAuth2\Server\ResourceServer;
|
||
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||
|
use Nyholm\Psr7Server\ServerRequestCreator;
|
||
|
use OpenEMR\Common\Acl\AclMain;
|
||
|
use OpenEMR\Common\Auth\OpenIDConnect\Repositories\AccessTokenRepository;
|
||
|
use OpenEMR\Common\Http\HttpRestRequest;
|
||
|
use OpenEMR\Common\Logging\EventAuditLogger;
|
||
|
use OpenEMR\Common\Logging\SystemLogger;
|
||
|
use OpenEMR\Common\Session\SessionUtil;
|
||
|
use OpenEMR\Services\TrustedUserService;
|
||
|
use Psr\Http\Message\ResponseInterface;
|
||
|
use Psr\Http\Message\ServerRequestInterface;
|
||
|
|
||
|
|
||
|
// also a handy place to add utility methods
|
||
|
// TODO before v6 release: refactor http_response_code(); for psr responses.
|
||
|
//
|
||
|
class RestConfig
|
||
|
{
|
||
|
/** @var routemap is an array of patterns and routes */
|
||
|
public static $ROUTE_MAP;
|
||
|
|
||
|
/** @var fhir routemap is an of patterns and routes */
|
||
|
public static $FHIR_ROUTE_MAP;
|
||
|
|
||
|
/** @var portal routemap is an of patterns and routes */
|
||
|
public static $PORTAL_ROUTE_MAP;
|
||
|
|
||
|
/** @var app root is the root directory of the application */
|
||
|
public static $APP_ROOT;
|
||
|
|
||
|
/** @var root url of the application */
|
||
|
public static $ROOT_URL;
|
||
|
// you can guess what the rest are!
|
||
|
public static $VENDOR_DIR;
|
||
|
public static $SITE;
|
||
|
public static $apisBaseFullUrl;
|
||
|
public static $webserver_root;
|
||
|
public static $web_root;
|
||
|
public static $server_document_root;
|
||
|
public static $publicKey;
|
||
|
private static $INSTANCE;
|
||
|
private static $IS_INITIALIZED = false;
|
||
|
/** @var set to true if local api call */
|
||
|
private static $localCall = false;
|
||
|
/** @var set to true if not rest call */
|
||
|
private static $notRestCall = false;
|
||
|
|
||
|
/** prevents external construction */
|
||
|
private function __construct()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an instance of the RestConfig singleton
|
||
|
*
|
||
|
* @return RestConfig
|
||
|
*/
|
||
|
public static function GetInstance(): \RestConfig
|
||
|
{
|
||
|
if (!self::$IS_INITIALIZED) {
|
||
|
self::Init();
|
||
|
}
|
||
|
|
||
|
if (!self::$INSTANCE instanceof self) {
|
||
|
self::$INSTANCE = new self();
|
||
|
}
|
||
|
|
||
|
return self::$INSTANCE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize the RestConfig object
|
||
|
*/
|
||
|
public static function Init(): void
|
||
|
{
|
||
|
if (self::$IS_INITIALIZED) {
|
||
|
return;
|
||
|
}
|
||
|
// The busy stuff.
|
||
|
self::setPaths();
|
||
|
self::setSiteFromEndpoint();
|
||
|
self::$ROOT_URL = self::$web_root . "/apis";
|
||
|
self::$VENDOR_DIR = self::$webserver_root . "/vendor";
|
||
|
self::$publicKey = self::$webserver_root . "/sites/" . self::$SITE . "/documents/certificates/oapublic.key";
|
||
|
self::$IS_INITIALIZED = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Basic paths when GLOBALS are not yet available.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
private static function SetPaths(): void
|
||
|
{
|
||
|
$isWindows = (stripos(PHP_OS_FAMILY, 'WIN') === 0);
|
||
|
// careful if moving this class to modify where's root.
|
||
|
self::$webserver_root = __DIR__;
|
||
|
if ($isWindows) {
|
||
|
//convert windows path separators
|
||
|
self::$webserver_root = str_replace("\\", "/", self::$webserver_root);
|
||
|
}
|
||
|
// Collect the apache server document root (and convert to windows slashes, if needed)
|
||
|
self::$server_document_root = realpath($_SERVER['DOCUMENT_ROOT']);
|
||
|
if ($isWindows) {
|
||
|
//convert windows path separators
|
||
|
self::$server_document_root = str_replace("\\", "/", self::$server_document_root);
|
||
|
}
|
||
|
self::$web_root = substr(self::$webserver_root, strspn(self::$webserver_root ^ self::$server_document_root, "\0"));
|
||
|
// Ensure web_root starts with a path separator
|
||
|
if (preg_match("/^[^\/]/", self::$web_root)) {
|
||
|
self::$web_root = "/" . self::$web_root;
|
||
|
}
|
||
|
// Will need these occasionally. sql init comes to mind!
|
||
|
$GLOBALS['rootdir'] = self::$web_root . "/interface";
|
||
|
// Absolute path to the source code include and headers file directory (Full path):
|
||
|
$GLOBALS['srcdir'] = self::$webserver_root . "/library";
|
||
|
// Absolute path to the location of documentroot directory for use with include statements:
|
||
|
$GLOBALS['fileroot'] = self::$webserver_root;
|
||
|
// Absolute path to the location of interface directory for use with include statements:
|
||
|
$GLOBALS['incdir'] = self::$webserver_root . "/interface";
|
||
|
// Absolute path to the location of documentroot directory for use with include statements:
|
||
|
$GLOBALS['webroot'] = self::$web_root;
|
||
|
// Static assets directory, relative to the webserver root.
|
||
|
$GLOBALS['assets_static_relative'] = self::$web_root . "/public/assets";
|
||
|
// Relative themes directory, relative to the webserver root.
|
||
|
$GLOBALS['themes_static_relative'] = self::$web_root . "/public/themes";
|
||
|
// Relative images directory, relative to the webserver root.
|
||
|
$GLOBALS['images_static_relative'] = self::$web_root . "/public/images";
|
||
|
// Static images directory, absolute to the webserver root.
|
||
|
$GLOBALS['images_static_absolute'] = self::$webserver_root . "/public/images";
|
||
|
//Composer vendor directory, absolute to the webserver root.
|
||
|
$GLOBALS['vendor_dir'] = self::$webserver_root . "/vendor";
|
||
|
}
|
||
|
|
||
|
private static function setSiteFromEndpoint(): void
|
||
|
{
|
||
|
// Get site from endpoint if available. Unsure about this though!
|
||
|
// Will fail during sql init otherwise.
|
||
|
$endPointParts = self::parseEndPoint(self::getRequestEndPoint());
|
||
|
if (count($endPointParts) > 1) {
|
||
|
$site_id = $endPointParts[0] ?? '';
|
||
|
if ($site_id) {
|
||
|
self::$SITE = $site_id;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function parseEndPoint($resource): array
|
||
|
{
|
||
|
if ($resource[0] === '/') {
|
||
|
$resource = substr($resource, 1);
|
||
|
}
|
||
|
return explode('/', $resource);
|
||
|
}
|
||
|
|
||
|
public static function getRequestEndPoint(): string
|
||
|
{
|
||
|
$resource = null;
|
||
|
if (!empty($_REQUEST['_REWRITE_COMMAND'])) {
|
||
|
$resource = "/" . $_REQUEST['_REWRITE_COMMAND'];
|
||
|
} elseif (!empty($_SERVER['REDIRECT_QUERY_STRING'])) {
|
||
|
$resource = str_replace('_REWRITE_COMMAND=', '/', $_SERVER['REDIRECT_QUERY_STRING']);
|
||
|
} else {
|
||
|
if (!empty($_SERVER['REQUEST_URI'])) {
|
||
|
if (strpos($_SERVER['REQUEST_URI'], '?') > 0) {
|
||
|
$resource = strstr($_SERVER['REQUEST_URI'], '?', true);
|
||
|
} else {
|
||
|
$resource = str_replace(self::$ROOT_URL, '', $_SERVER['REQUEST_URI']);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $resource;
|
||
|
}
|
||
|
|
||
|
public static function verifyAccessToken()
|
||
|
{
|
||
|
$logger = new SystemLogger();
|
||
|
$response = self::createServerResponse();
|
||
|
$request = self::createServerRequest();
|
||
|
$server = new ResourceServer(
|
||
|
new AccessTokenRepository(),
|
||
|
self::$publicKey
|
||
|
);
|
||
|
try {
|
||
|
$raw = $server->validateAuthenticatedRequest($request);
|
||
|
} catch (OAuthServerException $exception) {
|
||
|
$logger->error("RestConfig->verifyAccessToken() OAuthServerException", ["message" => $exception->getMessage()]);
|
||
|
return $exception->generateHttpResponse($response);
|
||
|
} catch (\Exception $exception) {
|
||
|
$logger->error("RestConfig->verifyAccessToken() Exception", ["message" => $exception->getMessage()]);
|
||
|
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
||
|
->generateHttpResponse($response);
|
||
|
}
|
||
|
|
||
|
return $raw;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns true if the access token for the given token id is valid. Otherwise returns the access denied response.
|
||
|
* @param $tokenId
|
||
|
* @return bool|ResponseInterface
|
||
|
*/
|
||
|
public static function validateAccessTokenRevoked($tokenId)
|
||
|
{
|
||
|
$repository = new AccessTokenRepository();
|
||
|
if ($repository->isAccessTokenRevokedInDatabase($tokenId)) {
|
||
|
$response = self::createServerResponse();
|
||
|
return OAuthServerException::accessDenied('Access token has been revoked')->generateHttpResponse($response);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public static function isTrustedUser($clientId, $userId)
|
||
|
{
|
||
|
$trustedUserService = new TrustedUserService();
|
||
|
$response = self::createServerResponse();
|
||
|
try {
|
||
|
if (!$trustedUserService->isTrustedUser($clientId, $userId)) {
|
||
|
(new SystemLogger())->debug(
|
||
|
"invalid Trusted User. Refresh Token revoked or logged out",
|
||
|
['clientId' => $clientId, 'userId' => $userId]
|
||
|
);
|
||
|
throw new OAuthServerException('Refresh Token revoked or logged out', 0, 'invalid _request', 400);
|
||
|
}
|
||
|
return $trustedUserService->getTrustedUser($clientId, $userId);
|
||
|
} catch (OAuthServerException $exception) {
|
||
|
return $exception->generateHttpResponse($response);
|
||
|
} catch (\Exception $exception) {
|
||
|
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
||
|
->generateHttpResponse($response);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function createServerResponse(): ResponseInterface
|
||
|
{
|
||
|
$psr17Factory = new Psr17Factory();
|
||
|
|
||
|
return $psr17Factory->createResponse();
|
||
|
}
|
||
|
|
||
|
public static function createServerRequest(): ServerRequestInterface
|
||
|
{
|
||
|
$psr17Factory = new Psr17Factory();
|
||
|
$creator = new ServerRequestCreator(
|
||
|
$psr17Factory, // ServerRequestFactory
|
||
|
$psr17Factory, // UriFactory
|
||
|
$psr17Factory, // UploadedFileFactory
|
||
|
$psr17Factory // StreamFactory
|
||
|
);
|
||
|
|
||
|
return $creator->fromGlobals();
|
||
|
}
|
||
|
|
||
|
public static function destroySession(): void
|
||
|
{
|
||
|
SessionUtil::apiSessionCookieDestroy();
|
||
|
}
|
||
|
|
||
|
public static function getPostData($data)
|
||
|
{
|
||
|
if (count($_POST)) {
|
||
|
return $_POST;
|
||
|
}
|
||
|
|
||
|
if ($post_data = file_get_contents('php://input')) {
|
||
|
if ($post_json = json_decode($post_data, true)) {
|
||
|
return $post_json;
|
||
|
}
|
||
|
parse_str($post_data, $post_variables);
|
||
|
if (count($post_variables)) {
|
||
|
return $post_variables;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
public static function authorization_check($section, $value, $user = ''): void
|
||
|
{
|
||
|
$result = AclMain::aclCheckCore($section, $value, $user);
|
||
|
if (!$result) {
|
||
|
if (!self::$notRestCall) {
|
||
|
http_response_code(401);
|
||
|
}
|
||
|
exit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Main function to check scope
|
||
|
// Use cases:
|
||
|
// Only sending $scopeType would be for something like 'openid'
|
||
|
// For using all 3 parameters would be for something like 'user/Organization.write'
|
||
|
// $scopeType = 'user', $resource = 'Organization', $permission = 'write'
|
||
|
public static function scope_check($scopeType, $resource = null, $permission = null): void
|
||
|
{
|
||
|
if (!empty($GLOBALS['oauth_scopes'])) {
|
||
|
// Need to ensure has scope
|
||
|
if (empty($resource)) {
|
||
|
// Simply check to see if $scopeType is an allowed scope
|
||
|
$scope = $scopeType;
|
||
|
} else {
|
||
|
// Resource scope check
|
||
|
$scope = $scopeType . '/' . $resource . '.' . $permission;
|
||
|
}
|
||
|
if (!in_array($scope, $GLOBALS['oauth_scopes'])) {
|
||
|
(new SystemLogger())->debug("RestConfig::scope_check scope not in access token", ['scope' => $scope, 'scopes_granted' => $GLOBALS['oauth_scopes']]);
|
||
|
http_response_code(401);
|
||
|
exit;
|
||
|
}
|
||
|
} else {
|
||
|
(new SystemLogger())->error("RestConfig::scope_check global scope array is empty");
|
||
|
http_response_code(401);
|
||
|
exit;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function setLocalCall(): void
|
||
|
{
|
||
|
self::$localCall = true;
|
||
|
}
|
||
|
|
||
|
public static function setNotRestCall(): void
|
||
|
{
|
||
|
self::$notRestCall = true;
|
||
|
}
|
||
|
|
||
|
public static function is_fhir_request($resource): bool
|
||
|
{
|
||
|
return stripos(strtolower($resource), "/fhir/") !== false;
|
||
|
}
|
||
|
|
||
|
public static function is_portal_request($resource): bool
|
||
|
{
|
||
|
return stripos(strtolower($resource), "/portal/") !== false;
|
||
|
}
|
||
|
|
||
|
public static function is_api_request($resource): bool
|
||
|
{
|
||
|
return stripos(strtolower($resource), "/api/") !== false;
|
||
|
}
|
||
|
|
||
|
public static function skipApiAuth($resource): bool
|
||
|
{
|
||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||
|
// we don't authenticate OPTIONS requests
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// ensure 1) sane site and 2) ensure the site exists on filesystem before even considering for skip api auth
|
||
|
if (empty(self::$SITE) || preg_match('/[^A-Za-z0-9\\-.]/', self::$SITE) || !file_exists(__DIR__ . '/sites/' . self::$SITE)) {
|
||
|
error_log("OpenEMR Error - api site error, so forced exit");
|
||
|
http_response_code(400);
|
||
|
exit();
|
||
|
}
|
||
|
// let the capability statement for FHIR or the SMART-on-FHIR through
|
||
|
if (
|
||
|
$resource === ("/" . self::$SITE . "/fhir/metadata") ||
|
||
|
$resource === ("/" . self::$SITE . "/fhir/.well-known/smart-configuration")
|
||
|
) {
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function apiLog($response = '', $requestBody = ''): void
|
||
|
{
|
||
|
$logResponse = $response;
|
||
|
|
||
|
// only log when using standard api calls (skip when using local api calls from within OpenEMR)
|
||
|
// and when api log option is set
|
||
|
if (!$GLOBALS['is_local_api'] && !self::$notRestCall && $GLOBALS['api_log_option']) {
|
||
|
if ($GLOBALS['api_log_option'] == 1) {
|
||
|
// Do not log the response and requestBody
|
||
|
$logResponse = '';
|
||
|
$requestBody = '';
|
||
|
}
|
||
|
if ($response instanceof ResponseInterface) {
|
||
|
if (self::shouldLogResponse($response)) {
|
||
|
$body = $response->getBody();
|
||
|
$logResponse = $body->getContents();
|
||
|
$body->rewind();
|
||
|
} else {
|
||
|
$logResponse = 'Content not application/json - Skip binary data';
|
||
|
}
|
||
|
} else {
|
||
|
$logResponse = (!empty($logResponse)) ? json_encode($response) : '';
|
||
|
}
|
||
|
|
||
|
// convert pertinent elements to json
|
||
|
$requestBody = (!empty($requestBody)) ? json_encode($requestBody) : '';
|
||
|
|
||
|
// prepare values and call the log function
|
||
|
$event = 'api';
|
||
|
$category = 'api';
|
||
|
$method = $_SERVER['REQUEST_METHOD'];
|
||
|
$url = $_SERVER['REQUEST_URI'];
|
||
|
$patientId = (int)($_SESSION['pid'] ?? 0);
|
||
|
$userId = (int)($_SESSION['authUserID'] ?? 0);
|
||
|
$api = [
|
||
|
'user_id' => $userId,
|
||
|
'patient_id' => $patientId,
|
||
|
'method' => $method,
|
||
|
'request' => $GLOBALS['resource'],
|
||
|
'request_url' => $url,
|
||
|
'request_body' => $requestBody,
|
||
|
'response' => $logResponse
|
||
|
];
|
||
|
if ($patientId === 0) {
|
||
|
$patientId = null; //entries in log table are blank for no patient_id, whereas in api_log are 0, which is why above $api value uses 0 when empty
|
||
|
}
|
||
|
EventAuditLogger::instance()->recordLogItem(1, $event, ($_SESSION['authUser'] ?? ''), ($_SESSION['authProvider'] ?? ''), 'api log', $patientId, $category, 'open-emr', null, null, '', $api);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function emitResponse($response, $build = false): void
|
||
|
{
|
||
|
if (headers_sent()) {
|
||
|
throw new RuntimeException('Headers already sent.');
|
||
|
}
|
||
|
$statusLine = sprintf(
|
||
|
'HTTP/%s %s %s',
|
||
|
$response->getProtocolVersion(),
|
||
|
$response->getStatusCode(),
|
||
|
$response->getReasonPhrase()
|
||
|
);
|
||
|
header($statusLine, true);
|
||
|
foreach ($response->getHeaders() as $name => $values) {
|
||
|
$responseHeader = sprintf('%s: %s', $name, $response->getHeaderLine($name));
|
||
|
header($responseHeader, false);
|
||
|
}
|
||
|
echo $response->getBody();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If the FHIR System scopes enabled or not. True if its turned on, false otherwise.
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function areSystemScopesEnabled()
|
||
|
{
|
||
|
return $GLOBALS['rest_system_scopes_api'] === '1';
|
||
|
}
|
||
|
|
||
|
public function authenticateUserToken($tokenId, $clientId, $userId): bool
|
||
|
{
|
||
|
$ip = collectIpAddresses();
|
||
|
|
||
|
// check for token
|
||
|
$accessTokenRepo = new AccessTokenRepository();
|
||
|
$authTokenExpiration = $accessTokenRepo->getTokenExpiration($tokenId, $clientId, $userId);
|
||
|
|
||
|
if (empty($authTokenExpiration)) {
|
||
|
EventAuditLogger::instance()->newEvent('api', '', '', 0, "API failure: " . $ip['ip_string'] . ". Token not found for client[" . $clientId . "] and user " . $userId . ".");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Ensure token not expired (note an expired token should have already been caught by oauth2, however will also check here)
|
||
|
$currentDateTime = date("Y-m-d H:i:s");
|
||
|
$expiryDateTime = date("Y-m-d H:i:s", strtotime($authTokenExpiration));
|
||
|
if ($expiryDateTime <= $currentDateTime) {
|
||
|
EventAuditLogger::instance()->newEvent('api', '', '', 0, "API failure: " . $ip['ip_string'] . ". Token expired for client[" . $clientId . "] and user " . $userId . ".");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Token authentication passed
|
||
|
EventAuditLogger::instance()->newEvent('api', '', '', 1, "API success: " . $ip['ip_string'] . ". Token successfully used for client[" . $clientId . "] and user " . $userId . ".");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if we should log the response interface (we don't want to log binary documents or anything like that)
|
||
|
* We only log requests with a content-type of any form of json fhir+application/json or application/json
|
||
|
* @param ResponseInterface $response
|
||
|
* @return bool If the request should be logged, false otherwise
|
||
|
*/
|
||
|
private static function shouldLogResponse(ResponseInterface $response)
|
||
|
{
|
||
|
if ($response->hasHeader("Content-Type")) {
|
||
|
$contentType = $response->getHeaderLine("Content-Type");
|
||
|
if ($contentType === 'application/json') {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Grabs all of the context information for the request's access token and populates any context variables the
|
||
|
* request needs (such as patient binding information). Returns the populated request
|
||
|
* @param HttpRestRequest $restRequest
|
||
|
* @return HttpRestRequest
|
||
|
*/
|
||
|
public function populateTokenContextForRequest(HttpRestRequest $restRequest)
|
||
|
{
|
||
|
|
||
|
$context = $this->getTokenContextForRequest($restRequest);
|
||
|
// note that the context here is the SMART value that is returned in the response for an AccessToken in this
|
||
|
// case it is the patient value which is the logical id (ie uuid) of the patient.
|
||
|
$patientUuid = $context['patient'] ?? null;
|
||
|
if (!empty($patientUuid)) {
|
||
|
// we only set the bound patient access if the underlying user can still access the patient
|
||
|
if ($this->checkUserHasAccessToPatient($restRequest->getRequestUserId(), $patientUuid)) {
|
||
|
$restRequest->setPatientUuidString($patientUuid);
|
||
|
}
|
||
|
} else {
|
||
|
(new SystemLogger())->error("OpenEMR Error: api had patient launch scope but no patient was set in the "
|
||
|
. " session cache. Resources restricted with patient scopes will not return results");
|
||
|
}
|
||
|
return $restRequest;
|
||
|
}
|
||
|
|
||
|
public function getTokenContextForRequest(HttpRestRequest $restRequest)
|
||
|
{
|
||
|
$accessTokenRepo = new AccessTokenRepository();
|
||
|
// note this is pretty confusing as getAccessTokenId comes from the oauth_access_id which is the token NOT
|
||
|
// the database id even though this is called accessTokenId....
|
||
|
$token = $accessTokenRepo->getTokenByToken($restRequest->getAccessTokenId());
|
||
|
$context = $token['context'] ?? "{}"; // if there is no populated context we just return an empty return
|
||
|
try {
|
||
|
return json_decode($context, true);
|
||
|
} catch (\Exception $exception) {
|
||
|
(new SystemLogger())->error("OpenEMR Error: failed to decode token context json", ['exception' => $exception->getMessage()
|
||
|
, 'tokenId' => $restRequest->getAccessTokenId()]);
|
||
|
}
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Checks whether a user has access to the patient. Returns true if the user can access the given patient, false otherwise
|
||
|
* @param $userId The id from the users table that represents the user
|
||
|
* @param $patientUuid The uuid from the patient_data table that represents the patient
|
||
|
* @return bool True if has access, false otherwise
|
||
|
*/
|
||
|
private function checkUserHasAccessToPatient($userId, $patientUuid)
|
||
|
{
|
||
|
// TODO: the session should never be populated with the pid from the access token unless the user had access to
|
||
|
// it. However, if we wanted an additional check or if we anted to fire off any kind of event that does
|
||
|
// patient filtering by provider / clinic we would handle that here.
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** prevents external cloning */
|
||
|
private function __clone()
|
||
|
{
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Include our routes and init routes global
|
||
|
//
|
||
|
require_once(__DIR__ . "/_rest_routes.inc.php");
|