Skip to content
Permalink
Browse files

fix(plan): add criteria to select API-Key plan

  • Loading branch information...
tcompiegne authored and brasseld committed Feb 13, 2019
1 parent e037c17 commit b0981f8e785697e378d51adbf8e2da80cc336945
@@ -18,9 +18,10 @@
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.handlers.api.definition.Plan;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationContext;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;

import java.util.List;
import java.util.function.Function;
@@ -34,15 +35,17 @@

private final AuthenticationHandler wrapper;
private final Plan plan;
private final AuthenticationContext authenticationContext;

PlanBasedAuthenticationHandler(final AuthenticationHandler wrapper, final Plan plan) {
this.wrapper = wrapper;
this.plan = plan;
this.authenticationContext = convert(plan);
}

@Override
public boolean canHandle(Request request) {
return wrapper.canHandle(request);
public boolean canHandle(Request request, AuthenticationContext authenticationContext) {
return wrapper.canHandle(request, this.authenticationContext);
}

@Override
@@ -84,4 +87,10 @@ public String configuration() {
})
.collect(Collectors.toList());
}

private AuthenticationContext convert(Plan plan) {
AuthenticationContext authenticationContext = new AuthenticationContext();
authenticationContext.setId(plan.getId());
return authenticationContext;
}
}
@@ -47,14 +47,15 @@

