瀏覽代碼

Add RequestProtector app

pull/58/head
Kristijan Mitrovic 4 年之前
父節點
當前提交
447b06b2e3
共有 9 個文件被更改,包括 428 次插入1 次删除
  1. +115
    -0
      bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java
  2. +9
    -0
      bubble-server/src/main/java/bubble/app/request/RequestProtectorAppDataDriver.java
  3. +30
    -0
      bubble-server/src/main/java/bubble/rule/request/CookieReplacement.java
  4. +18
    -0
      bubble-server/src/main/java/bubble/rule/request/HttpHeaderReplacementFilter.java
  5. +37
    -0
      bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java
  6. +54
    -0
      bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java
  7. +75
    -0
      bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json
  8. +89
    -0
      bubble-server/src/main/resources/models/apps/request/request-icon.svg
  9. +1
    -1
      utils/cobbzilla-utils

+ 115
- 0
bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java 查看文件

@@ -0,0 +1,115 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.app.request;

import bubble.model.account.Account;
import bubble.model.app.AppRule;
import bubble.model.app.BubbleApp;
import bubble.model.app.config.AppConfigDriverBase;
import bubble.rule.request.CookieReplacement;
import bubble.rule.request.RequestProtectorConfig;
import bubble.rule.request.RequestProtectorRuleDriver;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.Set;

import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx;

@Slf4j
public class RequestProtectorAppConfigDriver extends AppConfigDriverBase {

public static final String VIEW_manageCookieReplacements = "manageCookieReplacements";

@Override public Object getView(Account account, BubbleApp app, String view, Map<String, String> params) {
switch (view) {
case VIEW_manageCookieReplacements:
return loadManageCookiesReplacements(account, app);
}
throw notFoundEx(view);
}

private Set<CookieReplacement> loadManageCookiesReplacements(Account account, BubbleApp app) {
final RequestProtectorConfig config = getConfig(account, app);
return config.getCookieReplacements();
}

private RequestProtectorConfig getConfig(Account account, BubbleApp app) {
return getConfig(account, app, RequestProtectorRuleDriver.class, RequestProtectorConfig.class);
}

public static final String ACTION_addCookieReplacement = "addCookieReplacement";
public static final String ACTION_removeCookieReplacement = "removeCookieReplacement";

public static final String PARAM_REGEX = "regex";
public static final String PARAM_REPLACEMENT = "replacement";

@Override public Object takeAppAction(Account account, BubbleApp app, String view, String action,
Map<String, String> params, JsonNode data) {
switch (action) {
case ACTION_addCookieReplacement:
return addCookieReplacement(account, app, data);
}
if (log.isWarnEnabled()) log.warn("takeAppAction: action not found: "+action);
throw notFoundEx(action);
}

private Set<CookieReplacement> addCookieReplacement(Account account, BubbleApp app, JsonNode data) {
final JsonNode regexNode = data.get(PARAM_REGEX);
if (regexNode == null || regexNode.textValue() == null || empty(regexNode.textValue().trim())) {
throw invalidEx("err.requestProtector.cookieRegexRequired");
}
final String regex = regexNode.textValue().trim().toLowerCase();

final JsonNode replacementNode = data.get(PARAM_REPLACEMENT);
final String replacement = replacementNode == null || replacementNode.textValue() == null
? ""
: replacementNode.textValue().trim().toLowerCase();

final RequestProtectorConfig config = getConfig(account, app).addCookieReplacement(regex, replacement);

final AppRule rule = loadRule(account, app);
loadDriver(account, rule, RequestProtectorRuleDriver.class); // validate proper driver
if (log.isDebugEnabled()) {
log.debug("addCookieReplacement: updating rule: " + rule.getName() + ", adding regex: " + regex);
}
ruleDAO.update(rule.setConfigJson(json(config)));

return config.getCookieReplacements();
}

@Override public Object takeItemAction(Account account, BubbleApp app, String view, String action, String id,
Map<String, String> params, JsonNode data) {
switch (action) {
case ACTION_removeCookieReplacement:
return removeCookieReplacement(account, app, id);
}
if (log.isWarnEnabled()) log.warn("takeItemAction: action not found: "+action);
throw notFoundEx(action);
}

private Set<CookieReplacement> removeCookieReplacement(Account account, BubbleApp app, String regex) {
final AppRule rule = loadRule(account, app);
loadDriver(account, rule, RequestProtectorRuleDriver.class); // validate proper driver
final RequestProtectorConfig config = getConfig(account, app);
if (log.isDebugEnabled()) {
log.debug("removeCookieReplacement: removing regex: " + regex + " from config.cookiesReplacements: "
+ config.getCookieReplacements().toString());
}

final RequestProtectorConfig updated = config.removeCookieReplacement(regex);
if (log.isDebugEnabled()) {
log.debug("removeCookieReplacement: updated.cookiesReplacements: "
+ updated.getCookieReplacements().toString());
}
ruleDAO.update(rule.setConfigJson(json(updated)));

return updated.getCookieReplacements();
}
}

