* @copyright Copyright (c) 2020 Dixon Whitmire * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 * */ class ApiTestClientTest extends TestCase { const EXAMPLE_API_ENDPOINT = "/apis/default/api/facility"; const EXAMPLE_API_ENDPOINT_INVALID_SITE = "/apis/baddefault/api/facility"; const EXAMPLE_API_ENDPOINT_SCOPE = "user/facility.read"; const API_ROUTE_SCOPE = "api:oemr"; /** * @var ApiTestClient */ private $client; /** * Configures the test client using environment variables and reasonable defaults */ protected function setUp(): void { $baseUrl = getenv("OPENEMR_BASE_URL_API", true) ?: "https://localhost"; $this->client = new ApiTestClient($baseUrl, false); } /** * @cover ::getConfig with a null value */ public function testGetConfigWithNull() { $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->expectException(\InvalidArgumentException::class); $this->client->getConfig(null); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * @cover ::getConfig for HTTP client settings */ public function testGetConfig() { $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertFalse($this->client->getConfig("http_errors")); $this->assertEquals(10, $this->client->getConfig("timeout")); $this->assertNotNull($this->client->getConfig("base_uri")); $actualHeaders = $this->client->getConfig("headers"); $this->assertEquals("application/json", $actualHeaders["Accept"]); $this->assertArrayHasKey("User-Agent", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests the automated testing when invalid credentials arguments are provided * @covers ::setAuthToken with invalid credential argument */ public function testApiAuthInvalidArgs() { try { $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT, array("foo" => "bar")); $this->assertFalse(true, "expected InvalidArgumentException"); } catch (\InvalidArgumentException $e) { $this->assertTrue(true); } $this->client->cleanupClient(); try { $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT, array("username" => "bar")); $this->assertFalse(true, "expected InvalidArgumentException"); } catch (\InvalidArgumentException $e) { $this->assertTrue(true); } $this->client->cleanupClient(); } /** * Tests OpenEMR OAuth when invalid client id is provided * @covers ::setAuthToken with invalid credentials */ public function testApiAuthInvalidClientId() { $actualValue = $this->client->setAuthToken( ApiTestClient::OPENEMR_AUTH_ENDPOINT, ["client_id" => ApiTestClient::BOGUS_CLIENTID] ); $this->assertEquals(401, $actualValue->getStatusCode()); $this->assertEquals('invalid_client', json_decode($actualValue->getBody())->error); $this->client->cleanupClient(); } /** * Tests OpenEMR OAuth when invalid user credentials are provided * @covers ::setAuthToken with invalid credentials */ public function testApiAuthInvalidUserCredentials() { $actualValue = $this->client->setAuthToken( ApiTestClient::OPENEMR_AUTH_ENDPOINT, array("username" => "bar", "password" => "boo") ); $this->assertEquals(400, $actualValue->getStatusCode()); $this->assertEquals('Failed Authentication', json_decode($actualValue->getBody())->hint); $this->client->cleanupClient(); } /** * Tests OpenEMR API Auth for the REST and FHIR APIs * @cover ::setAuthToken * @cover ::removeAuthToken */ public function testApiAuth() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayHasKey("Authorization", $actualHeaders); $authHeaderValue = substr($actualHeaders["Authorization"], 7); $this->assertGreaterThan(10, strlen($authHeaderValue)); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Auth for the REST and FHIR APIs (test refresh request after the auth) * @cover ::setAuthToken * @cover ::removeAuthToken */ public function testApiAuthThenRefresh() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayHasKey("Authorization", $actualHeaders); $authHeaderValue = substr($actualHeaders["Authorization"], 7); $this->assertGreaterThan(10, strlen($authHeaderValue)); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $refreshBody = [ "grant_type" => "refresh_token", "client_id" => $this->client->getClientId(), "refresh_token" => $this->client->getRefreshToken() ]; $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ); $authResponse = $this->client->post(ApiTestClient::OAUTH_TOKEN_ENDPOINT, $refreshBody, false); // set headers back to default $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/json" ] ); $this->assertEquals(200, $authResponse->getStatusCode()); $responseBody = json_decode($authResponse->getBody()); $this->assertGreaterThan(10, strlen($responseBody->id_token)); $this->assertGreaterThan(10, strlen($responseBody->access_token)); $this->assertGreaterThan(10, strlen($responseBody->refresh_token)); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Auth for the REST and FHIR APIs (test refresh request after the auth with bad refresh token) * @cover ::setAuthToken * @cover ::removeAuthToken */ public function testApiAuthThenBadRefresh() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayHasKey("Authorization", $actualHeaders); $authHeaderValue = substr($actualHeaders["Authorization"], 7); $this->assertGreaterThan(10, strlen($authHeaderValue)); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $refreshBody = [ "grant_type" => "refresh_token", "client_id" => $this->client->getClientId(), "refresh_token" => ApiTestClient::BOGUS_REFRESH_TOKEN ]; $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ); $authResponse = $this->client->post(ApiTestClient::OAUTH_TOKEN_ENDPOINT, $refreshBody, false); // set headers back to default $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/json" ] ); $this->assertEquals(401, $authResponse->getStatusCode()); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth for the REST and FHIR APIs */ public function testApiAuthExampleUse() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth for the REST and FHIR APIs (also does a * token refresh and use with new token) */ public function testApiAuthExampleUseThenRefreshThenUse() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $refreshBody = [ "grant_type" => "refresh_token", "client_id" => $this->client->getClientId(), "refresh_token" => $this->client->getRefreshToken() ]; $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ); $authResponse = $this->client->post(ApiTestClient::OAUTH_TOKEN_ENDPOINT, $refreshBody, false); // set headers back to default $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/json" ] ); $this->assertEquals(200, $authResponse->getStatusCode()); $responseBody = json_decode($authResponse->getBody()); $this->assertGreaterThan(10, strlen($responseBody->id_token)); $this->assertGreaterThan(10, strlen($responseBody->access_token)); $this->assertGreaterThan(10, strlen($responseBody->refresh_token)); $this->client->setBearer($responseBody->access_token); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth for the REST and FHIR APIs (also does a * token refresh and use with new token) with missing route scope */ public function testApiAuthExampleUseThenRefreshThenUseWithMissingRouteScope() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); // remove the route scope $scopeCustom = str_replace(self::API_ROUTE_SCOPE, '', ApiTestClient::ALL_SCOPES); $refreshBody = [ "grant_type" => "refresh_token", "client_id" => $this->client->getClientId(), "scope" => $scopeCustom, "refresh_token" => $this->client->getRefreshToken() ]; $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ); $authResponse = $this->client->post(ApiTestClient::OAUTH_TOKEN_ENDPOINT, $refreshBody, false); // set headers back to default $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/json" ] ); $this->assertEquals(200, $authResponse->getStatusCode()); $responseBody = json_decode($authResponse->getBody()); $this->assertGreaterThan(10, strlen($responseBody->id_token)); $this->assertGreaterThan(10, strlen($responseBody->access_token)); $this->assertGreaterThan(10, strlen($responseBody->refresh_token)); $this->client->setBearer($responseBody->access_token); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(401, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth for the REST and FHIR APIs (also does a * token refresh and use with new token) with missing endpoint scope */ public function testApiAuthExampleUseThenRefreshThenUseWithMissingEndpointScope() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); // remove the endpoint scope $scopeCustom = str_replace(self::EXAMPLE_API_ENDPOINT_SCOPE, '', ApiTestClient::ALL_SCOPES); $refreshBody = [ "grant_type" => "refresh_token", "client_id" => $this->client->getClientId(), "scope" => $scopeCustom, "refresh_token" => $this->client->getRefreshToken() ]; $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/x-www-form-urlencoded" ] ); $authResponse = $this->client->post(ApiTestClient::OAUTH_TOKEN_ENDPOINT, $refreshBody, false); // set headers back to default $this->client->setHeaders( [ "Accept" => "application/json", "Content-Type" => "application/json" ] ); $this->assertEquals(200, $authResponse->getStatusCode()); $responseBody = json_decode($authResponse->getBody()); $this->assertGreaterThan(10, strlen($responseBody->id_token)); $this->assertGreaterThan(10, strlen($responseBody->access_token)); $this->assertGreaterThan(10, strlen($responseBody->refresh_token)); $this->client->setBearer($responseBody->access_token); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(401, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth for the REST and FHIR APIs * Then test revoking user */ public function testApiAuthExampleUseThenRevoke() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $id_token = json_decode($actualValue->getBody())->id_token; $this->assertGreaterThan(10, strlen($id_token)); $actualResponse = $this->client->cleanupRevokeAuth(); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->assertEquals("You have been signed out. Thank you.", $actualResponse->getBody()); $actualResponse = $this->client->cleanupRevokeAuth(); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->assertEquals("You are currently not signed in.", $actualResponse->getBody()); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(400, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint with Invalid Site After Getting Auth for the REST and FHIR APIs */ public function testApiAuthExampleUseBadSite() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT_INVALID_SITE); $this->assertEquals(400, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth With Bad Bearer Token for the REST and FHIR APIs */ public function testApiAuthExampleUseBadToken() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $this->client->setBearer(ApiTestClient::BOGUS_ACCESS_TOKEN); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(401, $actualResponse->getStatusCode()); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * Tests OpenEMR API Example Endpoint After Getting Auth With Empty Bearer Token for the REST and FHIR APIs */ public function testApiAuthExampleUseEmptyToken() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT); $this->assertEquals(200, $actualValue->getStatusCode()); $this->assertGreaterThan(10, strlen($this->client->getIdToken())); $this->assertGreaterThan(10, strlen($this->client->getAccessToken())); $this->assertGreaterThan(10, strlen($this->client->getRefreshToken())); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(200, $actualResponse->getStatusCode()); $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); $actualResponse = $this->client->get(self::EXAMPLE_API_ENDPOINT); $this->assertEquals(401, $actualResponse->getStatusCode()); $this->client->cleanupRevokeAuth(); $this->client->cleanupClient(); } /** * @cover ::removeAuthToken when an auth token is not present */ public function testRemoveAuthTokenNoToken() { $this->client->removeAuthToken(); $actualHeaders = $this->client->getConfig("headers"); $this->assertArrayNotHasKey("Authorization", $actualHeaders); } public function testApiAuthPublicClientDoesNotReturnRefreshToken() { $actualValue = $this->client->setAuthToken(ApiTestClient::OPENEMR_AUTH_ENDPOINT, [], 'public'); $this->assertEquals(200, $actualValue->getStatusCode(), "public client authorization should return valid status code"); $this->assertNull($this->client->getRefreshToken(), "Refresh token should be empty for public client"); $this->assertNotNull($this->client->getAccessToken(), "Access token should be populated"); $this->assertNotNull($this->client->getIdToken(), "Id token should be populated"); } }