Forking what is left of ZeroNet and hopefully adding an AT Proto Frontend/Proxy
at main 275 lines 13 kB view raw
1import re 2import sys 3import json 4 5from Config import config 6from Plugin import PluginManager 7from Crypt import CryptBitcoin 8from . import UserPlugin 9from util.Flag import flag 10from Translate import translate as _ 11 12# We can only import plugin host clases after the plugins are loaded 13@PluginManager.afterLoad 14def importPluginnedClasses(): 15 global UserManager 16 from User import UserManager 17 18try: 19 local_master_addresses = set(json.load(open("%s/users.json" % config.data_dir)).keys()) # Users in users.json 20except Exception as err: 21 local_master_addresses = set() 22 23 24@PluginManager.registerTo("UiRequest") 25class UiRequestPlugin(object): 26 def __init__(self, *args, **kwargs): 27 self.user_manager = UserManager.user_manager 28 super(UiRequestPlugin, self).__init__(*args, **kwargs) 29 30 # Create new user and inject user welcome message if necessary 31 # Return: Html body also containing the injection 32 def actionWrapper(self, path, extra_headers=None): 33 34 match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path) 35 if not match: 36 return False 37 38 inner_path = match.group("inner_path").lstrip("/") 39 html_request = "." not in inner_path or inner_path.endswith(".html") # Only inject html to html requests 40 41 user_created = False 42 if html_request: 43 user = self.getCurrentUser() # Get user from cookie 44 if not user: # No user found by cookie 45 user = self.user_manager.create() 46 user_created = True 47 else: 48 user = None 49 50 # Disable new site creation if --multiuser_no_new_sites enabled 51 if config.multiuser_no_new_sites: 52 path_parts = self.parsePath(path) 53 if not self.server.site_manager.get(match.group("address")) and (not user or user.master_address not in local_master_addresses): 54 self.sendHeader(404) 55 return self.formatError("Not Found", "Adding new sites disabled on this proxy", details=False) 56 57 if user_created: 58 if not extra_headers: 59 extra_headers = {} 60 extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days 61 62 loggedin = self.get.get("login") == "done" 63 64 back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output 65 66 if not back_generator: # Wrapper error or not string returned, injection not possible 67 return False 68 69 elif loggedin: 70 back = next(back_generator) 71 inject_html = """ 72 <!-- Multiser plugin --> 73 <script nonce="{script_nonce}"> 74 setTimeout(function() { 75 zeroframe.cmd("wrapperNotification", ["done", "{message}<br><small>You have been logged in successfully</small>", 5000]) 76 }, 1000) 77 </script> 78 </body> 79 </html> 80 """.replace("\t", "") 81 if user.master_address in local_master_addresses: 82 message = "Hello master!" 83 else: 84 message = "Hello again!" 85 inject_html = inject_html.replace("{message}", message) 86 inject_html = inject_html.replace("{script_nonce}", self.getScriptNonce()) 87 return iter([re.sub(b"</body>\s*</html>\s*$", inject_html.encode(), back)]) # Replace the </body></html> tags with the injection 88 89 else: # No injection necessary 90 return back_generator 91 92 # Get the current user based on request's cookies 93 # Return: User object or None if no match 94 def getCurrentUser(self): 95 cookies = self.getCookies() 96 user = None 97 if "master_address" in cookies: 98 users = self.user_manager.list() 99 user = users.get(cookies["master_address"]) 100 return user 101 102 103@PluginManager.registerTo("UiWebsocket") 104class UiWebsocketPlugin(object): 105 def __init__(self, *args, **kwargs): 106 if config.multiuser_no_new_sites: 107 flag.no_multiuser(self.actionMergerSiteAdd) 108 109 super(UiWebsocketPlugin, self).__init__(*args, **kwargs) 110 111 # Let the page know we running in multiuser mode 112 def formatServerInfo(self): 113 server_info = super(UiWebsocketPlugin, self).formatServerInfo() 114 server_info["multiuser"] = True 115 if "ADMIN" in self.site.settings["permissions"]: 116 server_info["master_address"] = self.user.master_address 117 is_multiuser_admin = config.multiuser_local or self.user.master_address in local_master_addresses 118 server_info["multiuser_admin"] = is_multiuser_admin 119 return server_info 120 121 # Show current user's master seed 122 @flag.admin 123 def actionUserShowMasterSeed(self, to): 124 message = "<b style='padding-top: 5px; display: inline-block'>Your unique private key:</b>" 125 message += "<div style='font-size: 84%%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px'>%s</div>" % self.user.master_seed 126 message += "<small>(Save it, you can access your account using this information)</small>" 127 self.cmd("notification", ["info", message]) 128 129 # Logout user 130 @flag.admin 131 def actionUserLogout(self, to): 132 message = "<b>You have been logged out.</b> <a href='#Login' class='button' id='button_notification'>Login to another account</a>" 133 self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :) 134 135 script = "document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';" 136 script += "$('#button_notification').on('click', function() { zeroframe.cmd(\"userLoginForm\", []); });" 137 self.cmd("injectScript", script) 138 # Delete from user_manager 139 user_manager = UserManager.user_manager 140 if self.user.master_address in user_manager.users: 141 if not config.multiuser_local: 142 del user_manager.users[self.user.master_address] 143 self.response(to, "Successful logout") 144 else: 145 self.response(to, "User not found") 146 147 @flag.admin 148 def actionUserSet(self, to, master_address): 149 user_manager = UserManager.user_manager 150 user = user_manager.get(master_address) 151 if not user: 152 raise Exception("No user found") 153 154 script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % master_address 155 script += "zeroframe.cmd('wrapperReload', ['login=done']);" 156 self.cmd("notification", ["done", "Successful login, reloading page..."]) 157 self.cmd("injectScript", script) 158 159 self.response(to, "ok") 160 161 @flag.admin 162 def actionUserSelectForm(self, to): 163 if not config.multiuser_local: 164 raise Exception("Only allowed in multiuser local mode") 165 user_manager = UserManager.user_manager 166 body = "<span style='padding-bottom: 5px; display: inline-block'>" + "Change account:" + "</span>" 167 for master_address, user in user_manager.list().items(): 168 is_active = self.user.master_address == master_address 169 if user.certs: 170 first_cert = next(iter(user.certs.keys())) 171 title = "%s@%s" % (user.certs[first_cert]["auth_user_name"], first_cert) 172 else: 173 title = user.master_address 174 if len(user.sites) < 2 and not is_active: # Avoid listing ad-hoc created users 175 continue 176 if is_active: 177 css_class = "active" 178 else: 179 css_class = "noclass" 180 body += "<a href='#Select+user' class='select select-close user %s' title='%s'>%s</a>" % (css_class, user.master_address, title) 181 182 script = """ 183 $(".notification .select.user").on("click", function() { 184 $(".notification .select").removeClass('active') 185 zeroframe.response(%s, this.title) 186 return false 187 }) 188 """ % self.next_message_id 189 190 self.cmd("notification", ["ask", body], lambda master_address: self.actionUserSet(to, master_address)) 191 self.cmd("injectScript", script) 192 193 # Show login form 194 def actionUserLoginForm(self, to): 195 self.cmd("prompt", ["<b>Login</b><br>Your private key:", "password", "Login"], self.responseUserLogin) 196 197 # Login form submit 198 def responseUserLogin(self, master_seed): 199 user_manager = UserManager.user_manager 200 user = user_manager.get(CryptBitcoin.privatekeyToAddress(master_seed)) 201 if not user: 202 user = user_manager.create(master_seed=master_seed) 203 if user.master_address: 204 script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % user.master_address 205 script += "zeroframe.cmd('wrapperReload', ['login=done']);" 206 self.cmd("notification", ["done", "Successful login, reloading page..."]) 207 self.cmd("injectScript", script) 208 else: 209 self.cmd("notification", ["error", "Error: Invalid master seed"]) 210 self.actionUserLoginForm(0) 211 212 def hasCmdPermission(self, cmd): 213 flags = flag.db.get(self.getCmdFuncName(cmd), ()) 214 is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses 215 if is_public_proxy_user and "no_multiuser" in flags: 216 self.cmd("notification", ["info", _("This function ({cmd}) is disabled on this proxy!")]) 217 return False 218 else: 219 return super(UiWebsocketPlugin, self).hasCmdPermission(cmd) 220 221 def actionCertAdd(self, *args, **kwargs): 222 super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs) 223 master_seed = self.user.master_seed 224 message = """ 225 <style> 226 .masterseed { 227 font-size: 85%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px; width: 100%; 228 box-sizing: border-box; border: 0px; text-align: center; cursor: pointer; 229 } 230 </style> 231 <b>Hello, welcome to ZeroProxy!</b><div style='margin-top: 8px'>A new, unique account created for you:</div> 232 <input type='text' class='masterseed' id='button_notification_masterseed' value='Click here to show' readonly/> 233 <div style='text-align: center; font-size: 85%; margin-bottom: 10px;'> 234 or <a href='#Download' id='button_notification_download' 235 class='masterseed_download' download='zeronet_private_key.backup'>Download backup as text file</a> 236 </div> 237 <div> 238 This is your private key, <b>save it</b>, so you can login next time.<br> 239 <b>Warning: Without this key, your account will be lost forever!</b> 240 </div><br> 241 <a href='#' class='button' style='margin-left: 0px'>Ok, Saved it!</a><br><br> 242 <small>This site allows you to browse ZeroNet content, but if you want to secure your account <br> 243 and help to keep the network alive, then please run your own <a href='https://zeronet.io' target='_blank'>ZeroNet client</a>.</small> 244 """ 245 246 self.cmd("notification", ["info", message]) 247 248 script = """ 249 $("#button_notification_masterseed").on("click", function() { 250 this.value = "{master_seed}"; this.setSelectionRange(0,100); 251 }) 252 $("#button_notification_download").on("mousedown", function() { 253 this.href = window.URL.createObjectURL(new Blob(["ZeroNet user master seed:\\r\\n{master_seed}"])) 254 }) 255 """.replace("{master_seed}", master_seed) 256 self.cmd("injectScript", script) 257 258 def actionPermissionAdd(self, to, permission): 259 is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses 260 if permission == "NOSANDBOX" and is_public_proxy_user: 261 self.cmd("notification", ["info", "You can't disable sandbox on this proxy!"]) 262 self.response(to, {"error": "Denied by proxy"}) 263 return False 264 else: 265 return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission) 266 267 268@PluginManager.registerTo("ConfigPlugin") 269class ConfigPlugin(object): 270 def createArguments(self): 271 group = self.parser.add_argument_group("Multiuser plugin") 272 group.add_argument('--multiuser_local', help="Enable unsafe Ui functions and write users to disk", action='store_true') 273 group.add_argument('--multiuser_no_new_sites', help="Denies adding new sites by normal users", action='store_true') 274 275 return super(ConfigPlugin, self).createArguments()