+ 9
- 0
bubble-server/src/main/java/bubble/app/request/RequestProtectorAppDataDriver.java 查看文件

@@ -0,0 +1,9 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.app.request;

import bubble.model.app.config.AppDataDriverBase;

public class RequestProtectorAppDataDriver extends AppDataDriverBase {}

+ 30
- 0
bubble-server/src/main/java/bubble/rule/request/CookieReplacement.java 查看文件

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.rule.request;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.experimental.Accessors;

@NoArgsConstructor @Accessors(chain=true)
public class CookieReplacement implements Comparable<CookieReplacement> {

public String getId() { return regex; }
public void setId(String id) {} // noop

@Getter @Setter private String regex;
@Getter @Setter private String replacement;

public CookieReplacement(@NonNull final String regex, @NonNull final String replacement) {
this.regex = regex;
this.replacement = replacement;
}

@Override public int compareTo(@NonNull final CookieReplacement o) {
return getRegex().compareTo(o.getRegex().toLowerCase());
}
}

+ 18
- 0
bubble-server/src/main/java/bubble/rule/request/HttpHeaderReplacementFilter.java 查看文件

@@ -0,0 +1,18 @@
package bubble.rule.request;

import org.cobbzilla.util.io.regex.RegexLimitedReplacementFilter;

import java.util.regex.Pattern;

public class HttpHeaderReplacementFilter extends RegexLimitedReplacementFilter {
private static final String HTTP_HEADER_BORDER_REGEX = "\r?\n\r?\n";

private HttpHeaderReplacementFilter(String regex, int group, String replacement, String stopRegex,
int stopRegexMatchingFlags) {
// should not be used
}

public HttpHeaderReplacementFilter(String regex, String replacement) {
super(regex, 0, replacement, HTTP_HEADER_BORDER_REGEX, Pattern.MULTILINE);
}
}

+ 37
- 0
bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java 查看文件

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.rule.request;

import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.Set;
import java.util.TreeSet;

import static org.cobbzilla.util.daemon.ZillaRuntime.empty;

@Slf4j @Accessors(chain=true)
public class RequestProtectorConfig {

@Getter @Setter private Set<CookieReplacement> cookieReplacements = new TreeSet<>();
public boolean hasCookieReplacements() { return !empty(cookieReplacements); }
public boolean hasCookieReplacementFor(@NonNull final String regex) {
return hasCookieReplacements() && cookieReplacements.stream().anyMatch(r -> r.getRegex().equals(regex));
}

@NonNull public RequestProtectorConfig addCookieReplacement(@NonNull final String regex,
@NonNull final String replacement) {
cookieReplacements.add(new CookieReplacement(regex, replacement));
return this;
}

@NonNull public RequestProtectorConfig removeCookieReplacement(@NonNull final String regex) {
if (hasCookieReplacements()) cookieReplacements.removeIf(r -> r.getRegex().equals(regex));
return this;
}
}

+ 54
- 0
bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java 查看文件

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/
package bubble.rule.request;

import bubble.model.account.Account;
import bubble.model.app.AppMatcher;
import bubble.model.app.AppRule;
import bubble.model.app.BubbleApp;
import bubble.model.device.Device;
import bubble.resources.stream.FilterHttpRequest;
import bubble.rule.AbstractAppRuleDriver;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.input.ReaderInputStream;
import org.cobbzilla.util.io.regex.RegexFilterReader;

import java.io.InputStream;
import java.util.Iterator;

import static org.cobbzilla.util.string.StringUtil.UTF8cs;

@Slf4j
public class RequestProtectorRuleDriver extends AbstractAppRuleDriver {

@Override public <C> Class<C> getConfigClass() { return (Class<C>) RequestProtectorConfig.class; }

@Override public void init(JsonNode config, JsonNode userConfig, BubbleApp app, AppRule rule, AppMatcher matcher,
Account account, Device device) {
super.init(config, userConfig, app, rule, matcher, account, device);

// refresh list
final RequestProtectorConfig ruleConfig = getRuleConfig();
ruleConfig.getCookieReplacements();
}

@Override public InputStream doFilterResponse(FilterHttpRequest filterRequest, InputStream in) {
final RequestProtectorConfig config = getRuleConfig();
if (!config.hasCookieReplacements()) return in;

final Iterator<CookieReplacement> crIterator = config.getCookieReplacements().iterator();
CookieReplacement cr = crIterator.next();
RegexFilterReader reader = new RegexFilterReader(in, new HttpHeaderReplacementFilter(cr.getRegex(),
cr.getReplacement()));
while (crIterator.hasNext()) {
cr = crIterator.next();
reader = new RegexFilterReader(reader, new HttpHeaderReplacementFilter(cr.getRegex(),
cr.getReplacement()));
}

return new ReaderInputStream(reader, UTF8cs);
}
}

