-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #214 from Mirantis/ldap
LDAP authentication
- Loading branch information
Showing
19 changed files
with
301 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,3 +112,5 @@ kqueen/config/local.py | |
# Remote kubeconfig | ||
kubeconfig_remote | ||
|
||
.cache | ||
.pytest_cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,11 @@ LABEL maintainer="[email protected]" | |
# prepare directory | ||
WORKDIR /code | ||
|
||
# install dependencies | ||
RUN apt-get update && \ | ||
apt-get install --no-install-recommends -y libsasl2-dev python-dev libldap2-dev libssl-dev && \ | ||
rm -rf /var/lib/apt/lists/* | ||
|
||
# copy app | ||
COPY . . | ||
RUN pip install . | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
skips: ['B101'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
version: '2' | ||
services: | ||
ldap: | ||
image: osixia/openldap | ||
command: | ||
- --loglevel | ||
- debug | ||
ports: | ||
- 127.0.0.1:389:389 | ||
environment: | ||
- LDAP_ADMIN_PASSWORD=heslo123 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from .common import authenticate, identity, encrypt_password, is_authorized | ||
from .ldap import LDAPAuth | ||
from .local import LocalAuth | ||
|
||
__all__ = [ | ||
'authenticate', | ||
'identity', | ||
'encrypt_password', | ||
'is_authorized', | ||
'LDAPAuth', | ||
'LocalAuth' | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
class BaseAuth: | ||
RESERVED_KWARGS = ['verify'] | ||
|
||
def __init__(self, *args, **kwargs): | ||
"""Create auth object and establish connection | ||
Args: | ||
**kwargs: Keyword arguments specific to Auth engine | ||
""" | ||
|
||
for k, v in kwargs.items(): | ||
if k not in self.RESERVED_KWARGS: | ||
setattr(self, k, v) | ||
|
||
def verify(self, user, password): | ||
"""Vefifies username and password. | ||
Args: | ||
user (User): user object to verify | ||
password (str): Password to verify | ||
Returns: | ||
tuple: (user, error) | ||
user (User): User object if username and password matched, None otherwise | ||
error (string): Error message explaing authentication error | ||
""" | ||
|
||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from .base import BaseAuth | ||
|
||
import ldap | ||
import logging | ||
|
||
logger = logging.getLogger('kqueen_api') | ||
|
||
|
||
class LDAPAuth(BaseAuth): | ||
def __init__(self, *args, **kwargs): | ||
""" | ||
Implementation of :func:`~kqueen.auth.base.__init__` | ||
""" | ||
|
||
super(LDAPAuth, self).__init__(*args, **kwargs) | ||
|
||
if not hasattr(self, 'uri'): | ||
raise Exception('Parameter uri is required') | ||
|
||
self.connection = ldap.initialize(self.uri) | ||
|
||
@staticmethod | ||
def _email_to_dn(email): | ||
"""This function reads email and converts it to LDAP dn | ||
Args: | ||
email (str): e-mail address | ||
Returns: | ||
dn (str): LDAP dn, like 'cn=admin,dc=example,dc=org | ||
""" | ||
|
||
segments = [] | ||
|
||
if '@' in email: | ||
cn, dcs = email.split('@') | ||
else: | ||
cn = email | ||
dcs = '' | ||
|
||
if cn: | ||
segments.append('cn={}'.format(cn)) | ||
|
||
if '.' in dcs: | ||
for s in dcs.split('.'): | ||
segments.append('dc={}'.format(s)) | ||
|
||
return ','.join(segments) | ||
|
||
def verify(self, user, password): | ||
"""Implementation of :func:`~kqueen.auth.base.__init__` | ||
This function tries to bind LDAP and returns result | ||
""" | ||
|
||
dn = self._email_to_dn(user.username) | ||
|
||
try: | ||
bind = self.connection.simple_bind_s(dn, password) | ||
|
||
if bind: | ||
return user, None | ||
except ldap.INVALID_CREDENTIALS: | ||
logger.exception("Invalid LDAP credentials for {}".format(dn)) | ||
|
||
return None, "Invalid LDAP credentials" | ||
|
||
except ldap.LDAPError as e: | ||
logger.exception(e) | ||
|
||
return None, "LDAP auth failed, check log for error" | ||
|
||
finally: | ||
self.connection.unbind() | ||
|
||
return None, "All LDAP authentication methods failed" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from .base import BaseAuth | ||
from kqueen.models import User | ||
|
||
import bcrypt | ||
import logging | ||
|
||
logger = logging.getLogger('kqueen_api') | ||
|
||
|
||
class LocalAuth(BaseAuth): | ||
def verify(self, user, password): | ||
"""Implementation of :func:`~kqueen.auth.base.__init__` | ||
This function tries to find local user and verify password. | ||
""" | ||
|
||
if isinstance(user, User): | ||
user_password = user.password.encode('utf-8') | ||
given_password = password | ||
|
||
if bcrypt.checkpw(given_password, user_password): | ||
return user, None | ||
|
||
msg = "Local authentication failed" | ||
logger.info(msg) | ||
return None, msg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
from .base import BaseAuth | ||
|
||
import pytest | ||
|
||
|
||
class TestBaseAuth: | ||
def setup(self): | ||
self.engine = BaseAuth() | ||
|
||
def test_verify_raises(self): | ||
|
||
with pytest.raises(NotImplementedError): | ||
self.engine.verify('username', 'password') | ||
|
||
def test_pass_kwargs(self): | ||
kwargs = {"test1": "abc", "test2": 123} | ||
|
||
self.engine = BaseAuth(**kwargs) | ||
|
||
for k, v in kwargs.items(): | ||
assert getattr(self.engine, k) == v | ||
|
||
def test_kwargs_reserved(self): | ||
reserved = BaseAuth.RESERVED_KWARGS | ||
default_value = "abc" | ||
|
||
assert 'verify' in reserved | ||
|
||
kwargs = {k: default_value for k in reserved} | ||
|
||
self.engine = BaseAuth(**kwargs) | ||
|
||
for k in reserved: | ||
if hasattr(self.engine, k): | ||
assert getattr(self.engine, k) != default_value |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from .ldap import LDAPAuth | ||
from kqueen.models import User | ||
|
||
import pytest | ||
|
||
|
||
class TestAuthMethod: | ||
@pytest.fixture(autouse=True) | ||
def setup(self, user): | ||
self.user = user | ||
self.user.username = '[email protected]' | ||
self.user.password = '' | ||
self.user.save() | ||
|
||
self.auth_class = LDAPAuth(uri='ldap://127.0.0.1:389') | ||
|
||
def test_raise_on_missing_uri(self): | ||
with pytest.raises(Exception, msg='Parameter uri is required'): | ||
LDAPAuth() | ||
|
||
def test_login_pass(self): | ||
password = 'heslo123' | ||
user, error = self.auth_class.verify(self.user, password) | ||
|
||
assert isinstance(user, User) | ||
assert error is None | ||
|
||
def test_login_bad_pass(self): | ||
password = 'abc' | ||
user, error = self.auth_class.verify(self.user, password) | ||
|
||
assert not user | ||
assert error == "Invalid LDAP credentials" | ||
|
||
def test_bad_server(self): | ||
password = 'heslo123' | ||
auth_class = LDAPAuth(uri="ldap://127.0.0.1:55555") | ||
|
||
user, error = auth_class.verify(self.user, password) | ||
assert not user | ||
assert error == "LDAP auth failed, check log for error" | ||
|
||
@pytest.mark.parametrize('email, dn', [ | ||
('[email protected]', 'cn=admin,dc=example,dc=org'), | ||
('[email protected]', 'cn=name.surname,dc=mail,dc=example,dc=net'), | ||
('user', 'cn=user'), | ||
]) | ||
def test_email_to_dn(self, email, dn): | ||
assert self.auth_class._email_to_dn(email) == dn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.