Skip to content
This repository has been archived by the owner on Dec 8, 2021. It is now read-only.

Commit

Permalink
fix(plan): add criteria to select API-Key plan
Browse files Browse the repository at this point in the history
  • Loading branch information
tcompiegne authored and brasseld committed Feb 15, 2019
1 parent e037c17 commit b0981f8
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,15 +35,17 @@ public class PlanBasedAuthenticationHandler implements AuthenticationHandler {

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
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ public List<AuthenticationHandler> filter(List<AuthenticationHandler> securityPr

// 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));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -46,10 +54,20 @@ public class ApiKeyAuthenticationHandler implements AuthenticationHandler {
@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
Expand Down Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,6 +50,9 @@ public class ApiKeyAuthenticationHandlerTest {
@InjectMocks
private ApiKeyAuthenticationHandler authenticationHandler = new ApiKeyAuthenticationHandler();

@Mock
private ApiKeyRepository apiKeyRepository;

@Test
public void shouldNotHandleRequest() {
Request request = mock(Request.class);
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,18 @@ public interface AuthenticationHandler {
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -43,7 +40,7 @@ public class JWTAuthenticationHandler implements AuthenticationHandler {
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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,7 +37,7 @@ public class KeylessAuthenticationHandler implements AuthenticationHandler {
static final String KEYLESS_POLICY = "key-less";

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -43,7 +40,7 @@ public class OAuth2AuthenticationHandler implements AuthenticationHandler {
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()) {
Expand Down

0 comments on commit b0981f8

Please sign in to comment.