diff --git a/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppConfigDriver.java b/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppConfigDriver.java index 5b7a2a37..c94fa1ca 100644 --- a/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppConfigDriver.java +++ b/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppConfigDriver.java @@ -7,20 +7,18 @@ package bubble.app.bblock; import bubble.abp.BlockDecision; import bubble.abp.BlockListSource; import bubble.abp.BlockSpec; +import bubble.dao.app.AppDataDAO; +import bubble.dao.app.AppMatcherDAO; +import bubble.dao.app.AppSiteDAO; import bubble.model.account.Account; -import bubble.model.app.AppMatcher; -import bubble.model.app.AppRule; -import bubble.model.app.BubbleApp; -import bubble.model.app.RuleDriver; +import bubble.model.app.*; import bubble.model.app.config.AppConfigDriverBase; import bubble.model.device.Device; -import bubble.rule.bblock.BubbleBlockConfig; -import bubble.rule.bblock.BubbleBlockList; -import bubble.rule.bblock.BubbleBlockRuleDriver; -import bubble.rule.bblock.BubbleUserAgentBlock; +import bubble.rule.bblock.*; import bubble.server.BubbleConfiguration; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.ExpirationMap; import org.cobbzilla.util.string.ValidationRegexes; import org.cobbzilla.wizard.validation.ValidationResult; import org.springframework.beans.factory.annotation.Autowired; @@ -28,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.*; import java.util.stream.Collectors; +import static bubble.rule.bblock.BubbleBlockRuleDriver.fqdnFromKey; import static java.util.Collections.emptySet; +import static java.util.concurrent.TimeUnit.HOURS; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; import static org.cobbzilla.util.http.HttpSchemes.SCHEME_HTTPS; @@ -49,10 +49,27 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { public static final String VIEW_manageList = "manageList"; public static final String VIEW_manageRules = "manageRules"; public static final String VIEW_manageUserAgents = "manageUserAgents"; + public static final String VIEW_manageHideStats = "manageHideStats"; public static final AppMatcher TEST_MATCHER = new AppMatcher(); public static final Device TEST_DEVICE = new Device(); + public static final String PREFIX_APPDATA_HIDE_STATS = "hideStats_"; + public static final String DEFAULT_MATCHER_NAME = "BubbleBlockMatcher"; + public static final String DEFAULT_SITE_NAME = "All_Sites"; @Autowired private BubbleConfiguration configuration; + @Autowired private AppDataDAO dataDAO; + @Autowired private AppMatcherDAO matcherDAO; + @Autowired private AppSiteDAO siteDAO; + + private final Map defaultMatchers = new ExpirationMap<>(4, HOURS.toMillis(1)); + private AppMatcher getDefaultMatcher (Account account, BubbleApp app) { + return defaultMatchers.computeIfAbsent(account, a -> matcherDAO.findByAccountAndAppAndId(account.getUuid(), app.getUuid(), DEFAULT_MATCHER_NAME)); + } + + private final Map defaultSites = new ExpirationMap<>(4, HOURS.toMillis(1)); + private AppSite getDefaultSite (Account account, BubbleApp app) { + return defaultSites.computeIfAbsent(account, a -> siteDAO.findByAccountAndAppAndId(account.getUuid(), app.getUuid(), DEFAULT_SITE_NAME)); + } @Override public Object getView(Account account, BubbleApp app, String view, Map params) { String id = params.get(PARAM_ID); @@ -74,6 +91,9 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { case VIEW_manageUserAgents: return loadUserAgentBlocks(account, app); + + case VIEW_manageHideStats: + return loadHideStats(account, app); } throw notFoundEx(view); } @@ -83,7 +103,8 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { if (list == null) throw notFoundEx(id); if (list.hasAdditionalEntries()) { return Arrays.stream(list.getAdditionalEntries()) - .map(BlockListEntry::additionalRule).collect(Collectors.toCollection(TreeSet::new)); + .map(BlockListEntry::additionalRule) + .collect(Collectors.toCollection(TreeSet::new)); } return emptySet(); } @@ -103,11 +124,9 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { loadDriver(account, rule, BubbleBlockRuleDriver.class); // validate proper driver final BubbleBlockConfig blockConfig = json(rule.getConfigJson(), BubbleBlockConfig.class); - final List blockLists = new ArrayList<>(); - blockLists.addAll( Arrays.stream(blockConfig.getBlockLists()) + return Arrays.stream(blockConfig.getBlockLists()) .map(list -> list.setRule(rule)) - .collect(Collectors.toList()) ); - return blockLists; + .collect(Collectors.toList()); } private BubbleUserAgentBlock[] loadUserAgentBlocks(Account account, BubbleApp app) { @@ -120,6 +139,13 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { return empty(blocks) ? BubbleUserAgentBlock.NO_BLOCKS : blocks; } + private List loadHideStats(Account account, BubbleApp app) { + return dataDAO.findByAccountAndAppAndAndKeyPrefix(account.getUuid(), app.getUuid(), PREFIX_APPDATA_HIDE_STATS) + .stream() + .map(BubbleHideStats::new) + .collect(Collectors.toList()); + } + public static final String ACTION_enableList = "enableList"; public static final String ACTION_disableList = "disableList"; public static final String ACTION_createList = "createList"; @@ -130,6 +156,8 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { public static final String ACTION_createUserAgentBlock = "createUserAgentBlock"; public static final String ACTION_removeUserAgentBlock = "removeUserAgentBlock"; public static final String ACTION_testUrl = "testUrl"; + public static final String ACTION_removeHideStats = "removeHideStats"; + public static final String ACTION_createHideStats = "createHideStats"; public static final String PARAM_URL = "url"; public static final String PARAM_RULE = "rule"; @@ -137,6 +165,7 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { public static final String PARAM_TEST_URL = "testUrl"; public static final String PARAM_TEST_USER_AGENT = "testUserAgent"; public static final String PARAM_TEST_URL_PRIMARY = "testUrlPrimary"; + public static final String PARAM_FQDN = "fqdn"; @Override public Object takeAppAction(Account account, BubbleApp app, @@ -153,6 +182,8 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { return testUrl(account, app, data); case ACTION_createUserAgentBlock: return createUserAgentBlock(account, app, params, data); + case ACTION_createHideStats: + return createHideStats(account, app, params, data); } throw notFoundEx(action); } @@ -286,6 +317,26 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { return blockConfig.getUserAgentBlocks(); } + + private BubbleHideStats createHideStats(Account account, BubbleApp app, Map params, JsonNode data) { + final JsonNode fqdnNode = data.get(PARAM_FQDN); + if (fqdnNode == null) throw invalidEx("err.fqdn.required"); + final String fqdn = fqdnNode.textValue(); + final BubbleHideStats showStats = new BubbleHideStats(fqdn); + final AppData appData = dataDAO.set(showStats.toAppData(account, app, getDefaultMatcher(account, app), getDefaultSite(account, app))); + showStats.setId(appData.getUuid()); + return showStats; + } + + private List removeHideStats(Account account, BubbleApp app, String uuid) { + final AppData appData = dataDAO.findByAccountAndId(account.getUuid(), uuid); + if (appData != null) { + final String fqdn = fqdnFromKey(appData.getKey()); // sanity check that key is in the right format + dataDAO.delete(appData.getUuid()); + } + return loadHideStats(account, app); + } + @Override public Object takeItemAction(Account account, BubbleApp app, String view, @@ -329,6 +380,9 @@ public class BubbleBlockAppConfigDriver extends AppConfigDriverBase { case ACTION_removeUserAgentBlock: return removeUserAgentBlock(account, app, id); + + case ACTION_removeHideStats: + return removeHideStats(account, app, id); } throw notFoundEx(action); diff --git a/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppDataDriver.java b/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppDataDriver.java index de1ff4ce..77b13fd8 100644 --- a/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppDataDriver.java +++ b/bubble-server/src/main/java/bubble/app/bblock/BubbleBlockAppDataDriver.java @@ -5,5 +5,7 @@ package bubble.app.bblock; import bubble.app.analytics.TrafficAnalyticsAppDataDriver; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class BubbleBlockAppDataDriver extends TrafficAnalyticsAppDataDriver {} diff --git a/bubble-server/src/main/java/bubble/dao/app/AppDataDAO.java b/bubble-server/src/main/java/bubble/dao/app/AppDataDAO.java index 50fa0eee..ba2b8900 100644 --- a/bubble-server/src/main/java/bubble/dao/app/AppDataDAO.java +++ b/bubble-server/src/main/java/bubble/dao/app/AppDataDAO.java @@ -15,7 +15,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.hibernate.criterion.Restrictions.and; @@ -50,6 +53,10 @@ public class AppDataDAO extends AppTemplateEntityDAO { return filterExpired(findByFields("account", account, "app", app, "key", key)); } + public List findByAccountAndAppAndAndKeyPrefix(String account, String app, String keyPrefix) { + return filterExpired(findByFieldsEqualAndFieldLike("account", account, "app", app, "key", keyPrefix+"%")); + } + public List findEnabledByAccountAndAppAndSite(String account, String app, String site) { return filterExpired(findByFields("account", account, "app", app, "site", site, "enabled", true)); } @@ -68,6 +75,9 @@ public class AppDataDAO extends AppTemplateEntityDAO { return data; } + private final Map> dataSetCallbacks = new HashMap<>(); + public void registerCallback(String appUuid, Function callback) { dataSetCallbacks.put(appUuid, callback); } + public AppData set(AppData data) { final AppData found = findByAppAndSiteAndKey(data.getApp(), data.getSite(), data.getKey()); if (data.getAccount() == null) { @@ -76,11 +86,16 @@ public class AppDataDAO extends AppTemplateEntityDAO { if (app == null) return die("set: App not found: "+data.getApp()); data.setAccount(app.getAccount()); } - if (found == null) return create(data); + + final Function callback = dataSetCallbacks.get(data.getApp()); + log.info("set: found callback="+callback+" for app: "+data.getApp()); + + if (found == null) return callback == null ? create(data) : callback.apply(create(data).setCreating(true)); if (!found.getSite().equals(data.getSite())) return die("set: matcher mismatch: found ("+found.getUuid()+"/"+found.getKey()+") with site "+found.getSite()+", update has site: "+data.getSite()); found.update(data); - return update(found); + final AppData updated = update(found); + return callback != null ? callback.apply(updated) : updated; } public List findByExample(Account account, AppData basis) { @@ -111,4 +126,16 @@ public class AppDataDAO extends AppTemplateEntityDAO { log.info("deleteApp: deleted "+count+" AppData records for app "+uuid); } + @Override public void delete(String uuid) { + final AppData data = findByUuid(uuid); + if (data == null) return; + final BubbleApp app = appDAO.findByUuid(data.getApp()); + if (app != null) { + final Function callback = dataSetCallbacks.get(app.getUuid()); + if (callback != null) { + callback.apply(data.setDeleting(true)); + } + } + super.delete(uuid); + } } diff --git a/bubble-server/src/main/java/bubble/model/app/AppData.java b/bubble-server/src/main/java/bubble/model/app/AppData.java index 32b63d6e..4dbb7f18 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppData.java +++ b/bubble-server/src/main/java/bubble/model/app/AppData.java @@ -7,6 +7,7 @@ package bubble.model.app; import bubble.model.account.Account; import bubble.model.device.Device; import bubble.rule.RuleConfig; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.NoArgsConstructor; @@ -27,6 +28,7 @@ import javax.persistence.Transient; import javax.validation.constraints.Size; import static bubble.ApiConstants.EP_DATA; +import static org.cobbzilla.util.daemon.ZillaRuntime.bool; import static org.cobbzilla.util.daemon.ZillaRuntime.now; import static org.cobbzilla.util.reflect.ReflectionUtil.copy; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; @@ -156,4 +158,10 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { @JsonProperty @Override public long getCtime () { return super.getCtime(); } @JsonProperty @Override public long getMtime () { return super.getMtime(); } + @Transient @JsonIgnore @Getter @Setter private Boolean creating; + public boolean creating () { return bool(creating); } + + @Transient @JsonIgnore @Getter @Setter private Boolean deleting; + public boolean deleting () { return bool(deleting); } + } diff --git a/bubble-server/src/main/java/bubble/model/app/HasAppDataCallback.java b/bubble-server/src/main/java/bubble/model/app/HasAppDataCallback.java new file mode 100644 index 00000000..0f212691 --- /dev/null +++ b/bubble-server/src/main/java/bubble/model/app/HasAppDataCallback.java @@ -0,0 +1,14 @@ +package bubble.model.app; + +import bubble.model.account.Account; +import bubble.server.BubbleConfiguration; + +import java.util.function.Function; + +public interface HasAppDataCallback { + + void prime(Account account, BubbleApp app, BubbleConfiguration configuration); + + Function createCallback(Account account, BubbleApp app, BubbleConfiguration configuration); + +} diff --git a/bubble-server/src/main/java/bubble/model/app/config/AppConfigView.java b/bubble-server/src/main/java/bubble/model/app/config/AppConfigView.java index 5a48e94e..70b8c955 100644 --- a/bubble-server/src/main/java/bubble/model/app/config/AppConfigView.java +++ b/bubble-server/src/main/java/bubble/model/app/config/AppConfigView.java @@ -13,6 +13,7 @@ public class AppConfigView { @Getter @Setter private String name; @Getter @Setter private AppConfigScope scope; + @Getter @Setter private String when; @Getter @Setter private Boolean root = false; public boolean root() { return root != null && root; } diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java index 2d4f67f3..eb7ae626 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java @@ -178,7 +178,7 @@ public class FilterHttpResource { } if (isLocalIp) { - final boolean showStats = showStats(accountUuid); + final boolean showStats = showStats(accountUuid, connCheckRequest.getAddr(), connCheckRequest.getFqdns()); final DeviceSecurityLevel secLevel = device.getSecurityLevel(); if (showStats && secLevel.supportsRequestModification()) { // allow it for now @@ -236,7 +236,20 @@ public class FilterHttpResource { @Getter(lazy=true) private final BubbleNode thisNode = selfNodeService.getThisNode(); @Getter(lazy=true) private final BubbleNetwork thisNetwork = selfNodeService.getThisNetwork(); - public boolean showStats(String accountUuid) { return deviceIdService.doShowBlockStats(accountUuid); } + public boolean showStats(String accountUuid, String ip, String[] fqdns) { + if (!deviceIdService.doShowBlockStats(accountUuid)) return false; + for (String fqdn : fqdns) { + final Boolean show = deviceIdService.doShowBlockStatsForIpAndFqdn(ip, fqdn); + if (show != null) return show; + } + return true; + } + + public boolean showStats(String accountUuid, String ip, String fqdn) { + if (!deviceIdService.doShowBlockStats(accountUuid)) return false; + final Boolean show = deviceIdService.doShowBlockStatsForIpAndFqdn(ip, fqdn); + return show == null || show; + } @POST @Path(EP_MATCHERS+"/{requestId}") @Consumes(APPLICATION_JSON) @@ -277,7 +290,7 @@ public class FilterHttpResource { // if this is for a local ip, it's an automatic block // legitimate local requests would have been passthru and never reached here final boolean isLocalIp = isForLocalIp(filterRequest); - final boolean showStats = showStats(device.getAccount()); + final boolean showStats = showStats(device.getAccount(), filterRequest.getClientAddr(), filterRequest.getFqdn()); if (isLocalIp) { if (filterRequest.isBrowser() && showStats) { blockStats.record(filterRequest, FilterMatchDecision.abort_not_found); diff --git a/bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java b/bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java index e5c42c2a..0a5ff0f3 100644 --- a/bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java +++ b/bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java @@ -165,17 +165,19 @@ public abstract class AbstractAppRuleDriver implements AppRuleDriver { + getBubbleJs(filterRequest, filterCtx, bubbleJsTemplate, defaultSiteTemplate, siteJsInsertionVar, showIcon) + getScriptClose(); + final String prefix = getClass().getSimpleName()+".filterInsertJs("+filterRequest.getUrl()+"): "; + // Do any alternates apply? List alternates = null; if (modConfig.hasAlternateRegexReplacements()) { final FilterMatchersRequest request = filterRequest.getMatchersResponse().getRequest(); for (BubbleAlternateRegexReplacement alt : modConfig.getAlternateRegexReplacements()) { if (alt.matches(request.getFqdn())) { - if (log.isDebugEnabled()) log.debug("filterInsertJs: including alternate filter: "+alt); + if (log.isDebugEnabled()) log.debug(prefix + "including alternate filter: " +alt); if (alternates == null) alternates = new ArrayList<>(); alternates.add(alt); } else { - if (log.isDebugEnabled()) log.debug("filterInsertJs: NOT including alternate filter: "+alt); + if (log.isDebugEnabled()) log.debug(prefix + "NOT including alternate filter: " +alt); } } } @@ -184,20 +186,20 @@ public abstract class AbstractAppRuleDriver implements AppRuleDriver { RegexFilterReader reader; if (alternates != null) { final BubbleAlternateRegexReplacement firstAlt = alternates.get(0); - if (log.isInfoEnabled()) log.info("filterInsertJs: using alternate filter (0): "+firstAlt); + if (log.isInfoEnabled()) log.info(prefix + "using alternate filter (0): " +firstAlt); reader = new RegexFilterReader(new InputStreamReader(in), firstAlt.regexFilter(filterRequest, replacement)) .setName(filterNamePrefix + "(alt0: "+firstAlt.getFqdnMatch()+") " + firstAlt.getInsertionRegex()) .setMaxMatches(1); for (int i=1; i blockList = new AtomicReference<>(new BlockList()); private BlockList getBlockList() { return blockList.get(); } @@ -60,7 +66,11 @@ public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements private final static Map blockListCache = new ConcurrentHashMap<>(); - public boolean showStats() { return deviceService.doShowBlockStats(account.getUuid()); } + public boolean showStats(String ip, String fqdn) { + if (!deviceService.doShowBlockStats(account.getUuid())) return false; + final Boolean show = deviceService.doShowBlockStatsForIpAndFqdn(ip, fqdn); + return show == null || show; + } @Override public Class getConfigClass() { return (Class) BubbleBlockConfig.class; } @@ -68,7 +78,8 @@ public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements @Override public boolean couldModify(FilterHttpRequest request) { final BubbleBlockConfig config = getRuleConfig(); - return request.isHtml() && request.isBrowser() && (config.inPageBlocks() || showStats()); + final FilterMatchersRequest req = request.getMatchersResponse().getRequest(); + return request.isHtml() && request.isBrowser() && (config.inPageBlocks() || showStats(req.getClientAddr(), req.getFqdn())); } @Override public void init(JsonNode config, @@ -246,7 +257,7 @@ public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements return refererDecision; } } - if (showStats()) { + if (showStats(filter.getClientAddr(), filter.getFqdn())) { if (log.isInfoEnabled()) log.info(prefix+"decision was "+decisionType+" but showStats=true, returning match"); else if (extraLog) log.error(prefix+"decision was "+decisionType+" but showStats=true, returning match"); return FilterMatchDecision.match; @@ -355,7 +366,7 @@ public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements return in; } - final boolean showStats = showStats(); + final boolean showStats = showStats(request.getClientAddr(), request.getFqdn()); if (!bubbleBlockConfig.inPageBlocks() && !showStats) { if (log.isInfoEnabled()) log.info(prefix + "SEND: both inPageBlocks and showStats are false, returning as-is"); return in; @@ -398,4 +409,42 @@ public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements return ctx; } + public static String fqdnFromKey(String key) { + if (!key.startsWith(PREFIX_APPDATA_HIDE_STATS)) die("expected key to start with '"+ PREFIX_APPDATA_HIDE_STATS +"', was: "+ key); + return key.substring(PREFIX_APPDATA_HIDE_STATS.length()); + } + + @Override public void prime(Account account, BubbleApp app, BubbleConfiguration configuration) { + final DeviceIdService deviceIdService = configuration.getBean(DeviceIdService.class); + final AppDataDAO dataDAO = configuration.getBean(AppDataDAO.class); + log.info("priming app="+app.getName()); + dataDAO.findByAccountAndAppAndAndKeyPrefix(account.getUuid(), app.getUuid(), PREFIX_APPDATA_HIDE_STATS) + .forEach(data -> deviceIdService.setBlockStatsForFqdn(account, fqdnFromKey(data.getKey()), false)); + } + + @Override public Function createCallback(Account account, + BubbleApp app, + BubbleConfiguration configuration) { + return data -> { + final String prefix = "createCallbackB("+data.getKey()+"="+data.getData()+"): "; + log.info(prefix+"starting"); + if (data.getKey().startsWith(PREFIX_APPDATA_HIDE_STATS)) { + final DeviceIdService deviceIdService = configuration.getBean(DeviceIdService.class); + final String fqdn = fqdnFromKey(data.getKey()); + if (validateRegexMatches(HOST_PATTERN, fqdn)) { + if (data.deleting()) { + log.info(prefix+"unsetting fqdn: "+fqdn); + deviceIdService.unsetBlockStatsForFqdn(account, fqdn); + } else { + log.info(prefix+"setting fqdn: "+fqdn); + deviceIdService.setBlockStatsForFqdn(account, fqdn, false); + } + } else { + throw invalidEx("err.fqdn.invalid", "not a valid FQDN: "+fqdn, fqdn); + } + } + return data; + }; + } + } diff --git a/bubble-server/src/main/java/bubble/rule/bblock/BubbleHideStats.java b/bubble-server/src/main/java/bubble/rule/bblock/BubbleHideStats.java new file mode 100644 index 00000000..071e7caf --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/BubbleHideStats.java @@ -0,0 +1,42 @@ +package bubble.rule.bblock; + +import bubble.model.account.Account; +import bubble.model.app.AppData; +import bubble.model.app.AppMatcher; +import bubble.model.app.AppSite; +import bubble.model.app.BubbleApp; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; + +import static bubble.app.bblock.BubbleBlockAppConfigDriver.PREFIX_APPDATA_HIDE_STATS; +import static bubble.rule.bblock.BubbleBlockRuleDriver.fqdnFromKey; + +@NoArgsConstructor @Accessors(chain=true) +public class BubbleHideStats { + + @Getter @Setter private String id; + @Getter @Setter private String fqdn; + + public BubbleHideStats(String fqdn) { + setFqdn(fqdn); + } + + public BubbleHideStats(AppData datum) { + final String key = datum.getKey(); + setId(datum.getUuid()); + setFqdn(fqdnFromKey(key)); + } + + public AppData toAppData(Account account, BubbleApp app, AppMatcher matcher, AppSite site) { + return new AppData() + .setAccount(account.getUuid()) + .setApp(app.getUuid()) + .setMatcher(matcher.getUuid()) + .setSite(site.getUuid()) + .setKey(PREFIX_APPDATA_HIDE_STATS +fqdn) + .setData("true"); + } + +} diff --git a/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java b/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java index 8fc254b3..ebeacae3 100644 --- a/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java +++ b/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java @@ -21,6 +21,9 @@ public interface DeviceIdService { void initBlockStats (Account account); default boolean doShowBlockStats(String accountUuid) { return false; } + default Boolean doShowBlockStatsForIpAndFqdn(String ip, String fqdn) { return false; } + default void setBlockStatsForFqdn (Account account, String fqdn, boolean value) {} + default void unsetBlockStatsForFqdn (Account account, String fqdn) {} DeviceStatus getDeviceStatus(String deviceUuid); DeviceStatus getLiveDeviceStatus(String deviceUuid); diff --git a/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java b/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java index e568750e..c3270df8 100644 --- a/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java +++ b/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java @@ -12,6 +12,7 @@ import bubble.model.app.AppSite; import bubble.model.device.Device; import bubble.model.device.DeviceStatus; import bubble.server.BubbleConfiguration; +import bubble.service.stream.StandardRuleEngineService; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.util.collection.ExpirationMap; import org.cobbzilla.util.collection.SingletonList; @@ -68,6 +69,7 @@ public class StandardDeviceIdService implements DeviceIdService { @Autowired private RedisService redis; @Autowired private GeoService geoService; @Autowired private AppSiteDAO siteDAO; + @Autowired private StandardRuleEngineService ruleEngine; @Autowired private BubbleConfiguration configuration; private final Map deviceCache = new ExpirationMap<>(MINUTES.toMillis(10)); @@ -159,7 +161,7 @@ public class StandardDeviceIdService implements DeviceIdService { } } - public void initBlockStats (Account account) { + @Override public void initBlockStats (Account account) { final boolean showBlockStats = configuration.showBlockStatsSupported() && account.showBlockStats(); redis.set_plaintext(REDIS_KEY_ACCOUNT_SHOW_BLOCK_STATS+account.getUuid(), Boolean.toString(showBlockStats)); redis.del_matching_withPrefix(REDIS_KEY_CHUNK_FILTER_PASS+"*"); @@ -172,11 +174,17 @@ public class StandardDeviceIdService implements DeviceIdService { } } - public boolean doShowBlockStats(String accountUuid) { + @Override public boolean doShowBlockStats(String accountUuid) { return configuration.showBlockStatsSupported() && Boolean.parseBoolean(redis.get_plaintext(REDIS_KEY_ACCOUNT_SHOW_BLOCK_STATS + accountUuid)); } + @Override public Boolean doShowBlockStatsForIpAndFqdn(String ip, String fqdn) { + if (!configuration.showBlockStatsSupported()) return false; + final String val = redis.get_plaintext(REDIS_KEY_DEVICE_SHOW_BLOCK_STATS + ip + ":" + fqdn); + return val == null ? null : Boolean.parseBoolean(val); + } + public void showBlockStats (Device device) { final Set configuredIps = NetworkUtil.configuredIps(); final String privateIp = configuredIps.stream() @@ -200,6 +208,24 @@ public class StandardDeviceIdService implements DeviceIdService { } } + @Override public void setBlockStatsForFqdn(Account account, String fqdn, boolean value) { + for (Device device : deviceDAO.findByAccount(account.getUuid())) { + for (String ip : findIpsByDevice(device.getUuid())) { + redis.set_plaintext(REDIS_KEY_DEVICE_SHOW_BLOCK_STATS + ip + ":" + fqdn, String.valueOf(value)); + } + } + ruleEngine.flushMatchers(); + } + + @Override public void unsetBlockStatsForFqdn(Account account, String fqdn) { + for (Device device : deviceDAO.findByAccount(account.getUuid())) { + for (String ip : findIpsByDevice(device.getUuid())) { + redis.del_withPrefix(REDIS_KEY_DEVICE_SHOW_BLOCK_STATS + ip + ":" + fqdn); + } + } + ruleEngine.flushMatchers(); + } + private final ExpirationMap deviceStatusCache = new ExpirationMap<>(MINUTES.toMillis(2)); @Override public DeviceStatus getDeviceStatus(String deviceUuid) { diff --git a/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java b/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java index 392e3e45..2b48e588 100644 --- a/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java +++ b/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java @@ -5,16 +5,10 @@ package bubble.service.stream; import bubble.dao.account.AccountDAO; -import bubble.dao.app.AppMatcherDAO; -import bubble.dao.app.AppRuleDAO; -import bubble.dao.app.BubbleAppDAO; -import bubble.dao.app.RuleDriverDAO; +import bubble.dao.app.*; import bubble.dao.device.DeviceDAO; import bubble.model.account.Account; -import bubble.model.app.AppMatcher; -import bubble.model.app.AppRule; -import bubble.model.app.BubbleApp; -import bubble.model.app.RuleDriver; +import bubble.model.app.*; import bubble.model.cloud.AnsibleInstallType; import bubble.model.cloud.BubbleNetwork; import bubble.model.device.Device; @@ -45,6 +39,7 @@ public class StandardAppPrimerService implements AppPrimerService { @Autowired private AppMatcherDAO matcherDAO; @Autowired private AppRuleDAO ruleDAO; @Autowired private RuleDriverDAO driverDAO; + @Autowired private AppDataDAO dataDAO; @Autowired private RedisService redis; @Autowired private BubbleConfiguration configuration; @@ -129,6 +124,7 @@ public class StandardAppPrimerService implements AppPrimerService { .collect(Collectors.toList()) : new SingletonList<>(singleApp); for (BubbleApp app : appsToPrime) { + log.info("_prime: priming app: "+app.getUuid()+"/"+app.getName()); final List rules = ruleDAO.findByAccountAndApp(account.getUuid(), app.getUuid()); final List matchers = matcherDAO.findByAccountAndApp(account.getUuid(), app.getUuid()); for (AppRule rule : rules) { @@ -137,6 +133,16 @@ public class StandardAppPrimerService implements AppPrimerService { log.warn("_prime: driver not found for app/rule " + app.getName() + "/" + rule.getName() + ": " + rule.getDriver()); continue; } + // handle AppData callback registration with a basic driver + final AppRuleDriver cbDriver = driver.getDriver(); + if (cbDriver instanceof HasAppDataCallback) { + log.info("_prime: AppRuleDriver ("+cbDriver.getClass().getSimpleName()+") implements HasAppDataCallback, registering: "+app.getUuid()+"/"+app.getName()); + final HasAppDataCallback dataCallback = (HasAppDataCallback) cbDriver; + dataCallback.prime(account, app, configuration); + dataDAO.registerCallback(app.getUuid(), dataCallback.createCallback(account, app, configuration)); + } else { + log.info("_prime: AppRuleDriver ("+cbDriver.getClass().getSimpleName()+") does NOT implement HasAppDataCallback, NOT registering: "+app.getUuid()+"/"+app.getName()); + } for (Device device : devices) { final Set rejectDomains = new HashSet<>(); final Set blockDomains = new HashSet<>(); diff --git a/bubble-server/src/main/java/bubble/service/stream/StandardRuleEngineService.java b/bubble-server/src/main/java/bubble/service/stream/StandardRuleEngineService.java index 56222f27..160ce1ab 100644 --- a/bubble-server/src/main/java/bubble/service/stream/StandardRuleEngineService.java +++ b/bubble-server/src/main/java/bubble/service/stream/StandardRuleEngineService.java @@ -64,8 +64,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; import static org.apache.http.HttpHeaders.CONTENT_TYPE; import static org.apache.http.HttpHeaders.TRANSFER_ENCODING; -import static org.cobbzilla.util.daemon.ZillaRuntime.empty; -import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.http.HttpStatusCodes.OK; import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; import static org.cobbzilla.util.json.JsonUtil.json; @@ -197,7 +196,9 @@ public class StandardRuleEngineService implements RuleEngineService { public void enableCacheFlushing () { cachedFlushingEnabled.set(true); } public void disableCacheFlushing () { cachedFlushingEnabled.set(false); } - public Map flushCaches() { + public Map flushCaches() { return flushCaches(true); } + + public Map flushCaches(boolean prime) { if (!cachedFlushingEnabled.get()) { if (log.isDebugEnabled()) log.debug("flushCaches: flushing disabled"); return Collections.emptyMap(); @@ -206,9 +207,7 @@ public class StandardRuleEngineService implements RuleEngineService { ruleCache.clear(); if (log.isDebugEnabled()) log.debug("flushCaches: flushed "+ruleEngineCacheSize+" ruleCache entries"); - final RedisService matchersCache = getMatchersCache(); - final Long matcherCount = matchersCache.del_matching(ALL_KEYS); - if (log.isDebugEnabled()) log.debug("flushCaches: flushed "+matcherCount+" matchersCache entries"); + final Long matcherCount = flushMatchers(); final Long connCheckDeletions = redis.del_matching("bubble_conn_check_*"); if (log.isDebugEnabled()) log.debug("flushCaches: removed "+connCheckDeletions+" conn_check cache entries"); @@ -219,11 +218,18 @@ public class StandardRuleEngineService implements RuleEngineService { {"ruleEngineCache", ruleEngineCacheSize} }); if (log.isInfoEnabled()) log.info("flushCaches: flushed: "+json(flushStatus, COMPACT_MAPPER)); - appPrimerService.primeApps(); + if (prime) appPrimerService.primeApps(); return flushStatus; } } + public Long flushMatchers() { + final RedisService matchersCache = getMatchersCache(); + final Long matcherCount = matchersCache.del_matching(ALL_KEYS); + if (log.isDebugEnabled()) log.debug("flushCaches: flushed "+matcherCount+" matchersCache entries"); + return matcherCount; + } + private List initRules(FilterHttpRequest filterRequest) { return initRules(filterRequest.getAccount(), filterRequest.getDevice(), filterRequest.getMatchers()); } diff --git a/bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs b/bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs index 865d3da5..7a3f53dd 100644 --- a/bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs @@ -34,6 +34,28 @@ }); } +{{JS_PREFIX}}_app_title_span = function (defaultName) { + const appTitleSpan = document.createElement('span'); + const appImage = document.createElement('img'); + appImage.src = {{JS_PREFIX}}_asset_img_url('icon'); + appImage.width = 16; + const appTitle = document.createElement('strong'); + const appName = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_appName', defaultName); + appTitle.appendChild(document.createTextNode(appName)); + appTitleSpan.appendChild(appImage); + appTitleSpan.appendChild(appTitle); + return appTitleSpan; +} + +function {{JS_PREFIX}}_create_button(labelKey, labelDefault, onclick, labelFormat) { + const label = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, labelKey, labelDefault); + const btn = document.createElement('button'); + btn.style.fontSize = 'x-small'; + btn.addEventListener('click', onclick); + btn.appendChild(document.createTextNode(labelFormat ? labelFormat(label) : label)); + return btn; +} + if (typeof {{PAGE_PREFIX}}_icon_status === 'undefined') { let {{PAGE_PREFIX}}_doc_ready = false; @@ -129,17 +151,19 @@ if (typeof {{PAGE_PREFIX}}_icon_status === 'undefined') { } for (let i=0; i<{{PAGE_PREFIX}}_icon_status.length; i++) { const iconSpecs = {{PAGE_PREFIX}}_icon_status[i]; - let br = document.createElement('br'); - let link = document.createElement('a'); + const br = document.createElement('br'); + const link = document.createElement('a'); if (typeof iconSpecs.link === 'function') { link.onclick = function (ev) { iconSpecs.link(ev); return false; } } else { link.href = '{{{BUBBLE_HOME}}}/app/' + iconSpecs.app + '/' + iconSpecs.link; } - let img = document.createElement('img'); + const screenWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; + const img = document.createElement('img'); img.id = {{PAGE_PREFIX}}_getAppIconImgId(iconSpecs); img.src = {{PAGE_PREFIX}}_getAppIconImgSrc(iconSpecs); - img.width = 48; + {{PAGE_PREFIX}}_log('screenWidth='+screenWidth); + img.width = Math.min(128, screenWidth/8); link.appendChild(img); bubbleControlDiv.appendChild(br); bubbleControlDiv.appendChild(link); diff --git a/bubble-server/src/main/resources/bubble/rule/bblock/BubbleBlockRuleDriver_stats.js.hbs b/bubble-server/src/main/resources/bubble/rule/bblock/BubbleBlockRuleDriver_stats.js.hbs index 0bb1766d..50ed20e2 100644 --- a/bubble-server/src/main/resources/bubble/rule/bblock/BubbleBlockRuleDriver_stats.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/bblock/BubbleBlockRuleDriver_stats.js.hbs @@ -4,98 +4,46 @@ let {{JS_PREFIX}}_app_details = false; let {{JS_PREFIX}}_last_stats = null; let {{JS_PREFIX}}_app_stats_last_change = 0; const {{JS_PREFIX}}_app_stats_timeout = 35000; -const {{PAGE_PREFIX}}_extra_blocks = {}; -const {{PAGE_PREFIX}}_add_extra_block = function(name) { - if (!(name in {{PAGE_PREFIX}}_extra_blocks)) { - {{PAGE_PREFIX}}_extra_blocks[name] = 1; - } else { - {{PAGE_PREFIX}}_extra_blocks[name] = {{PAGE_PREFIX}}_extra_blocks[name] + 1; - } -} - -const {{PAGE_PREFIX}}_add_extra_allow = function() { - if (!('_' in {{PAGE_PREFIX}}_extra_blocks)) { - {{PAGE_PREFIX}}_extra_blocks['_'] = 1; - } else { - {{PAGE_PREFIX}}_extra_blocks['_'] = {{PAGE_PREFIX}}_extra_blocks['_'] + 1; - } -} - -const {{PAGE_PREFIX}}_remove_extra_allow = function() { - if (!('_' in {{PAGE_PREFIX}}_extra_blocks)) { - {{PAGE_PREFIX}}_extra_blocks['_'] = 0; - } else { - {{PAGE_PREFIX}}_extra_blocks['_'] = {{PAGE_PREFIX}}_extra_blocks['_'] - 1; +function {{JS_PREFIX}}_hide_app_details() { + const detailsDivId = '{{JS_PREFIX}}_detailsDiv'; + const detailsDiv = document.getElementById(detailsDivId); + {{JS_PREFIX}}_app_details = false; + if (detailsDiv != null) { + detailsDiv.style.display = 'none'; + while (detailsDiv.firstChild) { + detailsDiv.removeChild(detailsDiv.lastChild); + } } } -function {{JS_PREFIX}}_toggle_app_details(ev) { +function {{JS_PREFIX}}_show_app_details(ev) { const detailsDivId = '{{JS_PREFIX}}_detailsDiv'; let detailsDiv = document.getElementById(detailsDivId); - if ({{JS_PREFIX}}_app_details) { - {{JS_PREFIX}}_app_details = false; - if (detailsDiv != null) { - detailsDiv.style.display = 'none'; + {{JS_PREFIX}}_app_details = true; + {{JS_PREFIX}}_app_refresh(function () { + if ({{JS_PREFIX}}_last_stats != null) { + if (detailsDiv === null) { + detailsDiv = {{PAGE_PREFIX}}_createDetailsDiv(detailsDivId); + document.getElementsByTagName('body')[0].appendChild(detailsDiv); + } while (detailsDiv.firstChild) { detailsDiv.removeChild(detailsDiv.lastChild); } - } - } else { - const extraKeys = Object.keys({{PAGE_PREFIX}}_extra_blocks); - if (extraKeys.length === 0 - && ({{JS_PREFIX}}_last_stats === null || {{JS_PREFIX}}_last_stats.blocks.length === 0)) { - return; - } - {{JS_PREFIX}}_app_details = true; - {{JS_PREFIX}}_app_refresh(function () { - if ({{JS_PREFIX}}_last_stats != null) { - if (detailsDiv === null) { - detailsDiv = {{PAGE_PREFIX}}_createDetailsDiv(detailsDivId); - document.getElementsByTagName('body')[0].appendChild(detailsDiv); - detailsDiv.onclick = function (ev) { - {{JS_PREFIX}}_toggle_app_details(); - } - } - while (detailsDiv.firstChild) { - detailsDiv.removeChild(detailsDiv.lastChild); - } - detailsDiv.style.display = 'block'; - // add rows for blocked stuff... - if (extraKeys.length > 0) { - extraKeys.sort(function (a, b) { - return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'}); - }); - let totalBlocks = 0; - let totalAllowed = 0; - extraKeys.forEach(k => { - const blockCount = {{PAGE_PREFIX}}_extra_blocks[k]; - if (k === '_') { - totalAllowed = blockCount; - return; - } - totalBlocks += blockCount; - const useEm = k.startsWith('*'); - const token = useEm ? k.substring(1) : k; - const entryDiv = document.createElement('div'); - if (useEm) { - const em = document.createElement('em'); - em.appendChild(document.createTextNode(token + ': ' + blockCount)); - entryDiv.appendChild(em); - } else { - const entryText = document.createTextNode(token + ': ' + blockCount); - entryDiv.appendChild(entryText); - } - detailsDiv.appendChild(entryDiv); - }); - const extraSummary = document.createElement('strong'); - const summaryLabel = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_signalNoiseRatio', 'signal/noise'); - const fullTotal = totalAllowed + totalBlocks; - const ratio = 100.0 * (totalAllowed / fullTotal); - extraSummary.appendChild(document.createTextNode(summaryLabel + ': ' + totalAllowed + '/' + totalBlocks + ' ≈ ' + ratio.toLocaleString('{{ACCOUNT_LOCALE_HYPHEN}}', { maximumSignificantDigits: 3 }) + '%')); - detailsDiv.appendChild(extraSummary); - detailsDiv.appendChild(document.createElement('hr')); - } + detailsDiv.style.display = 'block'; + detailsDiv.appendChild({{JS_PREFIX}}_app_title_span('BlockParty!')); + detailsDiv.appendChild(document.createElement('hr')); + + // add rows for blocked stuff... + const adsAndTrackersLabel = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_adsAndTrackers', 'Blocked Ads/Trackers'); + const adsAndTrackersHeader = document.createElement('strong'); + adsAndTrackersHeader.appendChild(document.createTextNode(adsAndTrackersLabel)); + detailsDiv.appendChild(adsAndTrackersHeader); + if ({{JS_PREFIX}}_last_stats.blocks.length === 0) { + const emptyLabel = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_noAdsOrTrackers', '(no blocks)'); + detailsDiv.appendChild(document.createElement('br')); + detailsDiv.appendChild(document.createTextNode(emptyLabel)); + } else { for (let i = 0; i < {{JS_PREFIX}}_last_stats.blocks.length; i++) { const entry = {{JS_PREFIX}}_last_stats.blocks[i]; const entryDiv = document.createElement('div'); @@ -104,8 +52,32 @@ function {{JS_PREFIX}}_toggle_app_details(ev) { detailsDiv.appendChild(entryDiv); } } - }) - } + detailsDiv.appendChild(document.createElement('hr')); + const fqdn = window.location.hostname; + const hideStatsButton = {{JS_PREFIX}}_create_button('web_hideStats', 'Hide BlockParty for '+fqdn, function (e) { + // write appdata + const hide_show_stats_url = '/__bubble/api/filter/data/{{BUBBLE_DATA_ID}}/write'; + const requestOptions = { + method: 'POST', + body: JSON.stringify({key: 'hideStats_'+fqdn, data: 'false'}) + }; + fetch(hide_show_stats_url, requestOptions) + .then(() => window.location.reload()); + //.then(() => window.location.reload()); + return false; + }, label => label + fqdn); + + detailsDiv.appendChild(hideStatsButton); + detailsDiv.appendChild(document.createElement('hr')); + const closeButton = {{JS_PREFIX}}_create_button('web_close', 'close', function (e) { + e.stopPropagation(); + e.preventDefault(); + {{JS_PREFIX}}_hide_app_details(); + return false; + }); + detailsDiv.appendChild(closeButton); + } + }); } const {{JS_PREFIX}}_app_refresh = function (displayFunc) { @@ -166,12 +138,22 @@ let {{JS_PREFIX}}_app_refresh_interval = null; const {{JS_PREFIX}}_app = { jsPrefix: '{{JS_PREFIX}}', app: '{{BUBBLE_APP_NAME}}', - link: {{JS_PREFIX}}_toggle_app_details, + link: {{JS_PREFIX}}_show_app_details, icon: 'icon-gray', onReady: function () { {{JS_PREFIX}}_load_messages('web_', function (messages) { {{JS_PREFIX}}_messages = messages; - }) + }); + document.onkeydown = function(e) { + e = e || window.event; + let isEscape = false; + if ("key" in e) { + isEscape = (e.key === "Escape" || e.key === "Esc"); + } else { + isEscape = (e.keyCode === 27); + } + if (isEscape) {{JS_PREFIX}}_hide_app_details(); + }; {{JS_PREFIX}}_app_refresh_interval = window.setInterval({{JS_PREFIX}}_app_refresh, 5000); } }; diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs index e40f0856..0ba0516b 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs @@ -14,6 +14,42 @@ let {{JS_PREFIX}}_idle_interval = 2500; let {{JS_PREFIX}}_messages = null; +const {{PAGE_PREFIX}}_block_keyword_tally = {}; +const {{PAGE_PREFIX}}_block_author_tally = {}; +let {{PAGE_PREFIX}}_allow_tally = 0; + +function {{JS_PREFIX}}_tally_keyword_block(name) { + {{JS_PREFIX}}_incr_tally(name, {{PAGE_PREFIX}}_block_keyword_tally); +} + +function {{JS_PREFIX}}_incr_tally(name, tally) { + if (!(name in tally)) { + tally[name] = 1; + } else { + tally[name] = tally[name] + 1; + } +} + +function {{JS_PREFIX}}_decr_tally(name, tally) { + if (!(name in tally)) { + tally[name] = 0; + } else { + tally[name] = tally[name] - 1; + } +} + +function {{JS_PREFIX}}_tally_author_block(name) { + {{JS_PREFIX}}_incr_tally(name, {{PAGE_PREFIX}}_block_author_tally); +} + +function {{JS_PREFIX}}_tally_allow() { + {{PAGE_PREFIX}}_allow_tally++; +} + +function {{JS_PREFIX}}_untally_allow() { + {{PAGE_PREFIX}}_allow_tally--; +} + function {{JS_PREFIX}}_uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); @@ -163,26 +199,6 @@ function {{JS_PREFIX}}_mark_evaluated(node) { return firstEval; } -function {{JS_PREFIX}}_tally_block(name) { - // console.log('tally_block: '+name); - if (typeof {{PAGE_PREFIX}}_add_extra_block === 'function') { - {{PAGE_PREFIX}}_add_extra_block(name); - } -} - -function {{JS_PREFIX}}_tally_allow(name) { - // console.log('tally_allow: '+name); - if (typeof {{PAGE_PREFIX}}_add_extra_block === 'function') { - {{PAGE_PREFIX}}_add_extra_allow(); - } -} -function {{JS_PREFIX}}_untally_allow(name) { - // console.log('untally_allow: '+name); - if (typeof {{PAGE_PREFIX}}_add_extra_block === 'function') { - {{PAGE_PREFIX}}_remove_extra_allow(); - } -} - function {{JS_PREFIX}}_includes_block_keyword (element, firstEval) { const keywords = Object.keys(Object.assign({}, {{JS_PREFIX}}_blocked_keywords, {{JS_PREFIX}}_blocked_list_keywords)); if (keywords.length > 0) { @@ -193,14 +209,14 @@ function {{JS_PREFIX}}_includes_block_keyword (element, firstEval) { let cskw = kw.substring(1); if (html.indexOf(cskw) !== -1) { // {{PAGE_PREFIX}}_log('>>> includes_block_keyword: blocking based on case-sensitive keyword: ' + cskw); - {{JS_PREFIX}}_tally_block('*'+cskw); + {{JS_PREFIX}}_tally_keyword_block(cskw); if (!firstEval) {{JS_PREFIX}}_untally_allow(); return true; } } else { if (html.toLowerCase().indexOf(kw.toLowerCase()) !== -1) { // {{PAGE_PREFIX}}_log('>>> includes_block_keyword: blocking based on case-insensitive keyword: ' + kw); - {{JS_PREFIX}}_tally_block('*'+kw); + {{JS_PREFIX}}_tally_keyword_block(kw); if (!firstEval) {{JS_PREFIX}}_untally_allow(); return true; } @@ -284,15 +300,6 @@ function {{JS_PREFIX}}_hide_app_details() { } } -function {{JS_PREFIX}}_create_button(labelKey, labelDefault, onclick) { - const label = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, labelKey, labelDefault); - const btn = document.createElement('button'); - btn.style.fontSize = 'x-small'; - btn.addEventListener('click', onclick); - btn.appendChild(document.createTextNode(label)); - return btn; -} - function {{JS_PREFIX}}_show_app_details() { // {{PAGE_PREFIX}}_log('show_app_details called'); {{JS_PREFIX}}_refresh_blocks(); @@ -313,13 +320,71 @@ function {{JS_PREFIX}}_show_app_details() { } detailsDiv.style.display = 'block'; detailsDiv.style.visibility = 'visible'; + detailsDiv.appendChild({{JS_PREFIX}}_app_title_span('ShadowBan')); + detailsDiv.appendChild(document.createElement('hr')); + + const recentBlocksText = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_recentBlocks', 'Recent Blocks'); + const recentBlocksHeader = document.createElement('strong') + recentBlocksHeader.appendChild(document.createTextNode(recentBlocksText)); + detailsDiv.appendChild(recentBlocksHeader); + + const keywordTally = {{PAGE_PREFIX}}_block_keyword_tally; + let keywordTallyKeys = Object.keys({{PAGE_PREFIX}}_block_keyword_tally); + const authorTally = {{PAGE_PREFIX}}_block_author_tally; + let authorTallyKeys = Object.keys({{PAGE_PREFIX}}_block_author_tally); + if (keywordTallyKeys.length === 0 && authorTallyKeys.length === 0) { + const noRecentBlocksText = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_noRecentBlocks', '(empty)'); + detailsDiv.appendChild(document.createElement('br')); + detailsDiv.appendChild(document.createTextNode(noRecentBlocksText)); + } else { + let totalBlocks = 0; + if (keywordTallyKeys.length > 0) { + keywordTallyKeys = keywordTallyKeys.slice(); + keywordTallyKeys.sort(function (a, b) { + return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'}); + }); + const openQuote = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_openQuote', '“'); + const closeQuote = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_closeQuote', '”'); + keywordTallyKeys.forEach(keyword => { + const count = keywordTally[keyword]; + totalBlocks += count; + const entryDiv = document.createElement('div'); + const em = document.createElement('em'); + em.appendChild(document.createTextNode(openQuote + keyword + closeQuote + ': ' + count)); + entryDiv.appendChild(em); + detailsDiv.appendChild(entryDiv); + }); + } + if (authorTallyKeys.length > 0) { + authorTallyKeys = authorTallyKeys.slice(); + authorTallyKeys.sort(function (a, b) { + return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'}); + }); + authorTallyKeys.forEach(author => { + const count = authorTally[author]; + totalBlocks += count; + const entryDiv = document.createElement('div'); + entryDiv.appendChild(document.createTextNode(author + ': ' + count)); + detailsDiv.appendChild(entryDiv); + }); + } + const summaryDiv = document.createElement('div'); + const summaryHeader = document.createElement('strong'); + const summaryLabel = {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_signalNoiseRatio', 'signal/noise'); + const totalAllowed = {{PAGE_PREFIX}}_allow_tally; + const fullTotal = totalAllowed + totalBlocks; + const ratio = 100.0 * (totalAllowed / fullTotal); + summaryHeader.appendChild(document.createTextNode(summaryLabel + ': ' + totalAllowed + '/' + totalBlocks + ' ≈ ' + ratio.toLocaleString('{{ACCOUNT_LOCALE_HYPHEN}}', { maximumSignificantDigits: 3 }) + '%')); + summaryDiv.appendChild(summaryHeader); + detailsDiv.appendChild(summaryDiv); + } + detailsDiv.appendChild(document.createElement('hr')); let blocks = Object.keys({{JS_PREFIX}}_blocked_users); if (blocks !== null && blocks.length > 0) { blocks = blocks.slice(); // copy first, then sort case insensitive using user's locale blocks.sort(function (a, b) { return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'}); - return a.localeCompare(b, '{{ACCOUNT_LANG}}', {'sensitivity': 'base'}); }); } let keywords = Object.keys({{JS_PREFIX}}_blocked_keywords); @@ -469,7 +534,7 @@ function {{JS_PREFIX}}_show_app_details() { detailsDiv.appendChild(document.createElement('hr')); } } - const closeButton = {{JS_PREFIX}}_create_button('web_close', 'refresh', function (e) { + const closeButton = {{JS_PREFIX}}_create_button('web_close', 'close', function (e) { e.stopPropagation(); e.preventDefault(); {{JS_PREFIX}}_hide_app_details(); @@ -499,7 +564,17 @@ function {{JS_PREFIX}}_toggle_app_details(ev) { onReady: function () { {{JS_PREFIX}}_load_messages('web_', function (messages) { {{JS_PREFIX}}_messages = messages; - }) + }); + document.onkeydown = function(e) { + e = e || window.event; + let isEscape = false; + if ("key" in e) { + isEscape = (e.key === "Escape" || e.key === "Esc"); + } else { + isEscape = (e.keyCode === 27); + } + if (isEscape) {{JS_PREFIX}}_hide_app_details(); + }; {{JS_PREFIX}}_fetch_blocks(); } }); diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/site/FB.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/site/FB.js.hbs index 3d87773f..2f30309d 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/site/FB.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/site/FB.js.hbs @@ -147,7 +147,7 @@ function {{JS_PREFIX}}_should_block(blocked_users, article) { if (authorName.endsWith('/')) authorName = authorName.substring(0, authorName.length-1); if (authorName === 'profile.php') { // log('should_block returning true for '+authorName); - {{JS_PREFIX}}_tally_block({{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other')); + {{JS_PREFIX}}_tally_author_block({{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other')); if (!firstEval) {{JS_PREFIX}}_untally_allow(authorName); return true; } @@ -158,14 +158,14 @@ function {{JS_PREFIX}}_should_block(blocked_users, article) { const authorDisplayName = authorDisplay.innerHTML; if ({{JS_PREFIX}}_is_ad(article)) { // log('removing ad ('+authorDisplayName+')'); - {{JS_PREFIX}}_tally_block(authorName == null ? {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other') : authorName); - if (!firstEval) {{JS_PREFIX}}_untally_allow(authorName); + {{JS_PREFIX}}_tally_author_block(authorName == null ? {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other') : authorName); + if (!firstEval) {{JS_PREFIX}}_untally_allow(); return authorName == null ? true : authorName; } else if (authorName in blocked_users) { // log('should_block returning '+authorName); - {{JS_PREFIX}}_tally_block(authorName == null ? {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other') : authorName); - if (!firstEval) {{JS_PREFIX}}_untally_allow(authorName); + {{JS_PREFIX}}_tally_author_block(authorName == null ? {{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other') : authorName); + if (!firstEval) {{JS_PREFIX}}_untally_allow(); return authorName == null ? true : authorName; } else { @@ -194,7 +194,7 @@ function {{JS_PREFIX}}_should_block(blocked_users, article) { } if (firstEval) { // console.log('>>> allowing post with firstAuthor = '+firstAuthor); - {{JS_PREFIX}}_tally_allow(firstAuthor); + {{JS_PREFIX}}_tally_allow(); } return false; } diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/site/HackerNews.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/site/HackerNews.js.hbs index 6035d3b6..473d2f79 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/site/HackerNews.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/site/HackerNews.js.hbs @@ -40,7 +40,7 @@ function {{JS_PREFIX}}_apply_blocks(blocked_users) { const authorBlocked = author in blocked_users; if (authorBlocked || {{JS_PREFIX}}_includes_block_keyword(comment, firstEval)) { if (authorBlocked) { - {{JS_PREFIX}}_tally_block(author); + {{JS_PREFIX}}_tally_author_block(author); if (!firstEval) {{JS_PREFIX}}_untally_allow(); } blocking = true; @@ -54,7 +54,7 @@ function {{JS_PREFIX}}_apply_blocks(blocked_users) { blockNode.className = "bubble_block"; blockNode.innerHTML = ' [X]'; ageElement.parentNode.insertBefore(blockNode, ageElement.nextSibling); - {{JS_PREFIX}}_tally_allow(author); + {{JS_PREFIX}}_tally_allow(); } } } diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/site/MR.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/site/MR.js.hbs index 74263d9c..f8d1682e 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/site/MR.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/site/MR.js.hbs @@ -22,7 +22,7 @@ function {{JS_PREFIX}}_consider_block(comments, blocked_users) { if (userElement !== null) { const author = userElement.innerText; if (author in blocked_users) { - {{JS_PREFIX}}_tally_block(author); + {{JS_PREFIX}}_tally_author_block(author); if (!firstEval) {{JS_PREFIX}}_untally_allow(); comment.parentNode.removeChild(comment); continue; @@ -34,7 +34,7 @@ function {{JS_PREFIX}}_consider_block(comments, blocked_users) { blockNode.style['white-space'] = 'nowrap'; blockNode.innerHTML = '[X]  '; userElement.parentNode.insertBefore(blockNode, userElement); - {{JS_PREFIX}}_tally_allow(author); + {{JS_PREFIX}}_tally_allow(); } } } diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/site/Reason.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/site/Reason.js.hbs index 9d84c7b1..661a036d 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/site/Reason.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/site/Reason.js.hbs @@ -23,7 +23,7 @@ function {{JS_PREFIX}}_consider_block(comments, blocked_users) { if (userElement !== null) { const author = userElement.innerText; if (author in blocked_users) { - {{JS_PREFIX}}_tally_block(author); + {{JS_PREFIX}}_tally_author_block(author); if (!firstEval) {{JS_PREFIX}}_untally_allow(); comment.parentNode.removeChild(comment); continue; @@ -39,7 +39,7 @@ function {{JS_PREFIX}}_consider_block(comments, blocked_users) { if (replies !== null) { {{JS_PREFIX}}_consider_block(replies.querySelector('li.comment')); } - {{JS_PREFIX}}_tally_allow(author); + {{JS_PREFIX}}_tally_allow(); } } } diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/site/Twitter.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/site/Twitter.js.hbs index 0ba1fd4d..644111f2 100644 --- a/bubble-server/src/main/resources/bubble/rule/social/block/site/Twitter.js.hbs +++ b/bubble-server/src/main/resources/bubble/rule/social/block/site/Twitter.js.hbs @@ -41,7 +41,7 @@ function {{JS_PREFIX}}_apply_blocks(blocked_users) { const authorBlocked = authorName in blocked_users; if (authorBlocked || {{JS_PREFIX}}_includes_block_keyword(node)) { if (authorBlocked) { - {{JS_PREFIX}}_tally_block(authorName); + {{JS_PREFIX}}_tally_author_block(authorName); if (!firstEval) {{JS_PREFIX}}_untally_allow(); } console.log('removing post by author: ' + authorName); @@ -96,7 +96,7 @@ function {{JS_PREFIX}}_apply_blocks(blocked_users) { authorDiv.setAttribute('white-space', 'no-wrap'); authorDiv.parentNode.appendChild(blockControl); - {{JS_PREFIX}}_tally_allow(authorName); + {{JS_PREFIX}}_tally_allow(); } } } diff --git a/bubble-server/src/main/resources/logback.xml b/bubble-server/src/main/resources/logback.xml index 6f12d789..1f628404 100644 --- a/bubble-server/src/main/resources/logback.xml +++ b/bubble-server/src/main/resources/logback.xml @@ -54,11 +54,11 @@ - + - - + + diff --git a/bubble-server/src/main/resources/models/apps/bubble_block/bubbleApp_bubbleBlock.json b/bubble-server/src/main/resources/models/apps/bubble_block/bubbleApp_bubbleBlock.json index 99c24747..0d2383ef 100644 --- a/bubble-server/src/main/resources/models/apps/bubble_block/bubbleApp_bubbleBlock.json +++ b/bubble-server/src/main/resources/models/apps/bubble_block/bubbleApp_bubbleBlock.json @@ -38,7 +38,8 @@ {"name": "testUserAgent", "required": false}, {"name": "testUrlPrimary", "type": "flag"}, {"name": "urlRegex", "required": false}, - {"name": "userAgentRegex"} + {"name": "userAgentRegex"}, + {"name": "fqdn", "type": "fqdn"} ], "configViews": [{ "name": "manageLists", @@ -111,6 +112,20 @@ "successMessage": "decisionType" } ] + }, { + "name": "manageHideStats", + "scope": "app", + "root": "true", + "fields": ["fqdn"], + "when": "configs.showBlockStatsSupported", + "actions": [ + {"name": "removeHideStats", "index": 10}, + { + "name": "createHideStats", "scope": "app", "index": 10, + "params": ["fqdn"], + "button": "createHideStats" + } + ] }] }, "children": { @@ -196,6 +211,7 @@ {"name": "config.view.manageList", "value": "Manage Filter List"}, {"name": "config.view.manageRules", "value": "Manage Filter Rules"}, {"name": "config.view.manageUserAgents", "value": "Manage User-Agents"}, + {"name": "config.view.manageHideStats", "value": "Disable In-Page Block Stats"}, {"name": "config.field.name", "value": "Name"}, {"name": "config.field.description", "value": "Description"}, @@ -218,6 +234,8 @@ {"name": "config.field.urlRegex.description", "value": "A regular expression to match against the URL"}, {"name": "config.field.userAgentRegex", "value": "User-Agent"}, {"name": "config.field.userAgentRegex.description", "value": "A regular expression to match against the User-Agent"}, + {"name": "config.field.fqdn", "value": "Domain Name"}, + {"name": "config.field.fqdn.description", "value": "In-page block stats will be disabled for this domain"}, {"name": "config.action.enableList", "value": "Enable"}, {"name": "config.action.disableList", "value": "Disable"}, @@ -235,6 +253,10 @@ {"name": "config.button.createUserAgentBlock", "value": "Add"}, {"name": "config.action.removeUserAgentBlock", "value": "Remove Rule"}, {"name": "config.button.removeUserAgentBlock", "value": "Remove"}, + {"name": "config.action.createHideStats", "value": "Disable Block Stats"}, + {"name": "config.button.createHideStats", "value": "Add"}, + {"name": "config.action.removeHideStats", "value": "Remove"}, + {"name": "config.button.removeHideStats", "value": "Remove"}, {"name": "config.action.testUrl", "value": "Test URL"}, {"name": "config.button.testUrl", "value": "Test"}, {"name": "config.response.block", "value": "Block"}, @@ -243,7 +265,12 @@ {"name": "config.response.allow.description", "value": "Requests to this URL would be allowed by your Bubble, and would not be filtered"}, {"name": "config.response.filter", "value": "Filter"}, {"name": "config.response.filter.description", "value": "Requests to this URL would be allowed by your Bubble, but would be filtered"}, - {"name": "web_signalNoiseRatio", "value": "signal/noise"} + + {"name": "web_appName", "value": "BlockParty!"}, + {"name": "web_adsAndTrackers", "value": "Blocked Ads/Trackers"}, + {"name": "web_hideStats", "value": "Hide BlockParty for "}, + {"name": "web_noAdsOrTrackers", "value": "(no blocks)"}, + {"name": "web_close", "value": "close"} ] }] } diff --git a/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json b/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json index 3414b8ae..7dc5493f 100644 --- a/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json +++ b/bubble-server/src/main/resources/models/apps/user_block/bubbleApp_userBlock.json @@ -39,12 +39,18 @@ {"name": "action.disable", "value": "Disable Block"}, {"name": "action.delete", "value": "Delete Block"}, + {"name": "web_appName", "value": "ShadowBan"}, {"name": "web_blockedUsers", "value": "Users"}, {"name": "web_noUsersBlocked", "value": "(empty)"}, {"name": "web_blockedKeywords", "value": "Keywords"}, {"name": "web_noKeywordsBlocked", "value": "(empty)"}, {"name": "web_addKeyword", "value": "add"}, {"name": "web_advertOrOtherBlock", "value": "advert/other"}, + {"name": "web_recentBlocks", "value": "Recent Blocks"}, + {"name": "web_noRecentBlocks", "value": "(empty)"}, + {"name": "web_openQuote", "value": "“"}, + {"name": "web_closeQuote", "value": "”"}, + {"name": "web_signalNoiseRatio", "value": "signal/noise"}, {"name": "web_close", "value": "close"}, {"name": "web_blockedKeywordLists", "value": "Keyword Lists"}, {"name": "web_kwlist_us_news", "value": "US News Media"}, diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_conn_check.py b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_conn_check.py index 09bccb51..20c2fc57 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_conn_check.py +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_conn_check.py @@ -69,7 +69,12 @@ def get_device_security_level(client_addr, fqdns): return {'level': level} -def show_block_stats(client_addr): +def show_block_stats(client_addr, fqdns): + if fqdns is not None: + for fqdn in fqdns: + show = REDIS.get(REDIS_KEY_DEVICE_SHOW_BLOCK_STATS+client_addr+':'+fqdn) + if show is not None: + return show.decode() == 'true' show = REDIS.get(REDIS_KEY_DEVICE_SHOW_BLOCK_STATS+client_addr) if show is None: return False @@ -259,7 +264,7 @@ def next_layer(next_layer): elif 'block' in check and check['block']: bubble_log('next_layer: enabling block for server=' + server_addr+', fqdns='+str(fqdns)) bubble_activity_log(client_addr, server_addr, 'conn_block', fqdns) - if show_block_stats(client_addr) and security_level['level'] != SEC_BASIC: + if show_block_stats(client_addr, fqdns) and security_level['level'] != SEC_BASIC: next_layer.do_block = True next_layer.__class__ = TlsFeedback else: diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/dns_spoofing.py b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/dns_spoofing.py index fcd94012..cbb1ba4e 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/dns_spoofing.py +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/dns_spoofing.py @@ -82,6 +82,8 @@ class Rerouter: port = 80 is_http = True + # check if https and sni is missing but we have a host header, fill in the sni + host_header = flow.request.host_header # bubble_log("dns_spoofing.request: host_header is "+repr(host_header)) if host_header: diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/tasks/main.yml b/bubble-server/src/main/resources/packer/roles/mitmproxy/tasks/main.yml index 8307b89a..6f39cc5e 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/tasks/main.yml +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/tasks/main.yml @@ -41,7 +41,7 @@ get_url: url: https://github.com/getbubblenow/bubble-dist/raw/master/mitmproxy/mitmproxy.zip dest: /tmp/mitmproxy.zip - checksum: sha256:573bf83388a1634ffcd710121b9185b54b4dc62e9553d3bc654cc940783bda9d + checksum: sha256:c5817949c5159ad07f6434032df1249c8f4f0e74eeee6cef4e5d7d3676cdc49e - name: Unzip mitmproxy.zip unarchive: diff --git a/bubble-web b/bubble-web index 52bbba5c..a4944233 160000 --- a/bubble-web +++ b/bubble-web @@ -1 +1 @@ -Subproject commit 52bbba5ccbf22f5adf97c6e3aa7b2cb4d131885c +Subproject commit a4944233bf2bd32c76aceb8efb4f5574cc4eee4f