Skip to content

Commit

Permalink
Merge branch 'master' into #89-webserver-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
t-burch authored Nov 8, 2023
2 parents 73ac87e + 98bb9a2 commit 8d13cab
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,9 @@ private void cleanUpNotRunningNodes(HashSet<EtcdNodeInformation> newRunningNodes
shutdownRunningClusterNode(node);
}

HashSet<String> modules = new HashSet<>();
for (String module : runningNodesForModule.keySet()) {
modules.add(module);
}
HashSet<String> modules = new HashSet<>(runningNodesForModule.keySet());
for (String module : modules) {
if (runningNodesForModule.get(module).size() == 0) {
if (runningNodesForModule.get(module).isEmpty()) {
runningNodesForModule.remove(module);
shutDownRunningModuleServiceProxy(module);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class GraphQLProtectionInterceptor extends AbstractInterceptor {
private List<String> allowedMethods = Lists.newArrayList("GET", "POST");
private int maxRecursion = 3;
private int maxDepth = 7;
private int maxMutations = 5;

public GraphQLProtectionInterceptor() {
name = "GraphQL protection";
Expand Down Expand Up @@ -159,6 +160,9 @@ public Outcome handleRequest(Exchange exc) throws Exception {

ExecutableDocument ed = graphQLParser.parseRequest(new ByteArrayInputStream(((String) query).getBytes(UTF_8)));

if (countMutations(ed.getExecutableDefinitions()) > maxMutations)
return error(exc, 400, "Too many mutations defined in document.");

// so far, this ensures uniqueness of global names
List<String> e1 = new GraphQLValidator().validate(ed);
if (e1 != null && e1.size() > 0)
Expand Down Expand Up @@ -191,8 +195,6 @@ public Outcome handleRequest(Exchange exc) throws Exception {
.map(exd -> (OperationDefinition) exd).toList();
if (ods.size() == 0)
return error(exc, "Could not find an OperationDefinition in the GraphQL document.");
if (ods.size() > 1)
return error(exc, "Multiple OperationDefinitions with the same name in the GraphQL document.");
operationToExecute = ods.get(0);
}

Expand All @@ -203,6 +205,15 @@ public Outcome handleRequest(Exchange exc) throws Exception {
return Outcome.CONTINUE;
}

public int countMutations(List<ExecutableDefinition> definitions) {
return (int) definitions.stream()
.filter(definition -> definition instanceof OperationDefinition)
.map(definition -> (OperationDefinition) definition)
.filter(operation -> operation.getOperationType() != null)
.filter(operation -> operation.getOperationType().getOperation().equals("mutation"))
.count();
}

private String getDepthOrRecursionError(ExecutableDocument ed, OperationDefinition od) {
String err = checkSelections(ed, od, od.getSelections(), new ArrayList<>(), new HashSet<>());
if (err != null)
Expand Down Expand Up @@ -292,6 +303,20 @@ public boolean isAllowExtensions() {
return allowExtensions;
}

/**
* Limit how many mutations can be defined in a document query.
* @default 5
* @example 2
*/
@MCAttribute
public void setMaxMutations(int maxMutations) {
this.maxMutations = maxMutations;
}

public int getMaxMutations() {
return maxMutations;
}

/**
* Whether to allow GraphQL "extensions".
* @default false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ public Map<String, String> getAttributes() {
*/
@MCOtherAttributes
public void setAttributes(Map<String, String> attributes) {
for (Map.Entry<String, String> e : attributes.entrySet())
this.attributes.put(e.getKey(), e.getValue());
this.attributes.putAll(attributes);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import java.util.*;
import java.util.function.*;

import static com.predic8.membrane.core.interceptor.Interceptor.Flow.REQUEST;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.interceptor.flow.ConditionalInterceptor.LanguageType.*;
import static com.predic8.membrane.core.lang.ScriptingUtils.createParameterBindings;

/**
* @description <p>
Expand Down Expand Up @@ -69,8 +71,15 @@ public void init(Router router) throws Exception {
}

private boolean testCondition(Exchange exc) {
HashMap<String, Object> parameters = new HashMap<>();
parameters.put("exc", exc);
HashMap<String, Object> parameters = new HashMap<>() {{
put("Outcome", Outcome.class);
put("RETURN", RETURN);
put("CONTINUE", CONTINUE);
put("ABORT", Outcome.ABORT);
put("spring", router.getBeanFactory());
put("exc", exc);
putAll(createParameterBindings(router.getUriFactory(), exc, exc.getRequest(), REQUEST, false));
}};
return condition.apply(parameters);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,7 @@ private Map<String,Object> getObject(String objectName){
}

private HashSet<String> getClaimsFromJsonObject(String objectName){
HashSet<String> claims = new HashSet<>();
for(String claimName : getObject(objectName).keySet())
claims.add(claimName);

return claims;
return new HashSet<>(getObject(objectName).keySet());
}

public HashSet<String> getUserinfoClaims(){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,11 +280,18 @@ private void setServiceEndpoint(AbstractExchange exc, Mapping mapping) {
}

private String getNewDestination(AbstractExchange exc) {
return "http://" + ((AbstractServiceProxy) exc.getRule()).getTargetHost() + ":"
return getProtocol(exc) + "://" + ((AbstractServiceProxy) exc.getRule()).getTargetHost() + ":"
+ ((AbstractServiceProxy) exc.getRule()).getTargetPort()
+ exc.getRequest().getUri();
}

private String getProtocol(AbstractExchange exc) {
if(exc.getRule().getSslOutboundContext() != null) {
return "https";
}
return "http";
}

private String getURI(AbstractExchange exc) {
return exc.getRequest().getUri();
}
Expand All @@ -305,5 +312,4 @@ public void setMappings(List<Mapping> mappings) {
public String getShortDescription() {
return "Transforms REST requests into SOAP and responses vice versa.";
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import org.slf4j.*;

import java.io.*;
import java.net.URISyntaxException;
import java.util.*;
import java.util.regex.*;

import static com.predic8.membrane.core.exceptions.ProblemDetails.createProblemDetails;
import static com.predic8.membrane.core.interceptor.Interceptor.Flow.Set.*;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.interceptor.rewrite.RewriteInterceptor.Type.*;
Expand Down Expand Up @@ -147,8 +149,10 @@ public Outcome handleRequest(Exchange exc) throws Exception {
ListIterator<String> it = exc.getDestinations().listIterator();
while ( it.hasNext() ) {
String dest = it.next();
String pathQuery = getPathQueryOrSetError(router.getUriFactory(), dest, exc);
if (pathQuery == null)
return RETURN;

String pathQuery = URLUtil.getPathQuery(router.getUriFactory(), dest);
int pathBegin = -1;
int authorityBegin = dest.indexOf("//");
if (authorityBegin != -1)
Expand Down Expand Up @@ -187,14 +191,32 @@ public Outcome handleRequest(Exchange exc) throws Exception {
if (mapping != null && mapping.do_ == REWRITE) {
String newDest = replace(exc.getRequest().getUri(), mapping);
if (newDest.contains("://")) {
newDest = URLUtil.getPathQuery(router.getUriFactory(), newDest);
newDest = getPathQueryOrSetError(router.getUriFactory(), newDest, exc);
if (newDest == null)
return RETURN;
}
exc.getRequest().setUri(newDest);
}

return CONTINUE;
}

private String getPathQueryOrSetError(URIFactory factory, String destination, Exchange exc) throws URISyntaxException {
String pathQuery;
try {
pathQuery = URLUtil.getPathQuery(factory, destination);
} catch(URISyntaxException ignore) {
exc.setResponse(
createProblemDetails(
400,
"/uri-parser",
"This URL does not follow the URI specification. Confirm the validity of the provided URL.")
);
return null;
}
return pathQuery;
}

private void logMappings() {
for (Mapping m : mappings) {
log.debug("[from:"+m.from+"],[to:"+m.to+"],[do:"+m.do_+"]");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,7 @@ private List<ContainerNode> splitUnionExprIntoIntersectExceptExprs(ContainerNode
if (n instanceof UnparsedStringNode) {
List<String> parts = new ArrayList<>();
for (String part : splitOnOperand(((UnparsedStringNode)n).s, "|"))
for (String part2 : splitOnOperand(part, "union"))
parts.add(part2);
parts.addAll(splitOnOperand(part, "union"));
for (int i = 0; i < parts.size(); i++) {
if (i >= 1) {
// next IntersectExceptExpr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.net.URLDecoder;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

Expand Down Expand Up @@ -127,10 +128,8 @@ public List<String> getChildren(String url) {
String[] children = new File(normalize(url)).list();
if (children == null)
return null;
ArrayList<String> res = new ArrayList<>(children.length);
for (String child : children)
res.add(child);
return res;

return Arrays.asList(children);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions core/src/test/java/com/predic8/membrane/core/UnitTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.predic8.membrane.core.config.ReadRulesConfigurationTest;
import com.predic8.membrane.core.config.ReadRulesWithInterceptorsConfigurationTest;
import com.predic8.membrane.core.exchangestore.*;
import com.predic8.membrane.core.graphql.GraphQLProtectionInterceptorTest;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.http.cookie.*;
import com.predic8.membrane.core.interceptor.*;
Expand Down Expand Up @@ -130,6 +131,7 @@
XMLProtectorTest.class,
AbstractExchangeStoreTest.class,
JsonProtectionInterceptorTest.class,
GraphQLProtectionInterceptorTest.class,
BeautifierInterceptorTest.class,
ExchangeEvaluationContextTest.class,
PaddingHeaderInterceptorTest.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,23 @@

package com.predic8.membrane.core.graphql;

import com.predic8.membrane.core.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.interceptor.*;
import org.junit.jupiter.api.*;

import static com.predic8.membrane.core.http.MimeType.*;
import static java.net.URLEncoder.*;
import static java.nio.charset.StandardCharsets.*;
import static org.junit.jupiter.api.Assertions.*;
import com.predic8.membrane.core.HttpRouter;
import com.predic8.membrane.core.exchange.Exchange;
import com.predic8.membrane.core.graphql.model.*;
import com.predic8.membrane.core.http.Request;
import com.predic8.membrane.core.interceptor.Outcome;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import javax.security.auth.login.Configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.predic8.membrane.core.http.MimeType.APPLICATION_JSON;
import static java.net.URLEncoder.encode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class GraphQLProtectionInterceptorTest {

Expand Down Expand Up @@ -339,6 +346,42 @@ public void recursionNotOK() throws Exception {
Outcome.RETURN);
}

@Test
public void mutationsCountNotOK() throws Exception {
verifyPost("/",
APPLICATION_JSON,
"""
{"query":"mutation abc{} mutation abcd{} mutation abcde{} mutation abcdef{} mutation abcdefg{} mutation abcdefgh{}",
"operationName": ""}""",
Outcome.RETURN);
}

@Test
public void mutationsCountOK() throws Exception {
verifyPost("/",
APPLICATION_JSON,
"""
{"query":"mutation abc{} mutation abcd{} mutation abcde{} mutation abcdef{} mutation abcdefg{}",
"operationName": ""}""",
Outcome.CONTINUE);
}

@Test
public void countThreeMutations() {
List<ExecutableDefinition> definitions = Arrays.asList(
opDefOfType("mutation"),
opDefOfType("mutation"),
opDefOfType("mutation"),
opDefOfType("query")
);

assertEquals(3, i.countMutations(definitions));
}

private OperationDefinition opDefOfType(String type) {
return new OperationDefinition(new OperationType(type), "", new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}

@Test
public void invalidMethod() throws Exception {
Exchange e = new Request.Builder().put("/").contentType(APPLICATION_JSON).body("{}").buildExchange();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
package com.predic8.membrane.core.interceptor.rewrite;

import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE;
import static com.predic8.membrane.core.interceptor.Outcome.RETURN;
import static org.junit.jupiter.api.Assertions.*;

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

import com.predic8.membrane.core.interceptor.Outcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -77,11 +79,27 @@ void testRewrite() throws Exception {
}

@Test
void storeSample() throws Exception {
void storeSample() throws Exception {
exc.setRequest(MessageUtil.getGetRequest("https://api.predic8.de/store/products/"));
assertEquals(CONTINUE, di.handleRequest(exc));
assertEquals(CONTINUE, rewriter.handleRequest(exc));
assertEquals("https://api.predic8.de/shop/v2/products/", exc.getDestinations().get(0));
}

@Test
void invalidURI() throws Exception {
exc.setRequest(MessageUtil.getGetRequest("/buy/banana/%"));
exc.setRule(sp);

assertEquals(CONTINUE, di.handleRequest(exc));
assertEquals(RETURN, rewriter.handleRequest(exc));
assertEquals(
"""
{
"type" : "http://membrane-api.io/error/uri-parser",
"title" : "This URL does not follow the URI specification. Confirm the validity of the provided URL."
}""",
exc.getResponse().getBodyAsStringDecoded()
);
}
}
Loading

0 comments on commit 8d13cab

Please sign in to comment.