Skip to content

Commit

Permalink
added support for property replacements (from system properties and e…
Browse files Browse the repository at this point in the history
…nvironment variables) in policy configurations, endpoints, and endpoint properties
  • Loading branch information
EricWittmann committed Dec 11, 2015
1 parent 4bde983 commit 0eff881
Show file tree
Hide file tree
Showing 13 changed files with 503 additions and 1 deletion.
150 changes: 150 additions & 0 deletions distro/data/src/main/resources/data/basic-settings.apiman.json
@@ -0,0 +1,150 @@
{
"Metadata" : {
"exportedOn" : 1449850746726,
"apimanVersion" : "1.2.0-SNAPSHOT"
},
"Users" : [ {
"username" : "admin",
"fullName" : "admin",
"email" : "",
"joinedOn" : 1449850499639,
"admin" : false
} ],
"Gateways" : [ ],
"Plugins" : [ ],
"Roles" : [ {
"id" : "ClientAppDeveloper",
"name" : "Client App Developer",
"description" : "Users responsible for creating and managing client apps should be granted this role within an Organization.",
"createdBy" : "admin",
"createdOn" : 1449850701029,
"permissions" : [ "clientAdmin", "clientView", "clientEdit" ]
}, {
"id" : "OrganizationOwner",
"name" : "Organization Owner",
"description" : "Automatically granted to the user who creates an Organization. Grants all privileges.",
"createdBy" : "admin",
"createdOn" : 1449850685799,
"autoGrant" : true,
"permissions" : [ "orgAdmin", "clientAdmin", "planView", "apiView", "clientView", "apiAdmin", "clientEdit", "planEdit", "apiEdit", "orgEdit", "orgView", "planAdmin" ]
}, {
"id" : "APIDeveloper",
"name" : "API Developer",
"description" : "Users responsible for creating and managing APIs should be granted this role within an Organization.",
"createdBy" : "admin",
"createdOn" : 1449850717969,
"permissions" : [ "planView", "apiView", "apiAdmin", "planEdit", "apiEdit", "planAdmin" ]
} ],
"PolicyDefinitions" : [ {
"id" : "RateLimitingPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.RateLimitingPolicy",
"name" : "Rate Limiting Policy",
"description" : "Enforces rate configurable request rate limits on an API. This ensures that consumers can't overload an API with too many requests.",
"icon" : "tachometer",
"templates" : [ {
"template" : "Consumers are limited to @{limit} requests per @{granularity} per @{period}."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "QuotaPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.QuotaPolicy",
"name" : "Quota Policy",
"description" : "Provides a way to limit the total number of requests that can be sent to an API.",
"icon" : "exchange",
"templates" : [ {
"template" : "Consumers cannot exceed their quota of @{limit} requests per @{granularity} per @{period}."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "IgnoredResourcesPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.IgnoredResourcesPolicy",
"name" : "Ignored Resources Policy",
"description" : "Requests satisfying the provided regular expression will be ignored.",
"icon" : "eye-slash",
"templates" : [ {
"template" : "Requests matching any of the @{pathsToIgnore.size()} regular expressions provided will receive a 404 error code."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "IPWhitelistPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.IPWhitelistPolicy",
"name" : "IP Whitelist Policy",
"description" : "Only requests that originate from a specified set of valid IP addresses will be allowed through.",
"icon" : "thumbs-up",
"templates" : [ {
"template" : "Only requests that originate from the set of @{ipList.size()} configured IP address(es) will be allowed to invoke the managed API."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "TransferQuotaPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.TransferQuotaPolicy",
"name" : "Transfer Quota Policy",
"description" : "Provides a way to limit the total number of bytes that can be transferred from (or to) an API.",
"icon" : "download",
"templates" : [ {
"template" : "Consumers are limited to transferring @{limit} bytes per @{granularity} per @{period}."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "IPBlacklistPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.IPBlacklistPolicy",
"name" : "IP Blacklist Policy",
"description" : "Requests that originate from a specified set of valid IP addresses will be denied access.",
"icon" : "thumbs-down",
"templates" : [ {
"template" : "Requests that originate from the set of @{ipList.size()} configured IP address(es) will be denied access to the managed API."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "URLRewritingPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.URLRewritingPolicy",
"name" : "URL Rewriting Policy",
"description" : "Responses from the back-end API will be modified by fixing up any incorrect URLs found with modified ones. This is useful because apiman works through an API Gateway.",
"icon" : "pencil-square",
"templates" : [ {
"template" : "Responses will be modified by finding all text matching regular expression '@{fromRegex}' with '@{toReplacement}'."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "CachingPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.CachingPolicy",
"name" : "Caching Policy",
"description" : "Allows caching of API responses in the Gateway to reduce overall traffic to the back-end API.",
"icon" : "hdd-o",
"templates" : [ {
"template" : "API responses will be cached for @{ttl} seconds."
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "BASICAuthenticationPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.BasicAuthenticationPolicy",
"name" : "BASIC Authentication Policy",
"description" : "Enables HTTP BASIC Authentication on an API. Some configuration required.",
"icon" : "lock",
"templates" : [ {
"template" : "Access to the API is protected by BASIC Authentication through the '@{realm}' authentication realm. @if{forwardIdentityHttpHeader != null}Successfully authenticated requests will forward the authenticated identity to the back end API via the '@{forwardIdentityHttpHeader}' custom HTTP header.@end{}"
} ],
"formType" : "Default",
"deleted" : false
}, {
"id" : "AuthorizationPolicy",
"policyImpl" : "class:io.apiman.gateway.engine.policies.AuthorizationPolicy",
"name" : "Authorization Policy",
"description" : "Enables fine grained authorization to API resources based on authenticated user roles.",
"icon" : "users",
"templates" : [ {
"template" : "Appropriate authorization roles are required. There are @{rules.size()} authorization rules defined."
} ],
"formType" : "Default",
"deleted" : false
} ],
"Orgs" : [ ]
}
4 changes: 4 additions & 0 deletions gateway/engine/core/pom.xml
Expand Up @@ -30,6 +30,10 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
Expand Down
Expand Up @@ -50,14 +50,20 @@
import io.apiman.gateway.engine.policy.PolicyWithConfiguration;
import io.apiman.gateway.engine.policy.RequestChain;
import io.apiman.gateway.engine.policy.ResponseChain;
import io.apiman.gateway.engine.util.ApimanStrLookup;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang.text.StrLookup;
import org.apache.commons.lang.text.StrSubstitutor;

/**
* Manages a single request-response sequence. It is executed in the following
* order:
Expand All @@ -76,6 +82,10 @@
*/
public class ApiRequestExecutorImpl implements IApiRequestExecutor {

private static StrLookup LOOKUP = new ApimanStrLookup();
private static StrSubstitutor PROPERTY_SUBSTITUTOR = new StrSubstitutor(LOOKUP);


private IRegistry registry;
private ApiRequest request;
private Api api;
Expand Down Expand Up @@ -261,13 +271,16 @@ public void handle(Void result) {
public void handle(IAsyncResult<Api> result) {
if (result.isSuccess()) {
api = result.getResult();

if (api == null) {
Exception error = new InvalidApiException(Messages.i18n.format("EngineImpl.ApiNotFound")); //$NON-NLS-1$
resultHandler.handle(AsyncResultImpl.create(error, IEngineResult.class));
} else if (!api.isPublicAPI()) {
Exception error = new InvalidApiException(Messages.i18n.format("EngineImpl.ApiNotPublic")); //$NON-NLS-1$
resultHandler.handle(AsyncResultImpl.create(error, IEngineResult.class));
} else {
resolvePropertyReplacements(api);

request.setApi(api);
policies = api.getApiPolicies();
policyImpls = new ArrayList<>(policies.size());
Expand All @@ -284,6 +297,9 @@ public void handle(IAsyncResult<Api> result) {
public void handle(IAsyncResult<ApiContract> result) {
if (result.isSuccess()) {
ApiContract apiContract = result.getResult();

resolvePropertyReplacements(apiContract);

requestMetric.setClientOrgId(apiContract.getClient().getOrganizationId());
requestMetric.setClientId(apiContract.getClient().getClientId());
requestMetric.setClientVersion(apiContract.getClient().getVersion());
Expand Down Expand Up @@ -312,6 +328,63 @@ public void handle(IAsyncResult<ApiContract> result) {
}
}

/**
* @param api
*/
protected void resolvePropertyReplacements(Api api) {
if (api == null) {
return;
}
String endpoint = api.getEndpoint();
endpoint = resolveProperties(endpoint);
api.setEndpoint(endpoint);

Map<String, String> properties = api.getEndpointProperties();
for (Entry<String, String> entry : properties.entrySet()) {
String value = entry.getValue();
value = resolveProperties(value);
entry.setValue(value);
}

resolvePropertyReplacements(api.getApiPolicies());
}

/**
* @param apiContract
*/
protected void resolvePropertyReplacements(ApiContract apiContract) {
if (apiContract == null) {
return;
}
Api api = apiContract.getApi();
if (api != null) {
resolvePropertyReplacements(api);
}
resolvePropertyReplacements(apiContract.getPolicies());
}

/**
* @param apiPolicies
*/
private void resolvePropertyReplacements(List<Policy> apiPolicies) {
for (Policy policy : apiPolicies) {
String config = policy.getPolicyJsonConfig();
config = resolveProperties(config);
policy.setPolicyJsonConfig(config);
}
}

/**
* @param endpoint
*/
private String resolveProperties(String value) {
if (value.contains("${")) { //$NON-NLS-1$
return PROPERTY_SUBSTITUTOR.replace(value);
} else {
return value;
}
}

/**
* Validates that the contract being used for the request is valid against the
* api information included in the request. Basically the request includes
Expand Down
@@ -0,0 +1,40 @@
/*
* Copyright 2015 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apiman.gateway.engine.util;

import org.apache.commons.lang.text.StrLookup;

/**
* A simple string lookup class that supports system properties and environment
* variables.
*
* @author eric.wittmann@redhat.com
*/
public class ApimanStrLookup extends StrLookup {

/**
* @see org.apache.commons.lang.text.StrLookup#lookup(java.lang.String)
*/
@Override
public String lookup(String key) {
String replacement = System.getProperty(key);
if (replacement == null) {
System.getenv(key);
}
return replacement;
}

}
@@ -0,0 +1,36 @@
/*
* Copyright 2014 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apiman.gateway.test;

import io.apiman.gateway.test.junit.GatewayRestTestPlan;
import io.apiman.gateway.test.junit.GatewayRestTestSystemProperties;
import io.apiman.gateway.test.junit.GatewayRestTester;

import org.junit.runner.RunWith;

/**
* @author eric.wittmann@redhat.com
*/
@RunWith(GatewayRestTester.class)
@GatewayRestTestPlan("test-plans/simple/simple-echo-replacement-testPlan.xml")
@GatewayRestTestSystemProperties({
"SimpleEchoReplacementTest-path", "echo-replacement",
"SimpleEchoReplacementTest-field-1", "FIELD-1-VALUE",
"SimpleEchoReplacementTest-field-2", "FIELD-2-VALUE"
})
public class SimpleEchoReplacementTest {

}

0 comments on commit 0eff881

Please sign in to comment.