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.
This commit is contained in:
parent
2d52be000f
commit
85c2c4d507
4 changed files with 260 additions and 0 deletions
|
@ -18,6 +18,7 @@
|
||||||
envoy = import ./envoy args;
|
envoy = import ./envoy args;
|
||||||
deluge = import ./deluge args;
|
deluge = import ./deluge args;
|
||||||
grafana-plugins = import ./grafana-plugins args;
|
grafana-plugins = import ./grafana-plugins args;
|
||||||
|
unifiHacked = import ./unifi-hack args;
|
||||||
tiny-remapper = import ./tiny-remapper.nix args;
|
tiny-remapper = import ./tiny-remapper.nix args;
|
||||||
} // (import ./heptapod-runner.nix args)
|
} // (import ./heptapod-runner.nix args)
|
||||||
// (import ./lightspeed args)
|
// (import ./lightspeed args)
|
||||||
|
|
89
nix/pkgs/unifi-hack/aspect/MyLogin.aj
Normal file
89
nix/pkgs/unifi-hack/aspect/MyLogin.aj
Normal file
|
@ -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().<Admin>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 {}
|
||||||
|
|
||||||
|
}
|
122
nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map
Normal file
122
nix/pkgs/unifi-hack/aspect/ace.jar.tiny2map
Normal file
|
@ -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
|
48
nix/pkgs/unifi-hack/default.nix
Normal file
48
nix/pkgs/unifi-hack/default.nix
Normal file
|
@ -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
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in a new issue