From 85c2c4d507d06f36f8dadc11a65b087a2828bd19 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Mon, 4 Jan 2021 20:49:04 +0000 Subject: [PATCH] unifiHack: init This package takes the upstream Unifi package, and then applies a AspectJ aspect which replaces the auth logic with stuff which checks whether there's a Pomerium header. --- nix/pkgs/default.nix | 1 + nix/pkgs/unifi-hack/aspect/MyLogin.aj | 89 ++++++++++++++ nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map | 122 ++++++++++++++++++++ nix/pkgs/unifi-hack/default.nix | 48 ++++++++ 4 files changed, 260 insertions(+) create mode 100644 nix/pkgs/unifi-hack/aspect/MyLogin.aj create mode 100644 nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map create mode 100644 nix/pkgs/unifi-hack/default.nix diff --git a/nix/pkgs/default.nix b/nix/pkgs/default.nix index ec2b0f0e80..d83a5fc6c2 100644 --- a/nix/pkgs/default.nix +++ b/nix/pkgs/default.nix @@ -18,6 +18,7 @@ envoy = import ./envoy args; deluge = import ./deluge args; grafana-plugins = import ./grafana-plugins args; + unifiHacked = import ./unifi-hack args; tiny-remapper = import ./tiny-remapper.nix args; } // (import ./heptapod-runner.nix args) // (import ./lightspeed args) diff --git a/nix/pkgs/unifi-hack/aspect/MyLogin.aj b/nix/pkgs/unifi-hack/aspect/MyLogin.aj new file mode 100644 index 0000000000..7f98c6d749 --- /dev/null +++ b/nix/pkgs/unifi-hack/aspect/MyLogin.aj @@ -0,0 +1,89 @@ +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import com.ubnt.data.Admin; +import com.ubnt.data.X; +import com.ubnt.ace.view.AuthFilter; +import com.ubnt.service.DatabaseService; +import com.ubnt.data.oOOO.C; +import com.ubnt.data.oOOO.Filter; +import com.ubnt.service.system.SystemInitializingBean; + +aspect MyLogin { + private static final boolean LOG_STUFF = false; + + private static final void log(String stuff) { + if (!LOG_STUFF) return; + SystemInitializingBean.SYSTEM_LOGGER.error(stuff); + } + + static final X doLoginByHeader(HttpServletRequest request, HttpServletResponse response, X context) { + log("in doLoginByHeader"); + String currentEmail = ""; + if (context != null) { + Admin adminData = (Admin)context.getX("admin", X.EMPTY_INSTANCE); + currentEmail = adminData.getString("email"); + } + log("currentEmail: " + currentEmail); + String proxyEmail = request.getHeader("x-pomerium-claim-email"); + if (proxyEmail == null) { + proxyEmail = ""; + } + log("proxyEmail: " + proxyEmail); + if (!currentEmail.equals(proxyEmail)) { + // We're not logged in as who we should be. + if (proxyEmail.equals("")) { + // We should be logged out. + log("logging out"); + AuthFilter.getInner().logout(request, response); + return null; + } else { + // We should be logged in as somebody. + if (!currentEmail.equals("")) { + log("logging out first"); + // We have an existing session; log out first. + AuthFilter.getInner().logout(request, response); + } + + // Now log in as proxyEmail. + Admin newAdmin = null; + for (final Admin admin : DatabaseService.database().queryList(Admin.class, new C.Factory().or(Filter.equals("name", proxyEmail), Filter.equals("email", proxyEmail)).create())) { + newAdmin = admin; break; + } + if (newAdmin == null) { + // TODO: make this create a new user account? + log("couldn't find admin user email " + proxyEmail); + throw new NoMatchingUser(); + } + log("got admin with email " + proxyEmail); + return AuthFilter.getInner().createSession(newAdmin, false, false, request, response); + } + } + return context; + } + + pointcut authFilter(ServletRequest request, ServletResponse response, FilterChain filterChain): within(com.ubnt.ace.view.AuthFilter) && execution(void com.ubnt.ace.view.AuthFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)) && args(request, response, filterChain); + pointcut thePart(HttpServletRequest httpRequest, ServletRequest request, ServletResponse response, FilterChain filterChain): within(com.ubnt.ace.view.AuthFilter) && call(X com.ubnt.ace.view.AuthFilter.inner.getAnXGivenARequestSomehow(HttpServletRequest)) && args(httpRequest) && cflow(authFilter(request, response, filterChain)); + + // Wrap the part where we fetch the current user. + X around(HttpServletRequest httpRequest, ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain): thePart(httpRequest, servletRequest, servletResponse, filterChain) { + X originalContext = proceed(httpRequest, servletRequest, servletResponse, filterChain); + return doLoginByHeader((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse, originalContext); + } + + // Add some extra exception handling. + void around(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws java.io.IOException: authFilter(servletRequest, servletResponse, filterChain) { + try { + proceed(servletRequest, servletResponse, filterChain); + } catch (NoMatchingUser e) { + HttpServletResponse response = (HttpServletResponse)servletResponse; + response.sendError(HttpServletResponse.SC_FORBIDDEN, "You probably don't have a user account with a matching email."); + } + } + + public static class NoMatchingUser extends java.lang.RuntimeException {} + +} diff --git a/nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map b/nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map new file mode 100644 index 0000000000..913fbdec60 --- /dev/null +++ b/nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map @@ -0,0 +1,122 @@ +tiny 2 0 intermediary named +c com/ubnt/ace/api/ApiUtils com/ubnt/ace/api/ApiUtils +c com/ubnt/data/X com/ubnt/data/X + f Ljava/lang/String; Ô00000 ATTR_PREFIX_X + f Lorg/slf4j/Logger; õ00000 LOGGER + f Ljava/lang/String; Õo0000 MONGO_GTE + f Ljava/lang/String; ÕÒ0000 MONGO_ALL + f Ljava/lang/String; o00000 ATTR_NO_DELETE + f Ljava/lang/String; õO0000 ATTR_HIDDEN_ID + f Ljava/lang/String; ö00000 ATTR_HIDDEN + f Ljava/util/List; ÕO0000 EMPTY_LIST + f Ljava/lang/String; ôO0000 MONGO_INC + f Ljava/lang/String; ÔÒ0000 MONGO_EXISTS + f Ljava/lang/String; ôo0000 MONGO_IN + f Ljava/util/List; oÒ0000 EMPTY_INTEGER_LIST + f Ljava/lang/String; public ATTR_NO_EDIT + f Lcom/ubnt/data/X; ÖÒ0000 EMPTY_INSTANCE + f Ljava/lang/String; voidnew MONGO_TYPE + f Ljava/lang/String; öÒ0000 MONGO_PULL + f Ljava/lang/String; interfacesuper MONGO_REGEX + f Ljava/lang/String; õÒ0000 MONGO_SET + f Ljava/lang/String; ÒO0000 MONGO_RENAME + f Ljava/lang/String; Object MONGO_NE + f Lcom/fasterxml/jackson/databind/ObjectMapper; Õ00000 JACKSON_OBJECTMAPPER + f Ljava/lang/String; õo0000 MONGO_OR + f Ljava/util/List; ÖO0000 EMPTY_CLASS_LIST + f Ljava/lang/String; intsuper ID_ATTR_NAME + f Ljava/lang/String; oO0000 MONGO_NIN + f Ljava/lang/String; dosuper MONGO_LT + f Ljava/lang/String; ô00000 MONGO_UNSET + f Ljava/lang/String; float MONGO_GT + f Ljava/util/List; supersuper EMPTY_STRING_LIST +c com/ubnt/data/oOOO/o0OO com/ubnt/data/oOOO/Filter + m (Ljava/lang/String;Ljava/lang/Object;)Lcom/ubnt/data/oOOO/o0OO; Õ00000 equals +c com/ubnt/ace/void com/ubnt/ace/void + m (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; õ00000 hashPassword + m (Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;Ljava/lang/String;)V o00000 setCookie + m (Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;Ljava/lang/String;Z)V o00000 setCookie +c com/ubnt/ace/api/ApiServlet com/ubnt/ace/api/ApiServlet + m (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/ace/api/B; o00000 serviceAuthless + m (Ljava/lang/String;)Z o00000 skipAuthForPath +c com/ubnt/service/system/OOOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO com/ubnt/service/system/OOOoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + m (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lcom/ubnt/data/X;)V o00000 addToCache +c com/ubnt/data/oOOO/C com/ubnt/data/oOOO/C +c com/ubnt/data/oOOO/C$_o com/ubnt/data/oOOO/C$Factory + m (Ljava/lang/String;Ljava/lang/Object;)Lcom/ubnt/data/oOOO/C$_o; Ô00000 equals + m ()Lcom/ubnt/data/oOOO/C; super create + m ([Lcom/ubnt/data/oOOO/o0OO;)Lcom/ubnt/data/oOOO/C$_o; Ò00000 or +c com/ubnt/service/system/O00o com/ubnt/service/system/DatabaseBean + m (Ljava/lang/Class;Lcom/ubnt/data/oOOO/C;)V return deleteFromDB + m (Ljava/lang/Class;Lcom/ubnt/data/oOOO/C;)Ljava/util/List; new queryList +c com/ubnt/service/OoOO com/ubnt/service/DatabaseService + m ()Lcom/ubnt/service/system/O00o; õ00000 database +c com/ubnt/ace/view/AuthFilter com/ubnt/ace/view/AuthFilter + m (Ljavax/servlet/http/HttpServletRequest;)V Ô00000 checkDemoRequest + f [Ljava/lang/String; Ò00000 skipAuthUcorePaths + m (Ljavax/servlet/http/HttpServletRequest;Lcom/ubnt/data/X;)Lcom/ubnt/data/X; o00000 makeContext + f Lcom/ubnt/ace/view/AuthFilter$_Oo; Ø00000 innerInstance + f Lorg/slf4j/Logger; õ00000 logger + m ()Lcom/ubnt/ace/view/AuthFilter$_Oo; new getInner + f Ljava/lang/String; ø00000 DEMO + f Ljava/lang/String; null CONTEXT + f [Ljava/lang/String; Ö00000 skipAuthPaths + f J ö00000 thingyVersion + m (Ljavax/servlet/http/HttpServletRequest;)Z new isDemoRequest + f [Ljava/lang/String; Õ00000 requireAuthPaths + m (Ljava/lang/String;)Z o00000 authRequired +c com/ubnt/ace/view/AuthFilter$_Oo com/ubnt/ace/view/AuthFilter$inner + m (Ljavax/servlet/http/HttpServletRequest;)Lcom/ubnt/data/X; o00000 getAnXGivenARequestSomehow + m (Lcom/ubnt/data/Admin;ZZLjava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; Ò00000 createSSession + p 3 bl2 strict + p 2 bl remember + p 4 string accessToken + m (Ljava/lang/String;Lcom/ubnt/data/X;)V Õ00000 saveAccessTokenToCache + p 1 string accessToken + f Ljava/lang/String; Ô00000 SESSION_COOKIE_NAME + m (Ljava/lang/String;)Lcom/ubnt/data/X; float checkPrivilege + p 1 string adminId + f Ljava/lang/String; Ò00000 CSRF_TOKEN_NAME + m (Ljava/lang/String;Ljava/lang/String;Ljava/util/Optional;)Lcom/ubnt/data/Admin; o00000 login + p 2 string2 password + p 3 optional twoFAToken + p 1 string1 username + m (Lcom/ubnt/data/Admin;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSessionMaybe + m (Ljava/lang/String;)Lcom/ubnt/data/X; o00000 loadSessionById + m (Lcom/ubnt/data/Admin;ZZLjava/lang/String;Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSession + p 2 bl remember + p 3 bl2 strict + p 4 string accessToken + m (Lcom/ubnt/data/Admin;ZZLjavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)Lcom/ubnt/data/X; o00000 createSession + p 2 boolean2 remember + p 3 boolean3 strict + m (Ljava/lang/String;)Lcom/ubnt/data/X; ö00000 invalidateAccessToken + m (Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V o00000 logout +c com/ubnt/ace/api/for com/ubnt/ace/api/ApiException + f Lcom/ubnt/ace/api/for; Ô00000 NotFound + f Lcom/ubnt/data/X; ÕO0000 errorData + f Lcom/ubnt/ace/api/for; Ö00000 NotImplemented + f Lcom/ubnt/ace/api/for; oO0000 DeviceBusy + f Lcom/ubnt/ace/api/for; supersuper NoApplicableDevices + f Lcom/ubnt/ace/api/for; o00000 NoDelete + f Lcom/ubnt/ace/api/for; ÖO0000 LoginRequired + f Lcom/ubnt/ace/api/for; OO0000 NotSupported + f Lcom/ubnt/ace/api/for; ö00000 NotCompatible + f Lcom/ubnt/ace/api/for; õO0000 NoEdit + m ()Lcom/ubnt/data/X; o00000 getErrorData + f Lcom/ubnt/ace/api/for; Ò00000 UpgradeInProgress + f Lcom/ubnt/ace/api/for; Oo0000 InvalidArgs + f Lcom/ubnt/ace/api/for; public EmptyGoogleApiKey + f Lcom/ubnt/ace/api/for; classsuper IdInvalid + f Lcom/ubnt/ace/api/for; void Invalid + f Lcom/ubnt/ace/api/for; ô00000 InvalidLogin + f Lcom/ubnt/ace/api/for; float NoPermission + f Lcom/ubnt/ace/api/for; ÔO0000 MissingUbbDevice + f Lcom/ubnt/ace/api/for; Õ00000 IdRequired + f Lcom/ubnt/ace/api/for; ôO0000 NotUDM + f Lcom/ubnt/ace/api/for; õ00000 NotCloudKey + f Lcom/ubnt/ace/api/for; öO0000 InvalidPayload + f Lcom/ubnt/ace/api/for; ÒO0000 LocateSiteFailed + f Lcom/ubnt/ace/api/for; Object NoSuchCommand +c com/ubnt/service/system/O0oO com/ubnt/service/system/SystemInitializingBean + f Lorg/slf4j/Logger; õo0000 SYSTEM_LOGGER diff --git a/nix/pkgs/unifi-hack/default.nix b/nix/pkgs/unifi-hack/default.nix new file mode 100644 index 0000000000..69f6fa1ec9 --- /dev/null +++ b/nix/pkgs/unifi-hack/default.nix @@ -0,0 +1,48 @@ +{ depot, pkgs, ... }: +let + inherit (pkgs) stdenvNoCC jdk aspectj unifi; +in +stdenvNoCC.mkDerivation { + pname = "unifi-hack"; + version = "depot"; + + src = ./aspect; + + nativeBuildInputs = [ jdk aspectj depot.pkgs.tiny-remapper ]; + + buildPhase = '' + mkdir $NIX_BUILD_TOP/tmp + + cp $(type -p ajc) $NIX_BUILD_TOP/tmp/ajc + substituteInPlace $NIX_BUILD_TOP/tmp/ajc \ + --replace '-Xmx64M' "" + + ajc_classpath="$(ls ${unifi}/lib/*.jar | tr '\n' ':' | sed 's/:$//')" + tiny-remapper ${unifi}/lib/ace.jar $NIX_BUILD_TOP/tmp/acedeobf.jar ./ace.jar.tiny2map intermediary named + $NIX_BUILD_TOP/tmp/ajc -8 \ + -classpath "${aspectj}/lib/aspectjrt.jar:$ajc_classpath" \ + -inpath $NIX_BUILD_TOP/tmp/acedeobf.jar \ + -outjar $NIX_BUILD_TOP/tmp/aceaspected.jar \ + ./MyLogin.aj + tiny-remapper $NIX_BUILD_TOP/tmp/aceaspected.jar $NIX_BUILD_TOP/tmp/acereobf.jar ./ace.jar.tiny2map named intermediary + + # Repack the manifest + pushd $NIX_BUILD_TOP/tmp + jar xf acereobf.jar META-INF/MANIFEST.MF + awk \ + '/^[^ ]/ { inclasspath=0 } /Class-Path:/ { inclasspath=1 } { if (inclasspath) print; }' \ + META-INF/MANIFEST.MF > classpath.MF + substituteInPlace classpath.MF \ + --replace 'Class-Path: ' 'Class-Path: aspectjrt.jar ' + jar -ufm acereobf.jar classpath.MF + popd + ''; + + installPhase = '' + cp -R ${unifi} $out + chmod -R u+w $out + rm $out/lib/ace.jar + cp $NIX_BUILD_TOP/tmp/acereobf.jar $out/lib/ace.jar + cp ${aspectj}/lib/aspectjrt.jar $out/lib/aspectjrt.jar + ''; +}