Commit 214fd623719fa8934a2a56a4674f9f5e296752b5
1 parent
c7f367aa
Add code for a clickable HTML representation.
Showing
10 changed files
with
321 additions
and
35 deletions
| ... | ... | @@ -8,7 +8,6 @@ from struct import pack |
| 8 | 8 | from collections import deque |
| 9 | 9 | |
| 10 | 10 | from os.path import dirname, realpath |
| 11 | -from sys import argv, path | |
| 12 | 11 | path.append(dirname(realpath(__file__)) + '/lib') |
| 13 | 12 | |
| 14 | 13 | from Server import Server |
| ... | ... | @@ -20,10 +19,12 @@ from Communication.EndPoint import CommunicationEndPoint |
| 20 | 19 | from Protocol.Http.Http import Http |
| 21 | 20 | from Protocol.Websocket.Websocket import Websocket |
| 22 | 21 | |
| 22 | +from jinja2 import Environment, FileSystemLoader | |
| 23 | + | |
| 23 | 24 | from LdapTree import LdapTree |
| 24 | 25 | |
| 25 | 26 | class Application(EventHandler): |
| 26 | - def __init__(self, hosturi, binddn, basedn, password): | |
| 27 | + def __init__(self, ip, port, hosturi, binddn, basedn, password): | |
| 27 | 28 | super(Application, self).__init__() |
| 28 | 29 | |
| 29 | 30 | self._event_methods = { |
| ... | ... | @@ -35,17 +36,16 @@ class Application(EventHandler): |
| 35 | 36 | |
| 36 | 37 | self._websockets = [] |
| 37 | 38 | |
| 38 | - self._wstest = open('websocket.html', 'r+b') | |
| 39 | - self._wstestmm = mmap.mmap(self._wstest.fileno(), 0) | |
| 39 | + env = Environment(loader=FileSystemLoader( | |
| 40 | + dirname(realpath(__file__)) + '/templates')) | |
| 41 | + template = env.get_template('websocket.html.j2') | |
| 42 | + # TODO get ip and port or better our complete base uri here. | |
| 43 | + self._page = template.render(ip=ip, port=port) | |
| 40 | 44 | |
| 41 | 45 | random.seed() |
| 42 | 46 | |
| 43 | 47 | self.ldaptree = LdapTree(hosturi, binddn, basedn, password, False) |
| 44 | 48 | |
| 45 | - def __del__(self): | |
| 46 | - self._wstestmm.close() | |
| 47 | - self._wstest.close() | |
| 48 | - | |
| 49 | 49 | def _upgrade(self, event): |
| 50 | 50 | self._websockets.append(event.subject) |
| 51 | 51 | # let other also handle the upgrade .. no return True |
| ... | ... | @@ -64,13 +64,14 @@ class Application(EventHandler): |
| 64 | 64 | if event.data.isRequest(): |
| 65 | 65 | if event.data.getUri() == '/': |
| 66 | 66 | resp = protocol.createResponse(event.data, 200, 'OK') |
| 67 | - resp.setBody(self._wstestmm[0:]) | |
| 67 | + resp.setBody(self._page) | |
| 68 | 68 | elif event.data.getUri() == '/ldap': |
| 69 | 69 | resp = protocol.createResponse(event.data, 200, 'OK') |
| 70 | 70 | resp.setHeader('Content-Type', 'image/svg+xml') |
| 71 | 71 | resp.setBody(self.ldaptree.graph()) |
| 72 | 72 | else: |
| 73 | - resp = protocol.createResponse(event.data, 404, 'Not Found') | |
| 73 | + resp = protocol.createResponse( | |
| 74 | + event.data, 404, 'Not Found') | |
| 74 | 75 | resp.setBody('<h1>404 - Not Found</h1>') |
| 75 | 76 | |
| 76 | 77 | self.issueEvent(event.subject, 'send_msg', resp) |
| ... | ... | @@ -133,7 +134,9 @@ def main(): |
| 133 | 134 | usage() |
| 134 | 135 | sys.exit(2) |
| 135 | 136 | |
| 136 | - server = Server(Application(hosturi, binddn, basedn, password)) | |
| 137 | + server = Server( | |
| 138 | + Application( | |
| 139 | + args[0], int(args[1], hosturi, binddn, basedn, password)) | |
| 137 | 140 | server.bindTcp(args[0], int(args[1]), Http()) |
| 138 | 141 | server.start(1.0) |
| 139 | 142 | ... | ... |
LdapService2.py
0 → 100755
| 1 | +#!/usr/bin/python | |
| 2 | + | |
| 3 | +import time | |
| 4 | +import random | |
| 5 | +import mmap | |
| 6 | +from struct import pack | |
| 7 | +from collections import deque | |
| 8 | + | |
| 9 | +from os.path import dirname, realpath | |
| 10 | +import sys | |
| 11 | +reload(sys) | |
| 12 | +from sys import argv, path, setdefaultencoding | |
| 13 | +path.append(dirname(realpath(__file__)) + '/lib') | |
| 14 | +setdefaultencoding('utf-8') | |
| 15 | +import re | |
| 16 | + | |
| 17 | +from Server import Server | |
| 18 | + | |
| 19 | +from Event.EventHandler import EventHandler | |
| 20 | +from Event.EventDispatcher import EventDispatcher | |
| 21 | +from Communication.EndPoint import CommunicationEndPoint | |
| 22 | + | |
| 23 | +from Protocol.Http.Http import Http | |
| 24 | +from Protocol.Websocket.Websocket import Websocket | |
| 25 | +from jinja2 import Environment, FileSystemLoader | |
| 26 | + | |
| 27 | +from LdapTree import LdapTree | |
| 28 | + | |
| 29 | +class Application(EventHandler): | |
| 30 | + def __init__(self, ip, port, hosturi, binddn, basedn, password): | |
| 31 | + super(Application, self).__init__() | |
| 32 | + | |
| 33 | + self._event_methods = { | |
| 34 | + CommunicationEndPoint.eventId('new_msg') : self._handle_data, | |
| 35 | + } | |
| 36 | + | |
| 37 | + self._ldaptree = LdapTree(hosturi, binddn, basedn, password, False) | |
| 38 | + | |
| 39 | + env = Environment(loader=FileSystemLoader( | |
| 40 | + dirname(realpath(__file__)) + '/templates')) | |
| 41 | + self._template = env.get_template('simple.html.j2') | |
| 42 | + | |
| 43 | + random.seed() | |
| 44 | + | |
| 45 | + @property | |
| 46 | + def _body(self): | |
| 47 | + return self._template.render(ldaptree=self._ldaptree).encode('utf8') | |
| 48 | + | |
| 49 | + def _handle_data(self, event): | |
| 50 | + protocol = event.subject.getProtocol() | |
| 51 | + | |
| 52 | + if event.subject.hasProtocol(Http): | |
| 53 | + if event.data.isRequest(): | |
| 54 | + if event.data.getUri() == '/': | |
| 55 | + resp = protocol.createResponse(event.data, 200, 'OK') | |
| 56 | + resp.setBody(self._body) | |
| 57 | + else: | |
| 58 | + resp = protocol.createResponse(event.data, 404, 'Not Found') | |
| 59 | + resp.setBody('<h1>404 - Not Found</h1>') | |
| 60 | + | |
| 61 | + self.issueEvent(event.subject, 'send_msg', resp) | |
| 62 | + | |
| 63 | + return True | |
| 64 | + | |
| 65 | + | |
| 66 | +def usage(): | |
| 67 | + print "Usage: " + sys.argv[0] + " ARGUMENT... [OPTIONS]... bindip bindport\n" | |
| 68 | + print "Start a webserver on the given bindip and bindport. On the page a" | |
| 69 | + print "tree representation of all DNs starting with a given base DN is" | |
| 70 | + print "visualized." | |
| 71 | + print "Only simple binds to the directory with DN and password are supported.\n" | |
| 72 | + print "ARGUMENTS:\n" | |
| 73 | + print " {:30s} : {:s}".format('-H, --hosturi=URI', 'The URI to the ldap server to query in the form:') | |
| 74 | + print " {:30s} {:s}".format('', 'ldap[s]://host.uri[:port]') | |
| 75 | + print " {:30s} : {:s}".format('-D, --binddn=DN', 'The DN to use for the LDAP bind.') | |
| 76 | + print " {:30s} : {:s}".format('-p, --password=PASSWORD', 'The password to use for the LDAP bind.') | |
| 77 | + print " {:30s} : {:s}\n".format('-b, --basedn=DN', 'The DN to start the tree with.') | |
| 78 | + print "OPTIONS:\n" | |
| 79 | + print " {:30s} : {:s}".format('-h, --help', 'Show this help page') | |
| 80 | + | |
| 81 | +def main(): | |
| 82 | + try: | |
| 83 | + opts, args = getopt.getopt( | |
| 84 | + sys.argv[1:], | |
| 85 | + 'hH:D:b:p:', | |
| 86 | + ['help', 'hosturi=', 'binddn=', 'basedn=', 'password=']) | |
| 87 | + except getopt.GetoptError as err: | |
| 88 | + print str(err) | |
| 89 | + usage() | |
| 90 | + sys.exit(2) | |
| 91 | + | |
| 92 | + hosturi = binddn = basedn = password = None | |
| 93 | + | |
| 94 | + for o, a in opts: | |
| 95 | + if o in ["-h", "--help"]: | |
| 96 | + usage() | |
| 97 | + sys.exit(0) | |
| 98 | + elif o in ["-H", "--hosturi"]: | |
| 99 | + hosturi = a | |
| 100 | + elif o in ["-D", "--binddn"]: | |
| 101 | + binddn = a | |
| 102 | + elif o in ["-b", "--basedn"]: | |
| 103 | + basedn = a | |
| 104 | + elif o in ["-p", "--password"]: | |
| 105 | + password = a | |
| 106 | + else: | |
| 107 | + print "unknown parameter: " + a | |
| 108 | + usage() | |
| 109 | + sys.exit(2) | |
| 110 | + | |
| 111 | + if not hosturi or not binddn or not basedn or not password: | |
| 112 | + usage() | |
| 113 | + sys.exit(2) | |
| 114 | + | |
| 115 | + server = Server( | |
| 116 | + Application( | |
| 117 | + args[0], int(args[1], hosturi, binddn, basedn, password)) | |
| 118 | + server.bindTcp(args[0], int(args[1]), Http()) | |
| 119 | + server.start(1.0) | |
| 120 | + | |
| 121 | +if __name__ == '__main__': | |
| 122 | + main() | |
| 123 | +# vim: set ft=python et ts=8 sw=4 sts=4: | ... | ... |
| ... | ... | @@ -31,7 +31,7 @@ class Connection(CommunicationEndPoint): |
| 31 | 31 | if not self._current_msg or self._current_msg.ready(): |
| 32 | 32 | self._current_msg = self._protocol.createMessage( |
| 33 | 33 | self.getTransport().remote) |
| 34 | - | |
| 34 | + | |
| 35 | 35 | end = self._protocol.getParser().parse( |
| 36 | 36 | self._current_msg, self._read_buffer) |
| 37 | 37 | ... | ... |
| 1 | +from os.path import dirname, realpath | |
| 2 | + | |
| 1 | 3 | import ldap |
| 2 | 4 | import pygraphviz as pgv |
| 3 | 5 | |
| 6 | +from jinja2 import Environment, FileSystemLoader | |
| 7 | + | |
| 4 | 8 | class LdapTree(object): |
| 5 | 9 | def __init__(self, hosturi, binddn, basedn, password, use_gssapi): |
| 6 | 10 | #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1) |
| ... | ... | @@ -19,16 +23,23 @@ class LdapTree(object): |
| 19 | 23 | self._basedn = basedn |
| 20 | 24 | self._ldap_result = [] |
| 21 | 25 | |
| 26 | + self._data = None | |
| 27 | + | |
| 22 | 28 | def text(self, filename = None): |
| 23 | 29 | """ |
| 24 | 30 | Returns a text representing the directory. |
| 25 | 31 | If filename is given it will be written in that file. |
| 26 | 32 | """ |
| 33 | + env = Environment(loader=FileSystemLoader( | |
| 34 | + dirname(dirname(realpath(__file__))) + '/templates')) | |
| 35 | + template = env.get_template('simple.txt.j2') | |
| 36 | + text = template.render(ldaptree=self).encode('utf8') | |
| 37 | + | |
| 27 | 38 | if filename: |
| 28 | 39 | with open(filename, "w") as text_file: |
| 29 | - text_file.write(self._text(self._basedn, 0)) | |
| 40 | + text_file.write(text) | |
| 30 | 41 | else: |
| 31 | - return self._text(self._basedn, 0) | |
| 42 | + return text | |
| 32 | 43 | |
| 33 | 44 | def graph(self, filename = None): |
| 34 | 45 | """ |
| ... | ... | @@ -54,26 +65,11 @@ class LdapTree(object): |
| 54 | 65 | else: |
| 55 | 66 | return graph.draw(format='svg') |
| 56 | 67 | |
| 57 | - def _text(self, dn, level): | |
| 58 | - """ | |
| 59 | - Recursive function that returns a string representation of the | |
| 60 | - directory where each depth is indicated by a dash. | |
| 61 | - """ | |
| 62 | - result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) | |
| 63 | - indent = '-' * level | |
| 64 | - text = indent + dn + "\n" | |
| 65 | - | |
| 66 | - for entry in (entry[0] for entry in result): | |
| 67 | - if entry: | |
| 68 | - text += self._text(entry, level + 1) | |
| 69 | - | |
| 70 | - return text | |
| 71 | - | |
| 72 | 68 | def _graph(self, graph, dn): |
| 73 | 69 | """ |
| 74 | 70 | Recursive function creating a graphviz graph from the directory. |
| 75 | 71 | """ |
| 76 | - result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) | |
| 72 | + result = self.node(dn) | |
| 77 | 73 | minlen = thislen = 1 |
| 78 | 74 | edge_start = dn |
| 79 | 75 | |
| ... | ... | @@ -85,6 +81,7 @@ class LdapTree(object): |
| 85 | 81 | sub.add_node( |
| 86 | 82 | point, shape='circle', fixedsize='true', width='0.04', |
| 87 | 83 | label='', fillcolor='transparent') |
| 84 | + #sub.add_node(entry, URL='https://www.google.de/') | |
| 88 | 85 | sub.add_node(entry) |
| 89 | 86 | graph.add_edge(edge_start, point, arrowhead='none', |
| 90 | 87 | minlen=str(minlen)) |
| ... | ... | @@ -94,3 +91,62 @@ class LdapTree(object): |
| 94 | 91 | thislen += minlen |
| 95 | 92 | |
| 96 | 93 | return thislen |
| 94 | + | |
| 95 | + @property | |
| 96 | + def all(self): | |
| 97 | + if self._data == None: | |
| 98 | + self._data = {} | |
| 99 | + result = self._ldap.search_s(self._basedn, ldap.SCOPE_SUBTREE) | |
| 100 | + for entry in result: | |
| 101 | + self._data[entry[0]] = entry[1:][0] | |
| 102 | + | |
| 103 | + return self._data | |
| 104 | + | |
| 105 | + @property | |
| 106 | + def dn_tree(self): | |
| 107 | + retval = {} | |
| 108 | + for d in self.all.keys(): | |
| 109 | + current = retval | |
| 110 | + for k in reversed(d.split(',')): | |
| 111 | + try: | |
| 112 | + current = current[k] | |
| 113 | + except: | |
| 114 | + current[k] = {} | |
| 115 | + current = current[k] | |
| 116 | + return retval | |
| 117 | + | |
| 118 | + @property | |
| 119 | + def hirarchy(self): | |
| 120 | + return self._hirarchy(self.dn_tree) | |
| 121 | + | |
| 122 | + def _hirarchy(self, dn, base=[], depth=0): | |
| 123 | + """ | |
| 124 | + Hirarchie generates a flat list where each parent is | |
| 125 | + followed by all its childs, so that in a template one | |
| 126 | + can simple iterate over it to display the complete tree. | |
| 127 | + Recently I learned that "recursive loops" are possible | |
| 128 | + within jinja2. So we can alter the template that it | |
| 129 | + ensures child displays correctly | |
| 130 | + """ | |
| 131 | + retval = [] | |
| 132 | + for d in dn.keys(): | |
| 133 | + base_name = ','.join(reversed(base)) | |
| 134 | + name = ','.join(reversed(base + [d])) | |
| 135 | + retval.append((depth, base_name, name)) | |
| 136 | + retval += self._hirarchy(dn[d], base + [d], depth+1) | |
| 137 | + return retval | |
| 138 | + | |
| 139 | + def childs(self, dn): | |
| 140 | + """ | |
| 141 | + Recently I learned that "recursive loops" are possible | |
| 142 | + within jinja2. So we can alter the template that it | |
| 143 | + ensures child displays correctly. So this function should | |
| 144 | + return all child dn's to a given dn. | |
| 145 | + """ | |
| 146 | + return [d for d in self.hirarchy if d[1] == dn] | |
| 147 | + | |
| 148 | + def node(self, dn): | |
| 149 | + if dn in self.all: | |
| 150 | + return self.all[dn] | |
| 151 | + else: | |
| 152 | + return {} | ... | ... |
| ... | ... | @@ -62,13 +62,13 @@ class TcpSocket(Socket): |
| 62 | 62 | |
| 63 | 63 | def recv(self, size): |
| 64 | 64 | data = '' |
| 65 | - try: | |
| 65 | + try: | |
| 66 | 66 | data = self.socket.recv(size) |
| 67 | 67 | except socket.error as error: |
| 68 | 68 | if error.errno not in CONTINUE: |
| 69 | 69 | raise Transport.Error(Transport.Error.ERR_FAILED) |
| 70 | 70 | return None |
| 71 | - | |
| 71 | + | |
| 72 | 72 | if not data: |
| 73 | 73 | raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) |
| 74 | 74 | |
| ... | ... | @@ -76,7 +76,7 @@ class TcpSocket(Socket): |
| 76 | 76 | |
| 77 | 77 | def send(self, data, remote=None): |
| 78 | 78 | send = 0 |
| 79 | - try: | |
| 79 | + try: | |
| 80 | 80 | if self.socket: |
| 81 | 81 | send = self.socket.send(data) |
| 82 | 82 | except socket.error as error: | ... | ... |
templates/simple.html.j2
0 → 100644
| 1 | +<!DOCTYPE html> | |
| 2 | +<html> | |
| 3 | + <head> | |
| 4 | + <meta charset="utf-8"/> | |
| 5 | + <title>Ldap</title> | |
| 6 | + <style type="text/css"> | |
| 7 | + ul { | |
| 8 | + list-style-type: none; | |
| 9 | + } | |
| 10 | + ul.attributes { | |
| 11 | + border: 1px solid black; | |
| 12 | + font-weight: normal; | |
| 13 | + display: none; | |
| 14 | + padding: 5px; | |
| 15 | + margin: 0; | |
| 16 | + } | |
| 17 | + ul.childs { | |
| 18 | + display: none; | |
| 19 | + } | |
| 20 | + button { | |
| 21 | + cursor: pointer; | |
| 22 | + background-color: rgba(0,0,0,0); | |
| 23 | + border: none; | |
| 24 | + color: black; | |
| 25 | + padding: 2px; | |
| 26 | + text-align: center; | |
| 27 | + text-decoration: none; | |
| 28 | + display: inline-block; | |
| 29 | + font-size: 16px; | |
| 30 | + } | |
| 31 | + span.linked { | |
| 32 | + font-weight: bold; | |
| 33 | + cursor: pointer; | |
| 34 | + } | |
| 35 | + </style> | |
| 36 | + </head> | |
| 37 | + | |
| 38 | + <body> | |
| 39 | + <h1>Ldap</h1> | |
| 40 | + | |
| 41 | + <script language="Javascript"> | |
| 42 | + function toggle(e, cls) { | |
| 43 | + attr = e.parentElement.getElementsByClassName(cls)[0]; | |
| 44 | + if(attr.style.display == 'block') | |
| 45 | + attr.style.display = 'none'; | |
| 46 | + else | |
| 47 | + attr.style.display = 'block'; | |
| 48 | + } | |
| 49 | + </script> | |
| 50 | + | |
| 51 | + <ul class="tree"> | |
| 52 | + {% set depth=0 -%} | |
| 53 | + {% for d in ldaptree.hirarchy -%} | |
| 54 | + {% if depth>=d[0] and depth!=0 -%} | |
| 55 | + <ul class="childs" {% if depth < 3 -%}style="display: block;"{% endif -%}> | |
| 56 | + </ul> | |
| 57 | + </li> | |
| 58 | + {% endif -%} | |
| 59 | + {% if depth < d[0] -%} | |
| 60 | + {% set depth=d[0] -%} | |
| 61 | + <ul class="childs" {% if depth < 3 -%}style="display: block;"{% endif -%}> | |
| 62 | + {% endif -%} | |
| 63 | + {% if depth > d[0] -%} | |
| 64 | + {% for i in range(depth-d[0]) -%} | |
| 65 | + </ul> | |
| 66 | + </li> | |
| 67 | + {% endfor -%} | |
| 68 | + {% set depth=d[0] -%} | |
| 69 | + {% endif -%} | |
| 70 | + <li> | |
| 71 | + <span {% if ldaptree.childs(d[2]) -%}class="linked"{% endif -%} | |
| 72 | + onclick="toggle(this, 'childs')">dn: {{ d[2]|e }}</span> | |
| 73 | + <button onclick="toggle(this, 'attributes')">[Attributes]</button> | |
| 74 | + <ul class="attributes"> | |
| 75 | + {% for k in ldaptree.node(d[2]).keys() -%} | |
| 76 | + {% if ldaptree.node(d[2]) is string -%} | |
| 77 | + <li>{{ k }}: {{ ldaptree.node(d[2])[k]|e }}</li> | |
| 78 | + {% else -%} | |
| 79 | + {% for v in ldaptree.node(d[2])[k] -%} | |
| 80 | + <li>{{ k }}: {{ v|e }}</li> | |
| 81 | + {% endfor -%} | |
| 82 | + {% endif -%} | |
| 83 | + {% endfor -%} | |
| 84 | + </ul> | |
| 85 | + {% endfor -%} | |
| 86 | + </ul> | |
| 87 | + </body> | |
| 88 | +</html> | |
| 89 | +<!-- vim: set et ts=2 sw=2: --> | ... | ... |
templates/simple.txt.j2
0 → 100644
| 1 | +{% for d in ldaptree.hirarchy -%} | |
| 2 | +{{ '--'*d[0] }} dn: {{ d[2] }} | |
| 3 | +{% for k in ldaptree.node(d[2]).keys() -%} | |
| 4 | +{% if ldaptree.node(d[2]) is string -%} | |
| 5 | +{{ ' '*d[0] }} {{ k }}: {{ ldaptree.node(d[2])[k] }} | |
| 6 | +{% else -%} | |
| 7 | +{% for v in ldaptree.node(d[2])[k] -%} | |
| 8 | +{{ ' '*d[0] }} {{ k }}: {{ v }} | |
| 9 | +{% endfor -%} | |
| 10 | +{% endif -%} | |
| 11 | +{% endfor -%} | |
| 12 | +{% endfor -%} | ... | ... |
| ... | ... | @@ -80,7 +80,7 @@ |
| 80 | 80 | var interval = null |
| 81 | 81 | |
| 82 | 82 | wstest.data = 'Websockets are supported'; |
| 83 | - connection = new WebSocket('ws://127.0.0.1:8080/'); | |
| 83 | + connection = new WebSocket('ws://172.27.1.139:8080/'); | |
| 84 | 84 | console.log('Websockets test'); |
| 85 | 85 | |
| 86 | 86 | connection.onopen = function() { |
| ... | ... | @@ -114,7 +114,8 @@ |
| 114 | 114 | } |
| 115 | 115 | }/* ]]> */ |
| 116 | 116 | </script> |
| 117 | - <img src="http://127.0.0.1:8080/ldap" /> | |
| 117 | + <object data="http://{{ ip }}:{{ port }}/ldap" type="image/svg+xml" class="mailicon"> | |
| 118 | + </object> | |
| 118 | 119 | </body> |
| 119 | 120 | </html> |
| 120 | 121 | <!-- vim: set ts=4 sw=4: --> | ... | ... |
tests/.TestAll.py.swp
0 → 100644
No preview for this file type
Please
register
or
login
to post a comment