First public import
|
@ -0,0 +1,2 @@
|
|||
from webapp import app
|
||||
app.run('0.0.0.0', 20007, debug=True)
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python2
|
||||
# -*- coding: utf-8 -*-
|
||||
import flask
|
||||
import flask.ext.wtf
|
||||
import wtforms
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.secret_key = "foobarbaz"
|
||||
#app.add_url_rule('/login', views.login_form, methods=["GET"])
|
||||
#app.add_url_rule('/login', views.login_action, methods=["POST"])
|
||||
#app.add_url_rule('/logout', views.login_action)
|
||||
#app.add_url_rule('/passwd', views.passwd_form, methods=["GET"])
|
||||
#app.add_url_rule('/passwd', views.passwd_action, methods=["POST"])
|
||||
|
||||
# import views to route them
|
||||
import webapp.views
|
||||
|
||||
from webapp import validation, pools, config
|
||||
|
||||
@app.context_processor
|
||||
def inject_readable():
|
||||
return dict(readable_names=config.readable_names)
|
||||
|
||||
@app.context_processor
|
||||
def inject_static():
|
||||
return dict(STATIC_PATH=config.STATIC_PATH)
|
||||
|
||||
@app.template_filter('first')
|
||||
def ldap_first(v):
|
||||
return v and v[0]
|
||||
|
||||
@app.template_filter('readable')
|
||||
def readable_tf(n):
|
||||
return config.readable_names.get(n, n)
|
||||
|
||||
class LPKForm(flask.ext.wtf.Form):
|
||||
value = wtforms.fields.TextAreaField('Klucz Publiczny')
|
||||
|
||||
def initialize_forms():
|
||||
forms = {}
|
||||
for f in reduce(lambda a,b: a | b, config.can.values()):
|
||||
cls, attrs = config.fields.get(f, config.default_field)
|
||||
class AddForm(flask.ext.wtf.Form):
|
||||
value = cls(label=config.readable_names.get(f), **attrs)
|
||||
AddForm.__name__ == 'Add' + f
|
||||
forms[f] = AddForm
|
||||
print f, forms[f]
|
||||
forms[validation.sanitize_ldap(config.lpk_attr)] = LPKForm
|
||||
return forms
|
||||
|
||||
@app.before_first_request
|
||||
def start(*a, **kw):
|
||||
validation.sanitize_perms()
|
||||
validation.sanitize_readable()
|
||||
config.lpk_attr = validation.sanitize_ldap(config.lpk_attr)
|
||||
|
||||
app.connections = pools.ConnectionPool(config.ldap_url, timeout=300.0)
|
||||
def drop_profile(dn):
|
||||
if dn != config.admin_dn:
|
||||
del app.profiles[dn]
|
||||
app.connections.register_callback('drop', drop_profile)
|
||||
app.connections.start()
|
||||
|
||||
app.tokens = pools.TokenPool(timeout=600.0)
|
||||
app.tokens.start()
|
||||
|
||||
app.profiles = {}
|
||||
app.forms = initialize_forms()
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import flask.ext.wtf
|
||||
import wtforms
|
||||
ldap_url = 'ldap://ldap.hackerspace.pl'
|
||||
lpk_attr = 'sshPublicKey'
|
||||
irc_attr = 'ircnick'
|
||||
dn_format = "uid=%s,ou=people,dc=hackerspace,dc=pl"
|
||||
|
||||
admin_dn = 'cn=ldapweb,ou=Services,dc=hackerspace,dc=pl'
|
||||
admin_pw = 'foobarbaz'
|
||||
|
||||
api_allowed = [
|
||||
"/C=PL/ST=Mazowieckie/L=Warsaw/O=Hackerspace Warsaw/OU=API/CN=ood-web/name=ood-web/emailAddress=bofh@hackerspace.pl",
|
||||
"/C=PL/ST=Mazowieckie/L=Warsaw/O=Hackerspace Warsaw/OU=API/CN=patologia-new/name=itanic/emailAddress=bofh@hackerspace.pl",
|
||||
]
|
||||
|
||||
readable_names = {
|
||||
'givenname': u'Imię',
|
||||
'surname': 'Nazwisko',
|
||||
'loginshell': u'Powłoka',
|
||||
'telephonenumber': 'Nr tel.',
|
||||
'mobiletelephonenumber': 'Tel. kom.',
|
||||
lpk_attr: 'Klucz publiczny',
|
||||
irc_attr: 'IRC Nick',
|
||||
}
|
||||
|
||||
full_name = {
|
||||
'cn': 'commonname',
|
||||
'sn': 'surname',
|
||||
'mobile': 'mobiletelephonenumber',
|
||||
'l': 'locality',
|
||||
}
|
||||
|
||||
can_add = set([
|
||||
'telephonenumber',
|
||||
'mobiletelephonenumber',
|
||||
lpk_attr,
|
||||
])
|
||||
|
||||
perm_errors = {
|
||||
'add': 'You cannot add this attribute!',
|
||||
'mod': 'You cannot change this attribute!',
|
||||
'del': 'You cannot delete this attribute!',
|
||||
}
|
||||
|
||||
std_templates = {
|
||||
'add': 'ops/add.html',
|
||||
'mod': 'ops/mod.html',
|
||||
'del': 'ops/del.html',
|
||||
}
|
||||
|
||||
admin_required = set(['ircnick'])
|
||||
|
||||
can_delete = can_add | set(['ircnick'])
|
||||
|
||||
can_modify = can_add
|
||||
|
||||
can = { 'add':can_add, 'mod':can_modify, 'del':can_delete }
|
||||
|
||||
default_field = (wtforms.fields.StringField, {})
|
||||
fields = { 'telephonenumber': (wtforms.fields.StringField, {'validators': [wtforms.validators.Regexp(r'[+0-9 ]+')]})}
|
||||
|
||||
STATIC_PATH = 'https://static.hackerspace.pl/staging/'
|
||||
TOKEN_LENGTH = 32
|
|
@ -0,0 +1,60 @@
|
|||
import random
|
||||
import string
|
||||
import hashlib
|
||||
|
||||
import flask
|
||||
import ldap
|
||||
|
||||
from webapp import app, config, validation
|
||||
|
||||
class Attr(object):
|
||||
def __init__(self, name, value):
|
||||
name = validation.sanitize_ldap(name)
|
||||
self.name = name
|
||||
self.readable_name = config.readable_names.get(name, name)
|
||||
self.value = value
|
||||
self.uid = hashlib.sha1(name + value).hexdigest()
|
||||
def __unicode__(self):
|
||||
return self.value.decode('utf-8')
|
||||
|
||||
def get_dn():
|
||||
return flask.session.get('dn')
|
||||
|
||||
def get_connection(dn = None):
|
||||
dn = dn or get_dn()
|
||||
return app.connections[dn]
|
||||
|
||||
def get_admin_connection():
|
||||
conn = app.connections[config.admin_dn]
|
||||
if not conn:
|
||||
conn = app.connections.bind(config.admin_dn, config.admin_pw)
|
||||
return conn
|
||||
|
||||
def get_profile():
|
||||
return app.profiles[get_dn()]
|
||||
|
||||
def refresh_profile(dn=None):
|
||||
dn = dn or get_dn()
|
||||
conn = get_connection(dn)
|
||||
if not conn:
|
||||
return # no session, nothing to refresh i guess
|
||||
res = conn.search_s(dn, ldap.SCOPE_SUBTREE)
|
||||
assert(len(res) == 1)
|
||||
profile = {}
|
||||
for attr, vs in res[0][1].iteritems():
|
||||
for v in vs:
|
||||
a = Attr(attr, v)
|
||||
profile[a.uid] = a
|
||||
app.profiles[dn] = profile
|
||||
return profile
|
||||
|
||||
def generate_token():
|
||||
dn = get_dn()
|
||||
token = ''.join(random.choice(string.letters) for i in xrange(config.TOKEN_LENGTH))
|
||||
app.tokens.insert(dn, token)
|
||||
|
||||
def get_token():
|
||||
dn = get_dn()
|
||||
return app.tokens[dn]
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import sys
|
||||
import functools
|
||||
import threading
|
||||
import time
|
||||
|
||||
def locked(f):
|
||||
@functools.wraps(f)
|
||||
def func(self, *a, **kw):
|
||||
self.lock.acquire()
|
||||
try:
|
||||
return f(self, *a, **kw)
|
||||
finally:
|
||||
self.lock.release()
|
||||
return func
|
||||
|
||||
class LRUPool(threading.Thread):
|
||||
def __init__(self, timeout=60.0, **kw):
|
||||
threading.Thread.__init__(self, **kw)
|
||||
self.setDaemon(True)
|
||||
self.pool = {}
|
||||
self.timeout = timeout
|
||||
self.lock = threading.Lock()
|
||||
self.callbacks = {}
|
||||
def run(self):
|
||||
print >> sys.stderr, "pool starting"
|
||||
while True:
|
||||
time.sleep(self.timeout / 2)
|
||||
self.lock.acquire()
|
||||
now = time.time()
|
||||
for k, [c, atime] in self.pool.items():
|
||||
if now - atime > self.timeout:
|
||||
print 'popping', k, now, atime
|
||||
self._drop(k)
|
||||
self.lock.release()
|
||||
def register_callback(self, action, cb):
|
||||
self.callbacks.setdefault(action, []).append(cb)
|
||||
@locked
|
||||
def __getitem__(self, key):
|
||||
item = self.pool.get(key)
|
||||
if not item:
|
||||
return
|
||||
item[1] = time.time()
|
||||
return item[0]
|
||||
def _insert(self, key, item):
|
||||
self.pool[key] = [item, time.time()]
|
||||
return item
|
||||
@locked
|
||||
def insert(self, key, item):
|
||||
return self._insert(key, item)
|
||||
def _drop(self, key):
|
||||
for f in self.callbacks.get('drop',[]):
|
||||
f(key)
|
||||
return self.pool.pop(key, None)
|
||||
@locked
|
||||
def drop(self, key):
|
||||
return self._drop(key)
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import ldap
|
||||
|
||||
from webapp import lru, config
|
||||
|
||||
class ConnectionPool(lru.LRUPool):
|
||||
def __init__(self, url, use_tls=True, **kw):
|
||||
lru.LRUPool.__init__(self, **kw)
|
||||
self.use_tls = use_tls
|
||||
self.url = url
|
||||
self.admin_dn = config.admin_dn
|
||||
self.admin_pw = config.admin_pw
|
||||
@lru.locked
|
||||
def bind(self, dn, password):
|
||||
conn = ldap.initialize(self.url)
|
||||
if(self.use_tls):
|
||||
conn.start_tls_s()
|
||||
conn.simple_bind_s(dn, password)
|
||||
return self._insert(dn, conn)
|
||||
def unbind(self, dn):
|
||||
return self.drop(dn)
|
||||
|
||||
class TokenPool(lru.LRUPool):
|
||||
@lru.locked
|
||||
def find_owner(self, token):
|
||||
for k, v in self.pool.items():
|
||||
if v[0] == token:
|
||||
return k
|
||||
return None
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
td.key {
|
||||
font-size: 7pt;
|
||||
max-width: 300pt;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
td.token {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.login {
|
||||
position: relative;
|
||||
top: 100%;
|
||||
margin-top: -30px;
|
||||
float: right;
|
||||
|
||||
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
font-size: 14px;
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.login img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.login a {
|
||||
color: rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
#content {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: rgb(34, 34, 34);
|
||||
}
|
||||
|
||||
#actions a {
|
||||
font-family: verdana, sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#actions a img {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#actions li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#headerimg {
|
||||
width: 1024px;
|
||||
height: 100px;
|
||||
background-image: url("/static/servers.jpg");
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 10px;
|
||||
color: rgb(100, 100, 100);
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: rgb(120, 120, 120);
|
||||
}
|
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 464 B |
After Width: | Height: | Size: 619 B |
After Width: | Height: | Size: 524 B |
After Width: | Height: | Size: 610 B |
After Width: | Height: | Size: 533 B |
After Width: | Height: | Size: 703 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 467 B |
After Width: | Height: | Size: 592 B |
After Width: | Height: | Size: 605 B |
After Width: | Height: | Size: 714 B |
After Width: | Height: | Size: 612 B |
After Width: | Height: | Size: 581 B |
After Width: | Height: | Size: 634 B |
After Width: | Height: | Size: 685 B |
After Width: | Height: | Size: 670 B |
After Width: | Height: | Size: 656 B |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 487 B |
After Width: | Height: | Size: 525 B |
After Width: | Height: | Size: 585 B |
After Width: | Height: | Size: 478 B |
After Width: | Height: | Size: 547 B |
After Width: | Height: | Size: 581 B |
After Width: | Height: | Size: 510 B |
After Width: | Height: | Size: 483 B |
After Width: | Height: | Size: 520 B |
After Width: | Height: | Size: 432 B |
After Width: | Height: | Size: 492 B |
After Width: | Height: | Size: 493 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 555 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 473 B |
After Width: | Height: | Size: 465 B |
After Width: | Height: | Size: 426 B |
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 582 B |
After Width: | Height: | Size: 677 B |
After Width: | Height: | Size: 379 B |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 345 B |
After Width: | Height: | Size: 484 B |
After Width: | Height: | Size: 594 B |
After Width: | Height: | Size: 625 B |
After Width: | Height: | Size: 685 B |
After Width: | Height: | Size: 506 B |
After Width: | Height: | Size: 349 B |
After Width: | Height: | Size: 608 B |
After Width: | Height: | Size: 602 B |
After Width: | Height: | Size: 683 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 489 B |
After Width: | Height: | Size: 631 B |
After Width: | Height: | Size: 372 B |
After Width: | Height: | Size: 760 B |
After Width: | Height: | Size: 743 B |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 853 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 755 B |
After Width: | Height: | Size: 754 B |
After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 753 B |
After Width: | Height: | Size: 770 B |
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 714 B |
After Width: | Height: | Size: 734 B |
After Width: | Height: | Size: 738 B |
After Width: | Height: | Size: 669 B |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 773 B |
After Width: | Height: | Size: 811 B |
After Width: | Height: | Size: 794 B |
After Width: | Height: | Size: 777 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 738 B |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 816 B |
After Width: | Height: | Size: 824 B |
After Width: | Height: | Size: 813 B |
After Width: | Height: | Size: 836 B |
After Width: | Height: | Size: 850 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 363 B |
After Width: | Height: | Size: 475 B |
After Width: | Height: | Size: 793 B |
After Width: | Height: | Size: 593 B |