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