From ad247a2458f716ec2ad5c1d42a17a878fbdb826e Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Mon, 10 Feb 2020 00:56:12 -0500 Subject: [PATCH] refactor FilterDataResource from FilterHttpResource, add FilterAssetsResource --- .../src/main/java/bubble/ApiConstants.java | 2 +- .../stream/FilterAssetsResource.java | 41 ++++++ .../resources/stream/FilterDataResource.java | 122 +++++++++++++++++ .../resources/stream/FilterHttpRequest.java | 7 + .../resources/stream/FilterHttpResource.java | 128 +++++------------- 5 files changed, 205 insertions(+), 95 deletions(-) create mode 100644 bubble-server/src/main/java/bubble/resources/stream/FilterAssetsResource.java create mode 100644 bubble-server/src/main/java/bubble/resources/stream/FilterDataResource.java diff --git a/bubble-server/src/main/java/bubble/ApiConstants.java b/bubble-server/src/main/java/bubble/ApiConstants.java index d1394377..a2565740 100644 --- a/bubble-server/src/main/java/bubble/ApiConstants.java +++ b/bubble-server/src/main/java/bubble/ApiConstants.java @@ -181,10 +181,10 @@ public class ApiConstants { public static final String SEARCH_ENDPOINT = "/search"; public static final String DEBUG_ENDPOINT = "/debug"; public static final String BUBBLE_MAGIC_ENDPOINT = "/.bubble"; - public static final String EP_ASSETS = "/assets"; public static final String FILTER_HTTP_ENDPOINT = "/filter"; public static final String EP_APPLY = "/apply"; + public static final String EP_ASSETS = "/assets"; // requests to a first-party host with this prefix will be forwarded to bubble public static final String BUBBLE_FILTER_PASSTHRU = "/__bubble"; diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterAssetsResource.java b/bubble-server/src/main/java/bubble/resources/stream/FilterAssetsResource.java new file mode 100644 index 00000000..52aaf012 --- /dev/null +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterAssetsResource.java @@ -0,0 +1,41 @@ +package bubble.resources.stream; + +import bubble.dao.app.AppMessageDAO; +import bubble.model.account.Account; +import bubble.model.app.BubbleApp; +import org.glassfish.grizzly.http.server.Request; +import org.glassfish.jersey.server.ContainerRequest; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import static org.cobbzilla.wizard.resources.ResourceUtil.ok; + +public class FilterAssetsResource { + + private Account account; + private BubbleApp app; + + public FilterAssetsResource (Account account, BubbleApp app) { + this.account = account; + this.app = app; + } + + @Autowired private AppMessageDAO appMessageDAO; + + @GET @Path("/{assetId}") + @Produces(MediaType.WILDCARD) + public Response filterHttp(@Context Request req, + @Context ContainerRequest request, + @PathParam("assetId") String assetId) { + // todo: locate and send asset + return ok(); + } + +} diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterDataResource.java b/bubble-server/src/main/java/bubble/resources/stream/FilterDataResource.java new file mode 100644 index 00000000..90efde67 --- /dev/null +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterDataResource.java @@ -0,0 +1,122 @@ +package bubble.resources.stream; + +import bubble.dao.app.AppDataDAO; +import bubble.model.account.Account; +import bubble.model.app.AppData; +import bubble.model.app.AppDataFormat; +import bubble.model.app.AppMatcher; +import bubble.model.device.Device; +import lombok.extern.slf4j.Slf4j; +import org.glassfish.grizzly.http.server.Request; +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 java.util.List; +import java.util.stream.Collectors; + +import static bubble.ApiConstants.*; +import static org.cobbzilla.util.daemon.ZillaRuntime.empty; +import static org.cobbzilla.util.daemon.ZillaRuntime.shortError; +import static org.cobbzilla.util.http.HttpContentTypes.APPLICATION_JSON; +import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; +import static org.cobbzilla.util.json.JsonUtil.json; +import static org.cobbzilla.wizard.resources.ResourceUtil.*; + +@Slf4j +public class FilterDataResource { + + private Account account; + private Device device; + private AppMatcher matcher; + + public FilterDataResource (Account account, Device device, AppMatcher matcher) { + this.account = account; + this.device = device; + this.matcher = matcher; + } + + @Autowired private AppDataDAO dataDAO; + + @GET @Path(EP_READ) + @Produces(APPLICATION_JSON) + public Response readData(@Context Request req, + @Context ContainerRequest ctx, + @QueryParam("format") AppDataFormat format) { + + final List data = dataDAO.findEnabledByAccountAndAppAndSite(account.getUuid(), matcher.getApp(), matcher.getSite()); + + if (log.isDebugEnabled()) log.debug("readData: found "+data.size()+" AppData records"); + + if (format == null) format = AppDataFormat.key; + switch (format) { + case key: + return ok(data.stream().map(AppData::getKey).collect(Collectors.toList())); + case value: + return ok(data.stream().map(AppData::getData).collect(Collectors.toList())); + case key_value: + return ok(data.stream().collect(Collectors.toMap(AppData::getKey, AppData::getData))); + case full: + return ok(data); + default: + throw notFoundEx(format.name()); + } + } + + @POST @Path(EP_WRITE) + @Consumes(APPLICATION_JSON) + @Produces(APPLICATION_JSON) + public Response writeData(@Context Request req, + @Context ContainerRequest ctx, + AppData data) { + if (data == null || !data.hasKey()) throw invalidEx("err.key.required"); + return ok(writeData(data)); + } + + @GET @Path(EP_WRITE) + @Produces(APPLICATION_JSON) + public Response writeData(@Context Request req, + @Context ContainerRequest ctx, + @QueryParam(Q_DATA) String dataJson, + @QueryParam(Q_REDIRECT) String redirectLocation) { + if (empty(dataJson)) throw invalidEx("err.data.required"); + 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"); + + data = writeData(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 AppData writeData(AppData data) { + if (log.isDebugEnabled()) log.debug("writeData: received data=" + json(data, COMPACT_MAPPER)); + + data.setAccount(account.getUuid()); + data.setDevice(device.getUuid()); + data.setApp(matcher.getApp()); + data.setSite(matcher.getSite()); + data.setMatcher(matcher.getUuid()); + + if (log.isDebugEnabled()) log.debug("writeData: recording data=" + json(data, COMPACT_MAPPER)); + return dataDAO.set(data); + } + +} diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpRequest.java b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpRequest.java index 50b02bc4..c3b0e38e 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpRequest.java +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpRequest.java @@ -39,4 +39,11 @@ public class FilterHttpRequest { @JsonIgnore public String getUrl() { return !hasMatchers() || !matchersResponse.hasRequest() ? null : matchersResponse.getRequest().getUrl(); } + + public boolean hasApp(String appId) { + if (!hasMatchers()) return false; + for (AppMatcher m : getMatchers()) if (m.getApp().equals(appId)) return true; + return false; + } + } diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java index 88a4f25b..96777b03 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java @@ -1,12 +1,19 @@ package bubble.resources.stream; import bubble.dao.account.AccountDAO; -import bubble.dao.app.*; +import bubble.dao.app.AppMatcherDAO; +import bubble.dao.app.AppRuleDAO; +import bubble.dao.app.AppSiteDAO; +import bubble.dao.app.BubbleAppDAO; import bubble.dao.device.DeviceDAO; import bubble.model.account.Account; -import bubble.model.app.*; +import bubble.model.app.AppMatcher; +import bubble.model.app.AppRule; +import bubble.model.app.AppSite; +import bubble.model.app.BubbleApp; import bubble.model.device.Device; import bubble.rule.FilterMatchDecision; +import bubble.server.BubbleConfiguration; import bubble.service.cloud.DeviceIdService; import bubble.service.stream.StandardRuleEngineService; import lombok.Getter; @@ -59,9 +66,8 @@ public class FilterHttpResource { @Autowired private AppRuleDAO ruleDAO; @Autowired private DeviceDAO deviceDAO; @Autowired private DeviceIdService deviceIdService; - @Autowired private AppDataDAO dataDAO; - @Autowired private RedisService redis; + @Autowired private BubbleConfiguration configuration; private static final long ACTIVE_REQUEST_TIMEOUT = HOURS.toSeconds(4); @@ -402,113 +408,47 @@ public class FilterHttpResource { public Response passthru(@Context ContainerRequest request) { return ruleEngine.passthru(request); } - @GET @Path(EP_DATA+"/{requestId}/{matcherId}"+EP_READ) - @Produces(APPLICATION_JSON) - public Response readData(@Context Request req, - @Context ContainerRequest ctx, - @PathParam("requestId") String requestId, - @PathParam("matcherId") String matcherId, - @QueryParam("format") AppDataFormat format) { - - final FilterDataContext fdc = new FilterDataContext(req, requestId, matcherId); - final List data = dataDAO.findEnabledByAccountAndAppAndSite - (fdc.request.getAccount().getUuid(), fdc.matcher.getApp(), fdc.matcher.getSite()); - - if (log.isDebugEnabled()) log.debug("readData: found "+data.size()+" AppData records"); - - if (format == null) format = AppDataFormat.key; - switch (format) { - case key: - return ok(data.stream().map(AppData::getKey).collect(Collectors.toList())); - case value: - return ok(data.stream().map(AppData::getData).collect(Collectors.toList())); - case key_value: - return ok(data.stream().collect(Collectors.toMap(AppData::getKey, AppData::getData))); - case full: - return ok(data); - default: - throw notFoundEx(format.name()); - } - } + @Path(EP_DATA+"/{requestId}/{matcherId}") + public FilterDataResource getMatcherDataResource(@Context Request req, + @Context ContainerRequest ctx, + @PathParam("requestId") String requestId, + @PathParam("matcherId") String matcherId) { + final FilterSubContext filterCtx = new FilterSubContext(req, requestId); + if (!filterCtx.request.hasMatcher(matcherId)) throw notFoundEx(matcherId); - @POST @Path(EP_DATA+"/{requestId}/{matcherId}"+EP_WRITE) - @Consumes(APPLICATION_JSON) - @Produces(APPLICATION_JSON) - public Response writeData(@Context Request req, - @Context ContainerRequest ctx, - @PathParam("requestId") String requestId, - @PathParam("matcherId") String matcherId, - AppData data) { - if (data == null || !data.hasKey()) throw invalidEx("err.key.required"); - return ok(writeData(req, requestId, matcherId, data)); - } + final AppMatcher matcher = matcherDAO.findByAccountAndId(filterCtx.request.getAccount().getUuid(), matcherId); + if (matcher == null) throw notFoundEx(matcherId); - @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("."); - } + return configuration.subResource(FilterDataResource.class, filterCtx.request.getAccount(), filterCtx.request.getDevice(), matcher); } - 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); + @Path(EP_ASSETS+"/{requestId}/{appId}") + public FilterAssetsResource getAppAssetsResource(@Context Request req, + @Context ContainerRequest ctx, + @PathParam("requestId") String requestId, + @PathParam("appId") String appId) { + final FilterSubContext filterCtx = new FilterSubContext(req, requestId); + if (!filterCtx.request.hasApp(appId)) throw notFoundEx(appId); - data.setAccount(fdc.request.getAccount().getUuid()); - data.setDevice(fdc.request.getDevice().getUuid()); - data.setApp(fdc.matcher.getApp()); - data.setSite(fdc.matcher.getSite()); - data.setMatcher(fdc.matcher.getUuid()); + final BubbleApp app = appDAO.findByAccountAndId(filterCtx.request.getAccount().getUuid(), appId); + if (app == null) throw notFoundEx(appId); - if (log.isDebugEnabled()) log.debug("writeData: recording data=" + json(data, COMPACT_MAPPER)); - fdc.data = dataDAO.set(data); - return fdc; + return configuration.subResource(FilterAssetsResource.class, filterCtx.request.getAccount(), app); } - private class FilterDataContext { + private class FilterSubContext { public FilterHttpRequest request; - public AppMatcher matcher; - public AppData data; - public FilterDataContext(Request req, String requestId, String matcherId) { + public FilterSubContext(Request req, String requestId) { // only mitmproxy is allowed to call us, and this should always be a local address final String mitmAddr = req.getRemoteAddr(); if (!isLocalIpv4(mitmAddr)) throw forbiddenEx(); - if (empty(requestId) || empty(matcherId)) throw notFoundEx(); + if (empty(requestId)) throw notFoundEx(); request = getActiveRequest(requestId); if (request == null) throw notFoundEx(requestId); - if (!request.hasMatcher(matcherId)) throw notFoundEx(matcherId); - - matcher = matcherDAO.findByAccountAndId(request.getAccount().getUuid(), matcherId); - if (matcher == null) throw notFoundEx(matcherId); } } + } \ No newline at end of file