password protection for web pages

This commit is contained in:
Ville Rantanen
2020-06-19 09:19:17 +03:00
parent d6af4e874e
commit 3443ebab4a

View File

@@ -1,13 +1,18 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding=utf-8 # coding=utf-8
''' A script that creates an index for a folder. ''' A script that creates an index for a folder.
''' '''
import os,sys,time import os
import urllib import sys
import time
import string
import urllib.parse
from glob import fnmatch from glob import fnmatch
import base64
import random
VERSION = "20200427" VERSION = "20200619"
IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'tif', 'tiff'] IMAGE_EXTENSIONS = ['png', 'gif', 'jpg', 'jpeg', 'tif', 'tiff']
def setup(): def setup():
@@ -26,6 +31,8 @@ def setup():
help="Output filename (Default: index.html)") help="Output filename (Default: index.html)")
parser.add_argument("-p",action="store_false",dest="parent",default=True, parser.add_argument("-p",action="store_false",dest="parent",default=True,
help="Do no print .. link for parent folder.") help="Do no print .. link for parent folder.")
parser.add_argument("--password",type=str,dest="password",default=None,
help="Set a password to view page. The file list will be written to a randomly generated filename. Note: this is not secure, as the target link will still be unprotected if known.")
parser.add_argument("-r",action="store_true",dest="recursive",default=False, parser.add_argument("-r",action="store_true",dest="recursive",default=False,
help="Include all files recursively in the list. Do not include any folders.") help="Include all files recursively in the list. Do not include any folders.")
parser.add_argument("--images",action="store_true",dest="images",default=False, parser.add_argument("--images",action="store_true",dest="images",default=False,
@@ -57,7 +64,7 @@ def setup2HTML(opts):
return '<meta name="SimpleWebPageSetup" content="%s"/>'%";".join([ return '<meta name="SimpleWebPageSetup" content="%s"/>'%";".join([
'hidden=%s'%opts.hidden, 'hidden=%s'%opts.hidden,
'parent=%s'%opts.parent, 'parent=%s'%opts.parent,
'title=%s'%urllib.quote(opts.title), 'title=%s'%urllib.parse.quote(opts.title),
'images=%s'%opts.images 'images=%s'%opts.images
]) ])
@@ -73,7 +80,7 @@ def HTML2setup(opts):
(k,v) = s.split('=',1) (k,v) = s.split('=',1)
if k == 'hidden': opts.hidden = v == "True" if k == 'hidden': opts.hidden = v == "True"
if k == 'parent': opts.parent = v == "True" if k == 'parent': opts.parent = v == "True"
if k == 'title': opts.title = urllib.unquote(v) if k == 'title': opts.title = urllib.parse.unquote(v)
if k == 'images': opts.images = v == "True" if k == 'images': opts.images = v == "True"
read_config = True read_config = True
print("Reading options from existing " + opts.filename) print("Reading options from existing " + opts.filename)
@@ -109,7 +116,11 @@ def get_files_and_folders(opts):
def generate_index(opts): def generate_index(opts):
opts.password_filename = ""
dirs, files, path = get_files_and_folders(opts) dirs, files, path = get_files_and_folders(opts)
if opts.password != None:
opts.password_filename = opts.filename
opts.filename = generate_password_page(path, opts.filename, opts.password)
existing_config = False existing_config = False
if opts.filename in files: if opts.filename in files:
opts, existing_config = HTML2setup(opts) opts, existing_config = HTML2setup(opts)
@@ -117,6 +128,7 @@ def generate_index(opts):
print(opts.filename + " exists, and not generated with SimpleWebPage. Exiting.") print(opts.filename + " exists, and not generated with SimpleWebPage. Exiting.")
sys.exit(1) sys.exit(1)
files = [ f for f in files if f != opts.filename] files = [ f for f in files if f != opts.filename]
files = [ f for f in files if f != opts.password_filename]
files = match_files(files, opts.includes) files = match_files(files, opts.includes)
dirs.sort() dirs.sort()
files.sort() files.sort()
@@ -135,6 +147,50 @@ def generate_index(opts):
return return
def generate_password_page(path, password_file, password):
def ha(p):
h = 0
for c in p:
h = ((h<<5) - h) + ord(c)
h = h & 0xffffffff
if h > (1<<31) - 1:
h -= (1<<32)
return h
def scramble(p,t):
p = (p * (int(len(t)/len(p))+1))[:len(t)]
t = (t * (int(len(p)/len(t))+1))[:len(p)]
s = ''
for i in range(len(p)):
s += chr(ord(p[i]) + ord(t[i]))
return s
def enc(s):
return base64.b64encode(s.encode('latin1')).decode('ascii')
def random_string(stringLength=8):
letters = string.ascii_lowercase + string.digits
return ''.join(random.choice(letters) for i in range(stringLength))
def get_target(filename):
return "{0}.{2}{1}".format(
*os.path.splitext(filename) + (random_string(),)
)
target_file = get_target(password_file)
secret = "{}:{}".format(
ha(password),
enc(scramble(password, target_file))
)
with open(os.path.join(path, password_file), 'wt') as f:
f.write(get_password_page(secret))
return target_file
def get_filelink(path,fname,images=False): def get_filelink(path,fname,images=False):
if os.path.islink(os.path.join(path, fname)) and not os.path.exists(os.path.join(path, fname)): if os.path.islink(os.path.join(path, fname)) and not os.path.exists(os.path.join(path, fname)):
(fsize, fsstr, fsstrb, fdstr)=(0, "NA", "NA", "NA") (fsize, fsstr, fsstrb, fdstr)=(0, "NA", "NA", "NA")
@@ -149,7 +205,7 @@ def get_filelink(path,fname,images=False):
else: else:
fname_str = fname fname_str = fname
return '<tr><td><a class="link_file" href="%s">%s</a><td class="right">%s</td><td class="right bytes">%s</td><td class="right">%s</td></tr>\n'%( return '<tr><td><a class="link_file" href="%s">%s</a><td class="right">%s</td><td class="right bytes">%s</td><td class="right">%s</td></tr>\n'%(
urllib.quote(fname), urllib.parse.quote(fname),
fname_str, fname_str,
fsstr, fsstr,
fsstrb, fsstrb,
@@ -160,7 +216,7 @@ def get_filelink(path,fname,images=False):
def get_wget_lines(files): def get_wget_lines(files):
wget = "\n<!--\n" wget = "\n<!--\n"
for f in files: for f in files:
wget += "#FILE %s\n"%(urllib.quote(f),) wget += "#FILE %s\n"%(urllib.parse.quote(f),)
wget += "#WGET URL=[insert URL] && curl $URL | grep '^#FILE ' | cut -c7- | sed \"s,^,$URL,\" | xargs -n1 wget\n" wget += "#WGET URL=[insert URL] && curl $URL | grep '^#FILE ' | cut -c7- | sed \"s,^,$URL,\" | xargs -n1 wget\n"
wget += "-->\n" wget += "-->\n"
return wget return wget
@@ -168,7 +224,7 @@ def get_wget_lines(files):
def get_imagestr(fname): def get_imagestr(fname):
return '<img src="%s" title="%s"/>'%( return '<img src="%s" title="%s"/>'%(
urllib.quote(fname), urllib.parse.quote(fname),
fname fname
) )
@@ -176,11 +232,102 @@ def get_pathlink(path,dname):
fdate = time.localtime(os.path.getmtime(os.path.join(path, dname))) fdate = time.localtime(os.path.getmtime(os.path.join(path, dname)))
fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate) fdstr = time.strftime("%Y/%m/%d %H:%M:%S", fdate)
return '<tr><td><a class="link_dir" href="%s">&#8627;&nbsp;%s/</a><td class="right">[DIR]</td><td class="right bytes">0</td><td class="right">%s</td></tr>\n'%( return '<tr><td><a class="link_dir" href="%s">&#8627;&nbsp;%s/</a><td class="right">[DIR]</td><td class="right bytes">0</td><td class="right">%s</td></tr>\n'%(
urllib.quote(dname), urllib.parse.quote(dname),
dname, dname,
fdstr fdstr
) )
def get_password_page(secret):
return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<title>Password required</title>
<script language="JavaScript">
Object.defineProperty(String.prototype, 'hashCode', {
value: function() {
var hash = 0, i, chr;
for (i = 0; i < this.length; i++) {
chr = this.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
});
function scramble(p,t) {
p = p.padEnd(t.length, p);
t = t.padEnd(p.length,' ');
var sstr = '';
for (var i=0; i < t.length; i++) {
sstr += String.fromCharCode(p.charCodeAt(i) + t.charCodeAt(i));
}
return sstr;
}
function unscramble(p,t) {
p = p.padEnd(t.length, p);
t = t.padEnd(p.length, ' ');
var sstr = '';
for (var i=0; i < t.length; i++) {
sstr += String.fromCharCode(t.charCodeAt(i) - p.charCodeAt(i));
}
return sstr;
}
function pw_event(element) {
if(event.key === 'Enter') {
pw(element);
}
}
function pw(element) {
var pw = "SECRET";
if (element.value) {
pw = pw.split(':',2);
if (element.value.hashCode() == parseInt(pw[0])) {
this.location.href = unscramble(element.value, atob(pw[1]));
} else {
document.getElementById("message").innerHTML = "No match";
setTimeout(clear_message, 5000);
}
}
}
function clear_message() {
document.getElementById("message").innerHTML = '';
}
function newPass(password, target) {
console.log(
' var pw = "' + password.hashCode().toString() + ':' +
btoa(scramble(password, target)) + '";'
);
}
//console.log("Use newPass('pass','page.html'); to get a new hash");
</script>
<style>
#content {
text-align: center;
padding-top: 4rem;
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-weight: bold;
}
#message {
margin-top: 1rem;
}
#button {
margin-top: 1rem;
}
</style>
</head>
<body>
<div id="content">
<input type="password" placeholder="Password" class="password" onkeydown="pw_event(this)" id="password" onFocus="this.select()" autofocus/>
<br><button id="button" type="button" onclick="pw(document.getElementById('password'));">Enter</button>
<div id="message"></div>
<noscript><br>Javascript is required to access this area. Yours seems to be disabled.</noscript>
</div>
</body>
</html>""".replace("SECRET",secret)
def get_header(opts): def get_header(opts):
header='''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> header='''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html> <html>