From 896d52831b3a6060a7b5d13e1553b29c99256491 Mon Sep 17 00:00:00 2001 From: Daniele Venzano Date: Wed, 29 Nov 2017 16:46:51 +0100 Subject: [PATCH] Merge LDAP modules and implement more options (fixes #60 on github) --- zoe_api/auth/ldap.py | 26 ++++++++++--- zoe_api/auth/ldapsasl.py | 77 --------------------------------------- zoe_api/rest_api/utils.py | 5 +-- zoe_api/web/utils.py | 5 +-- zoe_lib/config.py | 9 +++-- 5 files changed, 31 insertions(+), 91 deletions(-) delete mode 100644 zoe_api/auth/ldapsasl.py diff --git a/zoe_api/auth/ldap.py b/zoe_api/auth/ldap.py index 8b0b51e..87ac987 100644 --- a/zoe_api/auth/ldap.py +++ b/zoe_api/auth/ldap.py @@ -19,6 +19,7 @@ import logging try: import ldap + import ldap.sasl except ImportError: ldap = None LDAP_AVAILABLE = False @@ -35,24 +36,39 @@ log = logging.getLogger(__name__) class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator): """A simple LDAP authenticator.""" - def __init__(self): + + def __init__(self, sasl): self.connection = ldap.initialize(get_conf().ldap_server_uri) self.base_dn = get_conf().ldap_base_dn + self.sasl = sasl + self.connection.protocol_version = ldap.VERSION3 + if self.sasl: + self.sasl_auth = ldap.sasl.sasl({}, 'GSSAPI') def auth(self, username, password): """Authenticate the user or raise an exception.""" search_filter = "uid=" + username uid = None role = 'guest' - bind_user = 'uid=' + username + "," + self.base_dn try: - self.connection.bind_s(bind_user, password) + if self.sasl: + self.connection.sasl_interactive_bind_s('', self.sasl_auth) + else: + self.connection.bind_s(get_conf().ldap_bind_user, get_conf().ldap_bind_password) + except ldap.LDAPError: + log.error('Unknown LDAP BIND user or wrong password.') + raise zoe_api.exceptions.ZoeAuthException('Unknown LDAP BIND user or wrong password.') + + try: result = self.connection.search_s(self.base_dn, ldap.SCOPE_SUBTREE, search_filter) if len(result) == 0: raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.') user_dict = result[0][1] uid = username - gid_numbers = [int(x) for x in user_dict['gidNumber']] + result = self.connection.compare_s(search_filter + ',' + self.base_dn, 'userPassword', password) + if result == 0: + raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.') + gid_numbers = [x.decode('utf-8') for x in user_dict[get_conf().ldap_group_name]] if get_conf().ldap_admin_gid in gid_numbers: role = 'admin' elif get_conf().ldap_user_gid in gid_numbers: @@ -60,7 +76,7 @@ class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator): elif get_conf().ldap_guest_gid in gid_numbers: role = 'guest' else: - log.warning('User {} has an unknown group ID ({}), using guest role'.format(username, result[0][1]['gidNumber'])) + log.warning('User {} has an unknown group ID ({}), using guest role'.format(username, gid_numbers)) role = 'guest' except ldap.LDAPError as ex: if ex.args[0]['desc'] == 'Invalid credentials': diff --git a/zoe_api/auth/ldapsasl.py b/zoe_api/auth/ldapsasl.py deleted file mode 100644 index 9042093..0000000 --- a/zoe_api/auth/ldapsasl.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2016, Quang-Nhat Hoang-Xuan -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""LDAP authentication module.""" - -import logging - -try: - import ldap - import ldap.sasl -except ImportError: - ldap = None - LDAP_AVAILABLE = False -else: - LDAP_AVAILABLE = True - -import zoe_api.auth.base -import zoe_api.exceptions - -from zoe_lib.config import get_conf - -log = logging.getLogger(__name__) - - -class LDAPSASLAuthenticator(zoe_api.auth.base.BaseAuthenticator): - """A simple LDAP authenticator.""" - - def __init__(self): - self.connection = ldap.initialize(get_conf().ldap_server_uri) - self.base_dn = get_conf().ldap_base_dn - self.connection.protocol_version = ldap.VERSION3 - self.sasl_auth = ldap.sasl.sasl({}, 'GSSAPI') - - def auth(self, username, password): - """Authenticate the user or raise an exception.""" - search_filter = "uid=" + username - uid = None - role = 'guest' - try: - self.connection.sasl_interactive_bind_s('', self.sasl_auth) - result = self.connection.search_s(self.base_dn, ldap.SCOPE_SUBTREE, search_filter) - - if len(result) == 0: - raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.') - user_dict = result[0][1] - uid = username - gid_numbers = [int(x) for x in user_dict['gidNumber']] - if get_conf().ldap_admin_gid in gid_numbers: - role = 'admin' - elif get_conf().ldap_user_gid in gid_numbers: - role = 'user' - elif get_conf().ldap_guest_gid in gid_numbers: - role = 'guest' - else: - log.warning('User {} has an unknown group ID ({}), using guest role'.format(username, result[0][1]['gidNumber'])) - role = 'guest' - except ldap.LDAPError as ex: - if ex.args[0]['desc'] == 'Invalid credentials': - raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.') - else: - log.exception("LDAP exception") - zoe_api.exceptions.ZoeAuthException('LDAP error.') - finally: - self.connection.unbind_s() - return uid, role diff --git a/zoe_api/rest_api/utils.py b/zoe_api/rest_api/utils.py index ef79a1b..bfa4efe 100644 --- a/zoe_api/rest_api/utils.py +++ b/zoe_api/rest_api/utils.py @@ -27,7 +27,6 @@ from zoe_api.exceptions import ZoeRestAPIException, ZoeNotFoundException, ZoeAut from zoe_api.auth.base import BaseAuthenticator # pylint-bug #1063 pylint: disable=unused-import from zoe_api.auth.ldap import LDAPAuthenticator from zoe_api.auth.file import PlainTextAuthenticator -from zoe_api.auth.ldapsasl import LDAPSASLAuthenticator log = logging.getLogger(__name__) @@ -88,9 +87,9 @@ def get_auth(handler: tornado.web.RequestHandler): if get_conf().auth_type == 'text': authenticator = PlainTextAuthenticator() # type: BaseAuthenticator elif get_conf().auth_type == 'ldap': - authenticator = LDAPAuthenticator() # type: BaseAuthenticator + authenticator = LDAPAuthenticator(sasl=False) # type: BaseAuthenticator elif get_conf().auth_type == 'ldapsasl': - authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator + authenticator = LDAPAuthenticator(sasl=True) # type: BaseAuthenticator else: raise ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type)) uid, role = authenticator.auth(username, password) diff --git a/zoe_api/web/utils.py b/zoe_api/web/utils.py index 2939c59..162fcb7 100644 --- a/zoe_api/web/utils.py +++ b/zoe_api/web/utils.py @@ -21,7 +21,6 @@ from zoe_lib.config import get_conf from zoe_api.auth.base import BaseAuthenticator # pylint: disable=unused-import from zoe_api.auth.ldap import LDAPAuthenticator -from zoe_api.auth.ldapsasl import LDAPSASLAuthenticator from zoe_api.auth.file import PlainTextAuthenticator import zoe_api.exceptions from zoe_api.web.custom_request_handler import ZoeRequestHandler @@ -73,9 +72,9 @@ def get_auth_login(username, password): # It it fails, continue with the normal authentication if get_conf().auth_type == 'ldap': - authenticator = LDAPAuthenticator() # type: BaseAuthenticator + authenticator = LDAPAuthenticator(sasl=False) # type: BaseAuthenticator elif get_conf().auth_type == 'ldapsasl': - authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator + authenticator = LDAPAuthenticator(sasl=True) # type: BaseAuthenticator else: raise zoe_api.exceptions.ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type)) uid, role = authenticator.auth(username, password) diff --git a/zoe_lib/config.py b/zoe_lib/config.py index 0f8f331..973d07c 100644 --- a/zoe_lib/config.py +++ b/zoe_lib/config.py @@ -86,10 +86,13 @@ def load_configuration(test_conf=None): argparser.add_argument('--auth-file', help='Path to the CSV file containing user,pass,role lines for text authentication', default='zoepass.csv') argparser.add_argument('--ldap-server-uri', help='LDAP server to use for authentication', default='ldap://localhost') + argparser.add_argument('--ldap-bind-user', help='Full LDAP user to use for binding', default='ou=something,dc=any,dc=local') + argparser.add_argument('--ldap-bind-password', help='Password for the bind user', default='mysecretpassword') argparser.add_argument('--ldap-base-dn', help='LDAP base DN for users', default='ou=something,dc=any,dc=local') - argparser.add_argument('--ldap-admin-gid', type=int, help='LDAP group ID for admins', default=5000) - argparser.add_argument('--ldap-user-gid', type=int, help='LDAP group ID for users', default=5001) - argparser.add_argument('--ldap-guest-gid', type=int, help='LDAP group ID for guests', default=5002) + argparser.add_argument('--ldap-admin-gid', help='LDAP group ID for admins', default='5000') + argparser.add_argument('--ldap-user-gid', help='LDAP group ID for users', default='5001') + argparser.add_argument('--ldap-guest-gid', help='LDAP group ID for guests', default='5002') + argparser.add_argument('--ldap-group-name', help='LDAP user field containing group names/IDs', default='gidNumber') # Proxy options argparser.add_argument('--proxy-path', help='Proxy base path', default='127.0.0.1') -- 2.18.1