MultiuserPlugin.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import re
  2. import sys
  3. import json
  4. from Config import config
  5. from Plugin import PluginManager
  6. from Crypt import CryptBitcoin
  7. import UserPlugin
  8. try:
  9. local_master_addresses = set(json.load(open("%s/users.json" % config.data_dir)).keys()) # Users in users.json
  10. except Exception, err:
  11. local_master_addresses = set()
  12. @PluginManager.registerTo("UiRequest")
  13. class UiRequestPlugin(object):
  14. def __init__(self, *args, **kwargs):
  15. self.user_manager = sys.modules["User.UserManager"].user_manager
  16. super(UiRequestPlugin, self).__init__(*args, **kwargs)
  17. # Create new user and inject user welcome message if necessary
  18. # Return: Html body also containing the injection
  19. def actionWrapper(self, path, extra_headers=None):
  20. match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
  21. if not match:
  22. return False
  23. inner_path = match.group("inner_path").lstrip("/")
  24. html_request = "." not in inner_path or inner_path.endswith(".html") # Only inject html to html requests
  25. user_created = False
  26. if html_request:
  27. user = self.getCurrentUser() # Get user from cookie
  28. if not user: # No user found by cookie
  29. user = self.user_manager.create()
  30. user_created = True
  31. else:
  32. user = None
  33. # Disable new site creation if --multiuser_no_new_sites enabled
  34. if config.multiuser_no_new_sites:
  35. path_parts = self.parsePath(path)
  36. if not self.server.site_manager.get(match.group("address")) and (not user or user.master_address not in local_master_addresses):
  37. self.sendHeader(404)
  38. return self.formatError("Not Found", "Adding new sites disabled on this proxy", details=False)
  39. if user_created:
  40. if not extra_headers:
  41. extra_headers = {}
  42. extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days
  43. loggedin = self.get.get("login") == "done"
  44. back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output
  45. if not back_generator: # Wrapper error or not string returned, injection not possible
  46. return False
  47. elif loggedin:
  48. back = back_generator.next()
  49. inject_html = """
  50. <!-- Multiser plugin -->
  51. <script>
  52. setTimeout(function() {
  53. zeroframe.cmd("wrapperNotification", ["done", "{message}<br><small>You have been logged in successfully</small>", 5000])
  54. }, 1000)
  55. </script>
  56. </body>
  57. </html>
  58. """.replace("\t", "")
  59. if user.master_address in local_master_addresses:
  60. message = "Hello master!"
  61. else:
  62. message = "Hello again!"
  63. inject_html = inject_html.replace("{message}", message)
  64. return iter([re.sub("</body>\s*</html>\s*$", inject_html, back)]) # Replace the </body></html> tags with the injection
  65. else: # No injection necessary
  66. return back_generator
  67. # Get the current user based on request's cookies
  68. # Return: User object or None if no match
  69. def getCurrentUser(self):
  70. cookies = self.getCookies()
  71. user = None
  72. if "master_address" in cookies:
  73. users = self.user_manager.list()
  74. user = users.get(cookies["master_address"])
  75. return user
  76. @PluginManager.registerTo("UiWebsocket")
  77. class UiWebsocketPlugin(object):
  78. def __init__(self, *args, **kwargs):
  79. self.multiuser_denied_cmds = (
  80. "siteDelete", "configSet", "serverShutdown", "serverUpdate", "siteClone",
  81. "siteSetOwned", "siteSetAutodownloadoptional", "dbReload", "dbRebuild",
  82. "mergerSiteDelete", "siteSetLimit", "siteSetAutodownloadBigfileLimit",
  83. "optionalLimitSet", "optionalHelp", "optionalHelpRemove", "optionalHelpAll", "optionalFilePin", "optionalFileUnpin", "optionalFileDelete",
  84. "muteAdd", "muteRemove", "siteblockAdd", "siteblockRemove", "filterIncludeAdd", "filterIncludeRemove"
  85. )
  86. if config.multiuser_no_new_sites:
  87. self.multiuser_denied_cmds += ("mergerSiteAdd", )
  88. super(UiWebsocketPlugin, self).__init__(*args, **kwargs)
  89. # Let the page know we running in multiuser mode
  90. def formatServerInfo(self):
  91. server_info = super(UiWebsocketPlugin, self).formatServerInfo()
  92. server_info["multiuser"] = True
  93. if "ADMIN" in self.site.settings["permissions"]:
  94. server_info["master_address"] = self.user.master_address
  95. return server_info
  96. # Show current user's master seed
  97. def actionUserShowMasterSeed(self, to):
  98. if "ADMIN" not in self.site.settings["permissions"]:
  99. return self.response(to, "Show master seed not allowed")
  100. message = "<b style='padding-top: 5px; display: inline-block'>Your unique private key:</b>"
  101. message += "<div style='font-size: 84%%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px'>%s</div>" % self.user.master_seed
  102. message += "<small>(Save it, you can access your account using this information)</small>"
  103. self.cmd("notification", ["info", message])
  104. # Logout user
  105. def actionUserLogout(self, to):
  106. if "ADMIN" not in self.site.settings["permissions"]:
  107. return self.response(to, "Logout not allowed")
  108. message = "<b>You have been logged out.</b> <a href='#Login' class='button' onclick='zeroframe.cmd(\"userLoginForm\", []); return false'>Login to another account</a>"
  109. message += "<script>document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/'</script>"
  110. self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :)
  111. # Delete from user_manager
  112. user_manager = sys.modules["User.UserManager"].user_manager
  113. if self.user.master_address in user_manager.users:
  114. if not config.multiuser_local:
  115. del user_manager.users[self.user.master_address]
  116. self.response(to, "Successful logout")
  117. else:
  118. self.response(to, "User not found")
  119. # Show login form
  120. def actionUserLoginForm(self, to):
  121. self.cmd("prompt", ["<b>Login</b><br>Your private key:", "password", "Login"], self.responseUserLogin)
  122. # Login form submit
  123. def responseUserLogin(self, master_seed):
  124. user_manager = sys.modules["User.UserManager"].user_manager
  125. user = user_manager.get(CryptBitcoin.privatekeyToAddress(master_seed))
  126. if not user:
  127. user = user_manager.create(master_seed=master_seed)
  128. if user.master_address:
  129. message = "Successfull login, reloading page..."
  130. message += "<script>document.cookie = 'master_address=%s;path=/;max-age=2592000;'</script>" % user.master_address
  131. message += "<script>zeroframe.cmd('wrapperReload', ['login=done'])</script>"
  132. self.cmd("notification", ["done", message])
  133. else:
  134. self.cmd("notification", ["error", "Error: Invalid master seed"])
  135. self.actionUserLoginForm(0)
  136. def hasCmdPermission(self, cmd):
  137. cmd = cmd[0].lower() + cmd[1:]
  138. if not config.multiuser_local and self.user.master_address not in local_master_addresses and cmd in self.multiuser_denied_cmds:
  139. self.cmd("notification", ["info", "This function is disabled on this proxy!"])
  140. return False
  141. else:
  142. return super(UiWebsocketPlugin, self).hasCmdPermission(cmd)
  143. def actionCertAdd(self, *args, **kwargs):
  144. super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs)
  145. master_seed = self.user.master_seed
  146. message = "<style>.masterseed { font-size: 95%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px }</style>"
  147. message += "<b>Hello, welcome to ZeroProxy!</b><div style='margin-top: 8px'>A new, unique account created for you:</div>"
  148. message += "<div class='masterseed'>" + master_seed + "</div>"
  149. message += "<div>This is your private key, <b>save it</b>, so you can login next time.<br>Without this key, your registered account will be lost forever!</div><br>"
  150. message += "<a href='#' class='button' style='margin-left: 0px'>Ok, Saved it!</a><br><br>"
  151. message += "<small>This site allows you to browse ZeroNet content, but if you want to secure your account <br>"
  152. message += "and help to make a better network, then please run your own <a href='https://zeronet.io' target='_blank'>ZeroNet client</a>.</small>"
  153. self.cmd("notification", ["info", message])
  154. def actionPermissionAdd(self, to, permission):
  155. if permission == "NOSANDBOX":
  156. self.cmd("notification", ["info", "You can't disable sandbox on this proxy!"])
  157. self.response(to, {"error": "Denied by proxy"})
  158. return False
  159. else:
  160. return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
  161. @PluginManager.registerTo("ConfigPlugin")
  162. class ConfigPlugin(object):
  163. def createArguments(self):
  164. group = self.parser.add_argument_group("Multiuser plugin")
  165. group.add_argument('--multiuser_local', help="Enable unsafe Ui functions and write users to disk", action='store_true')
  166. group.add_argument('--multiuser_no_new_sites', help="Denies adding new sites by normal users", action='store_true')
  167. return super(ConfigPlugin, self).createArguments()