-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Copy pathMultiuserPlugin.py
227 lines (193 loc) · 10.6 KB
/
MultiuserPlugin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
import re
import sys
import json
from Config import config
from Plugin import PluginManager
from Crypt import CryptBitcoin
import UserPlugin
try:
local_master_addresses = set(json.load(open("%s/users.json" % config.data_dir)).keys()) # Users in users.json
except Exception, err:
local_master_addresses = set()
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
def __init__(self, *args, **kwargs):
self.user_manager = sys.modules["User.UserManager"].user_manager
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
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
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)
if user_created:
if not extra_headers:
extra_headers = {}
extra_headers['Set-Cookie'] = "master_address=%s;path=/;max-age=2592000;" % user.master_address # = 30 days
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:
back = back_generator.next()
inject_html = """
<!-- Multiser plugin -->
<script nonce="{script_nonce}">
setTimeout(function() {
zeroframe.cmd("wrapperNotification", ["done", "{message}<br><small>You have been logged in successfully</small>", 5000])
}, 1000)
</script>
</body>
</html>
""".replace("\t", "")
if user.master_address in local_master_addresses:
message = "Hello master!"
else:
message = "Hello again!"
inject_html = inject_html.replace("{message}", message)
inject_html = inject_html.replace("{script_nonce}", self.getScriptNonce())
return iter([re.sub("</body>\s*</html>\s*$", inject_html, back)]) # Replace the </body></html> tags with the injection
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
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
def __init__(self, *args, **kwargs):
self.multiuser_denied_cmds = (
"sitePause", "siteResume", "siteDelete", "configSet", "serverShutdown", "serverUpdate", "siteClone",
"siteSetOwned", "siteSetAutodownloadoptional", "dbReload", "dbRebuild",
"mergerSiteDelete", "siteSetLimit", "siteSetAutodownloadBigfileLimit",
"optionalLimitSet", "optionalHelp", "optionalHelpRemove", "optionalHelpAll", "optionalFilePin", "optionalFileUnpin", "optionalFileDelete",
"muteAdd", "muteRemove", "siteblockAdd", "siteblockRemove", "filterIncludeAdd", "filterIncludeRemove"
)
if config.multiuser_no_new_sites:
self.multiuser_denied_cmds += ("mergerSiteAdd", )
super(UiWebsocketPlugin, self).__init__(*args, **kwargs)
# 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
return server_info
# Show current user's master seed
def actionUserShowMasterSeed(self, to):
if "ADMIN" not in self.site.settings["permissions"]:
return self.response(to, "Show master seed not allowed")
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
def actionUserLogout(self, to):
if "ADMIN" not in self.site.settings["permissions"]:
return self.response(to, "Logout not allowed")
message = "<b>You have been logged out.</b> <a href='#Login' class='button' id='button_notification'>Login to another account</a>"
self.cmd("notification", ["done", message, 1000000]) # 1000000 = Show ~forever :)
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)
# Delete from user_manager
user_manager = sys.modules["User.UserManager"].user_manager
if self.user.master_address in user_manager.users:
if not config.multiuser_local:
del user_manager.users[self.user.master_address]
self.response(to, "Successful logout")
else:
self.response(to, "User not found")
# 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):
user_manager = sys.modules["User.UserManager"].user_manager
user = user_manager.get(CryptBitcoin.privatekeyToAddress(master_seed))
if not user:
user = user_manager.create(master_seed=master_seed)
if user.master_address:
script = "document.cookie = 'master_address=%s;path=/;max-age=2592000;';" % user.master_address
script += "zeroframe.cmd('wrapperReload', ['login=done']);"
self.cmd("notification", ["done", "Successful login, reloading page..."])
self.cmd("injectScript", script)
else:
self.cmd("notification", ["error", "Error: Invalid master seed"])
self.actionUserLoginForm(0)
def hasCmdPermission(self, cmd):
cmd = cmd[0].lower() + cmd[1:]
if not config.multiuser_local and self.user.master_address not in local_master_addresses and cmd in self.multiuser_denied_cmds:
self.cmd("notification", ["info", "This function is disabled on this proxy!"])
return False
else:
return super(UiWebsocketPlugin, self).hasCmdPermission(cmd)
def actionCertAdd(self, *args, **kwargs):
super(UiWebsocketPlugin, self).actionCertAdd(*args, **kwargs)
master_seed = self.user.master_seed
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>
<input type='text' class='masterseed' id='button_notification_masterseed' value='Click here to show' readonly/>
<div style='text-align: center; font-size: 85%; margin-bottom: 10px;'>
or <a href='#Download' id='button_notification_download'
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>
"""
self.cmd("notification", ["info", message])
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)
def actionPermissionAdd(self, to, permission):
if permission == "NOSANDBOX":
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)
@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')
group.add_argument('--multiuser_no_new_sites', help="Denies adding new sites by normal users", action='store_true')
return super(ConfigPlugin, self).createArguments()