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

@ -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_url = "http://%s:%s@127.0.0.1:8336" % (rpc_user, rpc_pass)
rpc = AuthServiceProxy(rpc_url, timeout=60*5) 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): class Config(object):
def __init__(self): def __init__(self, argv):
self.version = "0.3.1" self.version = "0.3.1"
self.rev = 281 self.rev = 307
self.parser = self.createArguments() self.argv = argv
argv = sys.argv[:] # Copy command line arguments self.action = None
argv = self.parseConfig(argv) # Add arguments from config file self.createParser()
self.parseCommandline(argv) # Parse argv self.createArguments()
self.setAttributes()
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): def __str__(self):
return str(self.arguments).replace("Namespace", "Config") # Using argparse str output 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" coffeescript = "type %s | tools\\coffee\\coffee.cmd"
else: else:
coffeescript = None 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 use_openssl = True
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.register('type', 'bool', self.strToBool)
subparsers = parser.add_subparsers(title="Action to perform", dest="action")
# Main # 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 # SiteCreate
action = subparsers.add_parser("siteCreate", help='Create a new site') action = self.subparsers.add_parser("siteCreate", help='Create a new site')
# SiteSign # 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('address', help='Site to sign')
action.add_argument('privatekey', help='Private key (default: ask on execute)', nargs='?') 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)', 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') action.add_argument('--publish', help='Publish site after the signing', action='store_true')
# SitePublish # 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('address', help='Site to publish')
action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)', action.add_argument('peer_ip', help='Peer ip to publish (default: random peers ip from tracker)',
default=None, nargs='?') default=None, nargs='?')
@ -68,76 +62,76 @@ class Config(object):
default="content.json", metavar="inner_path") default="content.json", metavar="inner_path")
# SiteVerify # 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') action.add_argument('address', help='Site to verify')
# dbRebuild # 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') action.add_argument('address', help='Site to rebuild')
# dbQuery # 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('address', help='Site to query')
action.add_argument('query', help='Sql query') action.add_argument('query', help='Sql query')
# PeerPing # 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_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port', nargs='?') action.add_argument('peer_port', help='Peer port', nargs='?')
# PeerGetFile # 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_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port') action.add_argument('peer_port', help='Peer port')
action.add_argument('site', help='Site address') action.add_argument('site', help='Site address')
action.add_argument('filename', help='File name to request') action.add_argument('filename', help='File name to request')
# PeerGetFile # 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_ip', help='Peer ip')
action.add_argument('peer_port', help='Peer port') action.add_argument('peer_port', help='Peer port')
action.add_argument('cmd', help='Command to execute') action.add_argument('cmd', help='Command to execute')
action.add_argument('parameters', help='Parameters to command', nargs='?') action.add_argument('parameters', help='Parameters to command', nargs='?')
# CryptSign # 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('message', help='Message to sign')
action.add_argument('privatekey', help='Private key') action.add_argument('privatekey', help='Private key')
# Config parameters # Config parameters
parser.add_argument('--debug', help='Debug mode', action='store_true') self.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_socket', help='Debug socket connections', action='store_true')
parser.add_argument('--config_file', help='Path of config file', default="zeronet.conf", metavar="path") self.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") self.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('--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') self.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') self.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='*') self.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', self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically',
nargs='?', const="default_browser", metavar='browser_name') nargs='?', const="default_browser", metavar='browser_name')
parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr', self.parser.add_argument('--homepage', help='Web interface Homepage', default='1EU1tbG9oC1A8jz2ouVwGZyQ5asrNsE4Vr',
metavar='address') metavar='address')
parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, metavar='size') 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') self.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') self.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') self.parser.add_argument('--disable_udp', help='Disable UDP connections', action='store_true')
parser.add_argument('--proxy', help='Socks proxy address', metavar='ip:port') self.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') self.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', self.parser.add_argument('--use_openssl', help='Use OpenSSL liblary for speedup',
type='bool', choices=[True, False], default=use_openssl) type='bool', choices=[True, False], default=use_openssl)
parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true') self.parser.add_argument('--disable_encryption', help='Disable connection encryption', action='store_true')
parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory', self.parser.add_argument('--disable_sslcompression', help='Disable SSL compression to save memory',
type='bool', choices=[True, False], default=True) type='bool', choices=[True, False], default=True)
parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript,
metavar='executable_path') 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 # Find arguments specificed for current action
def getActionArguments(self): def getActionArguments(self):
@ -147,22 +141,77 @@ class Config(object):
back[argument.dest] = getattr(self, argument.dest) back[argument.dest] = getattr(self, argument.dest)
return back return back
# Try to find action from sys.argv # Try to find action from argv
def getAction(self, argv): def getAction(self, argv):
actions = [action.choices.keys() for action in self.parser._actions if action.dest == "action"][0] # Valid actions actions = [action.choices.keys() for action in self.parser._actions if action.dest == "action"][0] # Valid actions
found_action = False 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: if action in argv:
found_action = action found_action = action
break break
return found_action 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 # Parse command line arguments
def parseCommandline(self, argv): def parseCommandline(self, argv, silent=False):
# Find out if action is specificed on start # Find out if action is specificed on start
action = self.getAction(argv) 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") argv.append("main")
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:]) self.arguments = self.parser.parse_args(argv[1:])
# Parse config file # Parse config file
@ -187,9 +236,24 @@ class Config(object):
# Expose arguments as class attributes # Expose arguments as class attributes
def setAttributes(self): def setAttributes(self):
# Set attributes from arguments # Set attributes from arguments
if self.arguments:
args = vars(self.arguments) args = vars(self.arguments)
for key, val in args.items(): for key, val in args.items():
setattr(self, key, val) setattr(self, key, val)
def loadPlugins(self):
from Plugin import PluginManager
config = Config() @PluginManager.acceptPlugins
class ConfigPlugin(object):
def __init__(self, config):
self.parser = config.parser
self.createArguments()
def createArguments(self):
pass
ConfigPlugin(self)
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 elif len(peers_try) < 5: # Backup peers, add to end of the try list
peers_try.append(peer) 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 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)) 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 sys.path.append(os.path.abspath("src")) # Imports relative to src dir
from Config import config from Config import config
config.parse()
config.data_dir = "src/Test/testdata" # Use test data for unittests config.data_dir = "src/Test/testdata" # Use test data for unittests
from Crypt import CryptBitcoin from Crypt import CryptBitcoin
@ -382,13 +383,16 @@ class TestCase(unittest.TestCase):
1458664252141532163166741013621928587528255888800826689784628722366466547364755811L 1458664252141532163166741013621928587528255888800826689784628722366466547364755811L
) )
# Re-generate privatekey based on address_index
address, address_index, site_data = user.getNewSiteData() 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 user.sites = {} # Reset user data
self.assertNotEqual(user.getSiteData(address)["auth_address"], address) # Site address and auth address is different # Site address and auth address is different
self.assertEqual(user.getSiteData(address)["auth_privatekey"], site_data["auth_privatekey"]) # Re-generate auth_privatekey for site 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): def testSslCert(self):
from Crypt import CryptConnection from Crypt import CryptConnection
@ -409,8 +413,29 @@ class TestCase(unittest.TestCase):
os.unlink("%s/cert-rsa.pem" % config.data_dir) os.unlink("%s/cert-rsa.pem" % config.data_dir)
os.unlink("%s/key-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__": if __name__ == "__main__":
import logging import logging
logging.getLogger().setLevel(level=logging.CRITICAL) logging.getLogger().setLevel(level=logging.CRITICAL)
unittest.main(verbosity=2) 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.log = server.log
self.get = get # Get parameters self.get = get # Get parameters
self.env = env # Enviroment settings 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.start_response = start_response # Start response function
self.user = None 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 # Call the request handler function base on path
def route(self, path): def route(self, path):
if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict: # Restict Ui access by ip if config.ui_restrict and self.env['REMOTE_ADDR'] not in config.ui_restrict: # Restict Ui access by ip
@ -68,6 +64,9 @@ class UiRequest(object):
elif path == "/Console" and config.debug: elif path == "/Console" and config.debug:
return self.actionConsole() return self.actionConsole()
# Site media wrapper # Site media wrapper
else:
if self.get.get("wrapper") == "False":
return self.actionSiteMedia("/media" + path) # Only serve html files with frame
else: else:
body = self.actionWrapper(path) body = self.actionWrapper(path)
if body: if body:
@ -96,7 +95,16 @@ class UiRequest(object):
content_type = "application/octet-stream" content_type = "application/octet-stream"
return content_type 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): def getCookies(self):
raw_cookies = self.env.get('HTTP_COOKIE') raw_cookies = self.env.get('HTTP_COOKIE')
if raw_cookies: if raw_cookies:
@ -122,7 +130,8 @@ class UiRequest(object):
headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access headers.append(("Access-Control-Allow-Origin", "*")) # Allow json access
if self.env["REQUEST_METHOD"] == "OPTIONS": if self.env["REQUEST_METHOD"] == "OPTIONS":
# Allow json access # 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 = ( cacheable_type = (
content_type == "text/css" or content_type.startswith("image") or content_type == "text/css" or content_type.startswith("image") or
@ -157,8 +166,6 @@ class UiRequest(object):
def actionWrapper(self, path, extra_headers=None): def actionWrapper(self, path, extra_headers=None):
if not extra_headers: if not extra_headers:
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) match = re.match("/(?P<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
if match: if match:
@ -169,14 +176,6 @@ class UiRequest(object):
if self.env.get("HTTP_X_REQUESTED_WITH"): if self.env.get("HTTP_X_REQUESTED_WITH"):
return self.error403("Ajax request not allowed to load wrapper") # No ajax allowed on wrapper 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) site = SiteManager.site_manager.get(address)
if ( if (
@ -190,9 +189,25 @@ class UiRequest(object):
if not site: if not site:
return False return False
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[:]) 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 # Wrapper variable inits
query_string = "" query_string = ""
body_style = "" body_style = ""
@ -221,12 +236,13 @@ class UiRequest(object):
if content.get("viewport"): if content.get("viewport"):
meta_tags += '<meta name="viewport" id="viewport" content="%s">' % cgi.escape(content["viewport"], True) meta_tags += '<meta name="viewport" id="viewport" content="%s">' % cgi.escape(content["viewport"], True)
return self.render( yield self.render(
"src/Ui/template/wrapper.html", "src/Ui/template/wrapper.html",
server_url=server_url, server_url=server_url,
inner_path=inner_path, inner_path=inner_path,
file_url=file_url,
file_inner_path=file_inner_path, file_inner_path=file_inner_path,
address=address, address=site.address,
title=title, title=title,
body_style=body_style, body_style=body_style,
meta_tags=meta_tags, meta_tags=meta_tags,
@ -238,8 +254,6 @@ class UiRequest(object):
homepage=homepage homepage=homepage
) )
else: # Bad url
return False
# Returns if media request allowed from that referer # Returns if media request allowed from that referer
def isMediaRequestAllowed(self, site_address, referer): def isMediaRequestAllowed(self, site_address, referer):
@ -351,7 +365,7 @@ class UiRequest(object):
if not user: if not user:
self.log.error("No user found") self.log.error("No user found")
return self.error403() 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 site.websockets.append(ui_websocket) # Add to site websockets to allow notify on events
ui_websocket.start() ui_websocket.start()
for site_check in self.server.sites.values(): for site_check in self.server.sites.values():

View file

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

View file

@ -46,7 +46,7 @@ if (window.self !== window.top) window.stop();
<!-- Site Iframe --> <!-- 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 --> <!-- Site info -->
<script> <script>

View file

@ -1,5 +1,4 @@
# Included modules # Included modules
import os import os
import sys import sys
import time import time
@ -14,6 +13,7 @@ update_after_shutdown = False # If set True then update and restart zeronet aft
# Load config # Load config
from Config import config from Config import config
config.parse(silent=True) # Plugins need to access the configuration
# Create necessary files and dirs # Create necessary files and dirs
if not os.path.isdir(config.log_dir): 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): if not os.path.isfile("%s/users.json" % config.data_dir):
open("%s/users.json" % config.data_dir, "w").write("{}") open("%s/users.json" % config.data_dir, "w").write("{}")
# Setup logging # Setup logging
if config.action == "main": if config.action == "main":
if os.path.isfile("%s/debug.log" % config.log_dir): # Simple logrotate if os.path.isfile("%s/debug.log" % config.log_dir): # Simple logrotate
if os.path.isfile("%s/debug-last.log" % config.log_dir): 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 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 # Log current config
logging.debug("Config: %s" % config) logging.debug("Config: %s" % config)
@ -72,17 +75,17 @@ if config.proxy:
SocksProxy.monkeyPath(*config.proxy.split(":")) SocksProxy.monkeyPath(*config.proxy.split(":"))
# Load plugins
from Plugin import PluginManager
PluginManager.plugin_manager.loadPlugins()
# -- Actions -- # -- Actions --
@PluginManager.acceptPlugins @PluginManager.acceptPlugins
class Actions(object): 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): def main(self):
logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__)) logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__))
global ui_server, file_server global ui_server, file_server
@ -295,6 +298,5 @@ actions = Actions()
def start(): def start():
# Call function # Call function
func = getattr(actions, config.action, None)
action_kwargs = config.getActionArguments() action_kwargs = config.getActionArguments()
func(**action_kwargs) actions.call(config.action, action_kwargs)