@@ -0,0 +1,33 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ | |||||
*/ | |||||
package bubble.app.passthru; | |||||
import bubble.model.account.Account; | |||||
import bubble.model.app.BubbleApp; | |||||
import bubble.model.app.config.AppConfigDriver; | |||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.util.Map; | |||||
@Slf4j | |||||
public class TlsPassthruAppConfigDriver implements AppConfigDriver { | |||||
@Override public Object getView(Account account, BubbleApp app, String view, Map<String, String> params) { | |||||
// todo | |||||
return null; | |||||
} | |||||
@Override public Object takeAppAction(Account account, BubbleApp app, String view, String action, Map<String, String> params, JsonNode data) { | |||||
// todo | |||||
return null; | |||||
} | |||||
@Override public Object takeItemAction(Account account, BubbleApp app, String view, String action, String id, Map<String, String> params, JsonNode data) { | |||||
// todo | |||||
return null; | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ | |||||
*/ | |||||
package bubble.app.passthru; | |||||
import bubble.model.app.config.AppDataDriverBase; | |||||
public class TlsPassthruAppDataDriver extends AppDataDriverBase {} |
@@ -20,7 +20,7 @@ public class BubbleAppDAO extends AccountOwnedTemplateDAO<BubbleApp> { | |||||
@Autowired private AppDataDAO dataDAO; | @Autowired private AppDataDAO dataDAO; | ||||
@Autowired private RuleEngineService ruleEngineService; | @Autowired private RuleEngineService ruleEngineService; | ||||
@Override public Order getDefaultSortOrder() { return NAME_ASC; } | |||||
@Override public Order getDefaultSortOrder() { return PRIORITY_ASC; } | |||||
@Override public BubbleApp postUpdate(BubbleApp app, Object context) { | @Override public BubbleApp postUpdate(BubbleApp app, Object context) { | ||||
ruleEngineService.flushCaches(); | ruleEngineService.flushCaches(); | ||||
@@ -14,6 +14,7 @@ import lombok.NoArgsConstructor; | |||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import org.cobbzilla.util.collection.ArrayUtil; | import org.cobbzilla.util.collection.ArrayUtil; | ||||
import org.cobbzilla.util.collection.HasPriority; | |||||
import org.cobbzilla.wizard.model.Identifiable; | import org.cobbzilla.wizard.model.Identifiable; | ||||
import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity; | import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity; | ||||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | import org.cobbzilla.wizard.model.entityconfig.annotations.*; | ||||
@@ -26,6 +27,7 @@ import javax.persistence.Transient; | |||||
import javax.validation.constraints.Size; | import javax.validation.constraints.Size; | ||||
import static bubble.ApiConstants.*; | import static bubble.ApiConstants.*; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | import static org.cobbzilla.util.json.JsonUtil.json; | ||||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | ||||
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; | ||||
@@ -47,9 +49,9 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; | |||||
@ECIndex(of={"account", "template", "enabled"}), | @ECIndex(of={"account", "template", "enabled"}), | ||||
@ECIndex(of={"template", "enabled"}) | @ECIndex(of={"template", "enabled"}) | ||||
}) | }) | ||||
public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTemplate { | |||||
public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTemplate, HasPriority { | |||||
private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled", "dataConfig"}; | |||||
private static final String[] VALUE_FIELDS = {"url", "description", "template", "enabled", "priority", "dataConfig"}; | |||||
public BubbleApp(Account account, BubbleApp app) { | public BubbleApp(Account account, BubbleApp app) { | ||||
copy(this, app); | copy(this, app); | ||||
@@ -90,9 +92,11 @@ public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTe | |||||
public boolean hasDataConfig () { return getDataConfig() != null; } | public boolean hasDataConfig () { return getDataConfig() != null; } | ||||
private AppDataConfig ensureDefaults(AppDataConfig adc) { | private AppDataConfig ensureDefaults(AppDataConfig adc) { | ||||
for (AppDataField field : adc.getFields()) { | |||||
if (!adc.hasConfigField(field)) { | |||||
adc.setConfigFields(ArrayUtil.append(adc.getConfigFields(), field)); | |||||
if (!empty(adc.getFields())) { | |||||
for (AppDataField field : adc.getFields()) { | |||||
if (!adc.hasConfigField(field)) { | |||||
adc.setConfigFields(ArrayUtil.append(adc.getConfigFields(), field)); | |||||
} | |||||
} | } | ||||
} | } | ||||
return adc; | return adc; | ||||
@@ -114,7 +118,10 @@ public class BubbleApp extends IdentifiableBaseParentEntity implements AccountTe | |||||
@ECIndex @Column(nullable=false) | @ECIndex @Column(nullable=false) | ||||
@Getter @Setter private Boolean enabled = true; | @Getter @Setter private Boolean enabled = true; | ||||
@ECSearchable @ECField(index=90) | |||||
@ECSearchable @ECField(index=90) @Column(nullable=false) | |||||
@ECIndex @Getter @Setter private Integer priority; | |||||
@ECSearchable @ECField(index=100) | |||||
@ECIndex @Getter @Setter private Boolean needsUpdate = false; | @ECIndex @Getter @Setter private Boolean needsUpdate = false; | ||||
} | } |
@@ -93,14 +93,19 @@ public class MessagesResource { | |||||
private Map<String, String> loadMessages(Account caller, String locale, String group, MessageResourceFormat format) throws IOException { | private Map<String, String> loadMessages(Account caller, String locale, String group, MessageResourceFormat format) throws IOException { | ||||
final boolean isAppsGroup = group.equalsIgnoreCase(APPS_MESSAGE_GROUP); | final boolean isAppsGroup = group.equalsIgnoreCase(APPS_MESSAGE_GROUP); | ||||
if (isAppsGroup && caller == null) return Collections.emptyMap(); | |||||
if (isAppsGroup && caller == null) { | |||||
if (log.isDebugEnabled()) log.debug("loadMessages: returning empty app messages for caller=null"); | |||||
return Collections.emptyMap(); | |||||
} | |||||
locale = normalizeLocale(locale); | locale = normalizeLocale(locale); | ||||
final String cacheKey = (isAppsGroup ? caller.getUuid()+"/" : "") + locale + "/" + group + "/" + format; | final String cacheKey = (isAppsGroup ? caller.getUuid()+"/" : "") + locale + "/" + group + "/" + format; | ||||
if (!messageCache.containsKey(cacheKey)) { | if (!messageCache.containsKey(cacheKey)) { | ||||
final Properties props; | final Properties props; | ||||
if (isAppsGroup) { | if (isAppsGroup) { | ||||
if (log.isDebugEnabled()) log.debug("loadMessages: loading app messages for caller="+caller.getName()+", locale="+locale); | |||||
props = appMessageService.loadAppMessages(caller, locale); | props = appMessageService.loadAppMessages(caller, locale); | ||||
if (log.isDebugEnabled()) log.debug("loadMessages: loaded app messages for caller="+caller.getName()+", locale="+locale+", props.size="+props.size()); | |||||
} else { | } else { | ||||
props = new Properties(); | props = new Properties(); | ||||
props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); | props.load(new BufferedReader(new InputStreamReader(loadResourceAsStream(MESSAGE_RESOURCE_BASE + locale + MESSAGE_RESOURCE_PATH + group + "/" + RESOURCE_MESSAGES_PROPS), UTF8cs))); | ||||
@@ -206,6 +206,7 @@ public class FilterHttpResource { | |||||
case abort_ok: return FilterMatchersResponse.ABORT_OK; | case abort_ok: return FilterMatchersResponse.ABORT_OK; | ||||
case abort_not_found: return FilterMatchersResponse.ABORT_NOT_FOUND; | case abort_not_found: return FilterMatchersResponse.ABORT_NOT_FOUND; | ||||
case no_match: break; | case no_match: break; | ||||
case pass_thru: return FilterMatchersResponse.PASS_THRU; | |||||
case match: retainMatchers.put(matcher.getUuid(), matcher); break; | case match: retainMatchers.put(matcher.getUuid(), matcher); break; | ||||
} | } | ||||
} else { | } else { | ||||
@@ -336,12 +337,12 @@ public class FilterHttpResource { | |||||
final FilterMatchDecision decision = matchersResponse.getDecision(); | final FilterMatchDecision decision = matchersResponse.getDecision(); | ||||
if (decision != FilterMatchDecision.match) { | if (decision != FilterMatchDecision.match) { | ||||
switch (decision) { | switch (decision) { | ||||
case no_match: | |||||
if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was not match: "+ decision +", returning passthru"); | |||||
case no_match: case pass_thru: | |||||
if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was no_match/pass_thru (should not have received this): "+ decision +", returning passthru"); | |||||
return passthru(request); | return passthru(request); | ||||
case abort_not_found: | case abort_not_found: | ||||
case abort_ok: | case abort_ok: | ||||
if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was not match: "+ decision +", returning "+matchersResponse.httpStatus()); | |||||
if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was abort: "+ decision +", returning "+matchersResponse.httpStatus()); | |||||
return status(matchersResponse.httpStatus()); | return status(matchersResponse.httpStatus()); | ||||
default: | default: | ||||
if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was unknown: "+ decision +", returning passthru"); | if (log.isWarnEnabled()) log.warn(prefix + "FilterMatchersResponse decision was unknown: "+ decision +", returning passthru"); | ||||
@@ -23,6 +23,7 @@ public class FilterMatchersResponse { | |||||
public static final FilterMatchersResponse NO_MATCHERS = new FilterMatchersResponse().setDecision(FilterMatchDecision.no_match); | public static final FilterMatchersResponse NO_MATCHERS = new FilterMatchersResponse().setDecision(FilterMatchDecision.no_match); | ||||
public static final FilterMatchersResponse ABORT_OK = new FilterMatchersResponse().setDecision(FilterMatchDecision.abort_ok); | public static final FilterMatchersResponse ABORT_OK = new FilterMatchersResponse().setDecision(FilterMatchDecision.abort_ok); | ||||
public static final FilterMatchersResponse ABORT_NOT_FOUND = new FilterMatchersResponse().setDecision(FilterMatchDecision.abort_not_found); | public static final FilterMatchersResponse ABORT_NOT_FOUND = new FilterMatchersResponse().setDecision(FilterMatchDecision.abort_not_found); | ||||
public static final FilterMatchersResponse PASS_THRU = new FilterMatchersResponse().setDecision(FilterMatchDecision.pass_thru); | |||||
@Getter @Setter private FilterMatchersRequest request; | @Getter @Setter private FilterMatchersRequest request; | ||||
public boolean hasRequest () { return request != null; } | public boolean hasRequest () { return request != null; } | ||||
@@ -15,10 +15,11 @@ import static org.cobbzilla.util.http.HttpStatusCodes.OK; | |||||
@AllArgsConstructor | @AllArgsConstructor | ||||
public enum FilterMatchDecision { | public enum FilterMatchDecision { | ||||
no_match (OK), // associated matcher should not be included in request processing | |||||
match (OK), // associated should be included in request processing | |||||
abort_ok (OK), // abort request processing, return empty 200 OK response to client | |||||
abort_not_found (NOT_FOUND); // abort request processing, return empty 404 Not Found response to client | |||||
no_match (OK), // associated matcher should not be included in request processing | |||||
match (OK), // associated should be included in request processing | |||||
abort_ok (OK), // abort request processing, return empty 200 OK response to client | |||||
abort_not_found (NOT_FOUND), // abort request processing, return empty 404 Not Found response to client | |||||
pass_thru (OK); // pass-through TLS request, do not intercept | |||||
@JsonCreator public static FilterMatchDecision fromString (String v) { return enumFromString(FilterMatchDecision.class, v); } | @JsonCreator public static FilterMatchDecision fromString (String v) { return enumFromString(FilterMatchDecision.class, v); } | ||||
@@ -0,0 +1,31 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ | |||||
*/ | |||||
package bubble.rule.passthru; | |||||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||||
import lombok.Getter; | |||||
import lombok.Setter; | |||||
import java.util.Arrays; | |||||
import java.util.HashSet; | |||||
import java.util.Set; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
public class TlsPassthruConfig { | |||||
@Getter @Setter private String[] passthruFqdn; | |||||
@JsonIgnore @Getter(lazy=true) private final Set<String> passthruSet = initPassthruSet(); | |||||
private Set<String> initPassthruSet() { | |||||
final Set<String> set = new HashSet<>(); | |||||
if (!empty(passthruFqdn)) set.addAll(Arrays.asList(passthruFqdn)); | |||||
return set; | |||||
} | |||||
public boolean isPassthru(String fqdn) { return getPassthruSet().contains(fqdn); } | |||||
} |
@@ -0,0 +1,47 @@ | |||||
/** | |||||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||||
* For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ | |||||
*/ | |||||
package bubble.rule.passthru; | |||||
import bubble.model.account.Account; | |||||
import bubble.model.app.AppMatcher; | |||||
import bubble.model.app.AppRule; | |||||
import bubble.model.device.Device; | |||||
import bubble.resources.stream.FilterMatchersRequest; | |||||
import bubble.rule.AbstractAppRuleDriver; | |||||
import bubble.rule.FilterMatchDecision; | |||||
import bubble.service.stream.AppRuleHarness; | |||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.glassfish.grizzly.http.server.Request; | |||||
import org.glassfish.jersey.server.ContainerRequest; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
@Slf4j | |||||
public class TlsPassthruRuleDriver extends AbstractAppRuleDriver { | |||||
private TlsPassthruConfig passthruConfig; | |||||
@Override public void init(JsonNode config, JsonNode userConfig, AppRule rule, AppMatcher matcher, Account account, Device device) { | |||||
super.init(config, userConfig, rule, matcher, account, device); | |||||
passthruConfig = json(json(config), TlsPassthruConfig.class); | |||||
} | |||||
@Override public FilterMatchDecision preprocess(AppRuleHarness ruleHarness, | |||||
FilterMatchersRequest filter, | |||||
Account account, | |||||
Device device, | |||||
Request req, | |||||
ContainerRequest request) { | |||||
final String fqdn = filter.getFqdn(); | |||||
if (passthruConfig.isPassthru(fqdn)) { | |||||
if (log.isDebugEnabled()) log.debug("preprocess: returning pass_thru for fqdn="+fqdn); | |||||
return FilterMatchDecision.pass_thru; | |||||
} | |||||
if (log.isDebugEnabled()) log.debug("preprocess: returning no_match for fqdn="+fqdn); | |||||
return FilterMatchDecision.no_match; | |||||
} | |||||
} |
@@ -19,6 +19,7 @@ import org.springframework.stereotype.Service; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Properties; | import java.util.Properties; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.string.StringUtil.EMPTY; | import static org.cobbzilla.util.string.StringUtil.EMPTY; | ||||
@Service @Slf4j | @Service @Slf4j | ||||
@@ -125,8 +126,10 @@ public class AppMessageService { | |||||
} | } | ||||
// anything from data fields not yet defined, copy as config field name/desc | // anything from data fields not yet defined, copy as config field name/desc | ||||
for (AppDataField field : cfg.getFields()) { | |||||
ensureFieldNameAndDescription(props, cfgKeyPrefix, field.getName()); | |||||
if (!empty(cfg.getFields())) { | |||||
for (AppDataField field : cfg.getFields()) { | |||||
ensureFieldNameAndDescription(props, cfgKeyPrefix, field.getName()); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -55,6 +55,7 @@ | |||||
<logger name="org.cobbzilla.util.io.regex.RegexFilterReader" level="WARN" /> | <logger name="org.cobbzilla.util.io.regex.RegexFilterReader" level="WARN" /> | ||||
<logger name="org.cobbzilla.util.io.multi" level="INFO" /> | <logger name="org.cobbzilla.util.io.multi" level="INFO" /> | ||||
<logger name="bubble.rule.social.block" level="INFO" /> | <logger name="bubble.rule.social.block" level="INFO" /> | ||||
<logger name="bubble.rule.passthru" level="DEBUG" /> | |||||
<!-- <logger name="bubble.service.cloud.StandardNetworkService" level="INFO" />--> | <!-- <logger name="bubble.service.cloud.StandardNetworkService" level="INFO" />--> | ||||
<logger name="bubble.resources.notify" level="WARN" /> | <logger name="bubble.resources.notify" level="WARN" /> | ||||
<logger name="bubble.client" level="WARN" /> | <logger name="bubble.client" level="WARN" /> | ||||
@@ -4,6 +4,7 @@ | |||||
"url": "https://bubblev.com/apps/analytics", | "url": "https://bubblev.com/apps/analytics", | ||||
"template": true, | "template": true, | ||||
"enabled": true, | "enabled": true, | ||||
"priority": 100, | |||||
"dataConfig": { | "dataConfig": { | ||||
"dataDriver": "bubble.app.analytics.TrafficAnalyticsAppDataDriver", | "dataDriver": "bubble.app.analytics.TrafficAnalyticsAppDataDriver", | ||||
"presentation": "app", | "presentation": "app", | ||||
@@ -4,6 +4,7 @@ | |||||
"url": "https://bubblev.com/apps/bblock", | "url": "https://bubblev.com/apps/bblock", | ||||
"template": true, | "template": true, | ||||
"enabled": true, | "enabled": true, | ||||
"priority": 200, | |||||
"dataConfig": { | "dataConfig": { | ||||
"dataDriver": "bubble.app.bblock.BubbleBlockAppDataDriver", | "dataDriver": "bubble.app.bblock.BubbleBlockAppDataDriver", | ||||
"presentation": "app", | "presentation": "app", | ||||
@@ -0,0 +1,75 @@ | |||||
[{ | |||||
"name": "TlsPassthru", | |||||
"description": "Do not perform SSL interception for certificate-pinned domains", | |||||
"url": "https://bubblev.com/apps/passthru", | |||||
"template": true, | |||||
"enabled": true, | |||||
"priority": 1000000, | |||||
"dataConfig": { | |||||
"dataDriver": "bubble.app.passthru.TlsPassthruAppDataDriver", | |||||
"presentation": "app", | |||||
"configDriver": "bubble.app.passthru.TlsPassthruAppConfigDriver", | |||||
"configFields": [ | |||||
{"name": "name"}, | |||||
{"name": "description", "control": "textarea"}, | |||||
{"name": "url", "type": "http_url"}, | |||||
{"name": "tags"}, | |||||
{"name": "tagString"}, | |||||
{"name": "enabled", "type": "flag", "mode": "readOnly"}, | |||||
{"name": "rule"}, | |||||
{"name": "ruleType", "mode": "readOnly"}, | |||||
{"name": "testUrl", "type": "http_url"}, | |||||
{"name": "testUserAgent", "required": false}, | |||||
{"name": "testUrlPrimary", "type": "flag"}, | |||||
{"name": "urlRegex", "required": false}, | |||||
{"name": "userAgentRegex"} | |||||
], | |||||
"configViews": [{ | |||||
"name": "managePassthru", | |||||
"scope": "app", | |||||
"root": "true", | |||||
"fields": ["fqdn"], | |||||
"actions": [ | |||||
{"name": "removeFqdn", "index": 10}, | |||||
{ | |||||
"name": "addFqdn", "scope": "app", "index": 10, | |||||
"params": ["fqdn"], | |||||
"button": "addFqdn" | |||||
} | |||||
] | |||||
}] | |||||
}, | |||||
"children": { | |||||
"AppSite": [{ | |||||
"name": "All_Sites", | |||||
"url": "*", | |||||
"description": "All websites", | |||||
"template": true | |||||
}], | |||||
"AppRule": [{ | |||||
"name": "passthru", | |||||
"template": true, | |||||
"driver": "TlsPassthruRuleDriver", | |||||
"priority": -1000000, | |||||
"config": { | |||||
"passthruFqdn": [] | |||||
} | |||||
}], | |||||
"AppMessage": [{ | |||||
"locale": "en_US", | |||||
"messages": [ | |||||
{"name": "name", "value": "DirectConnect"}, | |||||
{"name": "icon", "value": "classpath:models/apps/passthru/passthru-icon.svg"}, | |||||
{"name": "summary", "value": "Network Bypass"}, | |||||
{"name": "description", "value": "Do not perform SSL interception for certificate-pinned domains"}, | |||||
{"name": "config.view.managePassthru", "value": "Manage Bypass Domains"}, | |||||
{"name": "config.field.fqdn", "value": "Hostname"}, | |||||
{"name": "config.field.fqdn.description", "value": "Bypass traffic interception for this hostname"}, | |||||
{"name": "config.action.addFqdn", "value": "Add New"}, | |||||
{"name": "config.button.addFqdn", "value": "Add"}, | |||||
{"name": "config.action.removeFqdn", "value": "Remove"} | |||||
] | |||||
}] | |||||
} | |||||
}] |
@@ -0,0 +1,14 @@ | |||||
[{ | |||||
"name": "TlsPassthru", | |||||
"children": { | |||||
"AppMatcher": [{ | |||||
"name": "TlsPassthruMatcher", | |||||
"template": true, | |||||
"site": "All_Sites", | |||||
"fqdn": "*", | |||||
"urlRegex": ".*", | |||||
"rule": "passthru", | |||||
"priority": -1000000 | |||||
}] | |||||
} | |||||
}] |
@@ -0,0 +1,105 @@ | |||||
<?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:4.5;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" /> | |||||
<path | |||||
inkscape:transform-center-y="-6.25" | |||||
d="m 59.885022,22.876992 10.825317,18.75 10.825317,18.75 -21.650635,0 -21.650635,-1e-6 10.825318,-18.749999 z" | |||||
inkscape:randomized="0" | |||||
inkscape:rounded="0" | |||||
inkscape:flatsided="false" | |||||
sodipodi:arg2="-0.52359878" | |||||
sodipodi:arg1="-1.5707963" | |||||
sodipodi:r2="12.5" | |||||
sodipodi:r1="25" | |||||
sodipodi:cy="47.876992" | |||||
sodipodi:cx="59.885021" | |||||
sodipodi:sides="3" | |||||
id="path4533" | |||||
style="fill:none;fill-opacity:1;stroke:#ff0000;stroke-width:4.5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" | |||||
sodipodi:type="star" /> | |||||
</g> | |||||
</svg> |
@@ -3,6 +3,8 @@ | |||||
"description": "ShadowBan User Blocker", | "description": "ShadowBan User Blocker", | ||||
"url": "https://bubblev.com/apps/UserBlocker", | "url": "https://bubblev.com/apps/UserBlocker", | ||||
"template": true, | "template": true, | ||||
"enabled": true, | |||||
"priority": 300, | |||||
"dataConfig": { | "dataConfig": { | ||||
"dataDriver": "bubble.app.social.block.UserBlockerAppDataDriver", | "dataDriver": "bubble.app.social.block.UserBlockerAppDataDriver", | ||||
"presentation": "site", | "presentation": "site", | ||||
@@ -1,34 +1,32 @@ | |||||
[ | [ | ||||
{ | |||||
"name": "TlsPassthruRuleDriver", | |||||
"driverClass": "bubble.rule.passthru.TlsPassthruRuleDriver", | |||||
"template": true, | |||||
"userConfig": { "fields": [] } | |||||
}, | |||||
{ | { | ||||
"name": "UserBlockerRuleDriver", | "name": "UserBlockerRuleDriver", | ||||
"driverClass": "bubble.rule.social.block.UserBlockerRuleDriver", | "driverClass": "bubble.rule.social.block.UserBlockerRuleDriver", | ||||
"template": true, | "template": true, | ||||
"userConfig": { | |||||
"fields": [] | |||||
} | |||||
"userConfig": { "fields": [] } | |||||
}, | }, | ||||
{ | { | ||||
"name": "JsUserBlockerRuleDriver", | "name": "JsUserBlockerRuleDriver", | ||||
"driverClass": "bubble.rule.social.block.JsUserBlockerRuleDriver", | "driverClass": "bubble.rule.social.block.JsUserBlockerRuleDriver", | ||||
"template": true, | "template": true, | ||||
"userConfig": { | |||||
"fields": [] | |||||
} | |||||
"userConfig": { "fields": [] } | |||||
}, | }, | ||||
{ | { | ||||
"name": "TrafficAnalyticsRuleDriver", | "name": "TrafficAnalyticsRuleDriver", | ||||
"driverClass": "bubble.rule.analytics.TrafficAnalyticsRuleDriver", | "driverClass": "bubble.rule.analytics.TrafficAnalyticsRuleDriver", | ||||
"template": true, | "template": true, | ||||
"userConfig": { | |||||
"fields": [] | |||||
} | |||||
"userConfig": { "fields": [] } | |||||
}, | }, | ||||
{ | { | ||||
"name": "BubbleBlockRuleDriver", | "name": "BubbleBlockRuleDriver", | ||||
"driverClass": "bubble.rule.bblock.BubbleBlockRuleDriver", | "driverClass": "bubble.rule.bblock.BubbleBlockRuleDriver", | ||||
"template": true, | "template": true, | ||||
"userConfig": { | |||||
"fields": [] | |||||
} | |||||
"userConfig": { "fields": [] } | |||||
} | } | ||||
] | ] |
@@ -1 +1 @@ | |||||
Subproject commit 725052160cf969e5b2f041b0f764710e433feb7e | |||||
Subproject commit 7cd7e0e140da2d048ad3addfac4bceb72ab5e89a |