Skip to content

Commit

Permalink
- Fix to remove duplicate call to grant application access to api.
Browse files Browse the repository at this point in the history
  • Loading branch information
rathnapandi committed Oct 23, 2024
1 parent 10ea640 commit 53189f5
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
public class APIManagerAPIAccessAdapter {

public static final String APIS = "/apis";

public enum Type {
organizations("Organization"),
applications("Application");
Expand Down Expand Up @@ -214,15 +215,9 @@ public APIAccess createAPIAccessForApplication(APIAccess apiAccess, String appli
try (CloseableHttpResponse httpResponse = (CloseableHttpResponse) request.execute()) {
int statusCode = httpResponse.getStatusLine().getStatusCode();
String response = EntityUtils.toString(httpResponse.getEntity());

if (statusCode != 201) {
if (statusCode == 409 && response.contains("resource already exists")) {
LOG.info("Unexpected response while creating/updating API Access: {} Response-Code: {} Response Body: {} Ignoring this error.", apiAccess, statusCode, response);
return apiAccess;
}else {
LOG.error("Error granting access to application id : {} for API-Proxy : {} using URI: {} Received Status-Code: {} Response: {}", applicationId, apiAccess.getApiId(), uri, statusCode, response);
throw new AppException("Can't grant access to API.", ErrorCode.ERR_GRANTING_ACCESS_TO_API);
}
LOG.error("Error granting access to application id : {} for API-Proxy : {} using URI: {} Received Status-Code: {} Response: {}", applicationId, apiAccess.getApiId(), uri, statusCode, response);
throw new AppException("Can't grant access to API.", ErrorCode.ERR_GRANTING_ACCESS_TO_API);
}
removeApplicationFromCache(applicationId);
return mapper.readValue(response, APIAccess.class);
Expand Down Expand Up @@ -290,7 +285,7 @@ public void deleteAPIAccess(APIAccess apiAccess, AbstractEntity parentEntity, Ty
if (statusCode < 200 || statusCode > 299) {
String errorResponse = EntityUtils.toString(httpResponse.getEntity());
LOG.error("Can't delete API access requests for application. Response-Code: {}. Got response: {}", statusCode, errorResponse);
throw new AppException("Can't delete API access requests for application. Response-Code: " + statusCode,ErrorCode.REVOKE_ACCESS_APPLICATION_ERR);
throw new AppException("Can't delete API access requests for application. Response-Code: " + statusCode, ErrorCode.REVOKE_ACCESS_APPLICATION_ERR);
}
removeFromCache(parentEntity.getId(), type);
}
Expand Down Expand Up @@ -329,7 +324,7 @@ public List<APIAccess> getMissingAPIAccesses(List<APIAccess> apiAccess, List<API
}


public List<ApiOrganizationSubscription> getSubscribedOrganizationsAndApplications(String apiId) throws AppException{
public List<ApiOrganizationSubscription> getSubscribedOrganizationsAndApplications(String apiId) throws AppException {
try {
URI uri = new URIBuilder(cmd.getAPIManagerURL()).setPath(cmd.getApiBasepath() + "/proxies/" + apiId + "/apiaccess").build();
RestAPICall request = new GETRequest(uri);
Expand All @@ -340,7 +335,8 @@ public List<ApiOrganizationSubscription> getSubscribedOrganizationsAndApplicatio
LOG.error("Can't get API access requests for API. Response-Code: {}. Got response: {}", statusCode, response);
throw new AppException("Can't get API access requests for API: " + statusCode, ErrorCode.API_MANAGER_COMMUNICATION);
}
return mapper.readValue(response, new TypeReference<>() {});
return mapper.readValue(response, new TypeReference<>() {
});
}
} catch (Exception e) {
throw new AppException("Can't delete API access requests for application.", ErrorCode.API_MANAGER_COMMUNICATION, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,8 @@ public void addClientOrganizations(API api) throws AppException {

public void addClientApplications(API api, APIFilter filter) throws AppException {
if (!filter.isIncludeClientApplications()) return;
List<ClientApplication> apps;
apps = APIManagerAdapter.getInstance().getAppAdapter().getAppsSubscribedWithAPI(api.getId());
List<ClientApplication> apps = APIManagerAdapter.getInstance().getAppAdapter().getAppsSubscribedWithAPI(api.getId());
LOG.debug("Adding client-applications : {}", apps);
api.setApplications(apps);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ public void setCreatedOn(Long createdOn) {
/**
* This avoids, that custom properties are wrapped within customProperties { ... }
* See http://www.cowtowncoder.com/blog/archives/2011/07/entry_458.html
*
* @return custom properties map
*/
@JsonAnyGetter
Expand Down Expand Up @@ -273,6 +274,12 @@ public int hashCode() {

@Override
public String toString() {
return "[" + getName() + " (" + getId() + ")]";
return "ClientApplication{" +
"name=" + getName() +
", id=" + getId() +
", enabled=" + enabled +
", state=" + state +
", organization=" + organization +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public void execute(APIChangeState changes, boolean reCreation) throws AppExcept
throw e;
}
rollback.addRollbackAction(new RollbackAPIProxy(createdAPI)); // In any case, register the API just created for a potential rollback

try {
if(EnvironmentProperties.OVERRIDE_CERTIFICATES){
//Ignore certificates downloaded from backend.
Expand Down Expand Up @@ -95,6 +94,8 @@ public void execute(APIChangeState changes, boolean reCreation) throws AppExcept
if (reCreation && actualAPI.getState().equals(API.STATE_PUBLISHED)) {
// In case, the existing API is already in use (Published), we have to grant access to our new imported API
apiAdapter.upgradeAccessToNewerAPI(createdAPI, actualAPI);
// copy the existing client applications, to avoid granting duplicate access to applications
createdAPI.setApplications(actualAPI.getApplications());
}
if (EnvironmentProperties.CHECK_CATALOG) {
apiAdapter.pollCatalogForPublishedState(createdAPI.getId(), createdAPI.getName(), createdAPI.getState());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
package com.axway.apim.apiimport.actions;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.axway.apim.adapter.APIManagerAdapter;
import com.axway.apim.adapter.apis.APIManagerAPIAccessAdapter;
import com.axway.apim.adapter.apis.APIManagerAPIAccessAdapter.Type;
import com.axway.apim.adapter.apis.OrgFilter;
import com.axway.apim.api.API;
import com.axway.apim.api.model.APIAccess;
import com.axway.apim.api.model.Organization;
import com.axway.apim.api.model.apps.ClientApplication;
import com.axway.apim.lib.CoreParameters;
import com.axway.apim.lib.error.AppException;
import com.axway.apim.lib.error.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ManageClientApps {

Expand Down Expand Up @@ -53,20 +50,21 @@ public void execute(boolean reCreation) throws AppException {
// Remove configured apps, for Non-Granted-Orgs!
removeNonGrantedClientApps(desiredState.getApplications());
}
List<ClientApplication> recreateActualApps;
// If an UNPUBLISHED API has been re-created, we have to create App-Subscriptions manually, as API-Manager Upgrade only works on PUBLISHED APIs
// But we only need to do this, if existing App-Subscriptions should be preserved (MODE_ADD).
if (reCreation && actualState.getState().equals(API.STATE_UNPUBLISHED) && CoreParameters.getInstance().getClientAppsMode().equals(CoreParameters.Mode.add)) {
removeNonGrantedClientApps(oldAPI.getApplications());
recreateActualApps = getMissingApps(oldAPI.getApplications(), actualState.getApplications());
LOG.debug("Copying applications from old api id : {} to new api id: {}", oldAPI.getId(), actualState.getId());
// Create previously existing App-Subscriptions
createAppSubscription(recreateActualApps, actualState.getId());
createAppSubscription(oldAPI.getApplications(), actualState.getId());
// Update the In-Memory actual state for further processing
actualState.setApplications(recreateActualApps);
actualState.setApplications(oldAPI.getApplications());
}
LOG.debug("Desired applications: {}", desiredState.getApplications());
LOG.debug("Actual applications: {}", actualState.getApplications());
List<ClientApplication> missingDesiredApps = getMissingApps(desiredState.getApplications(), actualState.getApplications());
List<ClientApplication> removingActualApps = getMissingApps(actualState.getApplications(), desiredState.getApplications());

LOG.debug("Missing Apps to be provisioned: {}", missingDesiredApps);
LOG.debug("Removing Apps from API access: {}", removingActualApps);
if (missingDesiredApps.isEmpty() && desiredState.getApplications() != null) {
LOG.info("All desired applications: {} have already a subscription. Nothing to do.", desiredState.getApplications());
} else {
Expand All @@ -75,14 +73,16 @@ public void execute(boolean reCreation) throws AppException {
if (!removingActualApps.isEmpty()) {
if (CoreParameters.getInstance().getClientAppsMode().equals(CoreParameters.Mode.replace)) {
LOG.info("Removing access for applications: {} from API: {} ", removingActualApps, actualState.getName());
removeAppSubscription(removingActualApps);
removeAppSubscription(removingActualApps, actualState);
} else {
LOG.info("NOT removing access for applications: {} from API: {} as clientAppsMode NOT set to replace.", removingActualApps, actualState.getName());
}
}

}

private void removeNonGrantedClientApps(List<ClientApplication> apps) throws AppException {

public void removeNonGrantedClientApps(List<ClientApplication> apps) {
if (apps == null) return;
ListIterator<ClientApplication> it = apps.listIterator();
ClientApplication app;
Expand All @@ -95,20 +95,13 @@ private void removeNonGrantedClientApps(List<ClientApplication> apps) throws App
}
}

private boolean hasClientAppPermission(ClientApplication app) throws AppException {
LOG.info("Application name : {}", app.getName());
LOG.info("Organization : {}", app.getOrganization());

String appsOrgId = app.getOrganization().getId();
Organization appsOrgs = APIManagerAdapter.getInstance().getOrgAdapter().getOrg(new OrgFilter.Builder().hasId(appsOrgId).build());
if (appsOrgs == null) return true;
// If the App belongs to the same Org as the API, it automatically has permission (esp. for Unpublished APIs)
if (app.getOrganization().equals((actualState).getOrganization())) return false;
public boolean hasClientAppPermission(ClientApplication app) {
LOG.info("Checking Application name : {} belong to granted organization : {}", app.getName(), app.getOrganization().getName());
if (actualState.getClientOrganizations() == null) {
LOG.debug("No Client-Orgs configured for this API, therefore other app has NO permission.");
return true;
}
return !actualState.getClientOrganizations().contains(appsOrgs);
return !actualState.getClientOrganizations().contains(app.getOrganization());
}

private void createAppSubscription(List<ClientApplication> missingDesiredApps, String apiId) throws AppException {
Expand All @@ -123,14 +116,18 @@ private void createAppSubscription(List<ClientApplication> missingDesiredApps, S
}
}

private void removeAppSubscription(List<ClientApplication> removingActualApps) throws AppException {
private void removeAppSubscription(List<ClientApplication> removingActualApps, API actualApi) throws AppException {
for (ClientApplication app : removingActualApps) {
// A Client-App that doesn't belong to a granted organization, can't have a subscription.
if (hasClientAppPermission(app)) continue;
LOG.debug("Removing API-Access for application {}", app.getName());
List<APIAccess> apiAccesses = APIManagerAdapter.getInstance().getAccessAdapter().getAPIAccess(app, APIManagerAPIAccessAdapter.Type.applications);
LOG.debug("App API Access : {}", app.getApiAccess());
try {
for (APIAccess apiAccess : app.getApiAccess()) {
accessAdapter.deleteAPIAccess(apiAccess, app, Type.applications);
for (APIAccess apiAccess : apiAccesses) {
if (apiAccess.getApiId().equals(actualApi.getId())) {
accessAdapter.deleteAPIAccess(apiAccess, app, Type.applications);
}
}
} catch (Exception e) {
LOG.error("Can't delete API access requests for application.");
Expand All @@ -139,16 +136,29 @@ private void removeAppSubscription(List<ClientApplication> removingActualApps) t
}
}

private List<ClientApplication> getMissingApps(List<ClientApplication> apps, List<ClientApplication> otherApps) {
public List<ClientApplication> getMissingApps(List<ClientApplication> apps, List<ClientApplication> otherApps) {
List<ClientApplication> missingApps = new ArrayList<>();
if (otherApps == null) otherApps = new ArrayList<>();
if (apps == null) apps = new ArrayList<>();
for (ClientApplication app : apps) {
if (otherApps.contains(app)) {
if (containsAppName(otherApps, app)) {
continue;
}
missingApps.add(app);
}
return missingApps;
}


public boolean containsAppName(List<ClientApplication> source, ClientApplication clientApplication) {
for (ClientApplication app : source) {
if (app.getName().equals(clientApplication.getName())) {
if (app.getOrganization() != null && clientApplication.getOrganization() != null) {
return app.getOrganization().getName().equals(clientApplication.getOrganization().getName());
}
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.axway.apim.apiimport.actions;

import com.axway.apim.WiremockWrapper;
import com.axway.apim.api.API;
import com.axway.apim.api.model.Organization;
import com.axway.apim.api.model.apps.ClientApplication;
import com.axway.apim.lib.CoreParameters;
import com.axway.apim.lib.utils.Utils;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;

public class ManageClientAppsTest extends WiremockWrapper {

@BeforeClass
public void initWiremock() {
super.initWiremock();
CoreParameters coreParameters = new CoreParameters();
coreParameters.setHostname("localhost");
coreParameters.setUsername("test");
coreParameters.setPassword(Utils.getEncryptedPassword());
}

@AfterClass
public void close() {
super.close();
}


@Test
public void containsAppNameEqual() throws Exception {
API desiredState = new API();
API actualState = new API();

ManageClientApps manageClientApps = new ManageClientApps(desiredState, actualState, null);
List<ClientApplication> source = new ArrayList<>();
Organization organization = new Organization();
organization.setName("Test Organization");
ClientApplication clientApplication = new ClientApplication();
clientApplication.setOrganization(organization);
clientApplication.setName("Test Client Application");
source.add(clientApplication);
Assert.assertTrue(manageClientApps.containsAppName(source, clientApplication));

}

@Test
public void containsAppNameNotEqual() throws Exception {
API desiredState = new API();
API actualState = new API();

ManageClientApps manageClientApps = new ManageClientApps(desiredState, actualState, null);
List<ClientApplication> source = new ArrayList<>();
Organization organization = new Organization();
organization.setName("Test Organization");
ClientApplication clientApplication = new ClientApplication();
clientApplication.setOrganization(organization);
clientApplication.setName("Test Client Application");
source.add(clientApplication);

ClientApplication newClientApplication = new ClientApplication();
newClientApplication.setOrganization(organization);
newClientApplication.setName("Test Client Application2");
source.add(clientApplication);


Assert.assertFalse(manageClientApps.containsAppName(source, newClientApplication));

}

@Test

public void getMissingApps() throws Exception {
API desiredState = new API();
API actualState = new API();

List<ClientApplication> source = new ArrayList<>();
Organization organization = new Organization();
organization.setName("Test Organization");
ClientApplication clientApplication = new ClientApplication();
clientApplication.setOrganization(organization);
clientApplication.setName("Test Client Application");
source.add(clientApplication);
List<ClientApplication> target = new ArrayList<>();
ClientApplication clientApplicationTarget = new ClientApplication();
clientApplicationTarget.setOrganization(organization);
clientApplicationTarget.setName("Test Client Application Target");
target.add(clientApplicationTarget);

ManageClientApps manageClientApps = new ManageClientApps(desiredState, actualState, null);
List<ClientApplication> missingApps = manageClientApps.getMissingApps(source, target);
Assert.assertEquals(missingApps.get(0).getName(), "Test Client Application");

}
}

0 comments on commit 53189f5

Please sign in to comment.