diff --git a/bubble-server/src/main/java/bubble/ApiConstants.java b/bubble-server/src/main/java/bubble/ApiConstants.java index d758609f..d4fd0cd3 100644 --- a/bubble-server/src/main/java/bubble/ApiConstants.java +++ b/bubble-server/src/main/java/bubble/ApiConstants.java @@ -37,6 +37,7 @@ public class ApiConstants { public static final String DEFAULT_LOCALE = "en_US"; private static final AtomicReference bubbleDefaultDomain = new AtomicReference<>(); + private static String initDefaultDomain() { final File f = new File(HOME_DIR, ".BUBBLE_DEFAULT_DOMAIN"); final String domain = FileUtil.toStringOrDie(f); @@ -178,6 +179,9 @@ public class ApiConstants { public static final String FILTER_HTTP_ENDPOINT = "/filter"; public static final String EP_APPLY = "/apply"; + // requests to a first-party host with this prefix will be forwarded to bubble + public static final String BUBBLE_FILTER_PASSTHRU = "/__bubble"; + // search constants public static final int MAX_SEARCH_PAGE = 50; public static final String Q_FILTER = "query"; @@ -187,6 +191,10 @@ public class ApiConstants { public static final String Q_SIZE = "size"; public static final String Q_SORT = "sort"; + // param for writing AppData via GET (see FilterHttpResource and UserBlockerStreamFilter) + public static final String Q_DATA = "data"; + public static final String Q_REDIRECT = "redirect"; + public static final int MAX_NOTIFY_LOG = 10000; public static final int ERROR_MAXLEN = 4000; diff --git a/bubble-server/src/main/java/bubble/model/account/Account.java b/bubble-server/src/main/java/bubble/model/account/Account.java index 35cb8449..5b4f3b02 100644 --- a/bubble-server/src/main/java/bubble/model/account/Account.java +++ b/bubble-server/src/main/java/bubble/model/account/Account.java @@ -3,8 +3,14 @@ package bubble.model.account; import bubble.dao.account.AccountInitializer; import bubble.model.app.AppData; import bubble.model.app.BubbleApp; +import bubble.model.app.RuleDriver; +import bubble.model.bill.AccountPayment; +import bubble.model.bill.AccountPaymentMethod; +import bubble.model.bill.AccountPlan; +import bubble.model.bill.Bill; import bubble.model.boot.ActivationRequest; import bubble.model.cloud.*; +import bubble.model.cloud.notify.ReceivedNotification; import bubble.model.cloud.notify.SentNotification; import bubble.model.device.Device; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -17,7 +23,7 @@ import org.cobbzilla.util.collection.ArrayUtil; import org.cobbzilla.wizard.filters.auth.TokenPrincipal; import org.cobbzilla.wizard.model.HashedPassword; import org.cobbzilla.wizard.model.Identifiable; -import org.cobbzilla.wizard.model.IdentifiableBase; +import org.cobbzilla.wizard.model.entityconfig.IdentifiableBaseParentEntity; import org.cobbzilla.wizard.model.entityconfig.annotations.*; import org.cobbzilla.wizard.model.search.SqlViewSearchResult; import org.cobbzilla.wizard.validation.ConstraintViolationBean; @@ -49,20 +55,28 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @ECType(root=true) @ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) -@ECTypeChildren(value={ +@ECTypeChildren(uriPrefix=ACCOUNTS_ENDPOINT+"/{Account.name}", value={ @ECTypeChild(type=Device.class, backref="account"), + @ECTypeChild(type=RuleDriver.class, backref="account"), @ECTypeChild(type=BubbleApp.class, backref="account"), @ECTypeChild(type=AppData.class, backref="account"), @ECTypeChild(type=AnsibleRole.class, backref="account"), @ECTypeChild(type=CloudService.class, backref="account"), + @ECTypeChild(type=BubbleFootprint.class, backref="account"), @ECTypeChild(type=BubbleDomain.class, backref="account"), @ECTypeChild(type=BubbleNetwork.class, backref="account"), @ECTypeChild(type=BubbleNode.class, backref="account"), - @ECTypeChild(type=SentNotification.class, backref="account") + @ECTypeChild(type=AccountPlan.class, backref="account"), + @ECTypeChild(type=AccountSshKey.class, backref="account"), + @ECTypeChild(type=Bill.class, backref="account"), + @ECTypeChild(type=AccountPaymentMethod.class, backref="account"), + @ECTypeChild(type=AccountPayment.class, backref="account"), + @ECTypeChild(type=SentNotification.class, backref="account"), + @ECTypeChild(type=ReceivedNotification.class, backref="account") }) @ECSearchDepth(fkDepth=none) @Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j -public class Account extends IdentifiableBase implements TokenPrincipal, SqlViewSearchResult { +public class Account extends IdentifiableBaseParentEntity implements TokenPrincipal, SqlViewSearchResult { public static final String[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy"}; public static final String[] ADMIN_UPDATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "suspended", "admin"); 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 34e0bc60..339fcbf4 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppData.java +++ b/bubble-server/src/main/java/bubble/model/app/AppData.java @@ -1,6 +1,7 @@ package bubble.model.app; import bubble.model.account.Account; +import bubble.model.device.Device; import bubble.rule.RuleConfig; import lombok.Getter; import lombok.NoArgsConstructor; @@ -45,7 +46,7 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { public static final String[] VALUE_FIELDS = {"data", "expiration", "template", "enabled"}; public static final String[] CREATE_FIELDS = ArrayUtil.append(VALUE_FIELDS, - "account", "app", "site", "matcher", "key"); + "account", "device", "app", "site", "matcher", "key"); @Override @Transient public String getName() { return getKey(); } public AppData setName(String n) { return setKey(n); } @@ -55,31 +56,36 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String account; - @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=20) + @ECSearchable(fkDepth=ECForeignKeySearchDepth.shallow) @ECField(index=20) + @ECForeignKey(entity=Device.class) + @Column(nullable=false, updatable=false, length=UUID_MAXLEN) + @Getter @Setter private String device; + + @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=30) @ECForeignKey(entity=BubbleApp.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String app; public boolean hasApp () { return app != null; } - @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=30) + @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=40) @ECForeignKey(entity=AppMatcher.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String matcher; public boolean hasMatcher() { return matcher != null; } - @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=40) + @ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=50) @ECForeignKey(entity=AppSite.class) @Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Getter @Setter private String site; public boolean hasSite() { return site != null; } - @ECSearchable(filter=true) @ECField(index=50) + @ECSearchable(filter=true) @ECField(index=60) @HasValue(message="err.key.required") @ECIndex @Column(nullable=false, updatable=false, length=5000) @Getter @Setter private String key; public boolean hasKey () { return key != null; } - @ECSearchable(filter=true) @ECField(index=60) + @ECSearchable(filter=true) @ECField(index=70) @Size(max=100000, message="err.data.length") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")") @Getter @Setter private String data; @@ -94,15 +100,15 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity { return setData(String.valueOf(val+1)); } - @ECSearchable(type=EntityFieldType.expiration_time) + @ECSearchable(type=EntityFieldType.expiration_time) @ECField(index=80) @ECIndex @Getter @Setter private Long expiration; - @ECSearchable + @ECSearchable @ECField(index=90) @ECIndex @Column(nullable=false) @Getter @Setter private Boolean template = false; public boolean template() { return bool(template); } - @ECSearchable + @ECSearchable @ECField(index=100) @ECIndex @Column(nullable=false) @Getter @Setter private Boolean enabled = true; public boolean enabled() { return bool(enabled); } diff --git a/bubble-server/src/main/java/bubble/model/app/AppRule.java b/bubble-server/src/main/java/bubble/model/app/AppRule.java index 4d12534d..d0dd6c3a 100644 --- a/bubble-server/src/main/java/bubble/model/app/AppRule.java +++ b/bubble-server/src/main/java/bubble/model/app/AppRule.java @@ -32,7 +32,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; @ECTypeURIs(baseURI=EP_RULES, listFields={"name", "app", "driver", "configJson"}) @Entity @NoArgsConstructor @Accessors(chain=true) @ECTypeChildren(uriPrefix=EP_RULES+"/{AppRule.name}", value={ - @ECTypeChild(type= AppData.class, backref="rule"), + @ECTypeChild(type=AppData.class, backref="rule"), }) @ECIndexes({ @ECIndex(unique=true, of={"account", "app", "name"}), diff --git a/bubble-server/src/main/java/bubble/model/device/Device.java b/bubble-server/src/main/java/bubble/model/device/Device.java index eb1d4e99..66967fa0 100644 --- a/bubble-server/src/main/java/bubble/model/device/Device.java +++ b/bubble-server/src/main/java/bubble/model/device/Device.java @@ -18,13 +18,15 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.validation.constraints.Size; +import static bubble.ApiConstants.EP_DEVICES; import static java.util.UUID.randomUUID; import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.cobbzilla.util.reflect.ReflectionUtil.copy; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; -@Entity @ECType(root=true) @ECTypeCreate(method="DISABLED") +@Entity @ECType(root=true) +@ECTypeURIs(baseURI=EP_DEVICES, listFields={"name", "enabled"}) @NoArgsConstructor @Accessors(chain=true) @ECIndexes({ @ECIndex(unique=true, of={"account", "network", "name"}), diff --git a/bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java b/bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java index e7c6100c..63a78f09 100644 --- a/bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java +++ b/bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java @@ -1,6 +1,5 @@ package bubble.resources; -import bubble.resources.driver.DriverAssetsResource; import bubble.server.BubbleConfiguration; import bubble.service.cloud.RequestCoordinationService; import lombok.extern.slf4j.Slf4j; @@ -8,14 +7,16 @@ import org.glassfish.jersey.server.ContainerRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import javax.ws.rs.*; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import static bubble.ApiConstants.BUBBLE_MAGIC_ENDPOINT; -import static bubble.server.BubbleServer.isRestoreMode; import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; -import static org.cobbzilla.wizard.resources.ResourceUtil.*; +import static org.cobbzilla.wizard.resources.ResourceUtil.ok; @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @@ -31,14 +32,4 @@ public class BubbleMagicResource { return ok("you are ok. the magic is ok too."); } - @Path("{requestId}/{driverClass}") - public DriverAssetsResource getAssets(@Context ContainerRequest ctx, - @PathParam("requestId") String requestId, - @PathParam("driverClass") String driverClass) { - if (isRestoreMode()) throw forbiddenEx(); - final String json = requestService.get(driverClass, requestId); - if (json == null) throw notFoundEx(requestId); - return configuration.subResource(DriverAssetsResource.class, requestId, driverClass); - } - } diff --git a/bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java b/bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java index aaeb757d..4e34a58c 100644 --- a/bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java +++ b/bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java @@ -2,11 +2,13 @@ package bubble.resources.app; import bubble.dao.account.AccountDAO; import bubble.dao.app.*; +import bubble.dao.device.DeviceDAO; 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 bubble.model.device.Device; import bubble.resources.account.AccountOwnedTemplateResource; import bubble.server.BubbleConfiguration; import lombok.extern.slf4j.Slf4j; @@ -32,6 +34,7 @@ public abstract class DataResourceBase extends AccountOwnedTemplateResource"; - final RegexReplacementFilter filter = new RegexReplacementFilter("", 0, replacement); + final RegexReplacementFilter filter = new RegexReplacementFilter("", replacement); final RegexFilterReader reader = new RegexFilterReader(new InputStreamReader(in), filter).setMaxMatches(1); return new ReaderInputStream(reader, UTF8cs); } @@ -47,7 +47,7 @@ public class JsUserBlocker extends AbstractAppRuleDriver { ctx.put(CTX_BUBBLE_REQUEST_ID, requestId); ctx.put(CTX_BUBBLE_HOME, configuration.getPublicUriBase()); ctx.put(CTX_SITE, getSiteName(matcher)); - ctx.put(CTX_BUBBLE_DATA_ID, requestId+"/"+matcher.getUuid()); + ctx.put(CTX_BUBBLE_DATA_ID, getDataId(requestId)); final String siteJs = HandlebarsUtil.apply(getHandlebars(), getSiteJsTemplate(), ctx); ctx.put(CTX_APPLY_BLOCKS_JS, siteJs); diff --git a/bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java b/bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java index da7c8cd2..7491ff99 100644 --- a/bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java +++ b/bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java @@ -15,8 +15,6 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Map; -import static bubble.ApiConstants.EP_ASSETS; -import static bubble.ApiConstants.EP_DATA; import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.string.StringUtil.UTF8cs; @@ -50,7 +48,7 @@ public class UserBlocker extends AbstractAppRuleDriver { protected UserBlockerConfig configObject() { return json(getFullConfig(), UserBlockerConfig.class); } @Override public InputStream doFilterResponse(String requestId, InputStream in) { - final UserBlockerStreamFilter filter = new UserBlockerStreamFilter(requestId, matcher, rule); + final UserBlockerStreamFilter filter = new UserBlockerStreamFilter(requestId, matcher, rule, configuration.getHttp().getBaseUri()); filter.configure(getFullConfig()); filter.setDataDAO(appDataDAO); RegexFilterReader reader = new RegexFilterReader(in, RESPONSE_BUFSIZ, filter).setName("mainFilterReader"); @@ -99,16 +97,6 @@ public class UserBlocker extends AbstractAppRuleDriver { return new ReaderInputStream(reader, UTF8cs); } - public String defaultBlockControlImageUri(String requestId) { - // todo: remove /api , just route .bubble to ourselves - return configuration.getHttp().getBaseUri() + "/.bubble/" + requestId + "/" + getClass().getName() + EP_ASSETS + "/@blockControl.png"; - } - - public String blockActionUri(String requestId) { - // todo: remove /api , just route .bubble to ourselves - return configuration.getHttp().getBaseUri() + "/.bubble/" + requestId + "/" + getClass().getName() + EP_DATA; - } - protected String startElementRegex(String el) { return "(<\\s*" + el + "[^>]*>)"; } protected String endElementRegex(final String el) { return "(<\\s*/\\s*" + el + "\\s*[^>]*>)"; } diff --git a/bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java b/bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java index 93c95eac..babffebc 100644 --- a/bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java +++ b/bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java @@ -1,13 +1,11 @@ package bubble.rule.social.block; -import bubble.ApiConstants; import bubble.BubbleHandlebars; import bubble.dao.app.AppDataDAO; import bubble.model.app.AppData; import bubble.model.app.AppMatcher; import bubble.model.app.AppRule; import com.fasterxml.jackson.databind.JsonNode; -import com.ning.http.util.Base64; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.util.collection.ExpirationEvictionPolicy; @@ -22,10 +20,13 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; +import static bubble.ApiConstants.*; +import static bubble.rule.AbstractAppRuleDriver.getDataId; import static bubble.rule.social.block.UserBlockerConfig.STANDARD_JS_ENGINE; import static java.util.concurrent.TimeUnit.MINUTES; import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; import static org.cobbzilla.util.json.JsonUtil.json; +import static org.cobbzilla.util.string.StringUtil.urlEncode; @Slf4j public class UserBlockerStreamFilter implements RegexStreamFilter { @@ -39,12 +40,14 @@ public class UserBlockerStreamFilter implements RegexStreamFilter { private String requestId; private AppMatcher matcher; private AppRule rule; + private String apiBase; @Setter private AppDataDAO dataDAO; - public UserBlockerStreamFilter(String requestId, AppMatcher matcher, AppRule rule) { + public UserBlockerStreamFilter(String requestId, AppMatcher matcher, AppRule rule, String apiBase) { this.requestId = requestId; this.matcher = matcher; this.rule = rule; + this.apiBase = apiBase; } private enum UserBlockerStreamState { seeking_comments, blocking_comments } @@ -115,13 +118,11 @@ public class UserBlockerStreamFilter implements RegexStreamFilter { } final String userId = data.getProperty(PROP_USER_ID); final AppData appData = new AppData() - .setAccount(rule.getAccount()) - .setApp(rule.getApp()) - .setMatcher(matcher.getUuid()) - .setSite(matcher.getSite()) .setKey(userId) .setData(Boolean.TRUE.toString()); - final String blockUrl = ApiConstants.DATA_ENDPOINT + "/"+ Base64.encode(json(appData, COMPACT_MAPPER).getBytes()); + final String dataId = getDataId(requestId, matcher); + final String blockUrl = BUBBLE_FILTER_PASSTHRU + apiBase + FILTER_HTTP_ENDPOINT + EP_DATA + "/" + dataId + EP_WRITE + + "?" +Q_DATA + "=" +urlEncode(json(appData, COMPACT_MAPPER)); final Map ctx = new HashMap<>(data.getProperties()); ctx.put(PROP_BLOCK_URL, blockUrl); return config.getCommentDecorator().decorate(BubbleHandlebars.instance.getHandlebars(), data.getData(), ctx); @@ -131,13 +132,11 @@ public class UserBlockerStreamFilter implements RegexStreamFilter { if (!config.hasBlockedCommentReplacement()) return ""; final Map ctx = new HashMap<>(); final AppData appData = new AppData() - .setAccount(rule.getAccount()) - .setApp(rule.getApp()) - .setMatcher(matcher.getUuid()) - .setSite(matcher.getSite()) .setKey(userId) .setData(Boolean.FALSE.toString()); - final String unblockUrl = ApiConstants.DATA_ENDPOINT+"/"+Base64.encode(json(appData, COMPACT_MAPPER).getBytes()); + final String dataId = getDataId(requestId, matcher); + final String unblockUrl = BUBBLE_FILTER_PASSTHRU + apiBase + FILTER_HTTP_ENDPOINT + EP_DATA + "/" + dataId + EP_WRITE + + "?" +Q_DATA + "=" +urlEncode(json(appData, COMPACT_MAPPER)); ctx.put(PROP_BLOCKED_USER, userId); ctx.put(PROP_UNBLOCK_URL, unblockUrl); 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 738585c7..62dd1c42 100644 --- a/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java +++ b/bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java @@ -20,8 +20,7 @@ import java.util.Map; import static bubble.ApiConstants.HOME_DIR; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.cobbzilla.util.daemon.ZillaRuntime.die; -import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @Service @Slf4j @@ -33,7 +32,6 @@ public class DeviceIdService { public static final FilenamePrefixFilter IP_FILE_FILTER = new FilenamePrefixFilter(IP_FILE_PREFIX); public static final String DEVICE_FILE_PREFIX = "device_"; - public static final FilenamePrefixFilter DEVICE_FILE_FILTER = new FilenamePrefixFilter(DEVICE_FILE_PREFIX); @Autowired private DeviceDAO deviceDAO; @Autowired private BubbleConfiguration configuration; @@ -44,9 +42,7 @@ public class DeviceIdService { public Device findDeviceByIp (String ipAddr) { if (!WG_DEVICES_DIR.exists()) { - if (configuration.testMode()) { - return new Device().setAccount(accountDAO.getFirstAdmin().getUuid()); - } + if (configuration.testMode()) return findTestDevice(ipAddr); throw invalidEx("err.deviceDir.notFound"); } @@ -95,4 +91,16 @@ public class DeviceIdService { } } + private Device findTestDevice(String ipAddr) { + final String adminUuid = accountDAO.getFirstAdmin().getUuid(); + final List adminDevices = deviceDAO.findByAccount(adminUuid); + if (empty(adminDevices)) { + log.warn("findDeviceByIp("+ipAddr+") test mode and no admin devices, returning dummy device"); + return new Device().setAccount(adminUuid).setName("dummy"); + } else { + log.warn("findDeviceByIp("+ipAddr+") test mode, returning first admin device"); + return adminDevices.get(0); + } + } + } diff --git a/bubble-server/src/main/java/bubble/service/stream/RuleEngine.java b/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java similarity index 96% rename from bubble-server/src/main/java/bubble/service/stream/RuleEngine.java rename to bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java index 53ac1fdb..d82136c2 100644 --- a/bubble-server/src/main/java/bubble/service/stream/RuleEngine.java +++ b/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java @@ -8,6 +8,7 @@ import bubble.model.app.AppMatcher; import bubble.model.app.AppRule; import bubble.model.app.RuleDriver; import bubble.model.device.Device; +import bubble.resources.stream.FilterHttpRequest; import bubble.resources.stream.FilterMatchersRequest; import bubble.rule.AppRuleDriver; import bubble.server.BubbleConfiguration; @@ -52,7 +53,6 @@ import java.util.List; import java.util.Map; import static bubble.client.BubbleApiClient.newHttpClientBuilder; -import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MINUTES; import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; @@ -63,7 +63,7 @@ import static org.cobbzilla.util.http.HttpStatusCodes.OK; import static org.cobbzilla.wizard.resources.ResourceUtil.send; @Service @Slf4j -public class RuleEngine { +public class RuleEngineService { @Autowired private AppMatcherDAO matcherDAO; @Autowired private AppRuleDAO ruleDAO; @@ -105,19 +105,17 @@ public class RuleEngine { } public Response applyRulesAndSendResponse(ContainerRequest request, - Account account, - Device device, URIBean ub, - String[] matcherIds) throws IOException { + FilterHttpRequest filterRequest) throws IOException { // sanity check - if (empty(matcherIds)) return passthru(request.getEntityStream()); + if (empty(filterRequest.getMatchers())) return passthru(request.getEntityStream()); // todo: we have at least 1 rule, so add another rule that inserts the global settings controls in the top-left // initialize drivers -- todo: cache drivers / todo: ensure cache is shorter than session timeout, // since drivers that talk thru API will get a session key in their config - final List rules = initRules(account, device, matcherIds); + final List rules = initRules(filterRequest.getAccount(), filterRequest.getDevice(), filterRequest.getMatchers()); final AppRuleHarness firstRule = rules.get(0); // filter request @@ -128,8 +126,7 @@ public class RuleEngine { final CloseableHttpResponse proxyResponse = httpClient.execute(get); // filter response. when stream is closed, close http client - final String requestId = randomUUID().toString(); - final InputStream responseEntity = firstRule.getDriver().filterResponse(requestId, new HttpClosingFilterInputStream(httpClient, proxyResponse)); + final InputStream responseEntity = firstRule.getDriver().filterResponse(filterRequest.getId(), new HttpClosingFilterInputStream(httpClient, proxyResponse)); // send response return sendResponse(responseEntity, proxyResponse); diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.css.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.css.hbs deleted file mode 100644 index bc427587..00000000 --- a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.css.hbs +++ /dev/null @@ -1,4 +0,0 @@ -.{{uniq}}_settings { position: fixed; top: 0; left: 0; } -.{{uniq}}_settings a { position:relative; } -.{{uniq}}_settings a span { position:absolute; display:none; z-index:99; } -.{{uniq}}_settings a:hover span { display:block; position: fixed; top: 0; left: 0; } diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.js.hbs b/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.js.hbs deleted file mode 100644 index 34d702ff..00000000 --- a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.js.hbs +++ /dev/null @@ -1,88 +0,0 @@ -var {{uniq}}_STATE = { - controls: null, - activeComment: null -}; -function {{uniq}}_offset(el) { - var rect = el.getBoundingClientRect(), - scrollLeft = window.pageXOffset || document.documentElement.scrollLeft, - scrollTop = window.pageYOffset || document.documentElement.scrollTop; - return { top: rect.top + scrollTop, left: rect.left + scrollLeft } -} - -function {{uniq}}_blockUser(userid) { - return function(event) { - var req = new XMLHttpRequest(); - req.onreadystatechange = function() { - if (this.readyState == 4){ - console.log('blockUser action completed: '+this.responseText); - } - } - req.open('GET', '{{blockActionUrl}}?key='+userid+'&value=true', true); - req.send(); - } -} - -function {{uniq}}_controlsOn(event) { -{{#if debug}} - console.log("bubbleControlsOn invoked"); -{{/if}} - element = event.target; - if ({{uniq}}_STATE.activeComment != null && {{uniq}}_STATE.activeComment === element) { -{{#if debug}} - console.log('bubbleControlsOn: same element, returning'); -{{/if}} - return; - } - {{uniq}}_STATE.activeComment = element; - - var pos = {{uniq}}_offset(element); - var controlStyle = 'position: absolute; display: block; top: '+pos.top+'; left: '+pos.left; - console.log('bubbleControlsOn: controlStyle='+controlStyle+' for element: '+element); - - userid = element{{userid}}; - console.log('detected userid: '+userid); - - {{uniq}}_STATE.controls = document.createElement("img"); - {{uniq}}_STATE.controls.src = '{{blockControlUrl}}'; - {{uniq}}_STATE.controls.alt = 'Block Comments From This User'; - {{uniq}}_STATE.controls.height = 32; - {{uniq}}_STATE.controls.style = controlStyle; - {{uniq}}_STATE.controls.addEventListener("click", {{uniq}}_blockUser(userid)); - - element.appendChild({{uniq}}_STATE.controls); -{{#if debug}} - console.log('inserted controls ' + {{uniq}}_STATE.controls + ' before element: ' + element); -{{/if}} -} -function {{uniq}}_controlsOff(event) { -{{#if debug}} - console.log("bubbleControlsOff invoked"); -{{/if}} - if ({{uniq}}_STATE.controls == null) { -{{#if debug}} - console.log("bubbleControlsOff: controls was null, returning"); -{{/if}} - return; - } - var rc = {{uniq}}_STATE.activeComment.removeChild({{uniq}}_STATE.controls); -{{#if debug}} - console.log('removed: '+rc); -{{/if}} - {{uniq}}_STATE.activeComment = null; -} - -var {{uniq}}_xPathResult = document.evaluate("{{{xpath}}}", document, null, XPathResult.ANY_TYPE, null); -var {{uniq}}_elements = []; -var {{uniq}}_element = {{uniq}}_xPathResult.iterateNext(); -while ({{uniq}}_element != null) { - {{uniq}}_elements.push({{uniq}}_element); - {{uniq}}_element = {{uniq}}_xPathResult.iterateNext(); -} -{{#if debug}} -console.log('found '+{{uniq}}_elements.length+' comments to decorate'); -{{/if}} -for (var {{uniq}}_i=0; {{uniq}}_i < {{uniq}}_elements.length; {{uniq}}_i++) { - {{uniq}}_element = {{uniq}}_elements[{{uniq}}_i]; - {{uniq}}_element.addEventListener("mouseenter", {{uniq}}_controlsOn); - {{uniq}}_element.addEventListener("mouseleave", {{uniq}}_controlsOff); -} diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_blockControl.png b/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_blockControl.png deleted file mode 100644 index da0567bc..00000000 Binary files a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_blockControl.png and /dev/null differ diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor.json b/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor.json deleted file mode 100644 index 0ea967f1..00000000 --- a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "UserBlocker", - "driverClass": "bubble.rule.social.block.UserBlocker", - "description": "A generalized blocker that can (with per-site configuration) identify which content represents user comments, block comments from blocked users, and decorate visible comments with a block button.", - "icon": "base64-encoded-256x256-png-goes-here", - "labels": [ - {"name": "all_data", "value": "All Blocked Users"}, - {"name": "site_data", "value": "Blocked Users"} - ] -} \ No newline at end of file diff --git a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor_en_US.json b/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor_en_US.json deleted file mode 100644 index 3060f11b..00000000 --- a/bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor_en_US.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "description": "X", - "labels": [ - {"name": "delete_button", "value": "Unblock User"} - ] -} \ No newline at end of file diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties index f579556c..f0cdebce 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties @@ -468,6 +468,8 @@ err.contact.unverified=Cannot configure an unverified contact; verify first err.country.invalid=Country is invalid, use a valid ISO 2-letter code err.credentialsJson.length=Credentials JSON is too long err.data.length=Data is too long +err.data.required=Data is required +err.data.invalid=Data is invalid err.delegatedPayment.notDelegated=Error locating payment service err.delegatedPayment.delegateNotFound=Error locating payment service err.delete.cannotDeleteSelf=You cannot delete yourself (you've still got a chance to win) diff --git a/bubble-server/src/test/java/bubble/test/ProxyTest.java b/bubble-server/src/test/java/bubble/test/ProxyTest.java index 471e5ad5..8f9598ed 100644 --- a/bubble-server/src/test/java/bubble/test/ProxyTest.java +++ b/bubble-server/src/test/java/bubble/test/ProxyTest.java @@ -1,6 +1,5 @@ package bubble.test; -import bubble.model.app.AppData; import bubble.server.BubbleConfiguration; import org.cobbzilla.wizard.server.RestServer; import org.cobbzilla.wizard.util.RestResponse; @@ -9,6 +8,8 @@ import org.junit.Test; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static bubble.ApiConstants.BUBBLE_FILTER_PASSTHRU; +import static org.cobbzilla.util.http.HttpStatusCodes.FOUND; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -35,8 +36,11 @@ public class ProxyTest extends ActivatedBubbleModelTestBase { final Matcher matcher = pattern.matcher(response.json); assertTrue("expected match for user to block", matcher.find()); final String blockUrl = matcher.group(1); - final AppData blockResponse = getApi().get(blockUrl, AppData.class); - assertEquals("expected user block to be saved correctly", "electricEmu", blockResponse.getKey()); + final String expectedPrefix = BUBBLE_FILTER_PASSTHRU + getConfiguration().getHttp().getBaseUri(); + assertTrue("expected prefix blockUrl not found", blockUrl.startsWith(expectedPrefix)); + + final RestResponse restResponse = getApi().doGet(blockUrl.substring(expectedPrefix.length())); + assertEquals("expected redirect", FOUND, restResponse.status); // second request, verify additional user blocked modelTest("filter/user_block/hn_request2"); diff --git a/bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java b/bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java index 16cc2033..cde627f9 100644 --- a/bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java +++ b/bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java @@ -3,6 +3,7 @@ package bubble.test; import bubble.client.BubbleApiClient; import lombok.Getter; import lombok.Setter; +import org.apache.http.impl.client.HttpClientBuilder; import org.cobbzilla.util.http.ApiConnectionInfo; import org.cobbzilla.wizard.auth.AuthResponse; import org.cobbzilla.wizard.auth.LoginRequest; @@ -34,6 +35,10 @@ public class TestBubbleApiClient extends BubbleApiClient { setConfiguration(configuration); } + @Override public HttpClientBuilder getHttpClientBuilder() { + return super.getHttpClientBuilder().disableRedirectHandling(); + } + @Getter(lazy=true) private final String superuserToken = initSuperuserToken(); private String initSuperuserToken() { final Map env = configuration.getEnvironment(); diff --git a/bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json b/bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json index 157c2d49..ab1e6f97 100644 --- a/bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json +++ b/bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json @@ -2,7 +2,7 @@ "name": "UserBlocker", "children": { "AppData": [ - {"matcher": "HNCommentMatcher", "site": "HackerNews", "key": "rvz", "data": "true", "template": true} + {"matcher": "HNCommentMatcher", "site": "HackerNews", "key": "rvz", "data": "true", "template": true, "device": "test"} ] } }] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json b/bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json index 67c5febe..a42d607c 100644 --- a/bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json +++ b/bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json @@ -2,7 +2,7 @@ "name": "UserBlocker", "children": { "AppData": [ - {"matcher": "LocalHNCommentMatcher", "site": "HackerNews", "key": "user2", "data": "true", "template": true} + {"matcher": "LocalHNCommentMatcher", "site": "HackerNews", "key": "user2", "data": "true", "template": true, "device": "test"} ] } }] \ No newline at end of file diff --git a/bubble-server/src/test/resources/models/manifest-proxy.json b/bubble-server/src/test/resources/models/manifest-proxy.json index d1311f57..68f7771c 100644 --- a/bubble-server/src/test/resources/models/manifest-proxy.json +++ b/bubble-server/src/test/resources/models/manifest-proxy.json @@ -1,5 +1,6 @@ [ "system/ruleDriver", + "system/account_testDevice", "manifest-app-user-block-hn", "manifest-app-user-block-localhost" ] diff --git a/bubble-server/src/test/resources/models/system/account_testDevice.json b/bubble-server/src/test/resources/models/system/account_testDevice.json new file mode 100644 index 00000000..e2e40cae --- /dev/null +++ b/bubble-server/src/test/resources/models/system/account_testDevice.json @@ -0,0 +1,8 @@ +[ + { + "name": "root", + "children": { + "Device": [ {"name": "test"} ] + } + } +] \ No newline at end of file diff --git a/utils/cobbzilla-utils b/utils/cobbzilla-utils index 7f944cf9..006bcd1f 160000 --- a/utils/cobbzilla-utils +++ b/utils/cobbzilla-utils @@ -1 +1 @@ -Subproject commit 7f944cf93d24bd47b5a8f124abddbc34bb2ef7f3 +Subproject commit 006bcd1ff4390bb37ae35d50fa12a70de7e408c1 diff --git a/utils/cobbzilla-wizard b/utils/cobbzilla-wizard index 4d814432..0e0c1f38 160000 --- a/utils/cobbzilla-wizard +++ b/utils/cobbzilla-wizard @@ -1 +1 @@ -Subproject commit 4d81443257d4e51fffa35a1b4555e94608301178 +Subproject commit 0e0c1f38345ec1055f8f07c3ca665fd041da27d5