+ 75
- 0
bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json 查看文件

@@ -0,0 +1,75 @@
[{
"name": "RequestProtector",
"description": "Change or remove parts of request/response - i.e. remove cross-domain cookies from response",
"url": "https://getbubblenow.com/apps/request",
"template": true,
"enabled": true,
"priority": 1000,
"canPrime": true,
"dataConfig": {
"dataDriver": "bubble.app.request.RequestProtectorAppDataDriver",
"presentation": "none",
"configDriver": "bubble.app.request.RequestProtectorAppConfigDriver",
"configFields": [
{"name": "regex", "truncate": false},
{"name": "replacement", "truncate": false}
],
"configViews": [{
"name": "manageCookieReplacements",
"scope": "app",
"root": "true",
"fields": [ "regex", "replacement" ],
"actions": [
{"name": "removeCookieReplacement", "index": 10},
{
"name": "addCookieReplacement", "scope": "app", "index": 10,
"params": [ "regex", "replacement" ],
"button": "addCookieReplacement"
}
]
}]
},
"children": {
"AppSite": [{
"name": "All_Sites",
"url": "*",
"description": "All websites",
"template": true
}],
"AppRule": [{
"name": "request",
"template": true,
"driver": "RequestProtectorRuleDriver",
"priority": -1000,
"config": { "cookieReplacements": [] }
}],
"AppMessage": [{
"locale": "en_US",
"messages": [
{ "name": "name", "value": "RequestProtector" },
{ "name": "icon", "value": "classpath:models/apps/request/request-icon.svg" },
{ "name": "summary", "value": "Request Protector" },
{
"name": "description",
"value": "Change or remove parts of request/response - i.e. remove cross-domain cookies from response"
},

{ "name": "config.view.manageCookieReplacements", "value": "Manage Cookie Replacements" },
{ "name": "config.field.regex", "value": "RegEx" },
{
"name": "config.field.regex.description",
"value": "Regular expression compared with full set cookie string value"
},
{ "name": "config.field.replacement", "value": "Replacement" },
{
"name": "config.field.replacement.description",
"value": "May use reference from regex as in Java's replaceAll method"
},
{ "name": "config.action.addCookieReplacement", "value": "Add" },
{ "name": "config.action.removeCookieReplacement", "value": "Remove" },

{ "name": "err.requestProtector.cookieRegexRequired", "value": "RegEx field is required" }
]
}]
}
}]

+ 89
- 0
bubble-server/src/main/resources/models/apps/request/request-icon.svg 查看文件

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="80"
height="80"
id="svg10029"
sodipodi:docname="Hazard light icon.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata9">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1680"
inkscape:window-height="998"
id="namedview7"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-page="true"
inkscape:zoom="2.085965"
inkscape:cx="-18.750976"
inkscape:cy="1.4877119"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g10556">
<sodipodi:guide
position="-143,40"
orientation="0,1"
id="guide819"
inkscape:locked="false" />
<inkscape:grid
type="xygrid"
id="grid823" />
<sodipodi:guide
position="40,55"
orientation="1,0"
id="guide4529"
inkscape:locked="false" />
</sodipodi:namedview>
<defs
id="defs10032" />
<g
transform="translate(-19.885022,2.1230082)"
id="g10556">
<path
sodipodi:type="star"
style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-width:10;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4531"
sodipodi:sides="3"
sodipodi:cx="59.885021"
sodipodi:cy="47.876992"
sodipodi:r1="40"
sodipodi:r2="20"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.52359878"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 59.885022,7.8769913 17.320507,29.9999997 17.320508,30.000001 -34.641016,-10e-7 -34.641016,-10e-7 17.320508,-29.999999 z"
inkscape:transform-center-y="-10" />
</g>
</svg>

+ 1
- 1
utils/cobbzilla-utils

@@ -1 +1 @@
Subproject commit ea72ac4a1619c4f5915047650cdd18b8a6202681
Subproject commit cad625431e357e94647a1d99da2efc171740d8e3

Loading…
取消
儲存