show shadownban blocks in shadowban app WIP. refactor, cleanup WIP. per site stats setting working. cleaning up in-page app ui WIP. allow disabling block stats per fqdn Co-authored-by: Jonathan Cobb <jonathan@kyuss.org> Reviewed-on: #45tags/v1.0.4
@@ -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<Account, AppMatcher> 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<Account, AppSite> 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<String, String> 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<BubbleBlockList> 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<BubbleHideStats> 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<String, String> 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<BubbleHideStats> 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); | |||
@@ -5,5 +5,7 @@ | |||
package bubble.app.bblock; | |||
import bubble.app.analytics.TrafficAnalyticsAppDataDriver; | |||
import lombok.extern.slf4j.Slf4j; | |||
@Slf4j | |||
public class BubbleBlockAppDataDriver extends TrafficAnalyticsAppDataDriver {} |
@@ -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<AppData> { | |||
return filterExpired(findByFields("account", account, "app", app, "key", key)); | |||
} | |||
public List<AppData> findByAccountAndAppAndAndKeyPrefix(String account, String app, String keyPrefix) { | |||
return filterExpired(findByFieldsEqualAndFieldLike("account", account, "app", app, "key", keyPrefix+"%")); | |||
} | |||
public List<AppData> 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<AppData> { | |||
return data; | |||
} | |||
private final Map<String, Function<AppData, AppData>> dataSetCallbacks = new HashMap<>(); | |||
public void registerCallback(String appUuid, Function<AppData, AppData> 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<AppData> { | |||
if (app == null) return die("set: App not found: "+data.getApp()); | |||
data.setAccount(app.getAccount()); | |||
} | |||
if (found == null) return create(data); | |||
final Function<AppData, AppData> 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<AppData> findByExample(Account account, AppData basis) { | |||
@@ -111,4 +126,16 @@ public class AppDataDAO extends AppTemplateEntityDAO<AppData> { | |||
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<AppData, AppData> callback = dataSetCallbacks.get(app.getUuid()); | |||
if (callback != null) { | |||
callback.apply(data.setDeleting(true)); | |||
} | |||
} | |||
super.delete(uuid); | |||
} | |||
} |
@@ -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); } | |||
} |
@@ -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<AppData, AppData> createCallback(Account account, BubbleApp app, BubbleConfiguration configuration); | |||
} |
@@ -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; } | |||
@@ -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); | |||
@@ -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<BubbleAlternateRegexReplacement> 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<alternates.size(); i++) { | |||
final BubbleAlternateRegexReplacement alt = alternates.get(i); | |||
if (log.isInfoEnabled()) log.info("filterInsertJs: using alternate filter ("+i+"): "+alt); | |||
if (log.isInfoEnabled()) log.info(prefix + "using alternate filter (" +i+"): "+alt); | |||
reader = new RegexFilterReader(reader, alt.regexFilter(filterRequest, replacement)) | |||
.setName(filterNamePrefix + "(alt"+i+": "+alt.getFqdnMatch()+") " + alt.getInsertionRegex()) | |||
.setMaxMatches(1); | |||
} | |||
} else { | |||
if (log.isInfoEnabled()) log.info("filterInsertJs: using default filter: "+getInsertionRegex()); | |||
if (log.isInfoEnabled()) log.info(prefix + "using default filter: " +getInsertionRegex()); | |||
reader = new RegexFilterReader(new InputStreamReader(in), new RegexReplacementFilter(getInsertionRegex(), replacement)) | |||
.setName(filterNamePrefix + getInsertionRegex()) | |||
.setMaxMatches(1); | |||
@@ -205,7 +207,7 @@ public abstract class AbstractAppRuleDriver implements AppRuleDriver { | |||
if (modConfig.hasAdditionalRegexReplacements()) { | |||
for (BubbleRegexReplacement re : modConfig.getAdditionalRegexReplacements()) { | |||
if (log.isInfoEnabled()) log.info("filterInsertJs: using additional filter: "+re.getInsertionRegex()); | |||
if (log.isInfoEnabled()) log.info(prefix + "using additional filter: " +re.getInsertionRegex()); | |||
reader = new RegexFilterReader(reader, re.regexFilter(filterRequest, replacement)) | |||
.setName(filterNamePrefix+" (additional) "+re.getInsertionRegex()); | |||
} | |||
@@ -5,6 +5,7 @@ | |||
package bubble.rule; | |||
import bubble.model.account.Account; | |||
import bubble.model.app.AppData; | |||
import bubble.model.app.AppMatcher; | |||
import bubble.model.app.AppRule; | |||
import bubble.model.app.BubbleApp; | |||
@@ -26,6 +27,7 @@ import java.io.ByteArrayInputStream; | |||
import java.io.InputStream; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.function.Function; | |||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
@@ -1,5 +1,6 @@ | |||
package bubble.rule; | |||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.ToString; | |||
@@ -13,7 +14,7 @@ public class BubbleAlternateRegexReplacement extends BubbleRegexReplacement { | |||
@Getter @Setter private String fqdnMatch; | |||
@Getter(lazy=true) private final Pattern pattern = Pattern.compile(fqdnMatch, CASE_INSENSITIVE); | |||
@JsonIgnore @Getter(lazy=true) private final Pattern pattern = Pattern.compile(fqdnMatch, CASE_INSENSITIVE); | |||
public boolean matches (String fqdn) { return getPattern().matcher(fqdn).matches(); } | |||
@@ -5,10 +5,9 @@ | |||
package bubble.rule.bblock; | |||
import bubble.abp.*; | |||
import bubble.dao.app.AppDataDAO; | |||
import bubble.model.account.Account; | |||
import bubble.model.app.AppMatcher; | |||
import bubble.model.app.AppRule; | |||
import bubble.model.app.BubbleApp; | |||
import bubble.model.app.*; | |||
import bubble.model.device.Device; | |||
import bubble.resources.stream.FilterHttpRequest; | |||
import bubble.resources.stream.FilterMatchersRequest; | |||
@@ -16,6 +15,8 @@ import bubble.rule.FilterMatchDecision; | |||
import bubble.rule.RequestModifierConfig; | |||
import bubble.rule.RequestModifierRule; | |||
import bubble.rule.analytics.TrafficAnalyticsRuleDriver; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.service.cloud.DeviceIdService; | |||
import bubble.service.stream.AppRuleHarness; | |||
import bubble.service.stream.ConnectionCheckResponse; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
@@ -32,19 +33,24 @@ import java.net.URI; | |||
import java.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.atomic.AtomicReference; | |||
import java.util.function.Function; | |||
import static bubble.app.bblock.BubbleBlockAppConfigDriver.PREFIX_APPDATA_HIDE_STATS; | |||
import static bubble.service.stream.HttpStreamDebug.getLogFqdn; | |||
import static java.util.concurrent.TimeUnit.DAYS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.io.StreamUtil.stream2string; | |||
import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.string.StringUtil.EMPTY; | |||
import static org.cobbzilla.util.string.StringUtil.getPackagePath; | |||
import static org.cobbzilla.util.string.ValidationRegexes.HOST_PATTERN; | |||
import static org.cobbzilla.util.string.ValidationRegexes.validateRegexMatches; | |||
import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; | |||
@Slf4j | |||
public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver implements RequestModifierRule { | |||
public class BubbleBlockRuleDriver extends TrafficAnalyticsRuleDriver | |||
implements RequestModifierRule, HasAppDataCallback { | |||
private final AtomicReference<BlockList> 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<String, BlockListSource> 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 <C> Class<C> getConfigClass() { return (Class<C>) 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<AppData, AppData> 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; | |||
}; | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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); | |||
@@ -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<String, Device> 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<String> 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<String, DeviceStatus> deviceStatusCache = new ExpirationMap<>(MINUTES.toMillis(2)); | |||
@Override public DeviceStatus getDeviceStatus(String deviceUuid) { | |||
@@ -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<AppRule> rules = ruleDAO.findByAccountAndApp(account.getUuid(), app.getUuid()); | |||
final List<AppMatcher> 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<String> rejectDomains = new HashSet<>(); | |||
final Set<String> blockDomains = new HashSet<>(); | |||
@@ -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<Object, Object> flushCaches() { | |||
public Map<Object, Object> flushCaches() { return flushCaches(true); } | |||
public Map<Object, Object> 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<AppRuleHarness> initRules(FilterHttpRequest filterRequest) { | |||
return initRules(filterRequest.getAccount(), filterRequest.getDevice(), filterRequest.getMatchers()); | |||
} | |||
@@ -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); | |||
@@ -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); | |||
} | |||
}; | |||
@@ -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(); | |||
} | |||
}); |
@@ -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; | |||
} | |||
@@ -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 = ' [<b><a href="#" onclick="{{JS_PREFIX}}_block_user(\''+author+'\'); return false;">X</a></b>]'; | |||
ageElement.parentNode.insertBefore(blockNode, ageElement.nextSibling); | |||
{{JS_PREFIX}}_tally_allow(author); | |||
{{JS_PREFIX}}_tally_allow(); | |||
} | |||
} | |||
} | |||
@@ -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 = '[<b><a href="#" onclick="{{JS_PREFIX}}_block_user(\'' + author.replace("'", "\\\'") + '\'); return false;">X</a></b>] '; | |||
userElement.parentNode.insertBefore(blockNode, userElement); | |||
{{JS_PREFIX}}_tally_allow(author); | |||
{{JS_PREFIX}}_tally_allow(); | |||
} | |||
} | |||
} | |||
@@ -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(); | |||
} | |||
} | |||
} | |||
@@ -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(); | |||
} | |||
} | |||
} | |||
@@ -54,11 +54,11 @@ | |||
<!-- <logger name="org.cobbzilla.util.io.multi.MultiStream" level="TRACE" />--> | |||
<!-- <logger name="bubble.filters.BubbleRateLimitFilter" level="TRACE" />--> | |||
<!-- <logger name="org.cobbzilla.wizard.filters.RateLimitFilter" level="TRACE" />--> | |||
<!-- <logger name="bubble.service.stream.StandardRuleEngineService" level="DEBUG" />--> | |||
<logger name="bubble.service.stream.StandardRuleEngineService" level="DEBUG" /> | |||
<logger name="bubble.service.stream.ActiveStreamState" level="WARN" /> | |||
<logger name="bubble.resources.stream" level="WARN" /> | |||
<!-- <logger name="bubble.resources.stream.FilterHttpResource" level="DEBUG" />--> | |||
<logger name="bubble.resources.stream.FilterHttpResource" level="WARN" /> | |||
<logger name="bubble.resources.stream.FilterHttpResource" level="DEBUG" /> | |||
<!-- <logger name="bubble.resources.stream.FilterHttpResource" level="WARN" />--> | |||
<logger name="bubble.service.stream" level="INFO" /> | |||
<!-- <logger name="bubble.service.dbfilter" level="DEBUG" />--> | |||
<!-- <logger name="bubble.service.account.StandardAccountMessageService" level="DEBUG" />--> | |||
@@ -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"} | |||
] | |||
}] | |||
} |
@@ -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"}, | |||
@@ -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: | |||
@@ -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: | |||
@@ -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: | |||
@@ -1 +1 @@ | |||
Subproject commit 52bbba5ccbf22f5adf97c6e3aa7b2cb4d131885c | |||
Subproject commit a4944233bf2bd32c76aceb8efb4f5574cc4eee4f |