Browse Source

Add code for a clickable HTML representation.

master
Georg Hopp 10 years ago
parent
commit
214fd62371
  1. 25
      LdapService.py
  2. 123
      LdapService2.py
  3. 2
      ldaptree.py
  4. 2
      lib/Communication/Connection.py
  5. 92
      lib/LdapTree.py
  6. 6
      lib/Transport/TcpSocket.py
  7. 89
      templates/simple.html.j2
  8. 12
      templates/simple.txt.j2
  9. 5
      templates/websocket.html.j2
  10. BIN
      tests/.TestAll.py.swp

25
LdapService.py

@ -8,7 +8,6 @@ from struct import pack
from collections import deque
from os.path import dirname, realpath
from sys import argv, path
path.append(dirname(realpath(__file__)) + '/lib')
from Server import Server
@ -20,10 +19,12 @@ from Communication.EndPoint import CommunicationEndPoint
from Protocol.Http.Http import Http
from Protocol.Websocket.Websocket import Websocket
from jinja2 import Environment, FileSystemLoader
from LdapTree import LdapTree
class Application(EventHandler):
def __init__(self, hosturi, binddn, basedn, password):
def __init__(self, ip, port, hosturi, binddn, basedn, password):
super(Application, self).__init__()
self._event_methods = {
@ -35,17 +36,16 @@ class Application(EventHandler):
self._websockets = []
self._wstest = open('websocket.html', 'r+b')
self._wstestmm = mmap.mmap(self._wstest.fileno(), 0)
env = Environment(loader=FileSystemLoader(
dirname(realpath(__file__)) + '/templates'))
template = env.get_template('websocket.html.j2')
# TODO get ip and port or better our complete base uri here.
self._page = template.render(ip=ip, port=port)
random.seed()
self.ldaptree = LdapTree(hosturi, binddn, basedn, password, False)
def __del__(self):
self._wstestmm.close()
self._wstest.close()
def _upgrade(self, event):
self._websockets.append(event.subject)
# let other also handle the upgrade .. no return True
@ -64,13 +64,14 @@ class Application(EventHandler):
if event.data.isRequest():
if event.data.getUri() == '/':
resp = protocol.createResponse(event.data, 200, 'OK')
resp.setBody(self._wstestmm[0:])
resp.setBody(self._page)
elif event.data.getUri() == '/ldap':
resp = protocol.createResponse(event.data, 200, 'OK')
resp.setHeader('Content-Type', 'image/svg+xml')
resp.setBody(self.ldaptree.graph())
else:
resp = protocol.createResponse(event.data, 404, 'Not Found')
resp = protocol.createResponse(
event.data, 404, 'Not Found')
resp.setBody('<h1>404 - Not Found</h1>')
self.issueEvent(event.subject, 'send_msg', resp)
@ -133,7 +134,9 @@ def main():
usage()
sys.exit(2)
server = Server(Application(hosturi, binddn, basedn, password))
server = Server(
Application(
args[0], int(args[1], hosturi, binddn, basedn, password))
server.bindTcp(args[0], int(args[1]), Http())
server.start(1.0)

123
LdapService2.py

@ -0,0 +1,123 @@
#!/usr/bin/python
import time
import random
import mmap
from struct import pack
from collections import deque
from os.path import dirname, realpath
import sys
reload(sys)
from sys import argv, path, setdefaultencoding
path.append(dirname(realpath(__file__)) + '/lib')
setdefaultencoding('utf-8')
import re
from Server import Server
from Event.EventHandler import EventHandler
from Event.EventDispatcher import EventDispatcher
from Communication.EndPoint import CommunicationEndPoint
from Protocol.Http.Http import Http
from Protocol.Websocket.Websocket import Websocket
from jinja2 import Environment, FileSystemLoader
from LdapTree import LdapTree
class Application(EventHandler):
def __init__(self, ip, port, hosturi, binddn, basedn, password):
super(Application, self).__init__()
self._event_methods = {
CommunicationEndPoint.eventId('new_msg') : self._handle_data,
}
self._ldaptree = LdapTree(hosturi, binddn, basedn, password, False)
env = Environment(loader=FileSystemLoader(
dirname(realpath(__file__)) + '/templates'))
self._template = env.get_template('simple.html.j2')
random.seed()
@property
def _body(self):
return self._template.render(ldaptree=self._ldaptree).encode('utf8')
def _handle_data(self, event):
protocol = event.subject.getProtocol()
if event.subject.hasProtocol(Http):
if event.data.isRequest():
if event.data.getUri() == '/':
resp = protocol.createResponse(event.data, 200, 'OK')
resp.setBody(self._body)
else:
resp = protocol.createResponse(event.data, 404, 'Not Found')
resp.setBody('<h1>404 - Not Found</h1>')
self.issueEvent(event.subject, 'send_msg', resp)
return True
def usage():
print "Usage: " + sys.argv[0] + " ARGUMENT... [OPTIONS]... bindip bindport\n"
print "Start a webserver on the given bindip and bindport. On the page a"
print "tree representation of all DNs starting with a given base DN is"
print "visualized."
print "Only simple binds to the directory with DN and password are supported.\n"
print "ARGUMENTS:\n"
print " {:30s} : {:s}".format('-H, --hosturi=URI', 'The URI to the ldap server to query in the form:')
print " {:30s} {:s}".format('', 'ldap[s]://host.uri[:port]')
print " {:30s} : {:s}".format('-D, --binddn=DN', 'The DN to use for the LDAP bind.')
print " {:30s} : {:s}".format('-p, --password=PASSWORD', 'The password to use for the LDAP bind.')
print " {:30s} : {:s}\n".format('-b, --basedn=DN', 'The DN to start the tree with.')
print "OPTIONS:\n"
print " {:30s} : {:s}".format('-h, --help', 'Show this help page')
def main():
try:
opts, args = getopt.getopt(
sys.argv[1:],
'hH:D:b:p:',
['help', 'hosturi=', 'binddn=', 'basedn=', 'password='])
except getopt.GetoptError as err:
print str(err)
usage()
sys.exit(2)
hosturi = binddn = basedn = password = None
for o, a in opts:
if o in ["-h", "--help"]:
usage()
sys.exit(0)
elif o in ["-H", "--hosturi"]:
hosturi = a
elif o in ["-D", "--binddn"]:
binddn = a
elif o in ["-b", "--basedn"]:
basedn = a
elif o in ["-p", "--password"]:
password = a
else:
print "unknown parameter: " + a
usage()
sys.exit(2)
if not hosturi or not binddn or not basedn or not password:
usage()
sys.exit(2)
server = Server(
Application(
args[0], int(args[1], hosturi, binddn, basedn, password))
server.bindTcp(args[0], int(args[1]), Http())
server.start(1.0)
if __name__ == '__main__':
main()
# vim: set ft=python et ts=8 sw=4 sts=4:

2
ldaptree.py

@ -1,7 +1,9 @@
#!/usr/bin/python
from os.path import dirname, realpath
import getopt, sys
reload(sys)
sys.path.append(dirname(realpath(__file__)) + '/lib')
sys.setdefaultencoding('utf-8')
import getpass
from LdapTree import LdapTree

2
lib/Communication/Connection.py

@ -31,7 +31,7 @@ class Connection(CommunicationEndPoint):
if not self._current_msg or self._current_msg.ready():
self._current_msg = self._protocol.createMessage(
self.getTransport().remote)
end = self._protocol.getParser().parse(
self._current_msg, self._read_buffer)

92
lib/LdapTree.py

@ -1,6 +1,10 @@
from os.path import dirname, realpath
import ldap
import pygraphviz as pgv
from jinja2 import Environment, FileSystemLoader
class LdapTree(object):
def __init__(self, hosturi, binddn, basedn, password, use_gssapi):
#ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1)
@ -19,16 +23,23 @@ class LdapTree(object):
self._basedn = basedn
self._ldap_result = []
self._data = None
def text(self, filename = None):
"""
Returns a text representing the directory.
If filename is given it will be written in that file.
"""
env = Environment(loader=FileSystemLoader(
dirname(dirname(realpath(__file__))) + '/templates'))
template = env.get_template('simple.txt.j2')
text = template.render(ldaptree=self).encode('utf8')
if filename:
with open(filename, "w") as text_file:
text_file.write(self._text(self._basedn, 0))
text_file.write(text)
else:
return self._text(self._basedn, 0)
return text
def graph(self, filename = None):
"""
@ -54,26 +65,11 @@ class LdapTree(object):
else:
return graph.draw(format='svg')
def _text(self, dn, level):
"""
Recursive function that returns a string representation of the
directory where each depth is indicated by a dash.
"""
result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL)
indent = '-' * level
text = indent + dn + "\n"
for entry in (entry[0] for entry in result):
if entry:
text += self._text(entry, level + 1)
return text
def _graph(self, graph, dn):
"""
Recursive function creating a graphviz graph from the directory.
"""
result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL)
result = self.node(dn)
minlen = thislen = 1
edge_start = dn
@ -85,6 +81,7 @@ class LdapTree(object):
sub.add_node(
point, shape='circle', fixedsize='true', width='0.04',
label='', fillcolor='transparent')
#sub.add_node(entry, URL='https://www.google.de/')
sub.add_node(entry)
graph.add_edge(edge_start, point, arrowhead='none',
minlen=str(minlen))
@ -94,3 +91,62 @@ class LdapTree(object):
thislen += minlen
return thislen
@property
def all(self):
if self._data == None:
self._data = {}
result = self._ldap.search_s(self._basedn, ldap.SCOPE_SUBTREE)
for entry in result:
self._data[entry[0]] = entry[1:][0]
return self._data
@property
def dn_tree(self):
retval = {}
for d in self.all.keys():
current = retval
for k in reversed(d.split(',')):
try:
current = current[k]
except:
current[k] = {}
current = current[k]
return retval
@property
def hirarchy(self):
return self._hirarchy(self.dn_tree)
def _hirarchy(self, dn, base=[], depth=0):
"""
Hirarchie generates a flat list where each parent is
followed by all its childs, so that in a template one
can simple iterate over it to display the complete tree.
Recently I learned that "recursive loops" are possible
within jinja2. So we can alter the template that it
ensures child displays correctly
"""
retval = []
for d in dn.keys():
base_name = ','.join(reversed(base))
name = ','.join(reversed(base + [d]))
retval.append((depth, base_name, name))
retval += self._hirarchy(dn[d], base + [d], depth+1)
return retval
def childs(self, dn):
"""
Recently I learned that "recursive loops" are possible
within jinja2. So we can alter the template that it
ensures child displays correctly. So this function should
return all child dn's to a given dn.
"""
return [d for d in self.hirarchy if d[1] == dn]
def node(self, dn):
if dn in self.all:
return self.all[dn]
else:
return {}

6
lib/Transport/TcpSocket.py

@ -62,13 +62,13 @@ class TcpSocket(Socket):
def recv(self, size):
data = ''
try:
try:
data = self.socket.recv(size)
except socket.error as error:
if error.errno not in CONTINUE:
raise Transport.Error(Transport.Error.ERR_FAILED)
return None
if not data:
raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE)
@ -76,7 +76,7 @@ class TcpSocket(Socket):
def send(self, data, remote=None):
send = 0
try:
try:
if self.socket:
send = self.socket.send(data)
except socket.error as error:

89
templates/simple.html.j2

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Ldap</title>
<style type="text/css">
ul {
list-style-type: none;
}
ul.attributes {
border: 1px solid black;
font-weight: normal;
display: none;
padding: 5px;
margin: 0;
}
ul.childs {
display: none;
}
button {
cursor: pointer;
background-color: rgba(0,0,0,0);
border: none;
color: black;
padding: 2px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
span.linked {
font-weight: bold;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Ldap</h1>
<script language="Javascript">
function toggle(e, cls) {
attr = e.parentElement.getElementsByClassName(cls)[0];
if(attr.style.display == 'block')
attr.style.display = 'none';
else
attr.style.display = 'block';
}
</script>
<ul class="tree">
{% set depth=0 -%}
{% for d in ldaptree.hirarchy -%}
{% if depth>=d[0] and depth!=0 -%}
<ul class="childs" {% if depth < 3 -%}style="display: block;"{% endif -%}>
</ul>
</li>
{% endif -%}
{% if depth < d[0] -%}
{% set depth=d[0] -%}
<ul class="childs" {% if depth < 3 -%}style="display: block;"{% endif -%}>
{% endif -%}
{% if depth > d[0] -%}
{% for i in range(depth-d[0]) -%}
</ul>
</li>
{% endfor -%}
{% set depth=d[0] -%}
{% endif -%}
<li>
<span {% if ldaptree.childs(d[2]) -%}class="linked"{% endif -%}
onclick="toggle(this, 'childs')">dn: {{ d[2]|e }}</span>
<button onclick="toggle(this, 'attributes')">[Attributes]</button>
<ul class="attributes">
{% for k in ldaptree.node(d[2]).keys() -%}
{% if ldaptree.node(d[2]) is string -%}
<li>{{ k }}: {{ ldaptree.node(d[2])[k]|e }}</li>
{% else -%}
{% for v in ldaptree.node(d[2])[k] -%}
<li>{{ k }}: {{ v|e }}</li>
{% endfor -%}
{% endif -%}
{% endfor -%}
</ul>
{% endfor -%}
</ul>
</body>
</html>
<!-- vim: set et ts=2 sw=2: -->

12
templates/simple.txt.j2

@ -0,0 +1,12 @@
{% for d in ldaptree.hirarchy -%}
{{ '--'*d[0] }} dn: {{ d[2] }}
{% for k in ldaptree.node(d[2]).keys() -%}
{% if ldaptree.node(d[2]) is string -%}
{{ ' '*d[0] }} {{ k }}: {{ ldaptree.node(d[2])[k] }}
{% else -%}
{% for v in ldaptree.node(d[2])[k] -%}
{{ ' '*d[0] }} {{ k }}: {{ v }}
{% endfor -%}
{% endif -%}
{% endfor -%}
{% endfor -%}

5
websocket.html → templates/websocket.html.j2

@ -80,7 +80,7 @@
var interval = null
wstest.data = 'Websockets are supported';
connection = new WebSocket('ws://127.0.0.1:8080/');
connection = new WebSocket('ws://172.27.1.139:8080/');
console.log('Websockets test');
connection.onopen = function() {
@ -114,7 +114,8 @@
}
}/* ]]> */
</script>
<img src="http://127.0.0.1:8080/ldap" />
<object data="http://{{ ip }}:{{ port }}/ldap" type="image/svg+xml" class="mailicon">
</object>
</body>
</html>
<!-- vim: set ts=4 sw=4: -->

BIN
tests/.TestAll.py.swp

Loading…
Cancel
Save