// Look into all plans for required authentication providers.
Collection<Plan> plans = api.getPlans();
securityProviders.forEach(provider -> {
Optional<Plan> first = plans
plans.forEach(plan -> {
Optional<AuthenticationHandler> optionalProvider = securityProviders
.stream()
.filter(plan -> provider.name().equalsIgnoreCase(plan.getSecurity()))
.filter(provider -> provider.name().equalsIgnoreCase(plan.getSecurity()))
.findFirst();
if (first.isPresent()) {
logger.debug("Security provider [{}] is required by, at least, one plan. Installing...", provider.name());
providers.add(new PlanBasedAuthenticationHandler(provider, first.get()));
if (optionalProvider.isPresent()) {
AuthenticationHandler provider = optionalProvider.get();
logger.debug("Security provider [{}] is required by the plan [{}]. Installing...", provider.name(), plan.getName());
providers.add(new PlanBasedAuthenticationHandler(provider, plan));
}
});

@@ -37,5 +37,9 @@
<artifactId>gravitee-gateway-security-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.gravitee.repository</groupId>
<artifactId>gravitee-repository</artifactId>
</dependency>
</dependencies>
</project>
@@ -18,23 +18,31 @@
import io.gravitee.common.http.GraviteeHttpHeader;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationContext;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.ApiKeyRepository;
import io.gravitee.repository.management.model.ApiKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* An api-key based {@link AuthenticationHandler}.
*
* @author David BRASSELY (david.brassely at graviteesource.com)
* @author GraviteeSource Team
*/
public class ApiKeyAuthenticationHandler implements AuthenticationHandler {
public class ApiKeyAuthenticationHandler implements AuthenticationHandler, InitializingBean {

private final Logger logger = LoggerFactory.getLogger(ApiKeyAuthenticationHandler.class);

@@ -46,10 +54,20 @@
@Value("${policy.api-key.param:api-key}")
private String apiKeyQueryParameter = "api-key";

@Autowired
private ApplicationContext applicationContext;

private ApiKeyRepository apiKeyRepository;

@Override
public void afterPropertiesSet() {
apiKeyRepository = applicationContext.getBean(ApiKeyRepository.class);
}

@Override
public boolean canHandle(Request request) {
public boolean canHandle(Request request, AuthenticationContext authenticationContext) {
final String apiKey = lookForApiKey(request);
return apiKey != null;
return apiKey != null && isMatchingCriteria(apiKey, authenticationContext);
}

@Override
@@ -81,4 +99,23 @@ private String lookForApiKey(Request request) {

return apiKey;
}

private boolean isMatchingCriteria(String apiKey, AuthenticationContext authenticationContext) {
if (apiKeyRepository == null || authenticationContext == null) {
// unable to determine matching criteria, select this plan
return true;
}

try {
Optional<ApiKey> apiKeyOptional = apiKeyRepository.findById(apiKey);
if (!apiKeyOptional.isPresent()) {
// no api-key found, any API key plan can be selected, the request will be rejected by the API Key policy whatsoever
return true;
}
return apiKeyOptional.get().getPlan().equals(authenticationContext.getId());
} catch (TechnicalException e) {
// technical exception, any API key plan can be selected, the request will be rejected by the API Key policy whatsoever
return true;
}
}
}
@@ -20,16 +20,22 @@
import io.gravitee.common.util.MultiValueMap;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.security.core.AuthenticationContext;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.repository.exceptions.TechnicalException;
import io.gravitee.repository.management.api.ApiKeyRepository;
import io.gravitee.repository.management.model.ApiKey;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -44,6 +50,9 @@
@InjectMocks
private ApiKeyAuthenticationHandler authenticationHandler = new ApiKeyAuthenticationHandler();

@Mock
private ApiKeyRepository apiKeyRepository;

@Test
public void shouldNotHandleRequest() {
Request request = mock(Request.class);
@@ -103,4 +112,48 @@ public void shouldReturnName() {
public void shouldReturnOrder() {
Assert.assertEquals(500, authenticationHandler.order());
}

@Test
public void shouldNotHandleRequest_wrongCriteria() throws TechnicalException {
Request request = mock(Request.class);
HttpHeaders headers = new HttpHeaders();
headers.set("X-Gravitee-Api-Key", "xxxxx-xxxx-xxxxx");
when(request.headers()).thenReturn(headers);

MultiValueMap<String, String> parameters = mock(MultiValueMap.class);
when(request.parameters()).thenReturn(parameters);

AuthenticationContext authenticationContext = mock(AuthenticationContext.class);
when(authenticationContext.getId()).thenReturn("wrong-plan-id");

ApiKey apiKey = mock(ApiKey.class);
when(apiKey.getPlan()).thenReturn("plan-id");

when(apiKeyRepository.findById("xxxxx-xxxx-xxxxx")).thenReturn(Optional.of(apiKey));

boolean handle = authenticationHandler.canHandle(request, authenticationContext);
Assert.assertFalse(handle);
}

@Test
public void shouldHandleRequest_withCriteria() throws TechnicalException {
Request request = mock(Request.class);
HttpHeaders headers = new HttpHeaders();
headers.set("X-Gravitee-Api-Key", "xxxxx-xxxx-xxxxx");
when(request.headers()).thenReturn(headers);

MultiValueMap<String, String> parameters = mock(MultiValueMap.class);
when(request.parameters()).thenReturn(parameters);

AuthenticationContext authenticationContext = mock(AuthenticationContext.class);
when(authenticationContext.getId()).thenReturn("plan-id");

ApiKey apiKey = mock(ApiKey.class);
when(apiKey.getPlan()).thenReturn("plan-id");

when(apiKeyRepository.findById("xxxxx-xxxx-xxxxx")).thenReturn(Optional.of(apiKey));

boolean handle = authenticationHandler.canHandle(request, authenticationContext);
Assert.assertTrue(handle);
}
}
@@ -0,0 +1,33 @@
/**
* Copyright (C) 2015 The Gravitee team (http://gravitee.io)
*
* 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.gravitee.gateway.security.core;

/**
* @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
* @author GraviteeSource Team
*/
public class AuthenticationContext {

private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}
@@ -46,7 +46,18 @@
* @param request Incoming HTTP request.
* @return Flag indicating that the incoming request can be handled by the authentication system.
*/
boolean canHandle(Request request);
default boolean canHandle(Request request) {
return canHandle(request, null);
}

/**
* Check that the incoming HTTP request can be handle by the underlying authentication system.
*
* @param request Incoming HTTP request.
* @param authenticationContext context data upon which incoming HTTP request can be handled.
* @return Flag indicating that the incoming request can be handled by the authentication system.
*/
boolean canHandle(Request request, AuthenticationContext authenticationContext);

/**
* Policies which will be run for each request after authentication method selection
@@ -19,6 +19,7 @@
import io.gravitee.gateway.api.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
@@ -67,6 +68,16 @@ public void initializeSecurityProviders() {
List<AuthenticationHandler> availableSecurityProviders =
securityProviderLoader.getSecurityProviders();

availableSecurityProviders.forEach(authenticationHandler -> {
if (authenticationHandler instanceof InitializingBean) {
try {
((InitializingBean) authenticationHandler).afterPropertiesSet();
} catch (Exception e) {
logger.debug("An error occurs while loading Security Provider [{}]", authenticationHandler.name(), e);
}
}
});

// Sort by order
Collections.sort(availableSecurityProviders, Comparator.comparingInt(AuthenticationHandler::order));

@@ -18,10 +18,7 @@
import io.gravitee.common.http.HttpHeaders;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.HookAuthenticationPolicy;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.*;
import io.gravitee.gateway.security.jwt.policy.CheckSubscriptionPolicy;
import org.springframework.util.StringUtils;

@@ -43,7 +40,7 @@
static final String BEARER_AUTHORIZATION_TYPE = "Bearer";

@Override
public boolean canHandle(Request request) {
public boolean canHandle(Request request, AuthenticationContext authenticationContext) {
List<String> authorizationHeaders = request.headers().get(HttpHeaders.AUTHORIZATION);

if (authorizationHeaders == null || authorizationHeaders.isEmpty()) {
@@ -17,9 +17,10 @@

import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.AuthenticationContext;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;

import java.util.Collections;
import java.util.List;
@@ -36,7 +37,7 @@
static final String KEYLESS_POLICY = "key-less";

@Override
public boolean canHandle(Request request) {
public boolean canHandle(Request request, AuthenticationContext authenticationContext) {
return true;
}

@@ -18,10 +18,7 @@
import io.gravitee.common.http.HttpHeaders;
import io.gravitee.gateway.api.ExecutionContext;
import io.gravitee.gateway.api.Request;
import io.gravitee.gateway.security.core.AuthenticationHandler;
import io.gravitee.gateway.security.core.AuthenticationPolicy;
import io.gravitee.gateway.security.core.HookAuthenticationPolicy;
import io.gravitee.gateway.security.core.PluginAuthenticationPolicy;
import io.gravitee.gateway.security.core.*;
import io.gravitee.gateway.security.oauth2.policy.CheckSubscriptionPolicy;
import org.springframework.util.StringUtils;

@@ -43,7 +40,7 @@
static final String BEARER_AUTHORIZATION_TYPE = "Bearer";

@Override
public boolean canHandle(Request request) {
public boolean canHandle(Request request, AuthenticationContext authenticationContext) {
List<String> authorizationHeaders = request.headers().get(HttpHeaders.AUTHORIZATION);

if (authorizationHeaders == null || authorizationHeaders.isEmpty()) {

0 comments on commit b0981f8

Please sign in to comment.
You can’t perform that action at this time.