Skip to content

Instantly share code, notes, and snippets.

@openwms
Last active May 26, 2022 11:44
Show Gist options
  • Save openwms/699808ce366442bb2f54cc1fbec1ec9b to your computer and use it in GitHub Desktop.
Save openwms/699808ce366442bb2f54cc1fbec1ec9b to your computer and use it in GitHub Desktop.
package org.openwms.gateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.ameba.Constants.HEADER_VALUE_X_TENANT;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.HEAD;
import static org.springframework.http.HttpMethod.OPTIONS;
import static org.springframework.http.HttpMethod.TRACE;
/**
* A AuthorizationMatrixGatewayFilterFactory supports a route/grant mapping like...
owms:
auth:
mappings:
- id: common-service
paths:
- path: /v1/locations/reset
verbs: post
grants: access.stockmanagement.action.blockbins
- path: /v1/location-groups/change-opmode
verbs: post
grants: access.resources.action.emergencymodecranes
- path: /v1/targets/**
verbs: post
grants: access.resources.action.blocklanes, access.resources.action.blockcranes
- path: /v1/ui/location-groups/change-max-errors
verbs: patch
grants: access.resources.action.errorcranes
- path: /v1/location-groups/reset-current-errors
verbs: post
grants: access.resources.action.errorcranes
- path: /v1/ui/transport-units/filtered #POST get with request body filters
verbs: post
grants: access.transportunits.screen
log-action-exclude: true
- path: /v1/transport-unit/inactivate
verbs: patch
grants: access.transportunit.actions.delete
- path: /v1/transport-unit/inactivate/**
verbs: patch
grants: access.transportunit.actions.delete
*
* @author Heiko Scherrer
*/
@Component
public class AuthorizationMatrixGatewayFilterFactory extends AbstractGatewayFilterFactory<AbstractGatewayFilterFactory.NameConfig> {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationMatrixGatewayFilterFactory.class);
public static final String GENERIC_ANY_ACCESS = "any";
private final PermissionsStore permissionsStore;
private final AuthorizationMatrix matrix;
private final AcceptablePathHelper acceptablePathHelper;
private Map<String, List<String>> grantMapping;
private final List<HttpMethod> allowedMethodsWithPermissions = Arrays.asList(GET, OPTIONS, TRACE, HEAD);
public AuthorizationMatrixGatewayFilterFactory(PermissionsStore permissionsStore, AuthorizationMatrix matrix, AcceptablePathHelper acceptablePathHelper) {
super(NameConfig.class);
this.permissionsStore = permissionsStore;
this.matrix = matrix;
this.acceptablePathHelper = acceptablePathHelper;
}
static class Aggregate {
String id;
List<String> val;
public Aggregate(String id, List<String> val) {
this.id = id;
this.val = val;
}
}
@PostConstruct
void onPostConstruct() {
grantMapping = new HashMap<>();
for (var m : matrix.getMappings()) {
grantMapping.putAll(m.getPaths().stream()
.flatMap(p -> p.getVerbs().stream()
.map(v -> v.toLowerCase() + p.getPath().toLowerCase())
.map(pv -> new Aggregate(m + pv, p.getGrants()))
).collect(Collectors.toMap(a -> a.id, a -> a.val)));
}
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(NAME_KEY);
}
@Override
public GatewayFilter apply(NameConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
var request = exchange.getRequest();
if (acceptablePathHelper.validAcceptablePath(request)) {
return chain.filter(exchange);
}
var identity = request.getHeaders().getFirst(HEADER_VALUE_X_TENANT);
if (identity == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
addOriginalRequestUrl(exchange, request.getURI());
var path = request.getURI().getRawPath();
Route route = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
var antPathMatcher = new AntPathMatcher(AntPathMatcher.DEFAULT_PATH_SEPARATOR);
Optional<Map.Entry<String, List<String>>> match = grantMapping.entrySet().stream().filter(e -> antPathMatcher.match(e.getKey(), route.getId() + request.getMethod().name().toLowerCase() + path.toLowerCase())).findFirst();
if (!allowedMethodsWithPermissions.contains(request.getMethod()) && match.isEmpty()) {
LOGGER.error("Method: [{}] Path: [{}] has no auth mapping", request.getMethod(), request.getPath());
}
if (permissionsStore.getPermissions(identity).isPresent() && allowedMethodsWithPermissions.contains(request.getMethod())) {
return chain.filter(exchange);
} else if (match.isEmpty() || permissionsStore.getPermissions(identity).isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
} else {
if (match.get().getValue().isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
var first = match.get().getValue()
.stream()
.filter(g -> permissionsStore.getPermissions(identity).get().contains(g))
.findFirst();
if (first.isEmpty()) {
var genericAny = match.get().getValue().stream().filter(GENERIC_ANY_ACCESS::equals).findFirst();
if (genericAny.isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}
}
return chain.filter(exchange);
}
@Override
public String toString() {
return filterToStringCreator(AuthorizationMatrixGatewayFilterFactory.this)
.append("name", config.getName()).toString();
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment