rev307, Plugin for password protected web ui, Faster page load times by yielding wrapper html, Reworked configuration parser to support plugin extensions, Initial site sync bugfix, Test for configuration parsing, Parse posted data function

This commit is contained in:
HelloZeroNet 2015-07-17 00:28:43 +02:00
parent 4c9a677369
commit a93ca2c3b4
11 changed files with 509 additions and 193 deletions

View file

@ -0,0 +1,117 @@
import string
import random
import time
import json
import re
from Config import config
from Plugin import PluginManager
if "sessions" not in locals().keys(): # To keep sessions between module reloads
sessions = {}
@PluginManager.registerTo("UiRequest")
class UiRequestPlugin(object):
sessions = sessions
last_cleanup = time.time()
def route(self, path):
if path.endswith("favicon.ico"):
return self.actionFile("src/Ui/media/img/favicon.ico")
else:
if config.ui_password:
if time.time() - self.last_cleanup > 60 * 60: # Cleanup expired sessions every hour
self.cleanup()
# Validate session
session_id = self.getCookies().get("session_id")
if session_id not in self.sessions: # Invalid session id, display login
return self.actionLogin()
return super(UiRequestPlugin, self).route(path)
# Action: Login
def actionLogin(self):
template = open("plugins/UiPassword/login.html").read()
self.sendHeader()
posted = self.getPosted()
if posted: # Validate http posted data
if self.checkPassword(posted.get("password")):
# Valid password, create session
session_id = self.randomString(26)
self.sessions[session_id] = {
"added": time.time(),
"keep": posted.get("keep")
}
# Redirect to homepage or referer
url = self.env.get("HTTP_REFERER", "")
if not url or re.sub("\?.*", "", url).endswith("/Login"):
url = "/" + config.homepage
cookie_header = ('Set-Cookie', "session_id=%s;path=/;max-age=2592000;" % session_id) # Max age = 30 days
self.start_response('301 Redirect', [('Location', url), cookie_header])
yield "Redirecting..."
else:
# Invalid password, show login form again
template = template.replace("{result}", "bad_password")
yield template
def checkPassword(self, password):
if password == config.ui_password:
return True
else:
return False
def randomString(self, chars):
return ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(chars))
@classmethod
def cleanup(cls):
for session_id, session in cls.sessions.items():
if session["keep"] and time.time() - session["added"] > 60 * 60 * 24 * 60: # Max 60days for keep sessions
del(cls.sessions[session_id])
elif not session["keep"] and time.time() - session["added"] > 60 * 60 * 24: # Max 24h for non-keep sessions
del(cls.sessions[session_id])
# Action: Display sessions
def actionSessions(self):
self.sendHeader()
yield "<pre>"
yield json.dumps(self.sessions, indent=4)
# Action: Logout
def actionLogout(self):
# Session id has to passed as get parameter or called without referer to avoid remote logout
session_id = self.getCookies().get("session_id")
if not self.env.get("HTTP_REFERER") or session_id == self.get.get("session_id"):
if session_id in self.sessions:
del self.sessions[session_id]
self.start_response('301 Redirect', [
('Location', "/"),
('Set-Cookie', "session_id=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
])
yield "Redirecting..."
else:
self.sendHeader()
yield "Error: Invalid session id"
@PluginManager.registerTo("ConfigPlugin")
class ConfigPlugin(object):
def createArguments(self):
group = self.parser.add_argument_group("UiPassword plugin")
group.add_argument('--ui_password', help='Password to access UiServer', default=None, metavar="password")
return super(ConfigPlugin, self).createArguments()
@PluginManager.registerTo("UiWebsocket")
class UiWebsocketPlugin(object):
def actionUiLogout(self, to):
permissions = self.getPermissions(to)
if "ADMIN" not in permissions:
return self.response(to, "You don't have permission to run this command")
session_id = self.request.getCookies().get("session_id", "")
message = "<script>document.location.href = '/Logout?session_id=%s'</script>" % session_id
self.cmd("notification", ["done", message])

View file

@ -0,0 +1 @@
import UiPasswordPlugin

View file

@ -0,0 +1,116 @@
<html>
<head>
<title>Log In</title>
<meta name="viewport" id="viewport" content="width=device-width, initial-scale=1.0">
</head>
<style>
body {
background-color: #323C4D; font-family: "Segoe UI", Helvetica, Arial; font-weight: lighter;
font-size: 22px; color: #333; letter-spacing: 1px; color: white; overflow: hidden;
}
.login { left: 50%; position: absolute; top: 50%; transform: translateX(-50%) translateY(-50%); width: 100%; max-width: 370px; text-align: center; }
*:focus { outline: 0; }
input[type=text], input[type=password] {
padding: 10px 0px; border: 0px; display: block; margin: 15px 0px; width: 100%; border-radius: 30px; transition: 0.3s ease-out; background-color: #DDD;
text-align: center; font-family: "Segoe UI", Helvetica, Arial; font-weight: lighter; font-size: 28px; border: 2px solid #323C4D;
}
input[type=text]:focus, input[type=password]:focus {
border: 2px solid #FFF; background-color: #FFF;
}
input[type=checkbox] { opacity: 0; }
input[type=checkbox]:checked + label { color: white; }
input[type=checkbox]:focus + label::before { background-color: #435065; }
input[type=checkbox]:checked + label::before { box-shadow: inset 0px 0px 0px 5px white; background-color: #4DCC6E; }
input.error { border: 2px solid #F44336 !important; animation: shake 1s }
label::before {
content: ""; width: 20px; height: 20px; background-color: #323C4D;
display: inline-block; margin-left: -20px; border-radius: 15px; box-shadow: inset 0px 0px 0px 2px #9EA5B3;
transition: all 0.1s; margin-right: 7px; position: relative; top: 2px;
}
label { vertical-align: -1px; color: #9EA5B3; transition: all 0.3s; }
.button {
padding: 13px; display: inline-block; margin: 15px 0px; width: 100%; border-radius: 30px; text-align: center; white-space: nowrap;
font-size: 28px; color: #333; background: linear-gradient(45deg, #6B14D3 0, #7A26E2 25%, #4962DD 90%);
box-sizing: border-box; margin-top: 50px; color: white; text-decoration: none; transition: 0.3s ease-out;
}
.button:hover, .button:focus { box-shadow: 0px 5px 30px rgba(0,0,0,0.3); }
.button:active { transform: translateY(1px); box-shadow: 0px 0px 20px rgba(0,0,0,0.5); transition: none; }
#login_form_submit { display: none; }
.login-anim { animation: login 1s cubic-bezier(0.785, 0.135, 0.15, 0.86) forwards; }
@keyframes login {
0% { width: 100%; }
60% { width: 63px; transform: scale(1); color: rgba(255,255,255,0); }
70% { width: 63px; transform: scale(1); color: rgba(255,255,255,0); }
100% { transform: scale(80); width: 63px; color: rgba(255,255,255,0); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
20%, 40%, 60%, 80% { transform: translateX(10px); }
}
</style>
<body>
<div class="login">
<form action="" method="post" id="login_form" onkeypress="return onFormKeypress(event)">
<!--<input type="text" name="username" placeholder="Username" required/>-->
<input type="password" name="password" placeholder="Password" required/>
<input type="checkbox" name="keep" id="keep"><label for="keep">Keep me logged in</label>
<div style="clear: both"></div>
<a href="Login" class="button" onclick="return submit()" id="login_button"><span>Log In</span></a>
<input type="submit" id="login_form_submit"/>
</form>
</div>
<script>
function onFormKeypress(e) {
if (event.keyCode == 13) {
submit()
return false
}
}
function submit() {
var form = document.getElementById("login_form")
if (form.checkValidity()) {
document.getElementById("login_button").className = "button login-anim"
setTimeout(function () {
document.getElementById("login_form_submit").click()
}, 1000)
} else {
document.getElementById("login_form_submit").click()
}
return false
}
function badPassword() {
var elem = document.getElementsByName("password")[0]
elem.className = "error"
elem.placeholder = "Wrong Password"
elem.focus()
elem.addEventListener('input', function() {
elem.className = ""
elem.placeholder = "Password"
})
}
result = "{result}"
if (result == "bad_password")
badPassword()
</script>
</body>
</html>

View file

@ -5,32 +5,32 @@ import time, json, os, sys, re, socket, json
# Supports subdomains and .bit on the end
def lookupDomain(domain):
domain = domain.lower()
#remove .bit on end
if domain[-4:] == ".bit":
domain = domain[0:-4]
#check for subdomain
if domain.find(".") != -1:
subdomain = domain[0:domain.find(".")]
domain = domain[domain.find(".")+1:]
else:
subdomain = ""
try:
domain_object = rpc.name_show("d/"+domain)
except:
#domain doesn't exist
return None
domain_json = json.loads(domain_object['value'])
try:
domain_address = domain_json["zeronet"][subdomain]
except:
#domain exists but doesn't have any zeronet value
return None
return domain_address
# Loading config...
@ -52,30 +52,3 @@ rpc_pass = re.search("rpcpassword=(.*)$", namecoin_conf, re.M).group(1)
rpc_url = "http://%s:%s@127.0.0.1:8336" % (rpc_user, rpc_pass)
rpc = AuthServiceProxy(rpc_url, timeout=60*5)
"""
while 1:
print "Waiting for new block",
sys.stdout.flush()
while 1:
try:
rpc = AuthServiceProxy(rpc_url, timeout=60*5)
if (int(rpc.getinfo()["blocks"]) > last_block): break
time.sleep(1)
rpc.waitforblock()
print "Found"
break # Block found
except socket.timeout: # Timeout
print ".",
sys.stdout.flush()
except Exception, err:
print "Exception", err.__class__, err
time.sleep(5)
last_block = int(rpc.getinfo()["blocks"])
for block_id in range(config["lastprocessed"]+1, last_block+1):
processBlock(block_id)
config["lastprocessed"] = last_block
open(config_path, "w").write(json.dumps(config, indent=2))
"""

View file

@ -6,14 +6,19 @@ import ConfigParser
class Config(object):
def __init__(self):
def __init__(self, argv):
self.version = "0.3.1"
self.rev = 281
self.parser = self.createArguments()
argv = sys.argv[:] # Copy command line arguments
argv = self.parseConfig(argv) # Add arguments from config file
self.parseCommandline(argv) # Parse argv
self.setAttributes()
self.rev = 307
self.argv = argv
self.action = None
self.createParser()
self.createArguments()
def createParser(self):
# Create parser
self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
self.parser.register('type', 'bool', self.strToBool)
self.subparsers = self.parser.add_subparsers(title="Action to perform", dest="action")
def __str__(self):
return str(self.arguments).replace("Namespace", "Config") # Using argparse str output
@ -29,28 +34,17 @@ class Config(object):
coffeescript = "type %s | tools\\coffee\\coffee.cmd"
else:
coffeescript = None
""" Probably fixed
if sys.platform.lower().startswith("darwin"):
# For some reasons openssl doesnt works on mac yet (https://github.com/HelloZeroNet/ZeroNet/issues/94)
use_openssl = False
else:
use_openssl = True
"""
use_openssl = True
# Create parser
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.register('type', 'bool', self.strToBool)
subparsers = parser.add_subparsers(title="Action to perform", dest="action")
# Main
action = subparsers.add_parser("main", help='Start UiServer and FileServer (default)')
action = self.subparsers.add_parser("main", help='Start UiServer and FileServer (default)')
# SiteCreate
action = subparsers.add_parser("siteCreate", help='Create a new site')
action = self.subparsers.add_parser("siteCreate", help='Create a new site')
# SiteSign
action = subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
action = self.subparsers.add_parser("siteSign", help='Update and sign content.json: address [privatekey]')
action.add_argument('address', help='Site to sign')
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?')
action.add_argument('--inner_path', help='File you want to sign (default: content.json)',
@ -58,7 +52,7 @@ class Config(object):
action.add_argument('--publish', help='Publish site after the signing', action='store_true')
# SitePublish
action = subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
action = self.subparsers.add_parser("sitePublish", help='Publish site to other peers: address')
action.add_argument('address', help='Site to publish')
action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)',
default=None, nargs='?')
@ -68,76 +62,76 @@ class Config(object):
default="content.json", metavar="inner_path")
# SiteVerify
action = subparsers.add_parser("siteVerify", help='Verify site files using sha512: address')
action = self.subparsers.add_parser("siteVerify", help='Verify site files using sha512: address')
action.add_argument('address', help='Site to verify')
# dbRebuild
action = subparsers.add_parser("dbRebuild", help='Rebuild site database cache')
action = self.subparsers.add_parser("dbRebuild", help='Rebuild site database cache')
action.add_argument('address', help='Site to rebuild')
# dbQuery
action = subparsers.add_parser("dbQuery", help='Query site sql cache')
action = self.subparsers.add_parser("dbQuery", help='Query site sql cache')
action.add_argument('address', help='Site to query')
action.add_argument('query', help='Sql query')
# PeerPing
action = subparsers.add_parser("peerPing", help='Send Ping command to peer')
action = self.subparsers.add_parser("peerPing", help='Send Ping command to peer')
action.add_argument('peer_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port', nargs='?')
# PeerGetFile
action = subparsers.add_parser("peerGetFile", help='Request and print a file content from peer')
action = self.subparsers.add_parser("peerGetFile", help='Request and print a file content from peer')
action.add_argument('peer_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port')
action.add_argument('site', help='Site address')
action.add_argument('filename', help='File name to request')
# PeerGetFile
action = subparsers.add_parser("peerCmd", help='Request and print a file content from peer')
action = self.subparsers.add_parser("peerCmd", help='Request and print a file content from peer')
action.add_argument('peer_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port')
action.add_argument('cmd', help='Command to execute')
action.add_argument('parameters', help='Parameters to command', nargs='?')
# CryptSign
action = subparsers.add_parser("cryptSign", help='Sign message using Bitcoin private key')
action = self.subparsers.add_parser("cryptSign", help='Sign message using Bitcoin private key')
action.add_argument('message', help='Message to sign')
action.add_argument('privatekey', help='Private key')
# Config parameters
parser.add_argument('--debug', help='Debug mode', action='store_true')
parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true')
self.parser.add_argument('--debug', help='Debug mode', action='store_true')
self.parser.add_argument('--debug_socket', help='Debug socket connections', action='store_true')
parser.add_argument('--config_file', help='Path of config file', default="zeronet.conf", metavar="path")
parser.add_argument('--data_dir', help='Path of data directory', default="data", metavar="path")
parser.add_argument('--log_dir', help='Path of logging directory', default="log", metavar="path")
self.parser.add_argument('--config_file', help='Path of config file', default="zeronet.conf", metavar="path")
self.parser.add_argument('--data_dir', help='Path of data directory', default="data", metavar="path")
self.parser.add_argument('--log_dir', help='Path of logging directory', default="log", metavar="path")
parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port')
parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip', nargs='*')
parser.add_argument('--open_browser', help='Open homepage in web browser automatically',
nargs='?', const="default_browser", metavar='browser_name')
parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr',
metavar='address')
parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, metavar='size')
self.parser.add_argument('--ui_ip', help='Web interface bind address', default="127.0.0.1", metavar='ip')
self.parser.add_argument('--ui_port', help='Web interface bind port', default=43110, type=int, metavar='port')
self.parser.add_argument('--ui_restrict', help='Restrict web access', default=False, metavar='ip', nargs='*')
self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically',
nargs='?', const="default_browser", metavar='browser_name')
self.parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr',
metavar='address')
self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, metavar='size')
parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
parser.add_argument('--fileserver_port', help='FileServer bind port', default=15441, type=int, metavar='port')
parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')
parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port')
parser.add_argument('--ip_external', help='External ip (tested on start if None)', metavar='ip')
parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup',
type='bool', choices=[True, False], default=use_openssl)
parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')
parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory',
type='bool', choices=[True, False], default=True)
self.parser.add_argument('--fileserver_ip', help='FileServer bind address', default="*", metavar='ip')
self.parser.add_argument('--fileserver_port', help='FileServer bind port', default=15441, type=int, metavar='port')
self.parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')
self.parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port')
self.parser.add_argument('--ip_external', help='External ip (tested on start if None)', metavar='ip')
self.parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup',
type='bool', choices=[True, False], default=use_openssl)
self.parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')
self.parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory',
type='bool', choices=[True, False], default=True)
parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
metavar='executable_path')
self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
metavar='executable_path')
parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev))
return parser
return self.parser
# Find arguments specificed for current action
def getActionArguments(self):
@ -147,23 +141,78 @@ class Config(object):
back[argument.dest] = getattr(self, argument.dest)
return back
# Try to find action from sys.argv
# Try to find action from argv
def getAction(self, argv):
actions = [action.choices.keys() for action in self.parser._actions if action.dest == "action"][0] # Valid actions
found_action = False
for action in actions: # See if any in sys.argv
for action in actions: # See if any in argv
if action in argv:
found_action = action
break
return found_action
# Move plugin parameters to end of argument list
def moveUnknownToEnd(self, argv, default_action):
valid_actions = sum([action.option_strings for action in self.parser._actions], [])
valid_parameters = []
plugin_parameters = []
plugin = False
for arg in argv:
if arg.startswith("--"):
if arg not in valid_actions:
plugin = True
else:
plugin = False
elif arg == default_action:
plugin = False
if plugin:
plugin_parameters.append(arg)
else:
valid_parameters.append(arg)
return valid_parameters + plugin_parameters
# Parse arguments from config file and command line
def parse(self, silent=False, parse_config=True):
if silent: # Don't display messages or quit on unknown parameter
original_print_message = self.parser._print_message
original_exit = self.parser.exit
def silent(parser, function_name):
parser.exited = True
return None
self.parser.exited = False
self.parser._print_message = lambda *args, **kwargs: silent(self.parser, "_print_message")
self.parser.exit = lambda *args, **kwargs: silent(self.parser, "exit")
argv = self.argv[:] # Copy command line arguments
if parse_config:
argv = self.parseConfig(argv) # Add arguments from config file
self.parseCommandline(argv, silent) # Parse argv
self.setAttributes()
if silent: # Restore original functions
if self.parser.exited and self.action == "main": # Argument parsing halted, don't start ZeroNet with main action
self.action = None
self.parser._print_message = original_print_message
self.parser.exit = original_exit
# Parse command line arguments
def parseCommandline(self, argv):
def parseCommandline(self, argv, silent=False):
# Find out if action is specificed on start
action = self.getAction(argv)
if len(argv) == 1 or not action: # If no action specificed set the main action
if not action:
argv.append("main")
self.arguments = self.parser.parse_args(argv[1:])
action = "main"
argv = self.moveUnknownToEnd(argv, action)
if silent:
res = self.parser.parse_known_args(argv[1:])
if res:
self.arguments = res[0]
else:
self.arguments = {}
else:
self.arguments = self.parser.parse_args(argv[1:])
# Parse config file
def parseConfig(self, argv):
@ -187,9 +236,24 @@ class Config(object):
# Expose arguments as class attributes
def setAttributes(self):
# Set attributes from arguments
args = vars(self.arguments)
for key, val in args.items():
setattr(self, key, val)
if self.arguments:
args = vars(self.arguments)
for key, val in args.items():
setattr(self, key, val)
def loadPlugins(self):
from Plugin import PluginManager
@PluginManager.acceptPlugins
class ConfigPlugin(object):
def __init__(self, config):
self.parser = config.parser
self.createArguments()
def createArguments(self):
pass
ConfigPlugin(self)
config = Config()
config = Config(sys.argv)

View file

@ -207,7 +207,7 @@ class Site:
elif len(peers_try) < 5: # Backup peers, add to end of the try list
peers_try.append(peer)
if since is not None: # No since definied, download from last modification time-1day
if since is None: # No since definied, download from last modification time-1day
since = self.settings.get("modified", 60 * 60 * 24) - 60 * 60 * 24
self.log.debug("Try to get listModifications from peers: %s since: %s" % (peers_try, since))

View file

@ -6,6 +6,7 @@ import time
sys.path.append(os.path.abspath("src")) # Imports relative to src dir
from Config import config
config.parse()
config.data_dir = "src/Test/testdata" # Use test data for unittests
from Crypt import CryptBitcoin
@ -382,13 +383,16 @@ class TestCase(unittest.TestCase):
1458664252141532163166741013621928587528255888800826689784628722366466547364755811L
)
# Re-generate privatekey based on address_index
address, address_index, site_data = user.getNewSiteData()
self.assertEqual(CryptBitcoin.hdPrivatekey(user.master_seed, address_index), site_data["privatekey"]) # Re-generate privatekey based on address_index
self.assertEqual(CryptBitcoin.hdPrivatekey(user.master_seed, address_index), site_data["privatekey"])
user.sites = {} # Reset user data
self.assertNotEqual(user.getSiteData(address)["auth_address"], address) # Site address and auth address is different
self.assertEqual(user.getSiteData(address)["auth_privatekey"], site_data["auth_privatekey"]) # Re-generate auth_privatekey for site
# Site address and auth address is different
self.assertNotEqual(user.getSiteData(address)["auth_address"], address)
# Re-generate auth_privatekey for site
self.assertEqual(user.getSiteData(address)["auth_privatekey"], site_data["auth_privatekey"])
def testSslCert(self):
from Crypt import CryptConnection
@ -409,8 +413,29 @@ class TestCase(unittest.TestCase):
os.unlink("%s/cert-rsa.pem" % config.data_dir)
os.unlink("%s/key-rsa.pem" % config.data_dir)
def testConfigParse(self):
import Config
config_test = Config.Config("zeronet.py".split(" "))
config_test.parse(silent=True, parse_config=False)
self.assertFalse(config_test.debug)
self.assertFalse(config_test.debug_socket)
config_test = Config.Config("zeronet.py --debug --debug_socket --ui_password hello".split(" "))
config_test.parse(silent=True, parse_config=False)
self.assertTrue(config_test.debug)
self.assertTrue(config_test.debug_socket)
args = "zeronet.py --unknown_arg --debug --debug_socket --ui_restrict 127.0.0.1 1.2.3.4 "
args += "--another_unknown argument --use_openssl False siteSign address privatekey --inner_path users/content.json"
config_test = Config.Config(args.split(" "))
config_test.parse(silent=True, parse_config=False)
self.assertTrue(config_test.debug)
self.assertIn("1.2.3.4", config_test.ui_restrict)
self.assertFalse(config_test.use_openssl)
self.assertEqual(config_test.inner_path, "users/content.json")
if __name__ == "__main__":
import logging
logging.getLogger().setLevel(level=logging.CRITICAL)
unittest.main(verbosity=2)
# unittest.main(verbosity=2, defaultTest="TestCase.testUserContentCert")
# unittest.main(verbosity=2, defaultTest="TestCase.testConfigParse")

