@@ -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; | ||||
@@ -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"); | ||||
@@ -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); } | ||||
@@ -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"}), | ||||
@@ -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"}), | ||||
@@ -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); | |||||
} | |||||
} | } |
@@ -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()); | ||||
@@ -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))); | |||||
} | |||||
} |
@@ -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 | ||||
@@ -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); | |||||
} | } | ||||
} | } | ||||
@@ -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)); | |||||
} | |||||
} |
@@ -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, | ||||
@@ -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); | ||||
@@ -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*[^>]*>)"; } | ||||
@@ -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); | ||||
@@ -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); | |||||
} | |||||
} | |||||
} | } |
@@ -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); |
@@ -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; } |
@@ -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); | |||||
} |
@@ -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"} | |||||
] | |||||
} |
@@ -1,6 +0,0 @@ | |||||
{ | |||||
"description": "X", | |||||
"labels": [ | |||||
{"name": "delete_button", "value": "Unblock User"} | |||||
] | |||||
} |
@@ -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) | ||||
@@ -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"); | ||||
@@ -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(); | ||||
@@ -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"} | |||||
] | ] | ||||
} | } | ||||
}] | }] |
@@ -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,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" | ||||
] | ] |
@@ -0,0 +1,8 @@ | |||||
[ | |||||
{ | |||||
"name": "root", | |||||
"children": { | |||||
"Device": [ {"name": "test"} ] | |||||
} | |||||
} | |||||
] |
@@ -1 +1 @@ | |||||
Subproject commit 7f944cf93d24bd47b5a8f124abddbc34bb2ef7f3 | |||||
Subproject commit 006bcd1ff4390bb37ae35d50fa12a70de7e408c1 |
@@ -1 +1 @@ | |||||
Subproject commit 4d81443257d4e51fffa35a1b4555e94608301178 | |||||
Subproject commit 0e0c1f38345ec1055f8f07c3ca665fd041da27d5 |