diff options
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | dosign.py | 19 | ||||
-rwxr-xr-x | index.py | 128 | ||||
-rw-r--r-- | static/app_subdap.png | bin | 0 -> 4579 bytes | |||
-rw-r--r-- | static/app_wiki.png | bin | 0 -> 3778 bytes | |||
-rw-r--r-- | static/details.js | 28 | ||||
-rw-r--r-- | static/fire.png | bin | 0 -> 8663 bytes | |||
-rw-r--r-- | static/layout.css | 92 | ||||
-rw-r--r-- | static/ldap.png | bin | 0 -> 10942 bytes | |||
-rw-r--r-- | static/logo.png | bin | 0 -> 28959 bytes | |||
-rw-r--r-- | templates/create.html | 21 | ||||
-rw-r--r-- | templates/error.html | 19 | ||||
-rw-r--r-- | templates/layout.xi | 24 | ||||
-rw-r--r-- | templates/login.html | 20 | ||||
-rw-r--r-- | templates/select.html | 17 | ||||
-rw-r--r-- | tmpl.py | 50 | ||||
-rw-r--r-- | versign.py | 30 |
17 files changed, 453 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45858e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +cherrypy.config +*.pyc +*.pem +*.pub +*.orig diff --git a/dosign.py b/dosign.py new file mode 100644 index 0000000..e0d3465 --- /dev/null +++ b/dosign.py @@ -0,0 +1,19 @@ +import M2Crypto.RSA +import M2Crypto.EVP +import time +import base64, urllib + +ts = int(time.time()) +user = 'equinox' +data = '%d:%s' % (ts, user) + +algo = 'sha256' + +digest = M2Crypto.EVP.MessageDigest(algo) +digest.update(data) +digval = digest.final() + +key = M2Crypto.RSA.load_key('rsa.pem') +signature = key.sign(digval, algo = algo) + +print urllib.urlencode([('user', user), ('ts', ts), ('signature', base64.urlsafe_b64encode(signature))]) diff --git a/index.py b/index.py new file mode 100755 index 0000000..0fd89af --- /dev/null +++ b/index.py @@ -0,0 +1,128 @@ +#!/usr/bin/python +# -*- coding: utf8 -*- + +import sys, os, cgi +appbase = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(appbase) + +import threading +import cherrypy +from tmpl import expose, render +from lxml import etree +from lxml.html import formfill +from StringIO import StringIO +import ldap +import mx.DateTime +from accountservice import accountservice + +class ldapobj(object): + def __init__(s, data): + if len(data) != 1: + raise ValueError, "invalid number of results" + dn, fields = data[0] + s._keys = fields.keys() + for k, v in fields.iteritems(): + s.__dict__[k] = v + s.dn = dn + def __getitem__(s, key): + l = s.__dict__[key] + if len(l) != 1: + raise IndexError, "invalid number of attributes" + return l[0] + def keys(s): + return s._keys + +class SubdapSite(object): + def __init__(s): + cherrypy.config.update({'error_page.404': s.http_404}) + + @expose('error.html') + def http_404(s, status, message, **kwargs): + return render(details = status + ' - ' + message) + + @expose('login.html') + def index(s): + return render(errors = {}) + @expose('login.html') + def login(s, username = None, password = None): + if username == None or password == None: + return render(errors = {'password': 'Login incorrect'}) + if username == '' or password == '': + return render(errors = {'password': 'Login incorrect'}) + + dn = "cn=%s,ou=people,dc=sublab,dc=org" % (username) + try: + l = ldap.initialize('ldaps://taifun.local.sublab.org/') + l.simple_bind_s(dn, password) + except ldap.INVALID_CREDENTIALS: + return render(errors = {'password': 'Login incorrect'}) + except ldap.LDAPError, e: + return render(errors = {'password': 'Login incorrect'}) + # e.message['info'] + + user = ldapobj(l.search_s(dn, ldap.SCOPE_BASE, '(objectclass=*)', [])) + return render('select.html', user = user) + +## x = '' +# for r in data: +# if r[0] != dn: +# continue +## x += '<tr><th>%s</th></tr>\n' % (r[0]) +# for k, v in r[1].iteritems(): +# for value in v: +# details.append([k, value]) +## x += '<tr><td>%s</td><td><pre>%s</pre></td></tr>\n' % (k, "<hr>".join(v)) + + @expose('create.html') + def create(s): + return render(errors = {}, username = '') + @expose('create.html') + def docreate(s, username = None, password = None, password2 = None): + errors = {} + if username == None or username == '': + errors['username'] = 'please specify an user name' + elif accountservice.name_valid(username) != 'valid': + errors['username'] = 'username invalid or taken' + if password == None or len(password) < 6: + errors['password'] = 'please specify a password of at least 6 characters' + if password2 != password: + errors['password2'] = 'passwords did not match' + if len(errors) > 0: + return render(errors = errors, username = username) + + accountservice.name_create(username, password) + return s.login(username, password) + + @cherrypy.expose + def kill(s): + import sys + sys.exit(0) + +config = { + 'global': { + 'server.socket_port': 8080, + 'server.socket_host': '127.0.0.1', +# 'server.socket_host': '::1', + 'tools.staticdir.root': appbase, + }, + '/': { + 'tools.decode.on': True, + 'tools.encode.on': True, + 'tools.gzip.on': True, + 'tools.proxy.on': True, +# 'tools.caching.on': False, +# 'tools.expires.secs': 0, +# 'tools.expires.force': True, + }, + '/static': { + 'tools.staticdir.on': True, + 'tools.staticdir.dir': 'static', + }, +} + +if len(sys.argv) >= 2 and sys.argv[1] == 'standalone': + cherrypy.quickstart(SubdapSite(), "/", config) +else: + cherrypy.config.update({'environment': 'embedded'}) + application = cherrypy.Application(SubdapSite(), script_name = None, config = config) + diff --git a/static/app_subdap.png b/static/app_subdap.png Binary files differnew file mode 100644 index 0000000..6e985bc --- /dev/null +++ b/static/app_subdap.png diff --git a/static/app_wiki.png b/static/app_wiki.png Binary files differnew file mode 100644 index 0000000..80c894b --- /dev/null +++ b/static/app_wiki.png diff --git a/static/details.js b/static/details.js new file mode 100644 index 0000000..bd42bb4 --- /dev/null +++ b/static/details.js @@ -0,0 +1,28 @@ + +function add(type) { + var node = document.getElementById("hperson"); + + ntr = document.createElement("tr"); + ntd = document.createElement("td"); + ntxt = document.createTextNode("x"); + ntd.appendChild(ntxt); + ntr.appendChild(ntd); + + node.parentNode.insertBefore(ntr, node); + + window.alert('tag' + node.nodeName); + for (node = node.nextSibling; node != null; node = node.nextSibling) { + window.alert('tag' + node.nodeName); + if (node.nodeName != 'TR') + continue; + var td = node.getElementsByTagName("TD"); + if (td == null) + continue; + var input = td[1].getElementsByTagName("INPUT"); + if (input == null) + continue; + for (var k = 0; k < input.length; k++) + input[k].style.display = 'none'; + } +} + diff --git a/static/fire.png b/static/fire.png Binary files differnew file mode 100644 index 0000000..4d9ff87 --- /dev/null +++ b/static/fire.png diff --git a/static/layout.css b/static/layout.css new file mode 100644 index 0000000..eb670f5 --- /dev/null +++ b/static/layout.css @@ -0,0 +1,92 @@ +body, table { + font-family:sans; + font-size:11pt; +} +body#formcont { + margin:auto; + text-align:center; +} +body#formcont < div { + display:inline-block; +} +img#logo { + display:inline-block; + margin:3em; + margin-right:0.5em; +} +body#formcont form, +body#formcont div.cont { + display:inline-block; + vertical-align:top; + text-align:right; + border-left:1px solid #ccc; + margin:3em; + margin-left:0.5em; + padding:2em; + min-height:180px; +} + +p.app { + border:1px solid #ccc; + padding:2pt; + padding-right:8pt; +} +p.app img { + display:inline-block; + margin:2px; + border:0px; + vertical-align:middle; +} +p.app a { + text-decoration:none; +} + +a#create { + display:block; + font-size:75%; + text-align:right; + margin-bottom:1em; +} +b.error { + display:block; + color:red; + font-style:italic; + font-size:9pt; + margin-bottom:3pt; + max-width:15em; +} +div#creatpw { + margin-top:1em; +} +input[type=submit] { + margin-top:1em; +} +body#error img { + float:left; + margin:2em; + margin-right:0.5em; +} +body#error div#cont { + margin:2em; + margin-left:260px; + border-left:1px solid #ccc; + padding:1em; + padding-bottom:10em; +} +body#error h1 { + margin-top:1em; +} +body#error code { + font-size:10pt; +} +body#error pre { + max-width:45em; + font-size:8.5pt; + border:1px solid #d73f0b; + padding:1em; + background-color:#210; + color:#ffb100; +} + + + diff --git a/static/ldap.png b/static/ldap.png Binary files differnew file mode 100644 index 0000000..cc7559c --- /dev/null +++ b/static/ldap.png diff --git a/static/logo.png b/static/logo.png Binary files differnew file mode 100644 index 0000000..e1968a0 --- /dev/null +++ b/static/logo.png diff --git a/templates/create.html b/templates/create.html new file mode 100644 index 0000000..2ac6563 --- /dev/null +++ b/templates/create.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/" + xmlns:sub="http://local.sublab.org/subdap/xmlns-templates" + xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="layout.xi"/> + <head> + <title>create</title> + </head> + <sub:bodyform> + <form action='docreate' method='POST'> + <div>uid: <input type='text' name='username' size="10" value="${username}"/></div> + <b class="error" py:if="'username' in errors">${errors.username}</b> + <div id="creatpw">password: <input type='password' name='password' size="10" value=""/></div> + <b class="error" py:if="'password' in errors">${errors.password}</b> + <div>repeat: <input type='password' name='password2' size="10" value=""/></div> + <b class="error" py:if="'password2' in errors">${errors.password2}</b> + <input type='submit' value='create'/> + </form> + </sub:bodyform> +</html> diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..0399863 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/" + xmlns:sub="http://local.sublab.org/subdap/xmlns-templates" + xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="layout.xi"/> + <head> + <title>login</title> + </head> + <body id="error"> + <img src="static/fire.png" alt="subdap"/> + <div id="cont"> + <h1>kaputt!</h1> + <p><code>subdap.exe</code> hat ein Problem festgestellt und musste beendet werden.<br/> + Bitte schicken sie einen Fehlerbericht an <code>equinox ! diac24 ! net</code>.</p> + <pre>${details}</pre> + </div> + </body> +</html> diff --git a/templates/layout.xi b/templates/layout.xi new file mode 100644 index 0000000..6a5418a --- /dev/null +++ b/templates/layout.xi @@ -0,0 +1,24 @@ +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/" + xmlns:sub="http://local.sublab.org/subdap/xmlns-templates" + py:strip=""> +<!-- vim:syntax=html + --> + + <py:match path="head" once="true"> + <head py:attrs="select('@*')"> + <title py:with="title = list(select('title/text()'))">subdap<py:if test="title">: ${title}</py:if></title> + <link rel="stylesheet" href="${url('static/layout.css')}" type="text/css"/> + ${select('*[local-name()!="title"]')} + </head> + </py:match> + + <py:match path="sub:bodyform" once="true"> + <body id="formcont" py:attrs="select('@*')"> + <div> + <img id="logo" src="static/ldap.png" alt="subdap"/> + ${select('*')} + </div> + </body> + </py:match> +</html> diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..41b6c43 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/" + xmlns:sub="http://local.sublab.org/subdap/xmlns-templates" + xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="layout.xi"/> + <head> + <title>login</title> + </head> + <sub:bodyform> + <form action='login' method='POST'> + <a href="create" id="create">Konto erstellen</a> + <div>uid: <input type='text' name='username' size="10"/></div> + <b class="error" py:if="'username' in errors">${errors.username}</b> + <div>passwort: <input type='password' name='password' size="10"/></div> + <b class="error" py:if="'password' in errors">${errors.password}</b> + <input type='submit' value='login'/> + </form> + </sub:bodyform> +</html> diff --git a/templates/select.html b/templates/select.html new file mode 100644 index 0000000..e7dc1f2 --- /dev/null +++ b/templates/select.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/" + xmlns:sub="http://local.sublab.org/subdap/xmlns-templates" + xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="layout.xi"/> + <head> + <title>login</title> + </head> + <sub:bodyform> + <div class="cont"> + Hallo ${user['cn']}! + <p class="app"><a href="http://sublab.org"><img src="${url('static/app_wiki.png')}" alt="wiki"/> sublab.org wiki</a></p> + <p class="app"><a href=""><img src="${url('static/app_subdap.png')}" alt="wiki"/> Benutzerdaten</a></p> + </div> + </sub:bodyform> +</html> @@ -0,0 +1,50 @@ +import os, traceback + +import cherrypy +from genshi.core import Stream +from genshi.output import encode, get_serializer +from genshi.template import Context, TemplateLoader + +loader = TemplateLoader( + os.path.join(os.path.dirname(__file__), 'templates'), + auto_reload=True +) + +def render(*args, **kwargs): + if args: + assert len(args) == 1, 'Expected exactly one argument, but got %r' % (args,) + template = loader.load(args[0]) + else: + template = cherrypy.thread_data.template + #ctxt = Context(url = cherrypy.url) + ctxt = Context(url = myurl) + ctxt.push(kwargs) + return template.generate(ctxt) + +def exc(exc): + return render('error.html', details = traceback.format_exc()) + +def expose(filename, method='html', encoding='utf-8', **options): + def decorate(func): + @cherrypy.expose + def wrapper(*args, **kwargs): + cherrypy.thread_data.template = loader.load(filename) + cherrypy.thread_data.func = func + opt = options.copy() + if method == 'html': + opt.setdefault('doctype', 'html') + serializer = get_serializer(method, **opt) + try: + stream = func(*args, **kwargs) + except Exception, e: + stream = exc(e) + if not isinstance(stream, Stream): + return stream + return encode(serializer(stream), method = serializer, encoding = encoding) + return wrapper + return decorate + +def myurl(url): + return '/subdap/' + url + + diff --git a/versign.py b/versign.py new file mode 100644 index 0000000..aa406df --- /dev/null +++ b/versign.py @@ -0,0 +1,30 @@ +import M2Crypto.RSA +import M2Crypto.EVP +import time, sys +import base64, cgi + +str = sys.argv[1] +data = cgi.parse_qs(str) + +ts = int(data['ts'][0]) +user = data['user'][0] +signature = base64.urlsafe_b64decode(data['signature'][0]) + +data = '%d:%s' % (ts, user) + +algo = 'sha256' + +digest = M2Crypto.EVP.MessageDigest(algo) +digest.update(data) +digval = digest.final() + +key = M2Crypto.RSA.load_pub_key('rsa.pub') + +try: + if key.verify(digval, signature, algo = algo): + print 'OKAY' + else: + print 'FAIL' +except: + print 'FAIL (hard)' + |