2015-09-08 03:07:44 +02:00
|
|
|
import re
|
|
|
|
import sys
|
2017-02-11 18:23:52 +01:00
|
|
|
import json
|
2016-03-11 13:26:54 +01:00
|
|
|
|
|
|
|
from Config import config
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
from Plugin import PluginManager
|
2016-03-11 13:26:54 +01:00
|
|
|
from Crypt import CryptBitcoin
|
2019-03-15 21:06:59 +01:00
|
|
|
from . import UserPlugin
|
2019-08-26 03:20:07 +02:00
|
|
|
from util.Flag import flag
|
2020-02-13 17:24:59 +01:00
|
|
|
from Translate import translate as _
|
2019-08-26 03:20:07 +02:00
|
|
|
|
2019-04-15 22:54:17 +02:00
|
|
|
# We can only import plugin host clases after the plugins are loaded
|
|
|
|
@PluginManager.afterLoad
|
|
|
|
def importPluginnedClasses():
|
|
|
|
global UserManager
|
|
|
|
from User import UserManager
|
|
|
|
|
2017-02-11 18:23:52 +01:00
|
|
|
try:
|
|
|
|
local_master_addresses = set(json.load(open("%s/users.json" % config.data_dir)).keys()) # Users in users.json
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2017-02-11 18:23:52 +01:00
|
|
|
local_master_addresses = set()
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
2017-08-19 18:55:21 +02:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
@PluginManager.registerTo("UiRequest")
|
|
|
|
class UiRequestPlugin(object):
|
2015-09-08 03:07:44 +02:00
|
|
|
def __init__(self, *args, **kwargs):
|
2019-04-15 12:31:33 +02:00
|
|
|
self.user_manager = UserManager.user_manager
|
2015-09-08 03:07:44 +02:00
|
|
|
super(UiRequestPlugin, self).__init__(*args, **kwargs)
|
|
|
|
|
|
|
|
# Create new user and inject user welcome message if necessary
|
|
|
|
# Return: Html body also containing the injection
|
|
|
|
def actionWrapper(self, path, extra_headers=None):
|
|
|
|
|
|
|
|
match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
|
|
|
|
if not match:
|
|
|
|
return False
|
2017-02-11 18:23:52 +01:00
|
|
|
|
2015-09-08 03:07:44 +02:00
|
|
|
inner_path = match.group("inner_path").lstrip("/")
|
|
|
|
html_request = "." not in inner_path or inner_path.endswith(".html") # Only inject html to html requests
|
|
|
|
|
|
|
|
user_created = False
|
|
|
|
if html_request:
|
|
|
|
user = self.getCurrentUser() # Get user from cookie
|
|
|
|
if not user: # No user found by cookie
|
|
|
|
user = self.user_manager.create()
|
|
|
|
user_created = True
|
2017-02-11 18:22:24 +01:00
|
|
|
else:
|
|
|
|
user = None
|
|
|
|
|
|
|
|
# Disable new site creation if --multiuser_no_new_sites enabled
|
|
|
|
if config.multiuser_no_new_sites:
|
|
|
|
path_parts = self.parsePath(path)
|
|
|
|
if not self.server.site_manager.get(match.group("address")) and (not user or user.master_address not in local_master_addresses):
|
|
|
|
self.sendHeader(404)
|
|
|
|
return self.formatError("Not Found", "Adding new sites disabled on this proxy", details=False)
|
2015-09-08 03:07:44 +02:00
|
|
|
|
|
|
|
if user_created:
|
|
|
|
if not extra_headers:
|
2018-03-06 11:58:56 +01:00
|
|
|
extra_headers = {}
|
|
|
|
extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days
|
2015-09-08 03:07:44 +02:00
|
|
|
|
|
|
|
loggedin = self.get.get("login") == "done"
|
|
|
|
|
|
|
|
back_generator = super(UiRequestPlugin, self).actionWrapper(path, extra_headers) # Get the wrapper frame output
|
|
|
|
|
|
|
|
if not back_generator: # Wrapper error or not string returned, injection not possible
|
|
|
|
return False
|
|
|
|
|
|
|
|
elif loggedin:
|
2019-03-15 21:06:59 +01:00
|
|
|
back = next(back_generator)
|
2015-09-08 03:07:44 +02:00
|
|
|
inject_html = """
|
|
|
|
<!-- Multiser plugin -->
|
2018-11-26 00:18:46 +01:00
|
|
|
<script nonce="{script_nonce}">
|
2015-09-08 03:07:44 +02:00
|
|
|
setTimeout(function() {
|
2018-02-21 03:14:15 +01:00
|
|
|
zeroframe.cmd("wrapperNotification", ["done", "{message}<br><small>You have been logged in successfully</small>", 5000])
|
2015-09-08 03:07:44 +02:00
|
|
|
}, 1000)
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
""".replace("\t", "")
|
2017-02-11 18:23:52 +01:00
|
|
|
if user.master_address in local_master_addresses:
|
|
|
|
message = "Hello master!"
|
|
|
|
else:
|
|
|
|
message = "Hello again!"
|
|
|
|
inject_html = inject_html.replace("{message}", message)
|
2018-11-26 00:18:46 +01:00
|
|
|
inject_html = inject_html.replace("{script_nonce}", self.getScriptNonce())
|
2019-04-04 13:24:42 +02:00
|
|
|
return iter([re.sub(b"</body>\s*</html>\s*$", inject_html.encode(), back)]) # Replace the </body></html> tags with the injection
|
2015-09-08 03:07:44 +02:00
|
|
|
|
|
|
|
else: # No injection necessary
|
|
|
|
return back_generator
|
|
|
|
|
|
|
|
# Get the current user based on request's cookies
|
|
|
|
# Return: User object or None if no match
|
|
|
|
def getCurrentUser(self):
|
|
|
|
cookies = self.getCookies()
|
|
|
|
user = None
|
|
|
|
if "master_address" in cookies:
|
|
|
|
users = self.user_manager.list()
|
|
|
|
user = users.get(cookies["master_address"])
|
|
|
|
return user
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
@PluginManager.registerTo("UiWebsocket")
|
|
|
|
class UiWebsocketPlugin(object):
|
2017-02-11 18:23:15 +01:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
if config.multiuser_no_new_sites:
|
2019-08-26 03:20:07 +02:00
|
|
|
flag.no_multiuser(self.actionMergerSiteAdd)
|
2017-02-11 18:23:15 +01:00
|
|
|
|
|
|
|
super(UiWebsocketPlugin, self).__init__(*args, **kwargs)
|
|
|
|
|
2015-09-08 03:07:44 +02:00
|
|
|
# Let the page know we running in multiuser mode
|
|
|
|
def formatServerInfo(self):
|
|
|
|
server_info = super(UiWebsocketPlugin, self).formatServerInfo()
|
|
|
|
server_info["multiuser"] = True
|
|
|
|
if "ADMIN" in self.site.settings["permissions"]:
|
|
|
|
server_info["master_address"] = self.user.master_address
|
2019-09-19 16:38:05 +02:00
|
|
|
is_multiuser_admin = config.multiuser_local or self.user.master_address in local_master_addresses
|
|
|
|
server_info["multiuser_admin"] = is_multiuser_admin
|
2015-09-08 03:07:44 +02:00
|
|
|
return server_info
|
|
|
|
|
|
|
|
# Show current user's master seed
|
2019-08-26 03:20:07 +02:00
|
|
|
@flag.admin
|
2015-09-08 03:07:44 +02:00
|
|
|
def actionUserShowMasterSeed(self, to):
|
|
|
|
message = "<b style='padding-top: 5px; display: inline-block'>Your unique private key:</b>"
|
|
|
|
message += "<div style='font-size: 84%%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px'>%s</div>" % self.user.master_seed
|
|
|
|
message += "<small>(Save it, you can access your account using this information)</small>"
|
|
|
|
self.cmd("notification", ["info", message])
|
|
|
|
|
|
|
|
# Logout user
|
2019-08-26 03:20:07 +02:00
|
|
|
@flag.admin
|
2015-09-08 03:07:44 +02:00
|
|
|
def actionUserLogout(self, to):
|
2018-11-26 00:19:49 +01:00
|
|
|
message = "<b>You have been logged out.</b> <a href='#Login' class='button' id='button_notification'>Login to another account</a>"
|
2015-09-08 03:07:44 +02:00
|
|
|
self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :)
|
2018-11-26 00:19:49 +01:00
|
|
|
|
|
|
|
script = "document.cookie = 'master_address=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/';"
|
|
|
|
script += "$('#button_notification').on('click', function() { zeroframe.cmd(\"userLoginForm\", []); });"
|
|
|
|
self.cmd("injectScript", script)
|
2015-09-08 03:07:44 +02:00
|
|
|
# Delete from user_manager
|
2019-04-15 12:31:33 +02:00
|
|
|
user_manager = UserManager.user_manager
|
2015-09-08 03:07:44 +02:00
|
|
|
if self.user.master_address in user_manager.users:
|
2016-03-11 13:26:54 +01:00
|
|
|
if not config.multiuser_local:
|
|
|
|
del user_manager.users[self.user.master_address]
|
2015-09-08 03:07:44 +02:00
|
|
|
self.response(to, "Successful logout")
|
|
|
|
else:
|
|
|
|
self.response(to, "User not found")
|
|
|
|
|
2019-10-06 03:05:39 +02:00
|
|
|
@flag.admin
|
|
|
|
def actionUserSet(self, to, master_address):
|
|
|
|
user_manager = UserManager.user_manager
|
|
|
|
user = user_manager.get(master_address)
|
|
|
|
if not user:
|
|
|
|
raise Exception("No user found")
|
|
|
|
|
|
|
|
script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % master_address
|
|
|
|
script += "zeroframe.cmd('wrapperReload', ['login=done']);"
|
|
|
|
self.cmd("notification", ["done", "Successful login, reloading page..."])
|
|
|
|
self.cmd("injectScript", script)
|
|
|
|
|
|
|
|
self.response(to, "ok")
|
|
|
|
|
|
|
|
@flag.admin
|
|
|
|
def actionUserSelectForm(self, to):
|
|
|
|
if not config.multiuser_local:
|
|
|
|
raise Exception("Only allowed in multiuser local mode")
|
|
|
|
user_manager = UserManager.user_manager
|
|
|
|
body = "<span style='padding-bottom: 5px; display: inline-block'>" + "Change account:" + "</span>"
|
|
|
|
for master_address, user in user_manager.list().items():
|
|
|
|
is_active = self.user.master_address == master_address
|
|
|
|
if user.certs:
|
|
|
|
first_cert = next(iter(user.certs.keys()))
|
|
|
|
title = "%s@%s" % (user.certs[first_cert]["auth_user_name"], first_cert)
|
|
|
|
else:
|
|
|
|
title = user.master_address
|
|
|
|
if len(user.sites) < 2 and not is_active: # Avoid listing ad-hoc created users
|
|
|
|
continue
|
|
|
|
if is_active:
|
|
|
|
css_class = "active"
|
|
|
|
else:
|
|
|
|
css_class = "noclass"
|
|
|
|
body += "<a href='#Select+user' class='select select-close user %s' title='%s'>%s</a>" % (css_class, user.master_address, title)
|
|
|
|
|
|
|
|
script = """
|
|
|
|
$(".notification .select.user").on("click", function() {
|
|
|
|
$(".notification .select").removeClass('active')
|
|
|
|
zeroframe.response(%s, this.title)
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
""" % self.next_message_id
|
|
|
|
|
|
|
|
self.cmd("notification", ["ask", body], lambda master_address: self.actionUserSet(to, master_address))
|
|
|
|
self.cmd("injectScript", script)
|
|
|
|
|
2015-09-08 03:07:44 +02:00
|
|
|
# Show login form
|
|
|
|
def actionUserLoginForm(self, to):
|
|
|
|
self.cmd("prompt", ["<b>Login</b><br>Your private key:", "password", "Login"], self.responseUserLogin)
|
|
|
|
|
|
|
|
# Login form submit
|
|
|
|
def responseUserLogin(self, master_seed):
|
2019-04-15 12:31:33 +02:00
|
|
|
user_manager = UserManager.user_manager
|
2016-03-11 13:26:54 +01:00
|
|
|
user = user_manager.get(CryptBitcoin.privatekeyToAddress(master_seed))
|
|
|
|
if not user:
|
|
|
|
user = user_manager.create(master_seed=master_seed)
|
2015-09-08 03:07:44 +02:00
|
|
|
if user.master_address:
|
2018-11-26 00:19:49 +01:00
|
|
|
script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % user.master_address
|
|
|
|
script += "zeroframe.cmd('wrapperReload', ['login=done']);"
|
2019-02-15 22:22:13 +01:00
|
|
|
self.cmd("notification", ["done", "Successful login, reloading page..."])
|
2018-11-26 00:19:49 +01:00
|
|
|
self.cmd("injectScript", script)
|
2015-09-08 03:07:44 +02:00
|
|
|
else:
|
|
|
|
self.cmd("notification", ["error", "Error: Invalid master seed"])
|
|
|
|
self.actionUserLoginForm(0)
|
Version 0.3.6, Rev879, Fix sidebar error on description missing, New trayicon, New favicon, Disable some functions on MultiUser proxies, New homepage, Replace only the last ? in SQL queries, Alwaays grant ADMIN permission to homepage site, Announce before publish if no peers, configSet, serverShutdown, ADMIN WebsocketAPI command, Stop Tor client before updating, Ignore peer ip packing error, Ignore db files from git, Fix safari ajax error when UiPassword enabled
2016-02-02 11:40:45 +01:00
|
|
|
|
2017-02-11 18:23:15 +01:00
|
|
|
def hasCmdPermission(self, cmd):
|
2019-08-26 03:20:07 +02:00
|
|
|
flags = flag.db.get(self.getCmdFuncName(cmd), ())
|
|
|
|
is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses
|
|
|
|
if is_public_proxy_user and "no_multiuser" in flags:
|
2020-02-13 17:24:59 +01:00
|
|
|
self.cmd("notification", ["info", _("This function ({cmd}) is disabled on this proxy!")])
|
2017-02-11 18:23:15 +01:00
|
|
|
return False
|
2017-02-06 18:04:00 +01:00
|
|
|
else:
|
2017-02-11 18:23:15 +01:00
|
|
|
return super(UiWebsocketPlugin, self).hasCmdPermission(cmd)
|
2017-02-06 18:04:00 +01:00
|
|
|
|
2017-11-29 13:49:41 +01:00
|
|
|
def actionCertAdd(self, *args, **kwargs):
|
|
|
|
super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs)
|
|
|
|
master_seed = self.user.master_seed
|
2018-10-30 04:39:13 +01:00
|
|
|
message = """
|
|
|
|
<style>
|
|
|
|
.masterseed {
|
|
|
|
font-size: 85%; background-color: #FFF0AD; padding: 5px 8px; margin: 9px 0px; width: 100%;
|
|
|
|
box-sizing: border-box; border: 0px; text-align: center; cursor: pointer;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
<b>Hello, welcome to ZeroProxy!</b><div style='margin-top: 8px'>A new, unique account created for you:</div>
|
2018-11-26 00:19:49 +01:00
|
|
|
<input type='text' class='masterseed' id='button_notification_masterseed' value='Click here to show' readonly/>
|
2018-10-30 04:39:13 +01:00
|
|
|
<div style='text-align: center; font-size: 85%; margin-bottom: 10px;'>
|
2018-11-26 00:19:49 +01:00
|
|
|
or <a href='#Download' id='button_notification_download'
|
2018-10-30 04:39:13 +01:00
|
|
|
class='masterseed_download' download='zeronet_private_key.backup'>Download backup as text file</a>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
This is your private key, <b>save it</b>, so you can login next time.<br>
|
|
|
|
<b>Warning: Without this key, your account will be lost forever!</b>
|
|
|
|
</div><br>
|
|
|
|
<a href='#' class='button' style='margin-left: 0px'>Ok, Saved it!</a><br><br>
|
|
|
|
<small>This site allows you to browse ZeroNet content, but if you want to secure your account <br>
|
|
|
|
and help to keep the network alive, then please run your own <a href='https://zeronet.io' target='_blank'>ZeroNet client</a>.</small>
|
2019-02-15 22:19:29 +01:00
|
|
|
"""
|
2018-11-26 00:19:49 +01:00
|
|
|
|
2018-02-21 03:14:15 +01:00
|
|
|
self.cmd("notification", ["info", message])
|
2017-11-29 13:49:41 +01:00
|
|
|
|
2018-11-26 00:19:49 +01:00
|
|
|
script = """
|
|
|
|
$("#button_notification_masterseed").on("click", function() {
|
|
|
|
this.value = "{master_seed}"; this.setSelectionRange(0,100);
|
|
|
|
})
|
|
|
|
$("#button_notification_download").on("mousedown", function() {
|
|
|
|
this.href = window.URL.createObjectURL(new Blob(["ZeroNet user master seed:\\r\\n{master_seed}"]))
|
|
|
|
})
|
|
|
|
""".replace("{master_seed}", master_seed)
|
|
|
|
self.cmd("injectScript", script)
|
|
|
|
|
2018-02-21 03:14:43 +01:00
|
|
|
def actionPermissionAdd(self, to, permission):
|
2019-10-24 12:01:31 +02:00
|
|
|
is_public_proxy_user = not config.multiuser_local and self.user.master_address not in local_master_addresses
|
|
|
|
if permission == "NOSANDBOX" and is_public_proxy_user:
|
2018-02-21 03:14:43 +01:00
|
|
|
self.cmd("notification", ["info", "You can't disable sandbox on this proxy!"])
|
|
|
|
self.response(to, {"error": "Denied by proxy"})
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return super(UiWebsocketPlugin, self).actionPermissionAdd(to, permission)
|
|
|
|
|
2016-03-11 13:26:54 +01:00
|
|
|
|
|
|
|
@PluginManager.registerTo("ConfigPlugin")
|
|
|
|
class ConfigPlugin(object):
|
|
|
|
def createArguments(self):
|
|
|
|
group = self.parser.add_argument_group("Multiuser plugin")
|
|
|
|
group.add_argument('--multiuser_local', help="Enable unsafe Ui functions and write users to disk", action='store_true')
|
2017-02-11 18:22:24 +01:00
|
|
|
group.add_argument('--multiuser_no_new_sites', help="Denies adding new sites by normal users", action='store_true')
|
2016-03-11 13:26:54 +01:00
|
|
|
|
|
|
|
return super(ConfigPlugin, self).createArguments()
|