From c79d89c0b7d9e9e8af64f679be0f45fa8acbd955 Mon Sep 17 00:00:00 2001 From: crudo Date: Thu, 21 Dec 2017 13:44:54 +0100 Subject: [PATCH] Add API for retrieving user info --- config.yml | 4 +++- openldap/Makefile | 2 +- src/phi/api/rest.py | 29 ++++++++++++++++++++--------- src/phi/api/routes.py | 6 +++--- src/phi/api/utils.py | 6 ++++++ src/phi/app.py | 1 - src/phi/ldap/client.py | 24 +++++++++++++++++------- src/phi/ldap/commands.py | 10 ---------- src/phi/ldap/entry.py | 36 ++++++++++++++++++++++++++++++++++++ src/phi/ldap/user.py | 26 ++++++++++++++++++++++++++ src/phi/ldap/utils.py | 3 +++ 11 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 src/phi/api/utils.py delete mode 100644 src/phi/ldap/commands.py create mode 100644 src/phi/ldap/entry.py create mode 100644 src/phi/ldap/user.py create mode 100644 src/phi/ldap/utils.py diff --git a/config.yml b/config.yml index aca8c90..100a0f6 100644 --- a/config.yml +++ b/config.yml @@ -18,6 +18,8 @@ ldap: password: phi base_dn: dc=unit,dc=macaomilano,dc=org + attribute_id: uid + attribute_mail: mail logging: @@ -44,5 +46,5 @@ logging: level: DEBUG handlers: [console, file] ldap3: - level: DEBUG + level: WARNING handlers: [console, file] diff --git a/openldap/Makefile b/openldap/Makefile index d88e671..e743396 100644 --- a/openldap/Makefile +++ b/openldap/Makefile @@ -32,7 +32,7 @@ populate: .PHONY: inspect inspect: - ldapsearch -ZZ -h 127.0.0.1 \ + ldapsearch -ZZ -H ldap://127.0.0.1 \ -x -D "cn=root,dc=unit,dc=macaomilano,dc=org" -w root \ -b "dc=unit,dc=macaomilano,dc=org" \ '(objectclass=*)' diff --git a/src/phi/api/rest.py b/src/phi/api/rest.py index 9a06925..c9c2a61 100644 --- a/src/phi/api/rest.py +++ b/src/phi/api/rest.py @@ -1,12 +1,23 @@ -from aiohttp.web import json_response +from aiohttp.web import json_response, View +from aiohttp.web import HTTPNotFound, HTTPUnprocessableEntity + +from phi.logging import get_logger +from phi.ldap.user import get_user_by_uid +from phi.api.utils import serialize + +log = get_logger(__name__) -def status(request): - response = { - 'ldap': { - 'connected': not request.app['ldap_client'].connection.closed, - 'bound': request.app['ldap_client'].connection.bound - } - } +class User(View): + async def get(self): + uid = self.request.match_info.get('uid', None) + if uid is None: + return HTTPUnprocessableEntity() - return json_response(response) + client = self.request.app['ldap_client'] + user = get_user_by_uid(client, uid) + + if not user: + return HTTPNotFound() + + return json_response(serialize(user)) diff --git a/src/phi/api/routes.py b/src/phi/api/routes.py index 191ce7b..fb7de51 100644 --- a/src/phi/api/routes.py +++ b/src/phi/api/routes.py @@ -1,8 +1,8 @@ -from aiohttp.web import get +from aiohttp.web import route -from phi.api.rest import status +from phi.api.rest import User api_routes = [ - get('/status', status) + route('*', '/user/{uid}', User) ] diff --git a/src/phi/api/utils.py b/src/phi/api/utils.py new file mode 100644 index 0000000..a71bfca --- /dev/null +++ b/src/phi/api/utils.py @@ -0,0 +1,6 @@ +from datetime import datetime + + +def serialize(d): + return {k: (v.isoformat() if isinstance(v, datetime) else v) + for k, v in d.items()} diff --git a/src/phi/app.py b/src/phi/app.py index 691fedb..1012b4e 100644 --- a/src/phi/app.py +++ b/src/phi/app.py @@ -10,7 +10,6 @@ def setup_app(config): app = web.Application(loop=loop) app['config'] = config - api = api_app(config) app.add_subapp('/api', api) diff --git a/src/phi/ldap/client.py b/src/phi/ldap/client.py index 429f2ef..17cd169 100644 --- a/src/phi/ldap/client.py +++ b/src/phi/ldap/client.py @@ -1,30 +1,39 @@ from threading import Lock -from ldap3.utils.log import set_library_log_detail_level, BASIC +from ldap3.utils.log import set_library_log_detail_level, PROTOCOL from phi.logging import get_logger from phi.ldap.connection import make_connection from phi.ldap.connection import open_connection, close_connection log = get_logger(__name__) -set_library_log_detail_level(BASIC) +set_library_log_detail_level(PROTOCOL) class Client: - def __init__(self, host=None, port=389, - encryption=None, ciphers=None, validate=False, - ca_certs=None, username=None, password=None, - base_dn=None): + def __init__(self, + host=None, port=389, + encryption=None, ciphers=None, validate=False, ca_certs=None, + username=None, password=None, + base_dn=None, + attribute_id='uid', attribute_mail='mail'): log.info("Initializing LDAP Client.") + self.host = host self.port = port + self.encryption = encryption self.ciphers = ciphers self.validate = validate self.ca_certs = ca_certs + self.username = username self.password = password + self.base_dn = base_dn + self.attribute_id = attribute_id + self.attribute_mail = attribute_mail + self.connection_lock = Lock() self.connection = make_connection(host=self.host, port=self.port, encryption=self.encryption, @@ -41,7 +50,8 @@ class Client: self.connection_lock.release() else: self.connection_lock.release() - raise Exception("Trying to open a connection, but it is already open.") + raise Exception("Trying to open a connection, " + "but it is already open.") def close(self): self.connection_lock.acquire() diff --git a/src/phi/ldap/commands.py b/src/phi/ldap/commands.py deleted file mode 100644 index 7a9ae72..0000000 --- a/src/phi/ldap/commands.py +++ /dev/null @@ -1,10 +0,0 @@ -def inspect(client): - id = client.connection.search(client.base_dn, - '(objectclass=*)') - response, result = client.connection.get_response(id) - return response - - -def whoami(client): - response = client.connection.extend.standard.who_am_i() - return response diff --git a/src/phi/ldap/entry.py b/src/phi/ldap/entry.py new file mode 100644 index 0000000..c9e9a2c --- /dev/null +++ b/src/phi/ldap/entry.py @@ -0,0 +1,36 @@ +from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES + +from phi.logging import get_logger + +log = get_logger(__name__) + + +def get_entry_by_uid(client, uid): + log.info("Searching entry with identifier: {}".format(uid)) + + filter_ = "({}={})".format(client.attribute_id, uid) + log.debug("Search filter: {}".format(filter_)) + + response_id = client.connection.search( + client.base_dn, filter_, + search_scope='SUBTREE', + attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES] + ) + + response, result, request = client.connection.get_response( + response_id, get_request=True + ) + + log.debug("Request: {}".format(request)) + log.debug("Response: {}".format(response)) + log.debug("Result: {}".format(result)) + + if not response: + return None + + if response[1:]: + log.erorr("Looking for exactly one result but server gave {}. " + "Taking the first and ignoring the rest." + .format(len(response))) + + return response[0] diff --git a/src/phi/ldap/user.py b/src/phi/ldap/user.py new file mode 100644 index 0000000..29e2e5a --- /dev/null +++ b/src/phi/ldap/user.py @@ -0,0 +1,26 @@ +from phi.ldap.entry import get_entry_by_uid +from phi.ldap.utils import flatten_attributes + + +def user_attributes_mapping(client): + return { + client.attribute_id: 'uid', + client.attribute_mail: 'mail', + 'createTimestamp': 'created_at', + 'modifyTimestamp': 'modified_at' + } + + +def get_user_by_uid(client, uid): + entry = get_entry_by_uid(client, uid) + + if not entry: + return None + + mapping = user_attributes_mapping(client) + + user = {mapping[k]: v + for k, v in entry['attributes'].items() + if k in mapping.keys()} + + return flatten_attributes(user) diff --git a/src/phi/ldap/utils.py b/src/phi/ldap/utils.py new file mode 100644 index 0000000..a44ab92 --- /dev/null +++ b/src/phi/ldap/utils.py @@ -0,0 +1,3 @@ +def flatten_attributes(d): + return {k: (v[0] if isinstance(v, list) else v) + for k, v in d.items()}