2015-07-12 20:36:46 +02:00
|
|
|
import json
|
|
|
|
import time
|
|
|
|
import sys
|
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
|
|
|
import os
|
2016-04-09 19:43:44 +02:00
|
|
|
import shutil
|
2016-09-04 18:05:17 +02:00
|
|
|
import re
|
2017-12-15 12:13:19 +01:00
|
|
|
import copy
|
2019-01-10 14:10:07 +01:00
|
|
|
import logging
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
import gevent
|
|
|
|
|
2015-01-12 02:03:45 +01:00
|
|
|
from Config import config
|
|
|
|
from Site import SiteManager
|
2019-04-08 18:14:31 +02:00
|
|
|
from Crypt import CryptBitcoin
|
2015-01-17 18:50:56 +01:00
|
|
|
from Debug import Debug
|
2015-04-24 02:36:00 +02:00
|
|
|
from util import QueryJson, RateLimit
|
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-11-18 20:09:51 +01:00
|
|
|
from Translate import translate as _
|
2017-10-04 12:32:40 +02:00
|
|
|
from util import helper
|
2018-11-08 01:16:08 +01:00
|
|
|
from util import SafeRe
|
2019-08-26 03:02:30 +02:00
|
|
|
from util.Flag import flag
|
2018-03-18 21:27:04 +01:00
|
|
|
from Content.ContentManager import VerifyError, SignError
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-03-20 21:53:39 +01: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.acceptPlugins
|
|
|
|
class UiWebsocket(object):
|
2015-07-17 00:28:43 +02:00
|
|
|
def __init__(self, ws, site, server, user, request):
|
2015-07-12 20:36:46 +02:00
|
|
|
self.ws = ws
|
|
|
|
self.site = site
|
|
|
|
self.user = user
|
|
|
|
self.log = site.log
|
2015-07-17 00:28:43 +02:00
|
|
|
self.request = request
|
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
|
|
|
self.permissions = []
|
2015-07-12 20:36:46 +02:00
|
|
|
self.server = server
|
|
|
|
self.next_message_id = 1
|
|
|
|
self.waiting_cb = {} # Waiting for callback. Key: message_id, Value: function pointer
|
|
|
|
self.channels = [] # Channels joined to
|
2017-12-02 15:35:32 +01:00
|
|
|
self.state = {"sending": False} # Shared state of websocket connection
|
2015-07-12 20:36:46 +02:00
|
|
|
self.send_queue = [] # Messages to send to client
|
|
|
|
|
|
|
|
# Start listener loop
|
|
|
|
def start(self):
|
|
|
|
ws = self.ws
|
|
|
|
if self.site.address == config.homepage and not self.site.page_requested:
|
|
|
|
# Add open fileserver port message or closed port error to homepage at first request after start
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
self.site.page_requested = True # Dont add connection notification anymore
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
file_server = main.file_server
|
2019-01-23 02:11:31 +01:00
|
|
|
if not file_server.port_opened or file_server.tor_manager.start_onions is None:
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
self.site.page_requested = False # Not ready yet, check next time
|
|
|
|
else:
|
2017-05-22 11:13:45 +02:00
|
|
|
try:
|
|
|
|
self.addHomepageNotifications()
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2017-05-22 11:13:45 +02:00
|
|
|
self.log.error("Uncaught Exception: " + Debug.formatException(err))
|
Version 0.3.5, Rev830, Full Tor mode support with hidden services, Onion stats in Sidebar, GeoDB download fix using Tor, Gray out disabled sites in Stats page, Tor hidden service status in stat page, Benchmark sha256, Skyts tracker out expodie in, 2 new tracker using ZeroNet protocol, Keep SSL cert option between restarts, SSL Certificate pinning support for connections, Site lock support for connections, Certificate pinned connections using implicit SSL, Flood protection whitelist support, Foreign keys support for DB layer, Not support for SQL query helper, 0 length file get bugfix, Pex onion address support, Faster port testing, Faster uPnP port opening, Need connections more often on owned sites, Delay ZeroHello startup message if port check or Tor manager not ready yet, Use lockfiles to avoid double start, Save original socket on proxy monkey patching to get ability to connect localhost directly, Handle atomic write errors, Broken gevent https workaround helper, Rsa crypt functions, Plugin to Bootstrap using ZeroNet protocol
2016-01-05 00:20:52 +01:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
for notification in self.site.notifications: # Send pending notification messages
|
2017-05-22 11:13:45 +02:00
|
|
|
# send via WebSocket
|
2015-07-12 20:36:46 +02:00
|
|
|
self.cmd("notification", notification)
|
2017-05-22 11:13:45 +02:00
|
|
|
# just in case, log them to terminal
|
|
|
|
if notification[0] == "error":
|
|
|
|
self.log.error("\n*** %s\n" % self.dedent(notification[1]))
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
self.site.notifications = []
|
2017-05-22 11:13:45 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
while True:
|
|
|
|
try:
|
2017-05-22 11:34:16 +02:00
|
|
|
if ws.closed:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
message = ws.receive()
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2017-05-22 11:34:16 +02:00
|
|
|
self.log.error("WebSocket receive error: %s" % Debug.formatException(err))
|
|
|
|
break
|
2015-08-16 11:51:00 +02:00
|
|
|
|
|
|
|
if message:
|
|
|
|
try:
|
2017-12-15 12:11:27 +01:00
|
|
|
req = json.loads(message)
|
|
|
|
self.handleRequest(req)
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2015-07-12 20:36:46 +02:00
|
|
|
if config.debug: # Allow websocket errors to appear on /Debug
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
main.DebugHook.handleError()
|
2017-04-21 11:26:17 +02:00
|
|
|
self.log.error("WebSocket handleRequest error: %s \n %s" % (Debug.formatException(err), message))
|
2017-05-22 11:13:45 +02:00
|
|
|
if not self.hasPlugin("Multiuser"):
|
|
|
|
self.cmd("error", "Internal error: %s" % Debug.formatException(err, "html"))
|
|
|
|
|
2019-07-01 16:27:20 +02:00
|
|
|
self.onClosed()
|
|
|
|
|
|
|
|
def onClosed(self):
|
|
|
|
pass
|
|
|
|
|
2017-05-22 11:13:45 +02:00
|
|
|
def dedent(self, text):
|
|
|
|
return re.sub("[\\r\\n\\x20\\t]+", " ", text.strip().replace("<br>", " "))
|
|
|
|
|
|
|
|
def addHomepageNotifications(self):
|
|
|
|
if not(self.hasPlugin("Multiuser")) and not(self.hasPlugin("UiPassword")):
|
|
|
|
bind_ip = getattr(config, "ui_ip", "")
|
|
|
|
whitelist = getattr(config, "ui_restrict", [])
|
|
|
|
# binds to the Internet, no IP whitelist, no UiPassword, no Multiuser
|
|
|
|
if ("0.0.0.0" == bind_ip or "*" == bind_ip) and (not whitelist):
|
|
|
|
self.site.notifications.append([
|
|
|
|
"error",
|
2019-03-15 21:06:59 +01:00
|
|
|
_("You are not going to set up a public gateway. However, <b>your Web UI is<br>" +
|
2018-03-20 21:53:39 +01:00
|
|
|
"open to the whole Internet.</b> " +
|
2017-05-22 11:13:45 +02:00
|
|
|
"Please check your configuration.")
|
|
|
|
])
|
|
|
|
|
|
|
|
def hasPlugin(self, name):
|
|
|
|
return name in PluginManager.plugin_manager.plugin_names
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2017-02-11 18:20:46 +01:00
|
|
|
# Has permission to run the command
|
|
|
|
def hasCmdPermission(self, cmd):
|
2019-08-26 03:02:30 +02:00
|
|
|
flags = flag.db.get(self.getCmdFuncName(cmd), ())
|
|
|
|
if "admin" in flags and "ADMIN" not in self.permissions:
|
2017-02-11 18:20:46 +01:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Has permission to access a site
|
2018-01-28 16:41:42 +01:00
|
|
|
def hasSitePermission(self, address, cmd=None):
|
2016-11-07 22:55:09 +01:00
|
|
|
if address != self.site.address and "ADMIN" not in self.site.settings["permissions"]:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
2018-03-20 21:55:12 +01:00
|
|
|
def hasFilePermission(self, inner_path):
|
|
|
|
valid_signers = self.site.content_manager.getValidSigners(inner_path)
|
|
|
|
return self.site.settings["own"] or self.user.getAuthAddress(self.site.address) in valid_signers
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Event in a channel
|
|
|
|
def event(self, channel, *params):
|
|
|
|
if channel in self.channels: # We are joined to channel
|
|
|
|
if channel == "siteChanged":
|
2018-04-28 21:59:12 +02:00
|
|
|
site = params[0]
|
2018-04-18 22:06:58 +02:00
|
|
|
site_info = self.formatSiteInfo(site, create_user=False)
|
2015-07-12 20:36:46 +02:00
|
|
|
if len(params) > 1 and params[1]: # Extra data
|
|
|
|
site_info.update(params[1])
|
|
|
|
self.cmd("setSiteInfo", site_info)
|
2017-11-05 23:37:53 +01:00
|
|
|
elif channel == "serverChanged":
|
|
|
|
server_info = self.formatServerInfo()
|
2019-10-06 03:12:47 +02:00
|
|
|
if len(params) > 0 and params[0]: # Extra data
|
|
|
|
server_info.update(params[0])
|
2017-11-05 23:37:53 +01:00
|
|
|
self.cmd("setServerInfo", server_info)
|
2018-04-28 21:59:12 +02:00
|
|
|
elif channel == "announcerChanged":
|
|
|
|
site = params[0]
|
|
|
|
announcer_info = self.formatAnnouncerInfo(site)
|
|
|
|
if len(params) > 1 and params[1]: # Extra data
|
|
|
|
announcer_info.update(params[1])
|
|
|
|
self.cmd("setAnnouncerInfo", announcer_info)
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Send response to client (to = message.id)
|
|
|
|
def response(self, to, result):
|
|
|
|
self.send({"cmd": "response", "to": to, "result": result})
|
|
|
|
|
|
|
|
# Send a command
|
|
|
|
def cmd(self, cmd, params={}, cb=None):
|
|
|
|
self.send({"cmd": cmd, "params": params}, cb)
|
|
|
|
|
|
|
|
# Encode to json and send message
|
|
|
|
def send(self, message, cb=None):
|
|
|
|
message["id"] = self.next_message_id # Add message id to allow response
|
|
|
|
self.next_message_id += 1
|
2016-04-09 19:49:23 +02:00
|
|
|
if cb: # Callback after client responded
|
2015-07-12 20:36:46 +02:00
|
|
|
self.waiting_cb[message["id"]] = cb
|
2017-10-29 23:39:36 +01:00
|
|
|
self.send_queue.append(message)
|
2017-12-02 15:31:36 +01:00
|
|
|
if self.state["sending"]:
|
2015-07-12 20:36:46 +02:00
|
|
|
return # Already sending
|
|
|
|
try:
|
|
|
|
while self.send_queue:
|
2017-12-02 15:31:36 +01:00
|
|
|
self.state["sending"] = True
|
2015-07-12 20:36:46 +02:00
|
|
|
message = self.send_queue.pop(0)
|
|
|
|
self.ws.send(json.dumps(message))
|
2017-12-02 15:31:36 +01:00
|
|
|
self.state["sending"] = False
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2015-07-12 20:36:46 +02:00
|
|
|
self.log.debug("Websocket send error: %s" % Debug.formatException(err))
|
2018-04-28 21:59:27 +02:00
|
|
|
self.state["sending"] = False
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2015-07-17 00:28:43 +02:00
|
|
|
def getPermissions(self, req_id):
|
|
|
|
permissions = self.site.settings["permissions"]
|
|
|
|
if req_id >= 1000000: # Its a wrapper command, allow admin commands
|
|
|
|
permissions = permissions[:]
|
|
|
|
permissions.append("ADMIN")
|
|
|
|
return permissions
|
|
|
|
|
2017-01-22 11:09:26 +01:00
|
|
|
def asyncWrapper(self, func):
|
2017-02-09 01:53:02 +01:00
|
|
|
def asyncErrorWatcher(func, *args, **kwargs):
|
|
|
|
try:
|
2017-10-26 10:40:02 +02:00
|
|
|
result = func(*args, **kwargs)
|
2018-09-06 00:55:54 +02:00
|
|
|
if result is not None:
|
2017-10-26 10:40:02 +02:00
|
|
|
self.response(args[0], result)
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2017-02-09 01:53:02 +01:00
|
|
|
if config.debug: # Allow websocket errors to appear on /Debug
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
main.DebugHook.handleError()
|
2017-02-09 01:53:02 +01:00
|
|
|
self.log.error("WebSocket handleRequest error: %s" % Debug.formatException(err))
|
|
|
|
self.cmd("error", "Internal error: %s" % Debug.formatException(err, "html"))
|
|
|
|
|
2017-01-22 11:09:26 +01:00
|
|
|
def wrapper(*args, **kwargs):
|
2017-02-09 01:53:02 +01:00
|
|
|
gevent.spawn(asyncErrorWatcher, func, *args, **kwargs)
|
2017-01-22 11:09:26 +01:00
|
|
|
return wrapper
|
|
|
|
|
2019-08-26 03:02:30 +02:00
|
|
|
def getCmdFuncName(self, cmd):
|
|
|
|
func_name = "action" + cmd[0].upper() + cmd[1:]
|
|
|
|
return func_name
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Handle incoming messages
|
2017-12-15 12:11:27 +01:00
|
|
|
def handleRequest(self, req):
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
cmd = req.get("cmd")
|
|
|
|
params = req.get("params")
|
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
|
|
|
self.permissions = self.getPermissions(req["id"])
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
if cmd == "response": # It's a response to a command
|
|
|
|
return self.actionResponse(req["to"], req["result"])
|
|
|
|
else: # Normal command
|
2019-08-26 03:02:30 +02:00
|
|
|
func_name = self.getCmdFuncName(cmd)
|
2015-07-12 20:36:46 +02:00
|
|
|
func = getattr(self, func_name, None)
|
|
|
|
if not func: # Unknown command
|
2019-08-26 03:02:30 +02:00
|
|
|
return self.response(req["id"], {"error": "Unknown command: %s" % cmd})
|
|
|
|
|
|
|
|
if not self.hasCmdPermission(cmd): # Admin commands
|
|
|
|
return self.response(req["id"], {"error": "You don't have permission to run %s" % cmd})
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2017-01-22 11:09:26 +01:00
|
|
|
# Execute in parallel
|
2019-08-26 03:08:57 +02:00
|
|
|
func_flags = flag.db.get(self.getCmdFuncName(cmd), ())
|
|
|
|
if func_flags and "async_run" in func_flags:
|
2017-01-22 11:09:26 +01:00
|
|
|
func = self.asyncWrapper(func)
|
|
|
|
|
2016-04-09 19:49:23 +02:00
|
|
|
# Support calling as named, unnamed parameters and raw first argument too
|
2015-07-12 20:36:46 +02:00
|
|
|
if type(params) is dict:
|
2017-10-26 10:40:02 +02:00
|
|
|
result = func(req["id"], **params)
|
2015-07-12 20:36:46 +02:00
|
|
|
elif type(params) is list:
|
2017-10-26 10:40:02 +02:00
|
|
|
result = func(req["id"], *params)
|
2015-08-16 11:51:00 +02:00
|
|
|
elif params:
|
2017-10-26 10:40:02 +02:00
|
|
|
result = func(req["id"], params)
|
2015-08-16 11:51:00 +02:00
|
|
|
else:
|
2017-10-26 10:40:02 +02:00
|
|
|
result = func(req["id"])
|
|
|
|
|
2018-09-06 00:55:54 +02:00
|
|
|
if result is not None:
|
2017-10-26 10:40:02 +02:00
|
|
|
self.response(req["id"], result)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
# Format site info
|
|
|
|
def formatSiteInfo(self, site, create_user=True):
|
2018-10-15 12:58:14 +02:00
|
|
|
content = site.content_manager.contents.get("content.json", {})
|
2015-07-12 20:36:46 +02:00
|
|
|
if content: # Remove unnecessary data transfer
|
|
|
|
content = content.copy()
|
|
|
|
content["files"] = len(content.get("files", {}))
|
Rev571, Optional file sizes to sidebar, Download all optional files option in sidebar, Optional file number in peer stats, Delete removed or changed optional files, Auto download optional files if autodownloadoptional checked, SiteReload command, Peer use global file server if no site defined, Allow browser cache video files, Allow more keepalive connections, Gevent 1.1 ranged request bugfix, Dont sent optional files details on websocket, Remove files from workermanager tasks if no longer in bad_files, Notify local client about changes on external siteSign
2015-11-09 00:44:03 +01:00
|
|
|
content["files_optional"] = len(content.get("files_optional", {}))
|
2015-07-12 20:36:46 +02:00
|
|
|
content["includes"] = len(content.get("includes", {}))
|
|
|
|
if "sign" in content:
|
|
|
|
del(content["sign"])
|
|
|
|
if "signs" in content:
|
|
|
|
del(content["signs"])
|
|
|
|
if "signers_sign" in content:
|
|
|
|
del(content["signers_sign"])
|
|
|
|
|
|
|
|
settings = site.settings.copy()
|
|
|
|
del settings["wrapper_key"] # Dont expose wrapper key
|
|
|
|
del settings["auth_key"] # Dont send auth key twice
|
|
|
|
|
|
|
|
ret = {
|
|
|
|
"auth_key": self.site.settings["auth_key"], # Obsolete, will be removed
|
|
|
|
"auth_address": self.user.getAuthAddress(site.address, create=create_user),
|
|
|
|
"cert_user_id": self.user.getCertUserId(site.address),
|
|
|
|
"address": site.address,
|
2019-07-01 16:27:34 +02:00
|
|
|
"address_short": site.address_short,
|
2015-07-12 20:36:46 +02:00
|
|
|
"settings": settings,
|
|
|
|
"content_updated": site.content_updated,
|
|
|
|
"bad_files": len(site.bad_files),
|
|
|
|
"size_limit": site.getSizeLimit(),
|
|
|
|
"next_size_limit": site.getNextSizeLimit(),
|
2015-08-16 11:51:00 +02:00
|
|
|
"peers": max(site.settings.get("peers", 0), len(site.peers)),
|
2015-07-12 20:36:46 +02:00
|
|
|
"started_task_num": site.worker_manager.started_task_num,
|
|
|
|
"tasks": len(site.worker_manager.tasks),
|
|
|
|
"workers": len(site.worker_manager.workers),
|
|
|
|
"content": content
|
|
|
|
}
|
|
|
|
if site.settings["own"]:
|
|
|
|
ret["privatekey"] = bool(self.user.getSiteData(site.address, create=create_user).get("privatekey"))
|
2019-04-15 15:49:04 +02:00
|
|
|
if site.isServing() and content:
|
2015-07-12 20:36:46 +02:00
|
|
|
ret["peers"] += 1 # Add myself if serving
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def formatServerInfo(self):
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
file_server = main.file_server
|
2019-01-23 02:11:31 +01:00
|
|
|
if file_server.port_opened == {}:
|
|
|
|
ip_external = None
|
|
|
|
else:
|
|
|
|
ip_external = any(file_server.port_opened.values())
|
2019-04-08 18:14:31 +02:00
|
|
|
back = {
|
2019-01-23 02:11:31 +01:00
|
|
|
"ip_external": ip_external,
|
|
|
|
"port_opened": file_server.port_opened,
|
2015-07-12 20:36:46 +02:00
|
|
|
"platform": sys.platform,
|
|
|
|
"fileserver_ip": config.fileserver_ip,
|
|
|
|
"fileserver_port": config.fileserver_port,
|
2018-09-05 14:33:21 +02:00
|
|
|
"tor_enabled": file_server.tor_manager.enabled,
|
|
|
|
"tor_status": file_server.tor_manager.status,
|
|
|
|
"tor_has_meek_bridges": file_server.tor_manager.has_meek_bridges,
|
2018-04-28 21:59:39 +02:00
|
|
|
"tor_use_bridges": config.tor_use_bridges,
|
2015-07-12 20:36:46 +02:00
|
|
|
"ui_ip": config.ui_ip,
|
|
|
|
"ui_port": config.ui_port,
|
|
|
|
"version": config.version,
|
|
|
|
"rev": config.rev,
|
2018-09-05 14:33:31 +02:00
|
|
|
"timecorrection": file_server.timecorrection,
|
2016-11-18 20:11:11 +01:00
|
|
|
"language": config.language,
|
2015-07-12 20:36:46 +02:00
|
|
|
"debug": config.debug,
|
2019-04-15 15:49:34 +02:00
|
|
|
"offline": config.offline,
|
2018-10-20 02:28:58 +02:00
|
|
|
"plugins": PluginManager.plugin_manager.plugin_names,
|
2019-08-03 01:34:21 +02:00
|
|
|
"plugins_rev": PluginManager.plugin_manager.plugins_rev,
|
2018-10-20 02:28:58 +02:00
|
|
|
"user_settings": self.user.settings
|
2015-07-12 20:36:46 +02:00
|
|
|
}
|
2019-04-08 18:14:31 +02:00
|
|
|
if "ADMIN" in self.site.settings["permissions"]:
|
|
|
|
back["updatesite"] = config.updatesite
|
|
|
|
back["dist_type"] = config.dist_type
|
|
|
|
back["lib_verify_best"] = CryptBitcoin.lib_verify_best
|
|
|
|
return back
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-04-28 22:00:15 +02:00
|
|
|
def formatAnnouncerInfo(self, site):
|
|
|
|
return {"address": site.address, "stats": site.announcer.stats}
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# - Actions -
|
|
|
|
|
2017-12-15 12:13:19 +01:00
|
|
|
def actionAs(self, to, address, cmd, params=[]):
|
2018-01-28 16:41:42 +01:00
|
|
|
if not self.hasSitePermission(address, cmd=cmd):
|
2017-12-15 12:13:19 +01:00
|
|
|
return self.response(to, "No permission for site %s" % address)
|
|
|
|
req_self = copy.copy(self)
|
|
|
|
req_self.site = self.server.sites.get(address)
|
|
|
|
req_self.hasCmdPermission = self.hasCmdPermission # Use the same permissions as current site
|
|
|
|
req_obj = super(UiWebsocket, req_self)
|
|
|
|
req = {"id": to, "cmd": cmd, "params": params}
|
|
|
|
req_obj.handleRequest(req)
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Do callback on response {"cmd": "response", "to": message_id, "result": result}
|
|
|
|
def actionResponse(self, to, result):
|
|
|
|
if to in self.waiting_cb:
|
|
|
|
self.waiting_cb[to](result) # Call callback function
|
|
|
|
else:
|
|
|
|
self.log.error("Websocket callback not found: %s, %s" % (to, result))
|
|
|
|
|
|
|
|
# Send a simple pong answer
|
|
|
|
def actionPing(self, to):
|
|
|
|
self.response(to, "pong")
|
|
|
|
|
|
|
|
# Send site details
|
|
|
|
def actionSiteInfo(self, to, file_status=None):
|
|
|
|
ret = self.formatSiteInfo(self.site)
|
|
|
|
if file_status: # Client queries file status
|
2016-04-09 19:49:23 +02:00
|
|
|
if self.site.storage.isFile(file_status): # File exist, add event done
|
2015-07-12 20:36:46 +02:00
|
|
|
ret["event"] = ("file_done", file_status)
|
|
|
|
self.response(to, ret)
|
|
|
|
|
2019-04-08 18:14:45 +02:00
|
|
|
def actionSiteBadFiles(self, to):
|
|
|
|
return list(self.site.bad_files.keys())
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Join to an event channel
|
2017-11-05 23:37:32 +01:00
|
|
|
def actionChannelJoin(self, to, channels):
|
|
|
|
if type(channels) != list:
|
|
|
|
channels = [channels]
|
|
|
|
|
|
|
|
for channel in channels:
|
|
|
|
if channel not in self.channels:
|
|
|
|
self.channels.append(channel)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2019-08-01 19:16:10 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Server variables
|
|
|
|
def actionServerInfo(self, to):
|
2018-07-16 01:48:27 +02:00
|
|
|
back = self.formatServerInfo()
|
|
|
|
self.response(to, back)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-04-28 22:01:12 +02:00
|
|
|
# Create a new wrapper nonce that allows to load html file
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-04-28 22:01:12 +02:00
|
|
|
def actionServerGetWrapperNonce(self, to):
|
|
|
|
wrapper_nonce = self.request.getWrapperNonce()
|
|
|
|
self.response(to, wrapper_nonce)
|
2018-04-28 22:00:15 +02:00
|
|
|
|
|
|
|
def actionAnnouncerInfo(self, to):
|
2018-07-16 01:48:27 +02:00
|
|
|
back = self.formatAnnouncerInfo(self.site)
|
|
|
|
self.response(to, back)
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-07-16 01:48:27 +02:00
|
|
|
def actionAnnouncerStats(self, to):
|
|
|
|
back = {}
|
2018-09-04 15:43:52 +02:00
|
|
|
trackers = self.site.announcer.getTrackers()
|
2019-03-15 21:06:59 +01:00
|
|
|
for site in list(self.server.sites.values()):
|
|
|
|
for tracker, stats in site.announcer.stats.items():
|
2018-09-04 15:43:52 +02:00
|
|
|
if tracker not in trackers:
|
|
|
|
continue
|
2018-07-16 01:48:27 +02:00
|
|
|
if tracker not in back:
|
|
|
|
back[tracker] = {}
|
2018-09-04 15:43:52 +02:00
|
|
|
is_latest_data = bool(stats["time_request"] > back[tracker].get("time_request", 0) and stats["status"])
|
2019-03-15 21:06:59 +01:00
|
|
|
for key, val in stats.items():
|
2018-07-16 01:48:27 +02:00
|
|
|
if key.startswith("num_"):
|
|
|
|
back[tracker][key] = back[tracker].get(key, 0) + val
|
|
|
|
elif is_latest_data:
|
|
|
|
back[tracker][key] = val
|
|
|
|
|
|
|
|
return back
|
2018-04-28 22:00:15 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Sign content.json
|
2017-10-16 15:11:43 +02:00
|
|
|
def actionSiteSign(self, to, privatekey=None, inner_path="content.json", remove_missing_optional=False, update_changed_files=False, response_ok=True):
|
2016-03-18 19:15:38 +01:00
|
|
|
self.log.debug("Signing: %s" % inner_path)
|
2015-07-12 20:36:46 +02:00
|
|
|
site = self.site
|
|
|
|
extend = {} # Extended info for signing
|
2016-08-10 12:21:34 +02:00
|
|
|
|
|
|
|
# Change to the file's content.json
|
|
|
|
file_info = site.content_manager.getFileInfo(inner_path)
|
|
|
|
if not inner_path.endswith("content.json"):
|
|
|
|
if not file_info:
|
|
|
|
raise Exception("Invalid content.json file: %s" % inner_path)
|
2015-07-12 20:36:46 +02:00
|
|
|
inner_path = file_info["content_inner_path"]
|
2016-08-10 12:21:34 +02:00
|
|
|
|
|
|
|
# Add certificate to user files
|
2018-11-08 01:16:08 +01:00
|
|
|
is_user_content = file_info and ("cert_signers" in file_info or "cert_signers_pattern" in file_info)
|
|
|
|
if is_user_content and privatekey is None:
|
2016-08-10 12:21:34 +02:00
|
|
|
cert = self.user.getCert(self.site.address)
|
|
|
|
extend["cert_auth_type"] = cert["auth_type"]
|
|
|
|
extend["cert_user_id"] = self.user.getCertUserId(site.address)
|
|
|
|
extend["cert_sign"] = cert["cert_sign"]
|
2018-11-08 01:16:08 +01:00
|
|
|
self.log.debug("Extending content.json with cert %s" % extend["cert_user_id"])
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-03-20 21:55:12 +01:00
|
|
|
if not self.hasFilePermission(inner_path):
|
2017-03-03 03:42:20 +01:00
|
|
|
self.log.error("SiteSign error: you don't own this site & site owner doesn't allow you to do so.")
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
return self.response(to, {"error": "Forbidden, you can only modify your own sites"})
|
2017-03-03 03:42:20 +01:00
|
|
|
|
2016-08-10 12:21:34 +02:00
|
|
|
if privatekey == "stored": # Get privatekey from sites.json
|
2015-07-12 20:36:46 +02:00
|
|
|
privatekey = self.user.getSiteData(self.site.address).get("privatekey")
|
2019-05-29 16:03:10 +02:00
|
|
|
if not privatekey:
|
|
|
|
self.cmd("notification", ["error", _["Content signing failed"] + "<br><small>Private key not found in sites.json </small>"])
|
|
|
|
self.response(to, {"error": "Site sign failed: Private key not stored."})
|
|
|
|
self.log.error("Site sign failed: %s: Private key not stored in sites.json" % inner_path)
|
|
|
|
return
|
2015-07-12 20:36:46 +02:00
|
|
|
if not privatekey: # Get privatekey from users.json auth_address
|
|
|
|
privatekey = self.user.getAuthPrivatekey(self.site.address)
|
|
|
|
|
|
|
|
# Signing
|
2016-05-16 22:26:40 +02:00
|
|
|
# Reload content.json, ignore errors to make it up-to-date
|
|
|
|
site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)
|
|
|
|
# Sign using private key sent by user
|
2017-06-19 16:11:47 +02:00
|
|
|
try:
|
2018-03-20 21:55:12 +01:00
|
|
|
site.content_manager.sign(inner_path, privatekey, extend=extend, update_changed_files=update_changed_files, remove_missing_optional=remove_missing_optional)
|
2018-03-18 21:27:04 +01:00
|
|
|
except (VerifyError, SignError) as err:
|
2017-06-19 16:11:47 +02:00
|
|
|
self.cmd("notification", ["error", _["Content signing failed"] + "<br><small>%s</small>" % err])
|
|
|
|
self.response(to, {"error": "Site sign failed: %s" % err})
|
2017-08-18 14:38:58 +02:00
|
|
|
self.log.error("Site sign failed: %s: %s" % (inner_path, Debug.formatException(err)))
|
2015-07-12 20:36:46 +02:00
|
|
|
return
|
2018-03-18 21:27:04 +01:00
|
|
|
except Exception as err:
|
|
|
|
self.cmd("notification", ["error", _["Content signing error"] + "<br><small>%s</small>" % Debug.formatException(err)])
|
|
|
|
self.response(to, {"error": "Site sign error: %s" % Debug.formatException(err)})
|
|
|
|
self.log.error("Site sign error: %s: %s" % (inner_path, Debug.formatException(err)))
|
|
|
|
return
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2016-03-16 21:08:19 +01:00
|
|
|
site.content_manager.loadContent(inner_path, add_bad_files=False) # Load new content.json, ignore errors
|
2016-04-25 02:26:13 +02:00
|
|
|
|
2016-05-16 22:26:40 +02:00
|
|
|
if update_changed_files:
|
|
|
|
self.site.updateWebsocket(file_done=inner_path)
|
|
|
|
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
if response_ok:
|
|
|
|
self.response(to, "ok")
|
2017-10-26 10:40:02 +02:00
|
|
|
else:
|
|
|
|
return inner_path
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
# Sign and publish content.json
|
2018-06-25 14:22:24 +02:00
|
|
|
def actionSitePublish(self, to, privatekey=None, inner_path="content.json", sign=True, remove_missing_optional=False, update_changed_files=False):
|
2015-07-12 20:36:46 +02:00
|
|
|
if sign:
|
2018-06-25 14:22:24 +02:00
|
|
|
inner_path = self.actionSiteSign(
|
|
|
|
to, privatekey, inner_path, response_ok=False,
|
|
|
|
remove_missing_optional=remove_missing_optional, update_changed_files=update_changed_files
|
|
|
|
)
|
2015-07-12 20:36:46 +02:00
|
|
|
if not inner_path:
|
|
|
|
return
|
|
|
|
# Publishing
|
|
|
|
if not self.site.settings["serving"]: # Enable site if paused
|
|
|
|
self.site.settings["serving"] = True
|
|
|
|
self.site.saveSettings()
|
|
|
|
self.site.announce()
|
|
|
|
|
2018-03-20 21:53:39 +01:00
|
|
|
if inner_path not in self.site.content_manager.contents:
|
2017-06-19 15:38:05 +02:00
|
|
|
return self.response(to, {"error": "File %s not found" % inner_path})
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
event_name = "publish %s %s" % (self.site.address, inner_path)
|
2016-03-30 23:15:18 +02:00
|
|
|
called_instantly = RateLimit.isAllowed(event_name, 30)
|
2016-08-10 12:22:27 +02:00
|
|
|
thread = RateLimit.callAsync(event_name, 30, self.doSitePublish, self.site, inner_path) # Only publish once in 30 seconds
|
2015-07-12 20:36:46 +02:00
|
|
|
notification = "linked" not in dir(thread) # Only display notification on first callback
|
|
|
|
thread.linked = True
|
2016-03-30 23:15:18 +02:00
|
|
|
if called_instantly: # Allowed to call instantly
|
|
|
|
# At the end callback with request id and thread
|
2017-01-16 13:41:57 +01:00
|
|
|
self.cmd("progress", ["publish", _["Content published to {0}/{1} peers."].format(0, 5), 0])
|
2016-08-10 12:22:27 +02:00
|
|
|
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=notification))
|
2016-03-30 23:15:18 +02:00
|
|
|
else:
|
|
|
|
self.cmd(
|
|
|
|
"notification",
|
2016-11-18 20:09:51 +01:00
|
|
|
["info", _["Content publish queued for {0:.0f} seconds."].format(RateLimit.delayLeft(event_name, 30)), 5000]
|
2016-03-30 23:15:18 +02:00
|
|
|
)
|
|
|
|
self.response(to, "ok")
|
|
|
|
# At the end display notification
|
2016-08-10 12:22:27 +02:00
|
|
|
thread.link(lambda thread: self.cbSitePublish(to, self.site, thread, notification, callback=False))
|
2016-03-30 23:15:18 +02:00
|
|
|
|
2016-08-10 12:22:27 +02:00
|
|
|
def doSitePublish(self, site, inner_path):
|
2017-01-16 13:41:57 +01:00
|
|
|
def cbProgress(published, limit):
|
|
|
|
progress = int(float(published) / limit * 100)
|
|
|
|
self.cmd("progress", [
|
|
|
|
"publish",
|
|
|
|
_["Content published to {0}/{1} peers."].format(published, limit),
|
|
|
|
progress
|
|
|
|
])
|
2016-08-10 12:22:27 +02:00
|
|
|
diffs = site.content_manager.getDiffs(inner_path)
|
2017-01-16 13:41:57 +01:00
|
|
|
back = site.publish(limit=5, inner_path=inner_path, diffs=diffs, cb_progress=cbProgress)
|
|
|
|
if back == 0: # Failed to publish to anyone
|
|
|
|
self.cmd("progress", ["publish", _["Content publish failed."], -100])
|
|
|
|
else:
|
|
|
|
cbProgress(back, back)
|
|
|
|
return back
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
# Callback of site publish
|
2016-08-10 12:22:27 +02:00
|
|
|
def cbSitePublish(self, to, site, thread, notification=True, callback=True):
|
2015-07-12 20:36:46 +02:00
|
|
|
published = thread.value
|
2016-04-09 19:49:23 +02:00
|
|
|
if published > 0: # Successfully published
|
2015-07-12 20:36:46 +02:00
|
|
|
if notification:
|
2017-01-16 13:41:57 +01:00
|
|
|
# self.cmd("notification", ["done", _["Content published to {0} peers."].format(published), 5000])
|
2015-07-12 20:36:46 +02:00
|
|
|
site.updateWebsocket() # Send updated site data to local websocket clients
|
2016-03-30 23:15:18 +02:00
|
|
|
if callback:
|
|
|
|
self.response(to, "ok")
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
if len(site.peers) == 0:
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
if any(main.file_server.port_opened.values()) or main.file_server.tor_manager.start_onions:
|
2015-07-12 20:36:46 +02:00
|
|
|
if notification:
|
2017-03-18 11:11:01 +01:00
|
|
|
self.cmd("notification", ["info", _["No peers found, but your content is ready to access."]])
|
2016-03-30 23:15:18 +02:00
|
|
|
if callback:
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
self.response(to, "ok")
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
if notification:
|
|
|
|
self.cmd("notification", [
|
|
|
|
"info",
|
2019-03-15 21:06:59 +01:00
|
|
|
_("""{_[Your network connection is restricted. Please, open <b>{0}</b> port]}<br>
|
2016-11-18 20:09:51 +01:00
|
|
|
{_[on your router to make your site accessible for everyone.]}""").format(config.fileserver_port)
|
2015-07-12 20:36:46 +02:00
|
|
|
])
|
2016-03-30 23:15:18 +02:00
|
|
|
if callback:
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
self.response(to, {"error": "Port not opened."})
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
else:
|
|
|
|
if notification:
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
self.response(to, {"error": "Content publish failed."})
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-04-03 14:49:40 +02:00
|
|
|
def actionSiteReload(self, to, inner_path):
|
|
|
|
self.site.content_manager.loadContent(inner_path, add_bad_files=False)
|
|
|
|
self.site.storage.verifyFiles(quick_check=True)
|
|
|
|
self.site.updateWebsocket()
|
|
|
|
return "ok"
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Write a file to disk
|
2016-09-04 18:05:17 +02:00
|
|
|
def actionFileWrite(self, to, inner_path, content_base64, ignore_bad_files=False):
|
2016-08-10 12:23:01 +02:00
|
|
|
valid_signers = self.site.content_manager.getValidSigners(inner_path)
|
|
|
|
auth_address = self.user.getAuthAddress(self.site.address)
|
2018-03-20 21:55:12 +01:00
|
|
|
if not self.hasFilePermission(inner_path):
|
2017-03-03 03:42:20 +01:00
|
|
|
self.log.error("FileWrite forbidden %s not in valid_signers %s" % (auth_address, valid_signers))
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
return self.response(to, {"error": "Forbidden, you can only modify your own files"})
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2016-09-04 18:05:17 +02:00
|
|
|
# Try not to overwrite files currently in sync
|
|
|
|
content_inner_path = re.sub("^(.*)/.*?$", "\\1/content.json", inner_path) # Also check the content.json from same directory
|
|
|
|
if (self.site.bad_files.get(inner_path) or self.site.bad_files.get(content_inner_path)) and not ignore_bad_files:
|
|
|
|
found = self.site.needFile(inner_path, update=True, priority=10)
|
|
|
|
if not found:
|
|
|
|
self.cmd(
|
2016-09-14 18:28:48 +02:00
|
|
|
"confirm",
|
2016-11-18 20:09:51 +01:00
|
|
|
[_["This file still in sync, if you write it now, then the previous content may be lost."], _["Write content anyway"]],
|
2019-03-15 21:06:59 +01:00
|
|
|
lambda res: self.actionFileWrite(to, inner_path, content_base64, ignore_bad_files=True)
|
2016-09-04 18:05:17 +02:00
|
|
|
)
|
|
|
|
return False
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
try:
|
|
|
|
import base64
|
|
|
|
content = base64.b64decode(content_base64)
|
2016-04-06 13:56:32 +02:00
|
|
|
# Save old file to generate patch later
|
2016-04-25 02:26:13 +02:00
|
|
|
if (
|
|
|
|
inner_path.endswith(".json") and not inner_path.endswith("content.json") and
|
|
|
|
self.site.storage.isFile(inner_path) and not self.site.storage.isFile(inner_path + "-old")
|
|
|
|
):
|
2016-04-09 19:43:44 +02:00
|
|
|
try:
|
|
|
|
self.site.storage.rename(inner_path, inner_path + "-old")
|
|
|
|
except Exception:
|
|
|
|
# Rename failed, fall back to standard file write
|
|
|
|
f_old = self.site.storage.open(inner_path, "rb")
|
|
|
|
f_new = self.site.storage.open(inner_path + "-old", "wb")
|
|
|
|
shutil.copyfileobj(f_old, f_new)
|
2016-04-06 13:56:32 +02:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
self.site.storage.write(inner_path, content)
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2017-03-03 03:42:20 +01:00
|
|
|
self.log.error("File write error: %s" % Debug.formatException(err))
|
2016-04-07 10:35:02 +02:00
|
|
|
return self.response(to, {"error": "Write error: %s" % Debug.formatException(err)})
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
if inner_path.endswith("content.json"):
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
self.site.content_manager.loadContent(inner_path, add_bad_files=False, force=True)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
|
|
|
self.response(to, "ok")
|
|
|
|
|
|
|
|
# Send sitechanged to other local users
|
|
|
|
for ws in self.site.websockets:
|
|
|
|
if ws != self:
|
|
|
|
ws.event("siteChanged", self.site, {"event": ["file_done", inner_path]})
|
|
|
|
|
2015-09-16 00:01:23 +02:00
|
|
|
def actionFileDelete(self, to, inner_path):
|
2018-03-20 21:55:12 +01:00
|
|
|
if not self.hasFilePermission(inner_path):
|
2017-03-03 03:42:20 +01:00
|
|
|
self.log.error("File delete error: you don't own this site & you are not approved by the owner.")
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
return self.response(to, {"error": "Forbidden, you can only modify your own files"})
|
2015-09-16 00:01:23 +02:00
|
|
|
|
2018-03-20 21:54:33 +01:00
|
|
|
need_delete = True
|
2017-01-27 12:02:14 +01:00
|
|
|
file_info = self.site.content_manager.getFileInfo(inner_path)
|
2017-06-27 18:08:28 +02:00
|
|
|
if file_info and file_info.get("optional"):
|
2018-03-20 21:54:33 +01:00
|
|
|
# Non-existing optional files won't be removed from content.json, so we have to do it manually
|
2017-01-27 14:01:55 +01:00
|
|
|
self.log.debug("Deleting optional file: %s" % inner_path)
|
|
|
|
relative_path = file_info["relative_path"]
|
|
|
|
content_json = self.site.storage.loadJson(file_info["content_inner_path"])
|
|
|
|
if relative_path in content_json.get("files_optional", {}):
|
|
|
|
del content_json["files_optional"][relative_path]
|
|
|
|
self.site.storage.writeJson(file_info["content_inner_path"], content_json)
|
|
|
|
self.site.content_manager.loadContent(file_info["content_inner_path"], add_bad_files=False, force=True)
|
2018-03-20 21:54:33 +01:00
|
|
|
need_delete = self.site.storage.isFile(inner_path) # File sill exists after removing from content.json (owned site)
|
2017-01-27 12:02:14 +01:00
|
|
|
|
2018-03-20 21:54:33 +01:00
|
|
|
if need_delete:
|
|
|
|
try:
|
|
|
|
self.site.storage.delete(inner_path)
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2018-03-20 21:54:33 +01:00
|
|
|
self.log.error("File delete error: %s" % err)
|
|
|
|
return self.response(to, {"error": "Delete error: %s" % err})
|
2015-09-16 00:01:23 +02:00
|
|
|
|
|
|
|
self.response(to, "ok")
|
|
|
|
|
|
|
|
# Send sitechanged to other local users
|
|
|
|
for ws in self.site.websockets:
|
|
|
|
if ws != self:
|
|
|
|
ws.event("siteChanged", self.site, {"event": ["file_deleted", inner_path]})
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Find data in json files
|
2017-04-21 11:26:17 +02:00
|
|
|
def actionFileQuery(self, to, dir_inner_path, query=None):
|
2015-07-12 20:36:46 +02:00
|
|
|
# s = time.time()
|
|
|
|
dir_path = self.site.storage.getPath(dir_inner_path)
|
2017-04-21 11:26:17 +02:00
|
|
|
rows = list(QueryJson.query(dir_path, query or ""))
|
2015-07-12 20:36:46 +02:00
|
|
|
# self.log.debug("FileQuery %s %s done in %s" % (dir_inner_path, query, time.time()-s))
|
|
|
|
return self.response(to, rows)
|
|
|
|
|
2017-01-05 02:26:58 +01:00
|
|
|
# List files in directory
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.async_run
|
2017-01-05 02:26:58 +01:00
|
|
|
def actionFileList(self, to, inner_path):
|
2018-08-16 16:22:17 +02:00
|
|
|
try:
|
|
|
|
return list(self.site.storage.walk(inner_path))
|
|
|
|
except Exception as err:
|
2019-03-29 02:31:05 +01:00
|
|
|
self.log.error("fileList %s error: %s" % (inner_path, Debug.formatException(err)))
|
2019-05-02 18:02:56 +02:00
|
|
|
return {"error": Debug.formatExceptionMessage(err)}
|
2017-02-25 05:47:38 +01:00
|
|
|
|
|
|
|
# List directories in a directory
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.async_run
|
2017-02-25 05:47:38 +01:00
|
|
|
def actionDirList(self, to, inner_path):
|
2018-08-16 16:22:17 +02:00
|
|
|
try:
|
|
|
|
return list(self.site.storage.list(inner_path))
|
|
|
|
except Exception as err:
|
2019-03-29 02:31:05 +01:00
|
|
|
self.log.error("dirList %s error: %s" % (inner_path, Debug.formatException(err)))
|
2019-05-02 18:02:56 +02:00
|
|
|
return {"error": Debug.formatExceptionMessage(err)}
|
2017-01-05 02:26:58 +01:00
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Sql query
|
|
|
|
def actionDbQuery(self, to, query, params=None, wait_for=None):
|
2017-03-15 13:13:21 +01:00
|
|
|
if config.debug or config.verbose:
|
2016-09-04 18:05:31 +02:00
|
|
|
s = time.time()
|
2015-07-12 20:36:46 +02:00
|
|
|
rows = []
|
|
|
|
try:
|
|
|
|
res = self.site.storage.query(query, params)
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err: # Response the error to client
|
2017-03-06 15:30:31 +01:00
|
|
|
self.log.error("DbQuery error: %s" % err)
|
2019-05-02 18:02:56 +02:00
|
|
|
return self.response(to, {"error": Debug.formatExceptionMessage(err)})
|
2015-07-12 20:36:46 +02:00
|
|
|
# Convert result to dict
|
|
|
|
for row in res:
|
|
|
|
rows.append(dict(row))
|
2016-09-04 18:05:31 +02:00
|
|
|
if config.verbose and time.time() - s > 0.1: # Log slow query
|
|
|
|
self.log.debug("Slow query: %s (%.3fs)" % (query, time.time() - s))
|
2015-07-12 20:36:46 +02:00
|
|
|
return self.response(to, rows)
|
|
|
|
|
|
|
|
# Return file content
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.async_run
|
2019-09-08 11:51:46 +02:00
|
|
|
def actionFileGet(self, to, inner_path, required=True, format="text", timeout=300, priority=6):
|
2015-07-12 20:36:46 +02:00
|
|
|
try:
|
Version 0.3.4, Rev656, CryptMessage plugin for AES and ECIES encryption, Added pyelliptic lib for OpenSSSL based encryption methods, Test CryptMessage plugin, Force reload content.json before signing and after write, Escaped Sql IN queries support, Test Sql parameter escaping, ui_websocket Test fixture, Plugin testing support, Always return websocket errors as dict, Wait for file on weboscket fileGet command if its already in bad_files queue, PushState and ReplaceState url manipulation support in wrapper API, Per auth-address localstorage, Longer timeout for udp tracker query
2015-12-10 21:36:20 +01:00
|
|
|
if required or inner_path in self.site.bad_files:
|
2017-01-22 11:09:45 +01:00
|
|
|
with gevent.Timeout(timeout):
|
2019-09-08 11:51:46 +02:00
|
|
|
self.site.needFile(inner_path, priority=priority)
|
2017-09-10 08:46:21 +02:00
|
|
|
body = self.site.storage.read(inner_path, "rb")
|
2019-05-19 14:31:16 +02:00
|
|
|
except (Exception, gevent.Timeout) as err:
|
2019-03-29 02:31:05 +01:00
|
|
|
self.log.error("%s fileGet error: %s" % (inner_path, Debug.formatException(err)))
|
2015-07-12 20:36:46 +02:00
|
|
|
body = None
|
2019-03-29 02:31:14 +01:00
|
|
|
|
|
|
|
if not body:
|
|
|
|
body = None
|
|
|
|
elif format == "base64":
|
2017-01-22 11:09:45 +01:00
|
|
|
import base64
|
2019-03-29 02:31:14 +01:00
|
|
|
body = base64.b64encode(body).decode()
|
|
|
|
else:
|
|
|
|
body = body.decode()
|
2018-09-07 11:33:57 +02:00
|
|
|
self.response(to, body)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.async_run
|
2019-09-08 11:51:46 +02:00
|
|
|
def actionFileNeed(self, to, inner_path, timeout=300, priority=6):
|
2017-08-09 14:20:49 +02:00
|
|
|
try:
|
|
|
|
with gevent.Timeout(timeout):
|
2019-09-08 11:51:46 +02:00
|
|
|
self.site.needFile(inner_path, priority=priority)
|
2019-09-02 21:34:29 +02:00
|
|
|
except (Exception, gevent.Timeout) as err:
|
2019-05-02 18:02:56 +02:00
|
|
|
return self.response(to, {"error": Debug.formatExceptionMessage(err)})
|
2017-08-09 14:20:49 +02:00
|
|
|
return self.response(to, "ok")
|
|
|
|
|
2018-03-29 02:41:30 +02:00
|
|
|
def actionFileRules(self, to, inner_path, use_my_cert=False, content=None):
|
|
|
|
if not content: # No content defined by function call
|
2015-07-12 20:36:46 +02:00
|
|
|
content = self.site.content_manager.contents.get(inner_path)
|
2018-03-29 02:41:30 +02:00
|
|
|
|
|
|
|
if not content: # File not created yet
|
|
|
|
cert = self.user.getCert(self.site.address)
|
|
|
|
if cert and cert["auth_address"] in self.site.content_manager.getValidSigners(inner_path):
|
|
|
|
# Current selected cert if valid for this site, add it to query rules
|
|
|
|
content = {}
|
|
|
|
content["cert_auth_type"] = cert["auth_type"]
|
|
|
|
content["cert_user_id"] = self.user.getCertUserId(self.site.address)
|
|
|
|
content["cert_sign"] = cert["cert_sign"]
|
|
|
|
|
|
|
|
rules = self.site.content_manager.getRules(inner_path, content)
|
|
|
|
if inner_path.endswith("content.json") and rules:
|
2015-07-12 20:36:46 +02:00
|
|
|
if content:
|
2019-03-15 21:06:59 +01:00
|
|
|
rules["current_size"] = len(json.dumps(content)) + sum([file["size"] for file in list(content.get("files", {}).values())])
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
rules["current_size"] = 0
|
|
|
|
return self.response(to, rules)
|
|
|
|
|
|
|
|
# Add certificate to user
|
|
|
|
def actionCertAdd(self, to, domain, auth_type, auth_user_name, cert):
|
|
|
|
try:
|
|
|
|
res = self.user.addCert(self.user.getAuthAddress(self.site.address), domain, auth_type, auth_user_name, cert)
|
|
|
|
if res is True:
|
|
|
|
self.cmd(
|
|
|
|
"notification",
|
2017-01-07 01:09:05 +01:00
|
|
|
["done", _("{_[New certificate added]:} <b>{auth_type}/{auth_user_name}@{domain}</b>.")]
|
2015-07-12 20:36:46 +02:00
|
|
|
)
|
2017-04-12 00:32:19 +02:00
|
|
|
self.user.setCert(self.site.address, domain)
|
|
|
|
self.site.updateWebsocket(cert_changed=domain)
|
2015-07-12 20:36:46 +02:00
|
|
|
self.response(to, "ok")
|
2016-04-09 19:49:12 +02:00
|
|
|
elif res is False:
|
|
|
|
# Display confirmation of change
|
|
|
|
cert_current = self.user.certs[domain]
|
2017-01-07 01:09:05 +01:00
|
|
|
body = _("{_[Your current certificate]:} <b>{cert_current[auth_type]}/{cert_current[auth_user_name]}@{domain}</b>")
|
2016-05-16 22:26:40 +02:00
|
|
|
self.cmd(
|
|
|
|
"confirm",
|
2016-11-18 20:09:51 +01:00
|
|
|
[body, _("Change it to {auth_type}/{auth_user_name}@{domain}")],
|
2019-03-15 21:06:59 +01:00
|
|
|
lambda res: self.cbCertAddConfirm(to, domain, auth_type, auth_user_name, cert)
|
2016-04-09 19:49:12 +02:00
|
|
|
)
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
self.response(to, "Not changed")
|
2019-03-15 21:06:59 +01:00
|
|
|
except Exception as err:
|
2018-11-26 00:08:22 +01:00
|
|
|
self.log.error("CertAdd error: Exception - %s (%s)" % (err.message, Debug.formatException(err)))
|
2015-07-12 20:36:46 +02:00
|
|
|
self.response(to, {"error": err.message})
|
|
|
|
|
2016-04-09 19:49:12 +02:00
|
|
|
def cbCertAddConfirm(self, to, domain, auth_type, auth_user_name, cert):
|
|
|
|
self.user.deleteCert(domain)
|
|
|
|
self.user.addCert(self.user.getAuthAddress(self.site.address), domain, auth_type, auth_user_name, cert)
|
|
|
|
self.cmd(
|
|
|
|
"notification",
|
2016-11-18 20:09:51 +01:00
|
|
|
["done", _("Certificate changed to: <b>{auth_type}/{auth_user_name}@{domain}</b>.")]
|
2016-04-09 19:49:12 +02:00
|
|
|
)
|
2017-04-12 00:32:19 +02:00
|
|
|
self.user.setCert(self.site.address, domain)
|
|
|
|
self.site.updateWebsocket(cert_changed=domain)
|
2016-04-09 19:49:12 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Select certificate for site
|
2018-11-08 01:16:08 +01:00
|
|
|
def actionCertSelect(self, to, accepted_domains=[], accept_any=False, accepted_pattern=None):
|
2015-07-12 20:36:46 +02:00
|
|
|
accounts = []
|
2018-02-18 05:46:49 +01:00
|
|
|
accounts.append(["", _["No certificate"], ""]) # Default option
|
2015-07-12 20:36:46 +02:00
|
|
|
active = "" # Make it active if no other option found
|
|
|
|
|
|
|
|
# Add my certs
|
|
|
|
auth_address = self.user.getAuthAddress(self.site.address) # Current auth address
|
2017-04-12 00:45:48 +02:00
|
|
|
site_data = self.user.getSiteData(self.site.address) # Current auth address
|
2018-11-08 01:16:08 +01:00
|
|
|
|
|
|
|
if not accepted_domains and not accepted_pattern: # Accept any if no filter defined
|
|
|
|
accept_any = True
|
|
|
|
|
2019-03-15 21:06:59 +01:00
|
|
|
for domain, cert in list(self.user.certs.items()):
|
2017-04-12 00:45:48 +02:00
|
|
|
if auth_address == cert["auth_address"] and domain == site_data.get("cert"):
|
2015-07-12 20:36:46 +02:00
|
|
|
active = domain
|
|
|
|
title = cert["auth_user_name"] + "@" + domain
|
2018-11-08 01:16:08 +01:00
|
|
|
accepted_pattern_match = accepted_pattern and SafeRe.match(accepted_pattern, domain)
|
|
|
|
if domain in accepted_domains or accept_any or accepted_pattern_match:
|
2015-07-12 20:36:46 +02:00
|
|
|
accounts.append([domain, title, ""])
|
|
|
|
else:
|
|
|
|
accounts.append([domain, title, "disabled"])
|
|
|
|
|
|
|
|
# Render the html
|
2016-11-18 20:09:51 +01:00
|
|
|
body = "<span style='padding-bottom: 5px; display: inline-block'>" + _["Select account you want to use in this site:"] + "</span>"
|
2015-07-12 20:36:46 +02:00
|
|
|
# Accounts
|
|
|
|
for domain, account, css_class in accounts:
|
|
|
|
if domain == active:
|
|
|
|
css_class += " active" # Currently selected option
|
2019-03-15 21:06:59 +01:00
|
|
|
title = _("<b>%s</b> <small>({_[currently selected]})</small>") % account
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
title = "<b>%s</b>" % account
|
|
|
|
body += "<a href='#Select+account' class='select select-close cert %s' title='%s'>%s</a>" % (css_class, domain, title)
|
2016-04-09 19:49:23 +02:00
|
|
|
# More available providers
|
|
|
|
more_domains = [domain for domain in accepted_domains if domain not in self.user.certs] # Domains we not displayed yet
|
2015-07-12 20:36:46 +02:00
|
|
|
if more_domains:
|
|
|
|
# body+= "<small style='margin-top: 10px; display: block'>Accepted authorization providers by the site:</small>"
|
|
|
|
body += "<div style='background-color: #F7F7F7; margin-right: -30px'>"
|
|
|
|
for domain in more_domains:
|
2019-03-15 21:06:59 +01:00
|
|
|
body += _("""
|
2018-11-26 00:07:08 +01:00
|
|
|
<a href='/{domain}' target='_top' class='select'>
|
2016-11-18 20:09:51 +01:00
|
|
|
<small style='float: right; margin-right: 40px; margin-top: -1px'>{_[Register]} »</small>{domain}
|
2015-07-12 20:36:46 +02:00
|
|
|
</a>
|
2016-11-18 20:09:51 +01:00
|
|
|
""")
|
2015-07-12 20:36:46 +02:00
|
|
|
body += "</div>"
|
|
|
|
|
2018-11-26 00:07:08 +01:00
|
|
|
script = """
|
2015-07-12 20:36:46 +02:00
|
|
|
$(".notification .select.cert").on("click", function() {
|
|
|
|
$(".notification .select").removeClass('active')
|
2018-02-21 03:13:27 +01:00
|
|
|
zeroframe.response(%s, this.title)
|
2015-07-12 20:36:46 +02:00
|
|
|
return false
|
|
|
|
})
|
2018-02-21 03:13:27 +01:00
|
|
|
""" % self.next_message_id
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-02-21 03:13:27 +01:00
|
|
|
self.cmd("notification", ["ask", body], lambda domain: self.actionCertSet(to, domain))
|
2018-11-26 00:07:08 +01:00
|
|
|
self.cmd("injectScript", script)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2016-04-09 19:49:23 +02:00
|
|
|
# - Admin actions -
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2016-08-10 12:12:47 +02:00
|
|
|
def actionPermissionAdd(self, to, permission):
|
|
|
|
if permission not in self.site.settings["permissions"]:
|
|
|
|
self.site.settings["permissions"].append(permission)
|
|
|
|
self.site.saveSettings()
|
2016-12-30 18:55:11 +01:00
|
|
|
self.site.updateWebsocket(permission_added=permission)
|
2016-08-10 12:12:47 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2016-08-10 12:12:47 +02:00
|
|
|
def actionPermissionRemove(self, to, permission):
|
|
|
|
self.site.settings["permissions"].remove(permission)
|
|
|
|
self.site.saveSettings()
|
2016-12-30 18:55:11 +01:00
|
|
|
self.site.updateWebsocket(permission_removed=permission)
|
2016-08-10 12:12:47 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2017-10-17 17:02:56 +02:00
|
|
|
def actionPermissionDetails(self, to, permission):
|
|
|
|
if permission == "ADMIN":
|
|
|
|
self.response(to, _["Modify your client's configuration and access all site"] + " <span style='color: red'>" + _["(Dangerous!)"] + "</span>")
|
2018-02-21 03:12:51 +01:00
|
|
|
elif permission == "NOSANDBOX":
|
2018-03-06 12:09:39 +01:00
|
|
|
self.response(to, _["Modify your client's configuration and access all site"] + " <span style='color: red'>" + _["(Dangerous!)"] + "</span>")
|
2019-04-17 11:39:00 +02:00
|
|
|
elif permission == "PushNotification":
|
|
|
|
self.response(to, _["Send notifications"])
|
2017-10-17 17:02:56 +02:00
|
|
|
else:
|
|
|
|
self.response(to, "")
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Set certificate that used for authenticate user for site
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionCertSet(self, to, domain):
|
|
|
|
self.user.setCert(self.site.address, domain)
|
|
|
|
self.site.updateWebsocket(cert_changed=domain)
|
2017-06-03 00:53:40 +02:00
|
|
|
self.response(to, "ok")
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2018-12-15 17:47:34 +01:00
|
|
|
# List user's certificates
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-11-30 15:56:20 +01:00
|
|
|
def actionCertList(self, to):
|
|
|
|
back = []
|
|
|
|
auth_address = self.user.getAuthAddress(self.site.address)
|
2019-03-15 21:06:59 +01:00
|
|
|
for domain, cert in list(self.user.certs.items()):
|
2018-11-30 15:56:20 +01:00
|
|
|
back.append({
|
|
|
|
"auth_address": cert["auth_address"],
|
|
|
|
"auth_type": cert["auth_type"],
|
|
|
|
"auth_user_name": cert["auth_user_name"],
|
|
|
|
"domain": domain,
|
|
|
|
"selected": cert["auth_address"] == auth_address
|
|
|
|
})
|
|
|
|
return back
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# List all site info
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-10-15 12:58:14 +02:00
|
|
|
def actionSiteList(self, to, connecting_sites=False):
|
2015-07-12 20:36:46 +02:00
|
|
|
ret = []
|
|
|
|
SiteManager.site_manager.load() # Reload sites
|
2019-03-15 21:06:59 +01:00
|
|
|
for site in list(self.server.sites.values()):
|
2018-10-15 12:58:14 +02:00
|
|
|
if not site.content_manager.contents.get("content.json") and not connecting_sites:
|
|
|
|
continue # Incomplete site
|
2015-07-12 20:36:46 +02:00
|
|
|
ret.append(self.formatSiteInfo(site, create_user=False)) # Dont generate the auth_address on listing
|
|
|
|
self.response(to, ret)
|
|
|
|
|
|
|
|
# Join to an event channel on all sites
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionChannelJoinAllsite(self, to, channel):
|
|
|
|
if channel not in self.channels: # Add channel to channels
|
|
|
|
self.channels.append(channel)
|
|
|
|
|
2019-03-15 21:06:59 +01:00
|
|
|
for site in list(self.server.sites.values()): # Add websocket to every channel
|
2015-07-12 20:36:46 +02:00
|
|
|
if self not in site.websockets:
|
|
|
|
site.websockets.append(self)
|
|
|
|
|
2019-08-01 19:16:10 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
# Update site content.json
|
2018-04-29 14:53:31 +02:00
|
|
|
def actionSiteUpdate(self, to, address, check_files=False, since=None, announce=False):
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
def updateThread():
|
2018-04-29 14:53:31 +02:00
|
|
|
site.update(announce=announce, check_files=check_files, since=since)
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
self.response(to, "Updated")
|
|
|
|
|
2015-07-12 20:36:46 +02:00
|
|
|
site = self.server.sites.get(address)
|
|
|
|
if site and (site.address == self.site.address or "ADMIN" in self.site.settings["permissions"]):
|
2018-08-26 22:44:55 +02:00
|
|
|
if not site.settings["serving"]:
|
|
|
|
site.settings["serving"] = True
|
|
|
|
site.saveSettings()
|
|
|
|
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
gevent.spawn(updateThread)
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
self.response(to, {"error": "Unknown site: %s" % address})
|
|
|
|
|
|
|
|
# Pause site serving
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionSitePause(self, to, address):
|
|
|
|
site = self.server.sites.get(address)
|
|
|
|
if site:
|
|
|
|
site.settings["serving"] = False
|
|
|
|
site.saveSettings()
|
|
|
|
site.updateWebsocket()
|
|
|
|
site.worker_manager.stopWorkers()
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
self.response(to, "Paused")
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
self.response(to, {"error": "Unknown site: %s" % address})
|
|
|
|
|
|
|
|
# Resume site serving
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionSiteResume(self, to, address):
|
|
|
|
site = self.server.sites.get(address)
|
|
|
|
if site:
|
|
|
|
site.settings["serving"] = True
|
|
|
|
site.saveSettings()
|
|
|
|
gevent.spawn(site.update, announce=True)
|
|
|
|
time.sleep(0.001) # Wait for update thread starting
|
|
|
|
site.updateWebsocket()
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
self.response(to, "Resumed")
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
self.response(to, {"error": "Unknown site: %s" % address})
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionSiteDelete(self, to, address):
|
|
|
|
site = self.server.sites.get(address)
|
|
|
|
if site:
|
2016-09-05 13:55:51 +02:00
|
|
|
site.delete()
|
2015-09-11 02:25:37 +02:00
|
|
|
self.user.deleteSiteData(address)
|
Rev957, Sidebar displays onion peers in graph, Sidebar display bad file retry number, Sidebar site Update/Pause/Delete, Ratelimit sidebar update, Encoded typo, Fix onion findHashId, More retry for bad files, Log file path errors, Testcase for self findhashIds, Testcase for Tor findHashId, Better Tor version parse, UiWebsocket callback on update/pause/resume/delete, Skip invalid postMessage messages
2016-03-09 00:48:57 +01:00
|
|
|
self.response(to, "Deleted")
|
2016-09-04 18:05:55 +02:00
|
|
|
import gc
|
|
|
|
gc.collect(2)
|
2015-07-12 20:36:46 +02:00
|
|
|
else:
|
|
|
|
self.response(to, {"error": "Unknown site: %s" % address})
|
|
|
|
|
2019-05-28 18:05:52 +02:00
|
|
|
def cbSiteClone(self, to, address, root_inner_path="", target_address=None, redirect=True):
|
2017-01-27 23:27:45 +01:00
|
|
|
self.cmd("notification", ["info", _["Cloning site..."]])
|
2015-07-12 20:36:46 +02:00
|
|
|
site = self.server.sites.get(address)
|
2019-05-28 18:05:52 +02:00
|
|
|
response = {}
|
2017-05-18 03:07:32 +02:00
|
|
|
if target_address:
|
|
|
|
target_site = self.server.sites.get(target_address)
|
|
|
|
privatekey = self.user.getSiteData(target_site.address).get("privatekey")
|
|
|
|
site.clone(target_address, privatekey, root_inner_path=root_inner_path)
|
|
|
|
self.cmd("notification", ["done", _["Site source code upgraded!"]])
|
|
|
|
site.publish()
|
2019-05-28 18:05:52 +02:00
|
|
|
response = {"address": target_address}
|
2017-05-18 03:07:32 +02:00
|
|
|
else:
|
|
|
|
# Generate a new site from user's bip32 seed
|
|
|
|
new_address, new_address_index, new_site_data = self.user.getNewSiteData()
|
|
|
|
new_site = site.clone(new_address, new_site_data["privatekey"], address_index=new_address_index, root_inner_path=root_inner_path)
|
|
|
|
new_site.settings["own"] = True
|
|
|
|
new_site.saveSettings()
|
2018-11-26 00:07:23 +01:00
|
|
|
self.cmd("notification", ["done", _["Site cloned"]])
|
2019-07-01 16:27:40 +02:00
|
|
|
if redirect:
|
2019-05-28 18:05:52 +02:00
|
|
|
self.cmd("redirect", "/%s" % new_address)
|
2017-05-18 03:07:32 +02:00
|
|
|
gevent.spawn(new_site.announce)
|
2019-05-28 18:05:52 +02:00
|
|
|
response = {"address": new_address}
|
|
|
|
self.response(to, response)
|
2018-10-30 04:49:42 +01:00
|
|
|
return "ok"
|
2017-05-18 03:07:32 +02:00
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.no_multiuser
|
2019-05-28 18:05:52 +02:00
|
|
|
def actionSiteClone(self, to, address, root_inner_path="", target_address=None, redirect=True):
|
2017-09-22 07:28:39 +02:00
|
|
|
if not SiteManager.site_manager.isAddress(address):
|
|
|
|
self.response(to, {"error": "Not a site: %s" % address})
|
|
|
|
return
|
|
|
|
|
|
|
|
if not self.server.sites.get(address):
|
2018-10-30 04:49:42 +01:00
|
|
|
# Don't expose site existence
|
2017-09-22 07:28:39 +02:00
|
|
|
return
|
|
|
|
|
2019-01-29 03:02:57 +01:00
|
|
|
site = self.server.sites.get(address)
|
|
|
|
if site.bad_files:
|
2019-03-15 21:06:59 +01:00
|
|
|
for bad_inner_path in list(site.bad_files.keys()):
|
2019-01-29 03:02:57 +01:00
|
|
|
is_user_file = "cert_signers" in site.content_manager.getRules(bad_inner_path)
|
2019-03-15 23:57:06 +01:00
|
|
|
if not is_user_file and bad_inner_path != "content.json":
|
2019-01-29 03:02:57 +01:00
|
|
|
self.cmd("notification", ["error", _["Clone error: Site still in sync"]])
|
|
|
|
return {"error": "Site still in sync"}
|
|
|
|
|
2017-09-22 07:21:51 +02:00
|
|
|
if "ADMIN" in self.getPermissions(to):
|
2019-05-28 18:05:52 +02:00
|
|
|
self.cbSiteClone(to, address, root_inner_path, target_address, redirect)
|
2017-09-22 07:21:51 +02:00
|
|
|
else:
|
|
|
|
self.cmd(
|
|
|
|
"confirm",
|
|
|
|
[_["Clone site <b>%s</b>?"] % address, _["Clone"]],
|
2019-05-28 18:05:52 +02:00
|
|
|
lambda res: self.cbSiteClone(to, address, root_inner_path, target_address, redirect)
|
2017-09-22 07:21:51 +02:00
|
|
|
)
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionSiteSetLimit(self, to, size_limit):
|
2015-08-16 11:51:00 +02:00
|
|
|
self.site.settings["size_limit"] = int(size_limit)
|
2015-07-12 20:36:46 +02:00
|
|
|
self.site.saveSettings()
|
2017-04-12 17:36:50 +02:00
|
|
|
self.response(to, "ok")
|
2017-12-15 12:13:35 +01:00
|
|
|
self.site.updateWebsocket()
|
2015-08-06 00:51:25 +02:00
|
|
|
self.site.download(blind_includes=True)
|
2015-07-12 20:36:46 +02:00
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-06-25 14:12:13 +02:00
|
|
|
def actionSiteAdd(self, to, address):
|
|
|
|
site_manager = SiteManager.site_manager
|
|
|
|
if address in site_manager.sites:
|
|
|
|
return {"error": "Site already added"}
|
|
|
|
else:
|
|
|
|
if site_manager.need(address):
|
|
|
|
return "ok"
|
|
|
|
else:
|
|
|
|
return {"error": "Invalid address"}
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.async_run
|
2019-02-02 00:11:47 +01:00
|
|
|
def actionSiteListModifiedFiles(self, to, content_inner_path="content.json"):
|
|
|
|
content = self.site.content_manager.contents[content_inner_path]
|
|
|
|
min_mtime = content.get("modified", 0)
|
|
|
|
site_path = self.site.storage.directory
|
|
|
|
modified_files = []
|
|
|
|
|
|
|
|
# Load cache if not signed since last modified check
|
2019-03-15 23:57:30 +01:00
|
|
|
if content.get("modified", 0) < self.site.settings["cache"].get("time_modified_files_check", 0):
|
2019-02-02 00:11:47 +01:00
|
|
|
min_mtime = self.site.settings["cache"].get("time_modified_files_check")
|
|
|
|
modified_files = self.site.settings["cache"].get("modified_files", [])
|
|
|
|
|
2019-03-15 21:06:59 +01:00
|
|
|
inner_paths = [content_inner_path] + list(content.get("includes", {}).keys()) + list(content.get("files", {}).keys())
|
2019-02-02 00:11:47 +01:00
|
|
|
|
|
|
|
for relative_inner_path in inner_paths:
|
|
|
|
inner_path = helper.getDirname(content_inner_path) + relative_inner_path
|
|
|
|
try:
|
|
|
|
is_mtime_newer = os.path.getmtime(self.site.storage.getPath(inner_path)) > min_mtime + 1
|
|
|
|
if is_mtime_newer:
|
|
|
|
if inner_path.endswith("content.json"):
|
|
|
|
is_modified = self.site.content_manager.isModified(inner_path)
|
|
|
|
else:
|
|
|
|
previous_size = content["files"][inner_path]["size"]
|
|
|
|
is_same_size = self.site.storage.getSize(inner_path) == previous_size
|
|
|
|
ext = inner_path.rsplit(".", 1)[-1]
|
|
|
|
is_text_file = ext in ["json", "txt", "html", "js", "css"]
|
|
|
|
if is_same_size:
|
|
|
|
if is_text_file:
|
|
|
|
is_modified = self.site.content_manager.isModified(inner_path) # Check sha512 hash
|
|
|
|
else:
|
|
|
|
is_modified = False
|
|
|
|
else:
|
|
|
|
is_modified = True
|
|
|
|
|
|
|
|
# Check ran, modified back to original value, but in the cache
|
|
|
|
if not is_modified and inner_path in modified_files:
|
|
|
|
modified_files.remove(inner_path)
|
|
|
|
else:
|
|
|
|
is_modified = False
|
|
|
|
except Exception as err:
|
|
|
|
if not self.site.storage.isFile(inner_path): # File deleted
|
|
|
|
is_modified = True
|
|
|
|
else:
|
|
|
|
raise err
|
|
|
|
if is_modified and inner_path not in modified_files:
|
|
|
|
modified_files.append(inner_path)
|
|
|
|
|
|
|
|
self.site.settings["cache"]["time_modified_files_check"] = time.time()
|
|
|
|
self.site.settings["cache"]["modified_files"] = modified_files
|
|
|
|
return {"modified_files": modified_files}
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2019-02-05 15:52:01 +01:00
|
|
|
def actionSiteSetSettingsValue(self, to, key, value):
|
|
|
|
if key not in ["modified_files_notification"]:
|
|
|
|
return {"error": "Can't change this key"}
|
|
|
|
|
|
|
|
self.site.settings[key] = value
|
|
|
|
|
|
|
|
return "ok"
|
|
|
|
|
2017-07-10 02:41:01 +02:00
|
|
|
def actionUserGetSettings(self, to):
|
2019-01-20 03:22:17 +01:00
|
|
|
settings = self.user.sites.get(self.site.address, {}).get("settings", {})
|
2017-07-10 02:41:01 +02:00
|
|
|
self.response(to, settings)
|
|
|
|
|
|
|
|
def actionUserSetSettings(self, to, settings):
|
2018-10-20 02:28:58 +02:00
|
|
|
self.user.setSiteSettings(self.site.address, settings)
|
|
|
|
self.response(to, "ok")
|
|
|
|
|
|
|
|
def actionUserGetGlobalSettings(self, to):
|
|
|
|
settings = self.user.settings
|
|
|
|
self.response(to, settings)
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
2018-10-20 02:28:58 +02:00
|
|
|
def actionUserSetGlobalSettings(self, to, settings):
|
|
|
|
self.user.settings = settings
|
|
|
|
self.user.save()
|
2017-07-10 02:41:01 +02:00
|
|
|
self.response(to, "ok")
|
|
|
|
|
2019-10-06 03:12:47 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
|
|
|
def actionServerErrors(self, to):
|
|
|
|
return self.server.logdb_errors.lines
|
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
2015-07-12 20:36:46 +02:00
|
|
|
def actionServerUpdate(self, to):
|
2019-04-09 15:07:56 +02:00
|
|
|
def cbServerUpdate(res):
|
2019-04-10 14:56:47 +02:00
|
|
|
self.response(to, res)
|
|
|
|
if not res:
|
|
|
|
return False
|
|
|
|
for websocket in self.server.websockets:
|
|
|
|
websocket.cmd(
|
|
|
|
"notification",
|
|
|
|
["info", _["Updating ZeroNet client, will be back in a few minutes..."], 20000]
|
|
|
|
)
|
|
|
|
websocket.cmd("updating")
|
|
|
|
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
main.update_after_shutdown = True
|
2019-11-01 19:36:44 +01:00
|
|
|
main.restart_after_shutdown = True
|
2019-04-09 15:07:56 +02:00
|
|
|
SiteManager.site_manager.save()
|
2019-04-15 12:31:33 +02:00
|
|
|
main.file_server.stop()
|
|
|
|
main.ui_server.stop()
|
2019-04-09 15:07:56 +02:00
|
|
|
|
|
|
|
self.cmd(
|
|
|
|
"confirm",
|
|
|
|
[_["Update <b>ZeroNet client</b> to latest version?"], _["Update"]],
|
|
|
|
cbServerUpdate
|
|
|
|
)
|
2015-11-17 12:48:03 +01:00
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.async_run
|
|
|
|
@flag.no_multiuser
|
2015-11-17 12:48:03 +01:00
|
|
|
def actionServerPortcheck(self, to):
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
|
|
|
file_server = main.file_server
|
2019-01-23 02:14:31 +01:00
|
|
|
file_server.portCheck()
|
|
|
|
self.response(to, file_server.port_opened)
|
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
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
2018-07-10 03:28:02 +02:00
|
|
|
def actionServerShutdown(self, to, restart=False):
|
2019-04-15 12:31:33 +02:00
|
|
|
import main
|
2018-07-10 03:28:02 +02:00
|
|
|
if restart:
|
2019-04-15 12:31:33 +02:00
|
|
|
main.restart_after_shutdown = True
|
|
|
|
main.file_server.stop()
|
|
|
|
main.ui_server.stop()
|
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
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
2017-10-04 12:31:57 +02:00
|
|
|
def actionServerShowdirectory(self, to, directory="backup", inner_path=""):
|
|
|
|
if self.request.env["REMOTE_ADDR"] != "127.0.0.1":
|
|
|
|
return self.response(to, {"error": "Only clients from 127.0.0.1 allowed to run this command"})
|
2017-09-16 20:57:20 +02:00
|
|
|
|
2017-10-04 12:31:57 +02:00
|
|
|
import webbrowser
|
2017-09-16 20:57:20 +02:00
|
|
|
if directory == "backup":
|
2017-10-04 12:31:57 +02:00
|
|
|
path = os.path.abspath(config.data_dir)
|
2017-09-16 20:57:20 +02:00
|
|
|
elif directory == "log":
|
2017-10-04 12:31:57 +02:00
|
|
|
path = os.path.abspath(config.log_dir)
|
|
|
|
elif directory == "site":
|
|
|
|
path = os.path.abspath(self.site.storage.getPath(helper.getDirname(inner_path)))
|
|
|
|
|
|
|
|
if os.path.isdir(path):
|
|
|
|
self.log.debug("Opening: %s" % path)
|
|
|
|
webbrowser.open('file://' + path)
|
|
|
|
return self.response(to, "ok")
|
|
|
|
else:
|
|
|
|
return self.response(to, {"error": "Not a directory"})
|
2017-07-27 16:29:39 +02:00
|
|
|
|
2019-08-26 03:09:48 +02:00
|
|
|
@flag.admin
|
|
|
|
@flag.no_multiuser
|
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
|
|
|
def actionConfigSet(self, to, key, value):
|
2019-04-15 15:49:53 +02:00
|
|
|
import main
|
2018-07-10 03:40:56 +02:00
|
|
|
if key not in config.keys_api_change_allowed:
|
2019-08-19 13:42:49 +02:00
|
|
|
self.response(to, {"error": "Forbidden: You cannot set this config key"})
|
2016-02-20 11:19:28 +01:00
|
|
|
return
|
|
|
|
|
2019-08-19 13:42:49 +02:00
|
|
|
if key == "open_browser":
|
|
|
|
if value not in ["default_browser", "False"]:
|
|
|
|
self.response(to, {"error": "Forbidden: Invalid value"})
|
|
|
|
return
|
|
|
|
|
2019-01-25 01:26:39 +01:00
|
|
|
# Remove empty lines from lists
|
|
|
|
if type(value) is list:
|
|
|
|
value = [line for line in value if line]
|
|
|
|
|
2016-11-07 22:51:43 +01:00
|
|
|
config.saveValue(key, value)
|
2016-11-18 20:10:24 +01:00
|
|
|
|
2018-07-10 03:39:33 +02:00
|
|
|
if key not in config.keys_restart_need:
|
|
|
|
if value is None: # Default value
|
|
|
|
setattr(config, key, config.parser.get_default(key))
|
|
|
|
setattr(config.arguments, key, config.parser.get_default(key))
|
|
|
|
else:
|
|
|
|
setattr(config, key, value)
|
|
|
|
setattr(config.arguments, key, value)
|
|
|
|
else:
|
|
|
|
config.need_restart = True
|
|
|
|
config.pending_changes[key] = value
|
2018-04-28 22:01:48 +02:00
|
|
|
|
2016-11-18 20:10:24 +01:00
|
|
|
if key == "language":
|
|
|
|
import Translate
|
|
|
|
for translate in Translate.translates:
|
|
|
|
translate.setLanguage(value)
|
2018-03-20 21:53:39 +01:00
|
|
|
message = _["You have successfully changed the web interface's language!"] + "<br>"
|
|
|
|
message += _["Due to the browser's caching, the full transformation could take some minute."]
|
|
|
|
self.cmd("notification", ["done", message, 10000])
|
2018-04-28 22:01:48 +02:00
|
|
|
|
|
|
|
if key == "tor_use_bridges":
|
2018-06-25 14:13:05 +02:00
|
|
|
if value is None:
|
2018-04-28 22:01:48 +02:00
|
|
|
value = False
|
|
|
|
else:
|
|
|
|
value = True
|
2019-04-15 12:31:33 +02:00
|
|
|
tor_manager = main.file_server.tor_manager
|
2018-04-28 22:01:48 +02:00
|
|
|
tor_manager.request("SETCONF UseBridges=%i" % value)
|
2016-11-18 20:10:24 +01:00
|
|
|
|
2018-07-10 03:43:31 +02:00
|
|
|
if key == "trackers_file":
|
|
|
|
config.loadTrackersFile()
|
|
|
|
|
2019-01-10 14:10:07 +01:00
|
|
|
if key == "log_level":
|
|
|
|
logging.getLogger('').setLevel(logging.getLevelName(config.log_level))
|
|
|
|
|
2019-01-25 01:24:28 +01:00
|
|
|
if key == "ip_external":
|
2019-04-15 12:31:33 +02:00
|
|
|
gevent.spawn(main.file_server.portCheck)
|
2019-01-25 01:24:28 +01:00
|
|
|
|
2019-04-15 15:49:53 +02:00
|
|
|
if key == "offline":
|
|
|
|
if value:
|
|
|
|
main.file_server.closeConnections()
|
|
|
|
else:
|
|
|
|
gevent.spawn(main.file_server.checkSites, check_files=False, force_port_check=True)
|
|
|
|
|
2019-11-01 19:36:44 +01:00
|
|
|
self.response(to, "ok")
|