diff --git a/helm-charts/templates/postgres/initdb-conf.yaml b/helm-charts/templates/postgres/initdb-conf.yaml index e94313fc7e..68dcfa635c 100644 --- a/helm-charts/templates/postgres/initdb-conf.yaml +++ b/helm-charts/templates/postgres/initdb-conf.yaml @@ -403,5 +403,23 @@ data: PRIMARY KEY (CONSUMER_KEY) ); -- End of Non Prod IDP table -- + + -- Insert Backoffice Internal Data -- + INSERT INTO api(uuid, api_name, api_version, context, status, organization, artifact) + VALUES ('01ed75e2-b30b-18c8-wwf2-25da7edd2231', 'Backoffice Test API', '1.0.0', 'test', + 'CREATED', 'a3b58ccf-6ecc-4557-b5bb-0a35cce38256', + '{"id": "01ed75e2-b30b-18c8-wwf2-25da7edd2231", "status": "CREATED", "apiName": "Backoffice Test API", "context": "test", "version": "1.0.0", + "visibility": "PUBLIC", "providerName": "admin", "accessControl": "RESTRICTED", "visibleGroups": null, "accessControlGroups": ["group1"]}'); + + INSERT INTO api_artifact(organization, api_uuid, api_definition, media_type) + VALUES ('a3b58ccf-6ecc-4557-b5bb-0a35cce38256', '01ed75e2-b30b-18c8-wwf2-25da7edd2231', + '\x7b226f70656e617069223a22332e302e30222c22696e666f223a7b227469746c65223a2253616d706c6520415049222c226465736372697074696f6e223a224f7074696f6e616c206d756c74696c696e65206f722073696e676c652d6c696e65206465736372697074696f6e20696e205b436f6d6d6f6e4d61726b5d28687474703a2f2f636f6d6d6f6e6d61726b2e6f72672f68656c702f29206f722048544d4c2e222c2276657273696f6e223a22302e312e39227d2c2273657276657273223a5b7b2275726c223a22687474703a2f2f6170692e6578616d706c652e636f6d2f7631222c226465736372697074696f6e223a224f7074696f6e616c20736572766572206465736372697074696f6e2c20652e672e204d61696e202870726f64756374696f6e2920736572766572227d2c7b2275726c223a22687474703a2f2f73746167696e672d6170692e6578616d706c652e636f6d222c226465736372697074696f6e223a224f7074696f6e616c20736572766572206465736372697074696f6e2c20652e672e20496e7465726e616c2073746167696e672073657276657220666f722074657374696e67227d5d2c227061746873223a7b222f7573657273223a7b22676574223a7b2273756d6d617279223a2252657475726e732061206c697374206f662075736572732e222c226465736372697074696f6e223a224f7074696f6e616c20657874656e646564206465736372697074696f6e20696e20436f6d6d6f6e4d61726b206f722048544d4c2e222c22726573706f6e736573223a7b22323030223a7b226465736372697074696f6e223a2241204a534f4e206172726179206f662075736572206e616d6573222c22636f6e74656e74223a7b226170706c69636174696f6e2f6a736f6e223a7b22736368656d61223a7b2274797065223a226172726179222c226974656d73223a7b2274797065223a22737472696e67227d7d7d7d7d7d7d7d7d7d', + 'HTTP'); + + INSERT INTO api_lc_event (api_uuid,previous_state,new_state,user_uuid,organization,event_date) + VALUES ('01ed75e2-b30b-18c8-wwf2-25da7edd2231', null, 'CREATED', 'apkuser', 'a3b58ccf-6ecc-4557-b5bb-0a35cce38256', + CURRENT_TIMESTAMP AT TIME ZONE 'UTC'); + + -- End of Insert Backoffice Internal Data -- commit; {{ end }} diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BackOfficeSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BackOfficeSteps.java new file mode 100644 index 0000000000..6c4d2c6b40 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BackOfficeSteps.java @@ -0,0 +1,33 @@ +package org.wso2.apk.integration.api; + +import java.util.HashMap; +import java.util.Map; +import io.cucumber.java.en.When; + +import org.apache.http.HttpResponse; +import org.wso2.apk.integration.utils.Constants; +import org.wso2.apk.integration.utils.Utils; +import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; + +public class BackOfficeSteps { + + private final SharedContext sharedContext; + + public BackOfficeSteps(SharedContext sharedContext) { + + this.sharedContext = sharedContext; + } + + @When("I make the GET APIs call to the backoffice") + public void make_a_deployment_request() throws Exception { + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doGet(Utils.getBackOfficeAPIURL(), + headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + } +} diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java index c19592166c..c2f0187a05 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -94,18 +94,22 @@ public void systemIsReady() { @Then("the response body should contain {string}") public void theResponseBodyShouldContain(String expectedText) throws IOException { - Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); + Assert.assertTrue(sharedContext.getResponseBody().contains(expectedText), + "Actual response body: " + sharedContext.getResponseBody()); } + @Then("the response body should not contain {string}") public void theResponseBodyShouldNotContain(String expectedText) throws IOException { - Assert.assertFalse(sharedContext.getResponseBody().contains(expectedText), "Actual response body: " + sharedContext.getResponseBody()); + Assert.assertFalse(sharedContext.getResponseBody().contains(expectedText), + "Actual response body: " + sharedContext.getResponseBody()); } @Then("the response body should contain") public void theResponseBodyShouldContain(DataTable dataTable) throws IOException { List responseBodyLines = dataTable.asList(String.class); for (String line : responseBodyLines) { - Assert.assertTrue(sharedContext.getResponseBody().contains(line), "Actual response body: " + sharedContext.getResponseBody()); + Assert.assertTrue(sharedContext.getResponseBody().contains(line), + "Actual response body: " + sharedContext.getResponseBody()); } } @@ -140,7 +144,8 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I // It will send request using a new thread and forget about the response @Then("I send {string} async request to {string} with body {string}") - public void sendAsyncHttpRequest(String httpMethod, String url, String body) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + public void sendAsyncHttpRequest(String httpMethod, String url, String body) + throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (sharedContext.getResponse() instanceof CloseableHttpResponse) { ((CloseableHttpResponse) sharedContext.getResponse()).close(); } @@ -220,7 +225,7 @@ public void waitForNextMinute() throws InterruptedException { if (secondsToWait > MAX_WAIT_FOR_NEXT_MINUTE_IN_SECONDS) { return; } - Thread.sleep((secondsToWait+1) * 1000); + Thread.sleep((secondsToWait + 1) * 1000); logger.info("Current time: " + LocalDateTime.now()); } @@ -229,7 +234,7 @@ public void waitForNextMinuteStrictly() throws InterruptedException { LocalDateTime now = LocalDateTime.now(); LocalDateTime nextMinute = now.plusMinutes(1).withSecond(0).withNano(0); long secondsToWait = now.until(nextMinute, ChronoUnit.SECONDS); - Thread.sleep((secondsToWait+1) * 1000); + Thread.sleep((secondsToWait + 1) * 1000); logger.info("Current time: " + LocalDateTime.now()); } @@ -259,8 +264,9 @@ public void containsHeader(String key, String value) { return; // Any value is acceptable } String actualValue = header.getValue(); - Assert.assertEquals(value, actualValue,"Header with key found but value mismatched."); + Assert.assertEquals(value, actualValue, "Header with key found but value mismatched."); } + @Then("the response headers not contains key {string}") public void notContainsHeader(String key) { key = Utils.resolveVariables(key, sharedContext.getValueStore()); @@ -269,11 +275,12 @@ public void notContainsHeader(String key) { Assert.fail("Response is null."); } Header header = response.getFirstHeader(key); - Assert.assertNull(header,"header contains in response headers"); + Assert.assertNull(header, "header contains in response headers"); } @Then("the {string} jwt should validate from JWKS {string} and contain") - public void decode_header_and_validate(String header,String jwksEndpoint, DataTable dataTable) throws MalformedURLException { + public void decode_header_and_validate(String header, String jwksEndpoint, DataTable dataTable) + throws MalformedURLException { List> claims = dataTable.asMaps(String.class, String.class); JsonObject jsonResponse = (JsonObject) JsonParser.parseString(sharedContext.getResponseBody()); String headerValue = jsonResponse.get("headers").getAsJsonObject().get(header).getAsString(); @@ -308,7 +315,7 @@ public void decode_header_and_validate(String header,String jwksEndpoint, DataTa Assert.assertEquals(claim.get("value"), claim1.toString(), "Actual " + "decoded JWT body: " + claimsSet); } - } catch (BadJOSEException | JOSEException|ParseException e) { + } catch (BadJOSEException | JOSEException | ParseException e) { logger.error("JWT Signature verification fail", e); Assert.fail("JWT Signature verification fail"); } @@ -319,9 +326,11 @@ public void iHaveValidSubscription() throws Exception { Map headers = new HashMap<>(); headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); - headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, + "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); - HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=client_credentials&scope=" + Constants.API_CREATE_SCOPE, + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, + "grant_type=client_credentials&scope=" + Constants.API_CREATE_SCOPE, Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); sharedContext.setAccessToken(Utils.extractToken(httpResponse)); sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); @@ -332,9 +341,11 @@ public void iHaveValidSubscriptionWithAPICreateScope() throws Exception { Map headers = new HashMap<>(); headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); - headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, + "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="); - HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=client_credentials", + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, + "grant_type=client_credentials", Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); sharedContext.setAccessToken(Utils.extractToken(httpResponse)); sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); @@ -353,9 +364,28 @@ public void iHaveValidSubscriptionWithScope(DataTable dataTable) throws Exceptio headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, Constants.SUBSCRIPTION_BASIC_AUTH_TOKEN); HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, - "grant_type=client_credentials&scope=" + scopes, - Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); + "grant_type=client_credentials&scope=" + scopes, + Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); sharedContext.setAccessToken(Utils.extractToken(httpResponse)); sharedContext.addStoreValue(Constants.ACCESS_TOKEN, sharedContext.getAccessToken()); } + + @Given("I have a valid subscription with groups") + public void iHaveValidSubscriptionWithGroups(DataTable dataTable) throws Exception { + List> rows = dataTable.asLists(String.class); + String groups = Constants.EMPTY_STRING; + for (List row : rows) { + String group = row.get(0); + groups += group + Constants.SPACE_STRING; + } + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, Constants.SUBSCRIPTION_BASIC_AUTH_TOKEN); + + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, + "grant_type=client_credentials&scope=" + Constants.API_VIEW_SCOPE + "&groups=" + groups, + Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); + sharedContext.setAccessToken(Utils.extractToken(httpResponse)); + sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); + } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java index 8c543c8b03..b97da434ee 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java @@ -25,12 +25,13 @@ public class Constants { public static final String DEFAULT_TOKEN_EP = "oauth2/token"; public static final String DEFAULT_API_CONFIGURATOR = "api/configurator/1.0.0/"; public static final String DEFAULT_API_DEPLOYER = "api/deployer/1.0.0/"; + public static final String DEFAULT_BACKOFFICE = "/api/backoffice/1.0.0/"; public static final String ACCESS_TOKEN = "accessToken"; public static final String EMPTY_STRING = ""; public static final String API_CREATE_SCOPE = "apk:api_create"; + public static final String API_VIEW_SCOPE = "apk:api_view"; public static final String SPACE_STRING = " "; - public static final String SUBSCRIPTION_BASIC_AUTH_TOKEN = - "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="; + public static final String SUBSCRIPTION_BASIC_AUTH_TOKEN = "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="; public class REQUEST_HEADERS { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java index 2097b61d0f..dd18f791fd 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java @@ -55,6 +55,12 @@ public static String getAPIDeployerURL() { + Constants.DEFAULT_API_DEPLOYER + "apis/deploy"; } + public static String getBackOfficeAPIURL() { + + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_BACKOFFICE + "apis"; + } + public static String getAPIUnDeployerURL() { return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" diff --git a/test/cucumber-tests/src/test/resources/tests/api/Visibility.feature b/test/cucumber-tests/src/test/resources/tests/api/Visibility.feature new file mode 100644 index 0000000000..2c6d04b8e8 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/Visibility.feature @@ -0,0 +1,16 @@ +Feature: API Visibility and Access Control + Scenario: View APIs with a groups claim and API view scope in the access token + Given The system is ready + And I have a valid subscription with groups + | group1 | + When I make the GET APIs call to the backoffice + Then the response status code should be 200 + And the response body should contain "\"count\":1" + + Scenario: View APIs without a groups claim in the access token + Given The system is ready + And I have a valid subscription with scopes + | apk:api_view | + When I make the GET APIs call to the backoffice + Then the response status code should be 200 + And the response body should contain "\"count\":0"