View file

@ -29,19 +29,15 @@ class UiRequest(object):
self.log = server.log
self.get = get # Get parameters
self.env = env # Enviroment settings
# ['CONTENT_LENGTH', 'CONTENT_TYPE', 'GATEWAY_INTERFACE', 'HTTP_ACCEPT', 'HTTP_ACCEPT_ENCODING', 'HTTP_ACCEPT_LANGUAGE',
# 'HTTP_COOKIE', 'HTTP_CACHE_CONTROL', 'HTTP_HOST', 'HTTP_HTTPS', 'HTTP_ORIGIN', 'HTTP_PROXY_CONNECTION', 'HTTP_REFERER',
# 'HTTP_USER_AGENT', 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_PORT', 'REQUEST_METHOD', 'SCRIPT_NAME',
# 'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL', 'SERVER_SOFTWARE', 'werkzeug.request', 'wsgi.errors',
# 'wsgi.input', 'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once', 'wsgi.url_scheme', 'wsgi.version']
self.start_response = start_response # Start response function
self.user = None
# Return posted variables as dict
def getPosted(self):
if self.env['REQUEST_METHOD'] == "POST":
return dict(cgi.parse_qsl(
self.env['wsgi.input'].readline().decode()
))
else:
return {}
# Call the request handler function base on path
def route(self, path):
if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict: # Restict Ui access by ip
@ -69,7 +65,10 @@ class UiRequest(object):
return self.actionConsole()
# Site media wrapper
else:
body = self.actionWrapper(path)
if self.get.get("wrapper") == "False":
return self.actionSiteMedia("/media" + path) # Only serve html files with frame
else:
body = self.actionWrapper(path)
if body:
return body
else:
@ -96,7 +95,16 @@ class UiRequest(object):
content_type = "application/octet-stream"
return content_type
# Returns: <dict> Cookies based on self.env
# Return: <dict> Posted variables
def getPosted(self):
if self.env['REQUEST_METHOD'] == "POST":
return dict(cgi.parse_qsl(
self.env['wsgi.input'].readline().decode()
))
else:
return {}
# Return: <dict> Cookies based on self.env
def getCookies(self):
raw_cookies = self.env.get('HTTP_COOKIE')
if raw_cookies:
@ -122,10 +130,11 @@ class UiRequest(object):
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access
if self.env["REQUEST_METHOD"] == "OPTIONS":
# Allow json access
headers.append(("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"))
headers.append(("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Cookie"))
headers.append(("Access-Control-Allow-Credentials", "true"))
cacheable_type = (
content_type == "text/css" or content_type.startswith("image") or
content_type == "text/css" or content_type.startswith("image") or
self.env["REQUEST_METHOD"] == "OPTIONS" or content_type == "application/javascript"
)
@ -157,8 +166,6 @@ class UiRequest(object):
def actionWrapper(self, path, extra_headers=None):
if not extra_headers:
extra_headers = []
if self.get.get("wrapper") == "False":
return self.actionSiteMedia("/media" + path) # Only serve html files with frame
match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
if match:
@ -169,14 +176,6 @@ class UiRequest(object):
if self.env.get("HTTP_X_REQUESTED_WITH"):
return self.error403("Ajax request not allowed to load wrapper") # No ajax allowed on wrapper
file_inner_path = inner_path
if not file_inner_path:
file_inner_path = "index.html" # If inner path defaults to index.html
if not inner_path and not path.endswith("/"):
inner_path = address + "/" # Fix relative resources loading if missing / end of site address
inner_path = re.sub(".*/(.+)", "\\1", inner_path) # Load innerframe relative to current url
site = SiteManager.site_manager.get(address)
if (
@ -190,57 +189,72 @@ class UiRequest(object):
if not site:
return False
self.sendHeader(extra_headers=extra_headers[:])
# Wrapper variable inits
query_string = ""
body_style = ""
meta_tags = ""
if self.env.get("QUERY_STRING"):
query_string = "?" + self.env["QUERY_STRING"] + "&wrapper=False"
else:
query_string = "?wrapper=False"
if self.isProxyRequest(): # Its a remote proxy request
if self.env["REMOTE_ADDR"] == "127.0.0.1": # Local client, the server address also should be 127.0.0.1
server_url = "http://127.0.0.1:%s" % self.env["SERVER_PORT"]
else: # Remote client, use SERVER_NAME as server's real address
server_url = "http://%s:%s" % (self.env["SERVER_NAME"], self.env["SERVER_PORT"])
homepage = "http://zero/" + config.homepage
else: # Use relative path
server_url = ""
homepage = "/" + config.homepage
if site.content_manager.contents.get("content.json"): # Got content.json
content = site.content_manager.contents["content.json"]
if content.get("background-color"):
body_style += "background-color: %s;" % \
cgi.escape(site.content_manager.contents["content.json"]["background-color"], True)
if content.get("viewport"):
meta_tags += '<meta name="viewport" id="viewport" content="%s">' % cgi.escape(content["viewport"], True)
return self.render(
"src/Ui/template/wrapper.html",
server_url=server_url,
inner_path=inner_path,
file_inner_path=file_inner_path,
address=address,
title=title,
body_style=body_style,
meta_tags=meta_tags,
query_string=query_string,
wrapper_key=site.settings["wrapper_key"],
permissions=json.dumps(site.settings["permissions"]),
show_loadingscreen=json.dumps(not site.storage.isFile(file_inner_path)),
rev=config.rev,
homepage=homepage
)
return self.renderWrapper(site, path, inner_path, title, extra_headers)
else: # Bad url
return False
def renderWrapper(self, site, path, inner_path, title, extra_headers):
self.sendHeader(extra_headers=extra_headers[:])
file_inner_path = inner_path
if not file_inner_path:
file_inner_path = "index.html" # If inner path defaults to index.html
address = re.sub("/.*", "", path.lstrip("/"))
if self.isProxyRequest() and (not path or "/" in path[1:]):
file_url = re.sub(".*/", "", inner_path)
else:
file_url = "/" + address + "/" + inner_path
# Wrapper variable inits
query_string = ""
body_style = ""
meta_tags = ""
if self.env.get("QUERY_STRING"):
query_string = "?" + self.env["QUERY_STRING"] + "&wrapper=False"
else:
query_string = "?wrapper=False"
if self.isProxyRequest(): # Its a remote proxy request
if self.env["REMOTE_ADDR"] == "127.0.0.1": # Local client, the server address also should be 127.0.0.1
server_url = "http://127.0.0.1:%s" % self.env["SERVER_PORT"]
else: # Remote client, use SERVER_NAME as server's real address
server_url = "http://%s:%s" % (self.env["SERVER_NAME"], self.env["SERVER_PORT"])
homepage = "http://zero/" + config.homepage
else: # Use relative path
server_url = ""
homepage = "/" + config.homepage
if site.content_manager.contents.get("content.json"): # Got content.json
content = site.content_manager.contents["content.json"]
if content.get("background-color"):
body_style += "background-color: %s;" % \
cgi.escape(site.content_manager.contents["content.json"]["background-color"], True)
if content.get("viewport"):
meta_tags += '<meta name="viewport" id="viewport" content="%s">' % cgi.escape(content["viewport"], True)
yield self.render(
"src/Ui/template/wrapper.html",
server_url=server_url,
inner_path=inner_path,
file_url=file_url,
file_inner_path=file_inner_path,
address=site.address,
title=title,
body_style=body_style,
meta_tags=meta_tags,
query_string=query_string,
wrapper_key=site.settings["wrapper_key"],
permissions=json.dumps(site.settings["permissions"]),
show_loadingscreen=json.dumps(not site.storage.isFile(file_inner_path)),
rev=config.rev,
homepage=homepage
)
# Returns if media request allowed from that referer
def isMediaRequestAllowed(self, site_address, referer):
referer_path = re.sub("http[s]{0,1}://.*?/", "/", referer).replace("/media", "") # Remove site address
@ -351,7 +365,7 @@ class UiRequest(object):
if not user:
self.log.error("No user found")
return self.error403()
ui_websocket = UiWebsocket(ws, site, self.server, user)
ui_websocket = UiWebsocket(ws, site, self.server, user, self)
site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start()
for site_check in self.server.sites.values():

View file

@ -15,11 +15,12 @@ from Plugin import PluginManager
@PluginManager.acceptPlugins
class UiWebsocket(object):
def __init__(self, ws, site, server, user):
def __init__(self, ws, site, server, user, request):
self.ws = ws
self.site = site
self.user = user
self.log = site.log
self.request = request
self.server = server
self.next_message_id = 1
self.waiting_cb = {} # Waiting for callback. Key: message_id, Value: function pointer
@ -101,21 +102,24 @@ class UiWebsocket(object):
except Exception, err:
self.log.debug("Websocket send error: %s" % Debug.formatException(err))
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
# Handle incoming messages
def handleRequest(self, data):
req = json.loads(data)
cmd = req.get("cmd")
params = req.get("params")
permissions = self.site.settings["permissions"]
if req["id"] >= 1000000: # Its a wrapper command, allow admin commands
permissions = permissions[:]
permissions.append("ADMIN")
permissions = self.getPermissions(req["id"])
admin_commands = (
"sitePause", "siteResume", "siteDelete", "siteList", "siteSetLimit", "siteClone",
"channelJoinAllsite",
"serverUpdate", "certSet"
"channelJoinAllsite", "serverUpdate", "certSet"
)
if cmd == "response": # It's a response to a command

View file

@ -13,7 +13,7 @@
<script>
// If we are inside iframe escape from it
if (window.self !== window.top) window.open(window.location.toString(), "_top");
if (window.self !== window.top) window.open(window.location.toString(), "_top");
if (window.self !== window.top) window.stop();
</script>
@ -46,7 +46,7 @@ if (window.self !== window.top) window.stop();
<!-- Site Iframe -->
<iframe src='{inner_path}{query_string}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups"></iframe>
<iframe src='{file_url}{query_string}' id='inner-iframe' sandbox="allow-forms allow-scripts allow-top-navigation allow-popups"></iframe>
<!-- Site info -->
<script>

View file

@ -1,5 +1,4 @@
# Included modules
import os
import sys
import time
@ -14,6 +13,7 @@ update_after_shutdown = False # If set True then update and restart zeronet aft
# Load config
from Config import config
config.parse(silent=True) # Plugins need to access the configuration
# Create necessary files and dirs
if not os.path.isdir(config.log_dir):
@ -25,9 +25,7 @@ if not os.path.isfile("%s/sites.json" % config.data_dir):
if not os.path.isfile("%s/users.json" % config.data_dir):
open("%s/users.json" % config.data_dir, "w").write("{}")
# Setup logging
if config.action == "main":
if os.path.isfile("%s/debug.log" % config.log_dir): # Simple logrotate
if os.path.isfile("%s/debug-last.log" % config.log_dir):
@ -58,6 +56,11 @@ else:
monkey.patch_all(thread=False) # Not thread: pyfilesystem and system tray icon not compatible
# Load plugins
from Plugin import PluginManager
PluginManager.plugin_manager.loadPlugins()
config.loadPlugins()
config.parse() # Parse again to add plugin configuration options
# Log current config
logging.debug("Config: %s" % config)
@ -72,17 +75,17 @@ if config.proxy:
SocksProxy.monkeyPath(*config.proxy.split(":"))
# Load plugins
from Plugin import PluginManager
PluginManager.plugin_manager.loadPlugins()
# -- Actions --
@PluginManager.acceptPlugins
class Actions(object):
# Default action: Start serving UiServer and FileServer
def call(self, function_name, kwargs):
func = getattr(self, function_name, None)
func(**kwargs)
# Default action: Start serving UiServer and FileServer
def main(self):
logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__))
global ui_server, file_server
@ -295,6 +298,5 @@ actions = Actions()
def start():
# Call function
func = getattr(actions, config.action, None)
action_kwargs = config.getActionArguments()
func(**action_kwargs)
actions.call(config.action, action_kwargs)