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:
parent
4c9a677369
commit
a93ca2c3b4
11 changed files with 509 additions and 193 deletions
117
plugins/disabled-UiPassword/UiPasswordPlugin.py
Normal file
117
plugins/disabled-UiPassword/UiPasswordPlugin.py
Normal 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])
|
1
plugins/disabled-UiPassword/__init__.py
Normal file
1
plugins/disabled-UiPassword/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
import UiPasswordPlugin
|
116
plugins/disabled-UiPassword/login.html
Normal file
116
plugins/disabled-UiPassword/login.html
Normal 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>
|
|
@ -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))
|
||||
"""
|
Loading…
Add table
Add a link
Reference in a new issue