Commit 896d5283 authored by Daniele Venzano's avatar Daniele Venzano

Merge LDAP modules and implement more options (fixes #60 on github)

parent 4cab582e
...@@ -19,6 +19,7 @@ import logging ...@@ -19,6 +19,7 @@ import logging
try: try:
import ldap import ldap
import ldap.sasl
except ImportError: except ImportError:
ldap = None ldap = None
LDAP_AVAILABLE = False LDAP_AVAILABLE = False
...@@ -35,24 +36,39 @@ log = logging.getLogger(__name__) ...@@ -35,24 +36,39 @@ log = logging.getLogger(__name__)
class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator): class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator):
"""A simple LDAP authenticator.""" """A simple LDAP authenticator."""
def __init__(self):
def __init__(self, sasl):
self.connection = ldap.initialize(get_conf().ldap_server_uri) self.connection = ldap.initialize(get_conf().ldap_server_uri)
self.base_dn = get_conf().ldap_base_dn 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): def auth(self, username, password):
"""Authenticate the user or raise an exception.""" """Authenticate the user or raise an exception."""
search_filter = "uid=" + username search_filter = "uid=" + username
uid = None uid = None
role = 'guest' role = 'guest'
bind_user = 'uid=' + username + "," + self.base_dn
try: 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) result = self.connection.search_s(self.base_dn, ldap.SCOPE_SUBTREE, search_filter)
if len(result) == 0: if len(result) == 0:
raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.') raise zoe_api.exceptions.ZoeAuthException('Unknown user or wrong password.')
user_dict = result[0][1] user_dict = result[0][1]
uid = username 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: if get_conf().ldap_admin_gid in gid_numbers:
role = 'admin' role = 'admin'
elif get_conf().ldap_user_gid in gid_numbers: elif get_conf().ldap_user_gid in gid_numbers:
...@@ -60,7 +76,7 @@ class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator): ...@@ -60,7 +76,7 @@ class LDAPAuthenticator(zoe_api.auth.base.BaseAuthenticator):
elif get_conf().ldap_guest_gid in gid_numbers: elif get_conf().ldap_guest_gid in gid_numbers:
role = 'guest' role = 'guest'
else: 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' role = 'guest'
except ldap.LDAPError as ex: except ldap.LDAPError as ex:
if ex.args[0]['desc'] == 'Invalid credentials': if ex.args[0]['desc'] == 'Invalid credentials':
......
# 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
...@@ -27,7 +27,6 @@ from zoe_api.exceptions import ZoeRestAPIException, ZoeNotFoundException, ZoeAut ...@@ -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.base import BaseAuthenticator # pylint-bug #1063 pylint: disable=unused-import
from zoe_api.auth.ldap import LDAPAuthenticator from zoe_api.auth.ldap import LDAPAuthenticator
from zoe_api.auth.file import PlainTextAuthenticator from zoe_api.auth.file import PlainTextAuthenticator
from zoe_api.auth.ldapsasl import LDAPSASLAuthenticator
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -88,9 +87,9 @@ def get_auth(handler: tornado.web.RequestHandler): ...@@ -88,9 +87,9 @@ def get_auth(handler: tornado.web.RequestHandler):
if get_conf().auth_type == 'text': if get_conf().auth_type == 'text':
authenticator = PlainTextAuthenticator() # type: BaseAuthenticator authenticator = PlainTextAuthenticator() # type: BaseAuthenticator
elif get_conf().auth_type == 'ldap': elif get_conf().auth_type == 'ldap':
authenticator = LDAPAuthenticator() # type: BaseAuthenticator authenticator = LDAPAuthenticator(sasl=False) # type: BaseAuthenticator
elif get_conf().auth_type == 'ldapsasl': elif get_conf().auth_type == 'ldapsasl':
authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator authenticator = LDAPAuthenticator(sasl=True) # type: BaseAuthenticator
else: else:
raise ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type)) raise ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type))
uid, role = authenticator.auth(username, password) uid, role = authenticator.auth(username, password)
......
...@@ -21,7 +21,6 @@ from zoe_lib.config import get_conf ...@@ -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.base import BaseAuthenticator # pylint: disable=unused-import
from zoe_api.auth.ldap import LDAPAuthenticator from zoe_api.auth.ldap import LDAPAuthenticator
from zoe_api.auth.ldapsasl import LDAPSASLAuthenticator
from zoe_api.auth.file import PlainTextAuthenticator from zoe_api.auth.file import PlainTextAuthenticator
import zoe_api.exceptions import zoe_api.exceptions
from zoe_api.web.custom_request_handler import ZoeRequestHandler from zoe_api.web.custom_request_handler import ZoeRequestHandler
...@@ -73,9 +72,9 @@ def get_auth_login(username, password): ...@@ -73,9 +72,9 @@ def get_auth_login(username, password):
# It it fails, continue with the normal authentication # It it fails, continue with the normal authentication
if get_conf().auth_type == 'ldap': if get_conf().auth_type == 'ldap':
authenticator = LDAPAuthenticator() # type: BaseAuthenticator authenticator = LDAPAuthenticator(sasl=False) # type: BaseAuthenticator
elif get_conf().auth_type == 'ldapsasl': elif get_conf().auth_type == 'ldapsasl':
authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator authenticator = LDAPAuthenticator(sasl=True) # type: BaseAuthenticator
else: else:
raise zoe_api.exceptions.ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type)) raise zoe_api.exceptions.ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type))
uid, role = authenticator.auth(username, password) uid, role = authenticator.auth(username, password)
......
...@@ -86,10 +86,13 @@ def load_configuration(test_conf=None): ...@@ -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('--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-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-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-admin-gid', 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-user-gid', 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-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 # Proxy options
argparser.add_argument('--proxy-path', help='Proxy base path', default='127.0.0.1') argparser.add_argument('--proxy-path', help='Proxy base path', default='127.0.0.1')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment