Browse Source

add device to AppData. remove app descriptor/metadata assets. fix tests

tags/v0.3.0
Jonathan Cobb 5 years ago
parent
commit
9954041ad5
31 changed files with 181 additions and 310 deletions
  1. +8
    -0
      bubble-server/src/main/java/bubble/ApiConstants.java
  2. +18
    -4
      bubble-server/src/main/java/bubble/model/account/Account.java
  3. +15
    -9
      bubble-server/src/main/java/bubble/model/app/AppData.java
  4. +1
    -1
      bubble-server/src/main/java/bubble/model/app/AppRule.java
  5. +3
    -1
      bubble-server/src/main/java/bubble/model/device/Device.java
  6. +5
    -14
      bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java
  7. +7
    -0
      bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java
  8. +0
    -76
      bubble-server/src/main/java/bubble/resources/driver/DriverAssetsResource.java
  9. +45
    -6
      bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java
  10. +14
    -3
      bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java
  11. +0
    -38
      bubble-server/src/main/java/bubble/resources/stream/SetAppDataResource.java
  12. +3
    -0
      bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java
  13. +2
    -2
      bubble-server/src/main/java/bubble/rule/social/block/JsUserBlocker.java
  14. +1
    -13
      bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java
  15. +12
    -13
      bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java
  16. +14
    -6
      bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java
  17. +6
    -9
      bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java
  18. +0
    -4
      bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.css.hbs
  19. +0
    -88
      bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.js.hbs
  20. BIN
     
  21. +0
    -10
      bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor.json
  22. +0
    -6
      bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor_en_US.json
  23. +2
    -0
      bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties
  24. +7
    -3
      bubble-server/src/test/java/bubble/test/ProxyTest.java
  25. +5
    -0
      bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java
  26. +1
    -1
      bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json
  27. +1
    -1
      bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json
  28. +1
    -0
      bubble-server/src/test/resources/models/manifest-proxy.json
  29. +8
    -0
      bubble-server/src/test/resources/models/system/account_testDevice.json
  30. +1
    -1
      utils/cobbzilla-utils
  31. +1
    -1
      utils/cobbzilla-wizard

+ 8
- 0
bubble-server/src/main/java/bubble/ApiConstants.java View File

@@ -37,6 +37,7 @@ public class ApiConstants {
public static final String DEFAULT_LOCALE = "en_US"; public static final String DEFAULT_LOCALE = "en_US";


private static final AtomicReference<String> bubbleDefaultDomain = new AtomicReference<>(); private static final AtomicReference<String> bubbleDefaultDomain = new AtomicReference<>();

private static String initDefaultDomain() { private static String initDefaultDomain() {
final File f = new File(HOME_DIR, ".BUBBLE_DEFAULT_DOMAIN"); final File f = new File(HOME_DIR, ".BUBBLE_DEFAULT_DOMAIN");
final String domain = FileUtil.toStringOrDie(f); 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 FILTER_HTTP_ENDPOINT = "/filter";
public static final String EP_APPLY = "/apply"; 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 // search constants
public static final int MAX_SEARCH_PAGE = 50; public static final int MAX_SEARCH_PAGE = 50;
public static final String Q_FILTER = "query"; 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_SIZE = "size";
public static final String Q_SORT = "sort"; 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 MAX_NOTIFY_LOG = 10000;
public static final int ERROR_MAXLEN = 4000; public static final int ERROR_MAXLEN = 4000;




+ 18
- 4
bubble-server/src/main/java/bubble/model/account/Account.java View File

@@ -3,8 +3,14 @@ package bubble.model.account;
import bubble.dao.account.AccountInitializer; import bubble.dao.account.AccountInitializer;
import bubble.model.app.AppData; import bubble.model.app.AppData;
import bubble.model.app.BubbleApp; 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.boot.ActivationRequest;
import bubble.model.cloud.*; import bubble.model.cloud.*;
import bubble.model.cloud.notify.ReceivedNotification;
import bubble.model.cloud.notify.SentNotification; import bubble.model.cloud.notify.SentNotification;
import bubble.model.device.Device; import bubble.model.device.Device;
import com.fasterxml.jackson.annotation.JsonIgnore; 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.filters.auth.TokenPrincipal;
import org.cobbzilla.wizard.model.HashedPassword; import org.cobbzilla.wizard.model.HashedPassword;
import org.cobbzilla.wizard.model.Identifiable; 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.entityconfig.annotations.*;
import org.cobbzilla.wizard.model.search.SqlViewSearchResult; import org.cobbzilla.wizard.model.search.SqlViewSearchResult;
import org.cobbzilla.wizard.validation.ConstraintViolationBean; import org.cobbzilla.wizard.validation.ConstraintViolationBean;
@@ -49,20 +55,28 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;


@ECType(root=true) @ECType(root=true)
@ECTypeURIs(baseURI=ACCOUNTS_ENDPOINT, listFields={"name", "url", "description", "admin", "suspended"}, isDeleteDefined=false) @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=Device.class, backref="account"),
@ECTypeChild(type=RuleDriver.class, backref="account"),
@ECTypeChild(type=BubbleApp.class, backref="account"), @ECTypeChild(type=BubbleApp.class, backref="account"),
@ECTypeChild(type=AppData.class, backref="account"), @ECTypeChild(type=AppData.class, backref="account"),
@ECTypeChild(type=AnsibleRole.class, backref="account"), @ECTypeChild(type=AnsibleRole.class, backref="account"),
@ECTypeChild(type=CloudService.class, backref="account"), @ECTypeChild(type=CloudService.class, backref="account"),
@ECTypeChild(type=BubbleFootprint.class, backref="account"),
@ECTypeChild(type=BubbleDomain.class, backref="account"), @ECTypeChild(type=BubbleDomain.class, backref="account"),
@ECTypeChild(type=BubbleNetwork.class, backref="account"), @ECTypeChild(type=BubbleNetwork.class, backref="account"),
@ECTypeChild(type=BubbleNode.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) @ECSearchDepth(fkDepth=none)
@Entity @NoArgsConstructor @Accessors(chain=true) @Slf4j @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[] UPDATE_FIELDS = {"url", "description", "autoUpdatePolicy"};
public static final String[] ADMIN_UPDATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "suspended", "admin"); public static final String[] ADMIN_UPDATE_FIELDS = ArrayUtil.append(UPDATE_FIELDS, "suspended", "admin");


+ 15
- 9
bubble-server/src/main/java/bubble/model/app/AppData.java View File

@@ -1,6 +1,7 @@
package bubble.model.app; package bubble.model.app;


import bubble.model.account.Account; import bubble.model.account.Account;
import bubble.model.device.Device;
import bubble.rule.RuleConfig; import bubble.rule.RuleConfig;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; 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[] VALUE_FIELDS = {"data", "expiration", "template", "enabled"};


public static final String[] CREATE_FIELDS = ArrayUtil.append(VALUE_FIELDS, 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(); } @Override @Transient public String getName() { return getKey(); }
public AppData setName(String n) { return setKey(n); } 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) @Column(nullable=false, updatable=false, length=UUID_MAXLEN)
@Getter @Setter private String account; @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) @ECForeignKey(entity=BubbleApp.class)
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Column(nullable=false, updatable=false, length=UUID_MAXLEN)
@Getter @Setter private String app; @Getter @Setter private String app;
public boolean hasApp () { return app != null; } public boolean hasApp () { return app != null; }


@ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=30)
@ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=40)
@ECForeignKey(entity=AppMatcher.class) @ECForeignKey(entity=AppMatcher.class)
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Column(nullable=false, updatable=false, length=UUID_MAXLEN)
@Getter @Setter private String matcher; @Getter @Setter private String matcher;
public boolean hasMatcher() { return matcher != null; } public boolean hasMatcher() { return matcher != null; }


@ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=40)
@ECSearchable(fkDepth=ECForeignKeySearchDepth.none) @ECField(index=50)
@ECForeignKey(entity=AppSite.class) @ECForeignKey(entity=AppSite.class)
@Column(nullable=false, updatable=false, length=UUID_MAXLEN) @Column(nullable=false, updatable=false, length=UUID_MAXLEN)
@Getter @Setter private String site; @Getter @Setter private String site;
public boolean hasSite() { return site != null; } public boolean hasSite() { return site != null; }


@ECSearchable(filter=true) @ECField(index=50)
@ECSearchable(filter=true) @ECField(index=60)
@HasValue(message="err.key.required") @HasValue(message="err.key.required")
@ECIndex @Column(nullable=false, updatable=false, length=5000) @ECIndex @Column(nullable=false, updatable=false, length=5000)
@Getter @Setter private String key; @Getter @Setter private String key;
public boolean hasKey () { return key != null; } 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") @Size(max=100000, message="err.data.length")
@Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")") @Type(type=ENCRYPTED_STRING) @Column(columnDefinition="varchar("+(100000+ENC_PAD)+")")
@Getter @Setter private String data; @Getter @Setter private String data;
@@ -94,15 +100,15 @@ public class AppData extends IdentifiableBase implements AppTemplateEntity {
return setData(String.valueOf(val+1)); 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; @ECIndex @Getter @Setter private Long expiration;


@ECSearchable
@ECSearchable @ECField(index=90)
@ECIndex @Column(nullable=false) @ECIndex @Column(nullable=false)
@Getter @Setter private Boolean template = false; @Getter @Setter private Boolean template = false;
public boolean template() { return bool(template); } public boolean template() { return bool(template); }


@ECSearchable
@ECSearchable @ECField(index=100)
@ECIndex @Column(nullable=false) @ECIndex @Column(nullable=false)
@Getter @Setter private Boolean enabled = true; @Getter @Setter private Boolean enabled = true;
public boolean enabled() { return bool(enabled); } public boolean enabled() { return bool(enabled); }


+ 1
- 1
bubble-server/src/main/java/bubble/model/app/AppRule.java View File

@@ -32,7 +32,7 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD;
@ECTypeURIs(baseURI=EP_RULES, listFields={"name", "app", "driver", "configJson"}) @ECTypeURIs(baseURI=EP_RULES, listFields={"name", "app", "driver", "configJson"})
@Entity @NoArgsConstructor @Accessors(chain=true) @Entity @NoArgsConstructor @Accessors(chain=true)
@ECTypeChildren(uriPrefix=EP_RULES+"/{AppRule.name}", value={ @ECTypeChildren(uriPrefix=EP_RULES+"/{AppRule.name}", value={
@ECTypeChild(type= AppData.class, backref="rule"),
@ECTypeChild(type=AppData.class, backref="rule"),
}) })
@ECIndexes({ @ECIndexes({
@ECIndex(unique=true, of={"account", "app", "name"}), @ECIndex(unique=true, of={"account", "app", "name"}),


+ 3
- 1
bubble-server/src/main/java/bubble/model/device/Device.java View File

@@ -18,13 +18,15 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;


import static bubble.ApiConstants.EP_DEVICES;
import static java.util.UUID.randomUUID; import static java.util.UUID.randomUUID;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; import static org.cobbzilla.util.reflect.ReflectionUtil.copy;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING; import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENCRYPTED_STRING;
import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD; 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) @NoArgsConstructor @Accessors(chain=true)
@ECIndexes({ @ECIndexes({
@ECIndex(unique=true, of={"account", "network", "name"}), @ECIndex(unique=true, of={"account", "network", "name"}),


+ 5
- 14
bubble-server/src/main/java/bubble/resources/BubbleMagicResource.java View File

@@ -1,6 +1,5 @@
package bubble.resources; package bubble.resources;


import bubble.resources.driver.DriverAssetsResource;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import bubble.service.cloud.RequestCoordinationService; import bubble.service.cloud.RequestCoordinationService;
import lombok.extern.slf4j.Slf4j; 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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.Context;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;


import static bubble.ApiConstants.BUBBLE_MAGIC_ENDPOINT; 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.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;
import static org.cobbzilla.wizard.resources.ResourceUtil.ok;


@Consumes(APPLICATION_JSON) @Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON) @Produces(APPLICATION_JSON)
@@ -31,14 +32,4 @@ public class BubbleMagicResource {
return ok("you are ok. the magic is ok too."); 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);
}

} }

+ 7
- 0
bubble-server/src/main/java/bubble/resources/app/DataResourceBase.java View File

@@ -2,11 +2,13 @@ package bubble.resources.app;


import bubble.dao.account.AccountDAO; import bubble.dao.account.AccountDAO;
import bubble.dao.app.*; import bubble.dao.app.*;
import bubble.dao.device.DeviceDAO;
import bubble.model.account.Account; import bubble.model.account.Account;
import bubble.model.app.AppData; import bubble.model.app.AppData;
import bubble.model.app.AppMatcher; import bubble.model.app.AppMatcher;
import bubble.model.app.AppSite; import bubble.model.app.AppSite;
import bubble.model.app.BubbleApp; import bubble.model.app.BubbleApp;
import bubble.model.device.Device;
import bubble.resources.account.AccountOwnedTemplateResource; import bubble.resources.account.AccountOwnedTemplateResource;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -32,6 +34,7 @@ public abstract class DataResourceBase extends AccountOwnedTemplateResource<AppD


@Autowired protected BubbleConfiguration configuration; @Autowired protected BubbleConfiguration configuration;
@Autowired protected AccountDAO accountDAO; @Autowired protected AccountDAO accountDAO;
@Autowired protected DeviceDAO deviceDAO;
@Autowired protected BubbleAppDAO appDAO; @Autowired protected BubbleAppDAO appDAO;
@Autowired protected AppDataDAO dataDAO; @Autowired protected AppDataDAO dataDAO;
@Autowired protected AppRuleDAO ruleDAO; @Autowired protected AppRuleDAO ruleDAO;
@@ -85,6 +88,10 @@ public abstract class DataResourceBase extends AccountOwnedTemplateResource<AppD
if (app == null) throw notFoundEx(request.getApp()); if (app == null) throw notFoundEx(request.getApp());
request.setApp(app.getUuid()); request.setApp(app.getUuid());


final Device device = deviceDAO.findByAccountAndId(caller.getUuid(), request.getDevice());
if (device == null) throw notFoundEx(request.getDevice());
request.setDevice(device.getUuid());

final AppSite site = siteDAO.findByAccountAndId(caller.getUuid(), request.getSite()); final AppSite site = siteDAO.findByAccountAndId(caller.getUuid(), request.getSite());
if (site == null) throw notFoundEx(request.getSite()); if (site == null) throw notFoundEx(request.getSite());
request.setSite(site.getUuid()); request.setSite(site.getUuid());


+ 0
- 76
bubble-server/src/main/java/bubble/resources/driver/DriverAssetsResource.java View File

@@ -1,76 +0,0 @@
package bubble.resources.driver;

import bubble.dao.app.AppDataDAO;
import bubble.model.app.AppData;
import bubble.rule.RuleConfig;
import bubble.rule.RuleConfigBase;
import bubble.rule.AppRuleDriver;
import bubble.service.cloud.RequestCoordinationService;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.wizard.stream.ByteStreamingOutput;
import org.cobbzilla.wizard.stream.SendableResource;
import org.glassfish.jersey.server.ContainerRequest;
import org.springframework.beans.factory.annotation.Autowired;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

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.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.http.HttpContentTypes.contentType;
import static org.cobbzilla.util.json.JsonUtil.FULL_MAPPER_ALLOW_COMMENTS_AND_UNKNOWN_FIELDS;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate;
import static org.cobbzilla.wizard.resources.ResourceUtil.*;

@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@Slf4j
public class DriverAssetsResource {

@Autowired private AppDataDAO dataDAO;
@Autowired private RequestCoordinationService requestService;

@Getter private String requestId;
@Getter private String driverClass;
@Getter(lazy=true) private final String json = requestService.get(getDriverClass(), getRequestId());

public DriverAssetsResource (String requestId, String driverClass) {
this.requestId = requestId;
this.driverClass = driverClass;
}

@Getter(lazy=true) private final AppRuleDriver driver = instantiate(getDriverClass());

@GET @Path(EP_ASSETS+"/{path}")
public Response get(@Context ContainerRequest ctx,
@PathParam("path") String path) {
final byte[] bytes = getDriver().locateBinaryResource(path);
if (bytes == null) return notFound(path);
return send(new SendableResource(new ByteStreamingOutput(bytes))
.setContentType(contentType(path))
.setContentLength((long) bytes.length));
}

@Getter(lazy=true) private final RuleConfig ruleConfig = initRuleConfig();
private RuleConfig initRuleConfig() {
try {
return json(getJson(), RuleConfigBase.class, FULL_MAPPER_ALLOW_COMMENTS_AND_UNKNOWN_FIELDS);
} catch (Exception e) {
return die("initRuleConfig: "+e);
}
}

@GET @Path(EP_DATA)
public Response get(@Context ContainerRequest ctx,
@QueryParam("key") String key,
@QueryParam("value") String value) {
final RuleConfig config = getRuleConfig();
return ok(dataDAO.set(new AppData(config).setKey(key).setData(value)));
}

}

+ 45
- 6
bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java View File

@@ -10,7 +10,7 @@ import bubble.model.app.AppDataFormat;
import bubble.model.app.AppMatcher; import bubble.model.app.AppMatcher;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.service.cloud.DeviceIdService; import bubble.service.cloud.DeviceIdService;
import bubble.service.stream.RuleEngine;
import bubble.service.stream.RuleEngineService;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.ArrayUtil; import org.cobbzilla.util.collection.ArrayUtil;
@@ -52,7 +52,7 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.*;
public class FilterHttpResource { public class FilterHttpResource {


@Autowired private AccountDAO accountDAO; @Autowired private AccountDAO accountDAO;
@Autowired private RuleEngine ruleEngine;
@Autowired private RuleEngineService ruleEngine;
@Autowired private AppMatcherDAO matcherDAO; @Autowired private AppMatcherDAO matcherDAO;
@Autowired private DeviceDAO deviceDAO; @Autowired private DeviceDAO deviceDAO;
@Autowired private DeviceIdService deviceIdService; @Autowired private DeviceIdService deviceIdService;
@@ -281,23 +281,62 @@ public class FilterHttpResource {
@PathParam("requestId") String requestId, @PathParam("requestId") String requestId,
@PathParam("matcherId") String matcherId, @PathParam("matcherId") String matcherId,
AppData data) { AppData data) {

if (data == null || !data.hasKey()) throw invalidEx("err.key.required"); if (data == null || !data.hasKey()) throw invalidEx("err.key.required");
if (log.isDebugEnabled()) log.debug("writeData: received data="+json(data, COMPACT_MAPPER));
return ok(writeData(req, requestId, matcherId, data));
}

@GET @Path(EP_DATA+"/{requestId}/{matcherId}"+EP_WRITE)
@Produces(APPLICATION_JSON)
public Response writeData(@Context Request req,
@Context ContainerRequest ctx,
@PathParam("requestId") String requestId,
@PathParam("matcherId") String matcherId,
@QueryParam(Q_DATA) String dataJson,
@QueryParam(Q_REDIRECT) String redirectLocation) {
if (empty(dataJson)) throw invalidEx("err.data.required");
final AppData data;
try {
data = json(dataJson, AppData.class);
} catch (Exception e) {
if (log.isDebugEnabled()) log.debug("writeData: invalid data="+dataJson+": "+shortError(e));
throw invalidEx("err.data.invalid");
}
if (!data.hasKey()) throw invalidEx("err.key.required");

final FilterDataContext fdc = writeData(req, requestId, matcherId, data);

if (!empty(redirectLocation)) {
if (redirectLocation.trim().equalsIgnoreCase(Boolean.FALSE.toString())) {
return ok(data);
} else {
return redirect(redirectLocation);
}
} else {
final String referer = req.getHeader("Referer");
if (referer != null) return redirect(referer);
return redirect(".");
}
}

private FilterDataContext writeData(Request req, String requestId, String matcherId, AppData data) {
if (log.isDebugEnabled()) log.debug("writeData: received data=" + json(data, COMPACT_MAPPER));
final FilterDataContext fdc = new FilterDataContext(req, requestId, matcherId); final FilterDataContext fdc = new FilterDataContext(req, requestId, matcherId);


data.setAccount(fdc.request.getAccount().getUuid()); data.setAccount(fdc.request.getAccount().getUuid());
data.setDevice(fdc.request.getDevice().getUuid());
data.setApp(fdc.matcher.getApp()); data.setApp(fdc.matcher.getApp());
data.setSite(fdc.matcher.getSite()); data.setSite(fdc.matcher.getSite());
data.setMatcher(fdc.matcher.getUuid()); data.setMatcher(fdc.matcher.getUuid());


if (log.isDebugEnabled()) log.debug("writeData: recording data="+json(data, COMPACT_MAPPER));
return ok(dataDAO.set(data));
if (log.isDebugEnabled()) log.debug("writeData: recording data=" + json(data, COMPACT_MAPPER));
fdc.data = dataDAO.set(data);
return fdc;
} }


private class FilterDataContext { private class FilterDataContext {
public FilterHttpRequest request; public FilterHttpRequest request;
public AppMatcher matcher; public AppMatcher matcher;
public AppData data;


public FilterDataContext(Request req, String requestId, String matcherId) { public FilterDataContext(Request req, String requestId, String matcherId) {
// only mitmproxy is allowed to call us, and this should always be a local address // only mitmproxy is allowed to call us, and this should always be a local address


+ 14
- 3
bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java View File

@@ -7,7 +7,7 @@ import bubble.model.app.AppMatcher;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import bubble.service.cloud.DeviceIdService; import bubble.service.cloud.DeviceIdService;
import bubble.service.stream.RuleEngine;
import bubble.service.stream.RuleEngineService;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.http.URIBean; import org.cobbzilla.util.http.URIBean;
@@ -29,9 +29,11 @@ import java.util.TreeSet;


import static bubble.ApiConstants.PROXY_ENDPOINT; import static bubble.ApiConstants.PROXY_ENDPOINT;
import static bubble.ApiConstants.getRemoteHost; import static bubble.ApiConstants.getRemoteHost;
import static java.util.UUID.randomUUID;
import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY; import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.string.StringUtil.EMPTY_ARRAY; import static org.cobbzilla.util.string.StringUtil.EMPTY_ARRAY;
import static org.cobbzilla.wizard.resources.ResourceUtil.*; import static org.cobbzilla.wizard.resources.ResourceUtil.*;


@@ -42,8 +44,9 @@ public class ReverseProxyResource {
@Autowired private BubbleConfiguration configuration; @Autowired private BubbleConfiguration configuration;
@Autowired private AppMatcherDAO matcherDAO; @Autowired private AppMatcherDAO matcherDAO;
@Autowired private AppRuleDAO ruleDAO; @Autowired private AppRuleDAO ruleDAO;
@Autowired private RuleEngine ruleEngine;
@Autowired private RuleEngineService ruleEngine;
@Autowired private DeviceIdService deviceIdService; @Autowired private DeviceIdService deviceIdService;
@Autowired private FilterHttpResource filterHttpResource;


@Getter(lazy=true) private final int prefixLength = configuration.getHttp().getBaseUri().length() + PROXY_ENDPOINT.length() + 1; @Getter(lazy=true) private final int prefixLength = configuration.getHttp().getBaseUri().length() + PROXY_ENDPOINT.length() + 1;


@@ -79,8 +82,16 @@ public class ReverseProxyResource {
} }
} }


final FilterHttpRequest filterRequest = new FilterHttpRequest()
.setId(randomUUID().toString())
.setAccount(account)
.setDevice(device)
.setMatchers(matcherIds.toArray(EMPTY_ARRAY));

filterHttpResource.getActiveRequestCache().set(filterRequest.getId(), json(filterRequest));

// if 'rules' is null or empty, this will passthru // if 'rules' is null or empty, this will passthru
return ruleEngine.applyRulesAndSendResponse(request, account, device, ub, matcherIds.toArray(EMPTY_ARRAY));
return ruleEngine.applyRulesAndSendResponse(request, ub, filterRequest);
} }
} }




+ 0
- 38
bubble-server/src/main/java/bubble/resources/stream/SetAppDataResource.java View File

@@ -1,38 +0,0 @@
package bubble.resources.stream;

import bubble.dao.app.AppDataDAO;
import bubble.model.app.AppData;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.string.Base64;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ContainerResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import java.io.IOException;

import static bubble.ApiConstants.DATA_ENDPOINT;
import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.wizard.resources.ResourceUtil.ok;

@Path(DATA_ENDPOINT)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@Service @Slf4j
public class SetAppDataResource {

@Autowired private AppDataDAO dataDAO;

@GET @Path("/{appDataBase64: .+}")
public Response get(@Context ContainerRequest request,
@Context ContainerResponse response,
@PathParam("appDataBase64") String appDataBase64) throws IOException {
final AppData appData = json(new String(Base64.decode(appDataBase64)), AppData.class);
return ok(dataDAO.set(appData));
}

}

+ 3
- 0
bubble-server/src/main/java/bubble/rule/AbstractAppRuleDriver.java View File

@@ -33,6 +33,9 @@ public abstract class AbstractAppRuleDriver implements AppRuleDriver {


public Handlebars getHandlebars () { return configuration.getHandlebars(); } public Handlebars getHandlebars () { return configuration.getHandlebars(); }


protected String getDataId(String requestId) { return getDataId(requestId, matcher); }
public static String getDataId(String requestId, AppMatcher matcher) { return requestId+"/"+matcher.getUuid(); }

@Override public void init(JsonNode config, @Override public void init(JsonNode config,
JsonNode userConfig, JsonNode userConfig,
AppRule rule, AppRule rule,


+ 2
- 2
bubble-server/src/main/java/bubble/rule/social/block/JsUserBlocker.java View File

@@ -34,7 +34,7 @@ public class JsUserBlocker extends AbstractAppRuleDriver {


@Override public InputStream doFilterResponse(String requestId, InputStream in) { @Override public InputStream doFilterResponse(String requestId, InputStream in) {
final String replacement = "<head><script>" + getBubbleJs(requestId) + "</script>"; final String replacement = "<head><script>" + getBubbleJs(requestId) + "</script>";
final RegexReplacementFilter filter = new RegexReplacementFilter("<head>", 0, replacement);
final RegexReplacementFilter filter = new RegexReplacementFilter("<head>", replacement);
final RegexFilterReader reader = new RegexFilterReader(new InputStreamReader(in), filter).setMaxMatches(1); final RegexFilterReader reader = new RegexFilterReader(new InputStreamReader(in), filter).setMaxMatches(1);
return new ReaderInputStream(reader, UTF8cs); 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_REQUEST_ID, requestId);
ctx.put(CTX_BUBBLE_HOME, configuration.getPublicUriBase()); ctx.put(CTX_BUBBLE_HOME, configuration.getPublicUriBase());
ctx.put(CTX_SITE, getSiteName(matcher)); 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); final String siteJs = HandlebarsUtil.apply(getHandlebars(), getSiteJsTemplate(), ctx);
ctx.put(CTX_APPLY_BLOCKS_JS, siteJs); ctx.put(CTX_APPLY_BLOCKS_JS, siteJs);


+ 1
- 13
bubble-server/src/main/java/bubble/rule/social/block/UserBlocker.java View File

@@ -15,8 +15,6 @@ import java.io.InputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.string.StringUtil.UTF8cs; 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); } protected UserBlockerConfig configObject() { return json(getFullConfig(), UserBlockerConfig.class); }


@Override public InputStream doFilterResponse(String requestId, InputStream in) { @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.configure(getFullConfig());
filter.setDataDAO(appDataDAO); filter.setDataDAO(appDataDAO);
RegexFilterReader reader = new RegexFilterReader(in, RESPONSE_BUFSIZ, filter).setName("mainFilterReader"); RegexFilterReader reader = new RegexFilterReader(in, RESPONSE_BUFSIZ, filter).setName("mainFilterReader");
@@ -99,16 +97,6 @@ public class UserBlocker extends AbstractAppRuleDriver {
return new ReaderInputStream(reader, UTF8cs); 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 startElementRegex(String el) { return "(<\\s*" + el + "[^>]*>)"; }
protected String endElementRegex(final String el) { return "(<\\s*/\\s*" + el + "\\s*[^>]*>)"; } protected String endElementRegex(final String el) { return "(<\\s*/\\s*" + el + "\\s*[^>]*>)"; }




+ 12
- 13
bubble-server/src/main/java/bubble/rule/social/block/UserBlockerStreamFilter.java View File

@@ -1,13 +1,11 @@
package bubble.rule.social.block; package bubble.rule.social.block;


import bubble.ApiConstants;
import bubble.BubbleHandlebars; import bubble.BubbleHandlebars;
import bubble.dao.app.AppDataDAO; import bubble.dao.app.AppDataDAO;
import bubble.model.app.AppData; import bubble.model.app.AppData;
import bubble.model.app.AppMatcher; import bubble.model.app.AppMatcher;
import bubble.model.app.AppRule; import bubble.model.app.AppRule;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.ning.http.util.Base64;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.collection.ExpirationEvictionPolicy; import org.cobbzilla.util.collection.ExpirationEvictionPolicy;
@@ -22,10 +20,13 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; 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 bubble.rule.social.block.UserBlockerConfig.STANDARD_JS_ENGINE;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER;
import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.string.StringUtil.urlEncode;


@Slf4j @Slf4j
public class UserBlockerStreamFilter implements RegexStreamFilter { public class UserBlockerStreamFilter implements RegexStreamFilter {
@@ -39,12 +40,14 @@ public class UserBlockerStreamFilter implements RegexStreamFilter {
private String requestId; private String requestId;
private AppMatcher matcher; private AppMatcher matcher;
private AppRule rule; private AppRule rule;
private String apiBase;
@Setter private AppDataDAO dataDAO; @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.requestId = requestId;
this.matcher = matcher; this.matcher = matcher;
this.rule = rule; this.rule = rule;
this.apiBase = apiBase;
} }


private enum UserBlockerStreamState { seeking_comments, blocking_comments } 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 String userId = data.getProperty(PROP_USER_ID);
final AppData appData = new AppData() final AppData appData = new AppData()
.setAccount(rule.getAccount())
.setApp(rule.getApp())
.setMatcher(matcher.getUuid())
.setSite(matcher.getSite())
.setKey(userId) .setKey(userId)
.setData(Boolean.TRUE.toString()); .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<String, Object> ctx = new HashMap<>(data.getProperties()); final Map<String, Object> ctx = new HashMap<>(data.getProperties());
ctx.put(PROP_BLOCK_URL, blockUrl); ctx.put(PROP_BLOCK_URL, blockUrl);
return config.getCommentDecorator().decorate(BubbleHandlebars.instance.getHandlebars(), data.getData(), ctx); return config.getCommentDecorator().decorate(BubbleHandlebars.instance.getHandlebars(), data.getData(), ctx);
@@ -131,13 +132,11 @@ public class UserBlockerStreamFilter implements RegexStreamFilter {
if (!config.hasBlockedCommentReplacement()) return ""; if (!config.hasBlockedCommentReplacement()) return "";
final Map<String, Object> ctx = new HashMap<>(); final Map<String, Object> ctx = new HashMap<>();
final AppData appData = new AppData() final AppData appData = new AppData()
.setAccount(rule.getAccount())
.setApp(rule.getApp())
.setMatcher(matcher.getUuid())
.setSite(matcher.getSite())
.setKey(userId) .setKey(userId)
.setData(Boolean.FALSE.toString()); .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_BLOCKED_USER, userId);
ctx.put(PROP_UNBLOCK_URL, unblockUrl); ctx.put(PROP_UNBLOCK_URL, unblockUrl);




+ 14
- 6
bubble-server/src/main/java/bubble/service/cloud/DeviceIdService.java View File

@@ -20,8 +20,7 @@ import java.util.Map;


import static bubble.ApiConstants.HOME_DIR; import static bubble.ApiConstants.HOME_DIR;
import static java.util.concurrent.TimeUnit.MINUTES; 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; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;


@Service @Slf4j @Service @Slf4j
@@ -33,7 +32,6 @@ public class DeviceIdService {
public static final FilenamePrefixFilter IP_FILE_FILTER = new FilenamePrefixFilter(IP_FILE_PREFIX); public static final FilenamePrefixFilter IP_FILE_FILTER = new FilenamePrefixFilter(IP_FILE_PREFIX);


public static final String DEVICE_FILE_PREFIX = "device_"; 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 DeviceDAO deviceDAO;
@Autowired private BubbleConfiguration configuration; @Autowired private BubbleConfiguration configuration;
@@ -44,9 +42,7 @@ public class DeviceIdService {
public Device findDeviceByIp (String ipAddr) { public Device findDeviceByIp (String ipAddr) {


if (!WG_DEVICES_DIR.exists()) { 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"); 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<Device> 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);
}
}

} }

bubble-server/src/main/java/bubble/service/stream/RuleEngine.java → bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java View File

@@ -8,6 +8,7 @@ import bubble.model.app.AppMatcher;
import bubble.model.app.AppRule; import bubble.model.app.AppRule;
import bubble.model.app.RuleDriver; import bubble.model.app.RuleDriver;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.resources.stream.FilterHttpRequest;
import bubble.resources.stream.FilterMatchersRequest; import bubble.resources.stream.FilterMatchersRequest;
import bubble.rule.AppRuleDriver; import bubble.rule.AppRuleDriver;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
@@ -52,7 +53,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;


import static bubble.client.BubbleApiClient.newHttpClientBuilder; 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.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; 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; import static org.cobbzilla.wizard.resources.ResourceUtil.send;


@Service @Slf4j @Service @Slf4j
public class RuleEngine {
public class RuleEngineService {


@Autowired private AppMatcherDAO matcherDAO; @Autowired private AppMatcherDAO matcherDAO;
@Autowired private AppRuleDAO ruleDAO; @Autowired private AppRuleDAO ruleDAO;
@@ -105,19 +105,17 @@ public class RuleEngine {
} }


public Response applyRulesAndSendResponse(ContainerRequest request, public Response applyRulesAndSendResponse(ContainerRequest request,
Account account,
Device device,
URIBean ub, URIBean ub,
String[] matcherIds) throws IOException {
FilterHttpRequest filterRequest) throws IOException {


// sanity check // 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 // 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, // 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 // since drivers that talk thru API will get a session key in their config
final List<AppRuleHarness> rules = initRules(account, device, matcherIds);
final List<AppRuleHarness> rules = initRules(filterRequest.getAccount(), filterRequest.getDevice(), filterRequest.getMatchers());
final AppRuleHarness firstRule = rules.get(0); final AppRuleHarness firstRule = rules.get(0);


// filter request // filter request
@@ -128,8 +126,7 @@ public class RuleEngine {
final CloseableHttpResponse proxyResponse = httpClient.execute(get); final CloseableHttpResponse proxyResponse = httpClient.execute(get);


// filter response. when stream is closed, close http client // 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 // send response
return sendResponse(responseEntity, proxyResponse); return sendResponse(responseEntity, proxyResponse);

+ 0
- 4
bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.css.hbs View File

@@ -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; }

+ 0
- 88
bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker.js.hbs View File

@@ -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);
}

BIN
View File


+ 0
- 10
bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor.json View File

@@ -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"}
]
}

+ 0
- 6
bubble-server/src/main/resources/bubble/rule/social/block/UserBlocker_descriptor_en_US.json View File

@@ -1,6 +0,0 @@
{
"description": "X",
"labels": [
{"name": "delete_button", "value": "Unblock User"}
]
}

+ 2
- 0
bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties View File

@@ -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.country.invalid=Country is invalid, use a valid ISO 2-letter code
err.credentialsJson.length=Credentials JSON is too long err.credentialsJson.length=Credentials JSON is too long
err.data.length=Data 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.notDelegated=Error locating payment service
err.delegatedPayment.delegateNotFound=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) err.delete.cannotDeleteSelf=You cannot delete yourself (you've still got a chance to win)


+ 7
- 3
bubble-server/src/test/java/bubble/test/ProxyTest.java View File

@@ -1,6 +1,5 @@
package bubble.test; package bubble.test;


import bubble.model.app.AppData;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import org.cobbzilla.wizard.server.RestServer; import org.cobbzilla.wizard.server.RestServer;
import org.cobbzilla.wizard.util.RestResponse; import org.cobbzilla.wizard.util.RestResponse;
@@ -9,6 +8,8 @@ import org.junit.Test;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;


@@ -35,8 +36,11 @@ public class ProxyTest extends ActivatedBubbleModelTestBase {
final Matcher matcher = pattern.matcher(response.json); final Matcher matcher = pattern.matcher(response.json);
assertTrue("expected match for user to block", matcher.find()); assertTrue("expected match for user to block", matcher.find());
final String blockUrl = matcher.group(1); 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 // second request, verify additional user blocked
modelTest("filter/user_block/hn_request2"); modelTest("filter/user_block/hn_request2");


+ 5
- 0
bubble-server/src/test/java/bubble/test/TestBubbleApiClient.java View File

@@ -3,6 +3,7 @@ package bubble.test;
import bubble.client.BubbleApiClient; import bubble.client.BubbleApiClient;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.apache.http.impl.client.HttpClientBuilder;
import org.cobbzilla.util.http.ApiConnectionInfo; import org.cobbzilla.util.http.ApiConnectionInfo;
import org.cobbzilla.wizard.auth.AuthResponse; import org.cobbzilla.wizard.auth.AuthResponse;
import org.cobbzilla.wizard.auth.LoginRequest; import org.cobbzilla.wizard.auth.LoginRequest;
@@ -34,6 +35,10 @@ public class TestBubbleApiClient extends BubbleApiClient {
setConfiguration(configuration); setConfiguration(configuration);
} }


@Override public HttpClientBuilder getHttpClientBuilder() {
return super.getHttpClientBuilder().disableRedirectHandling();
}

@Getter(lazy=true) private final String superuserToken = initSuperuserToken(); @Getter(lazy=true) private final String superuserToken = initSuperuserToken();
private String initSuperuserToken() { private String initSuperuserToken() {
final Map<String, String> env = configuration.getEnvironment(); final Map<String, String> env = configuration.getEnvironment();


+ 1
- 1
bubble-server/src/test/resources/models/apps/user_block/hn/bubbleApp_userBlock_hn_data.json View File

@@ -2,7 +2,7 @@
"name": "UserBlocker", "name": "UserBlocker",
"children": { "children": {
"AppData": [ "AppData": [
{"matcher": "HNCommentMatcher", "site": "HackerNews", "key": "rvz", "data": "true", "template": true}
{"matcher": "HNCommentMatcher", "site": "HackerNews", "key": "rvz", "data": "true", "template": true, "device": "test"}
] ]
} }
}] }]

+ 1
- 1
bubble-server/src/test/resources/models/apps/user_block/localhost/bubbleApp_userBlock_localhost_data.json View File

@@ -2,7 +2,7 @@
"name": "UserBlocker", "name": "UserBlocker",
"children": { "children": {
"AppData": [ "AppData": [
{"matcher": "LocalHNCommentMatcher", "site": "HackerNews", "key": "user2", "data": "true", "template": true}
{"matcher": "LocalHNCommentMatcher", "site": "HackerNews", "key": "user2", "data": "true", "template": true, "device": "test"}
] ]
} }
}] }]

+ 1
- 0
bubble-server/src/test/resources/models/manifest-proxy.json View File

@@ -1,5 +1,6 @@
[ [
"system/ruleDriver", "system/ruleDriver",
"system/account_testDevice",
"manifest-app-user-block-hn", "manifest-app-user-block-hn",
"manifest-app-user-block-localhost" "manifest-app-user-block-localhost"
] ]

+ 8
- 0
bubble-server/src/test/resources/models/system/account_testDevice.json View File

@@ -0,0 +1,8 @@
[
{
"name": "root",
"children": {
"Device": [ {"name": "test"} ]
}
}
]

+ 1
- 1
utils/cobbzilla-utils

@@ -1 +1 @@
Subproject commit 7f944cf93d24bd47b5a8f124abddbc34bb2ef7f3
Subproject commit 006bcd1ff4390bb37ae35d50fa12a70de7e408c1

+ 1
- 1
utils/cobbzilla-wizard

@@ -1 +1 @@
Subproject commit 4d81443257d4e51fffa35a1b4555e94608301178
Subproject commit 0e0c1f38345ec1055f8f07c3ca665fd041da27d5

Loading…
Cancel
Save