First stab at LDAP integration.

This commit is contained in:
Jake Moshenko 2014-05-09 17:39:43 -04:00
parent 580bb152fe
commit 027ada1f5c
6 changed files with 136 additions and 3 deletions

View file

@ -23,6 +23,9 @@ RUN tar xjf phantomjs.tar.bz2 && ln -s `pwd`/phantomjs*/bin/phantomjs /usr/bin/p
RUN apt-get install -y nodejs RUN apt-get install -y nodejs
RUN npm install -g grunt-cli RUN npm install -g grunt-cli
# LDAP
RUN apt-get install libldap2-dev libsasl2-dev
ADD binary_dependencies binary_dependencies ADD binary_dependencies binary_dependencies
RUN gdebi --n binary_dependencies/*.deb RUN gdebi --n binary_dependencies/*.deb

4
app.py
View file

@ -10,6 +10,7 @@ import features
from storage import Storage from storage import Storage
from data.userfiles import Userfiles from data.userfiles import Userfiles
from data.users import UserAuthentication
from util.analytics import Analytics from util.analytics import Analytics
from util.exceptionlog import Sentry from util.exceptionlog import Sentry
from data.billing import Billing from data.billing import Billing
@ -46,3 +47,6 @@ userfiles = Userfiles(app)
analytics = Analytics(app) analytics = Analytics(app)
billing = Billing(app) billing = Billing(app)
sentry = Sentry(app) sentry = Sentry(app)
from data import model
authentication = UserAuthentication(app, model)

View file

@ -72,6 +72,9 @@ class DefaultConfig(object):
STORAGE_TYPE = 'LocalStorage' STORAGE_TYPE = 'LocalStorage'
STORAGE_PATH = 'test/data/registry' STORAGE_PATH = 'test/data/registry'
# Authentication
AUTHENTICATION_TYPE = 'Database'
# Build logs # Build logs
BUILDLOGS = BuildLogs('logs.quay.io') # Change me BUILDLOGS = BuildLogs('logs.quay.io') # Change me

122
data/users.py Normal file
View file

@ -0,0 +1,122 @@
import ldap
import logging
logger = logging.getLogger(__name__)
class DatabaseUsers(object):
def __init__(self, app_db):
self._app_db = app_db
def verify_user(self, username_or_email, password):
""" Simply delegate to the model implementation. """
return self._app_db.verify_user(username_or_email, password)
class LDAPConnection(object):
def __init__(self, ldap_uri, user_dn, user_pw):
self._ldap_uri = ldap_uri
self._user_dn = user_dn
self._user_pw = user_pw
self._conn = None
def __enter__(self):
self._conn = ldap.initialize(self._ldap_uri)
self._conn.simple_bind_s(self._user_dn, self._user_pw)
return self._conn
def __exit__(self, exc_type, value, tb):
self._conn.unbind_s()
class LDAPUsers(object):
def __init__(self, app_db, ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr,
email_attr, passwd_attr):
self._app_db = app_db
self._ldap_conn = LDAPConnection(ldap_uri, admin_dn, admin_passwd)
self._base_dn = base_dn
self._user_rdn = user_rdn
self._uid_attr = uid_attr
self._email_attr = email_attr
self._passwd_attr = passwd_attr
def verify_user(self, username_or_email, password):
""" Verify the credentials with LDAP and if they are valid, create or update the user
in our database. """
with self._ldap_conn as conn:
user_search_dn = ','.join(self._user_rdn + self._base_dn)
query = '(|({0}={2})({1}={2}))'.format(self._uid_attr, self._email_attr,
username_or_email)
user = conn.search_s(user_search_dn, ldap.SCOPE_SUBTREE, query)
if len(user) != 1:
return None
found_dn, found_response = user[0]
# First validate the password
valid_passwd = conn.compare_s(found_dn, self._passwd_attr, password) == 1
if not valid_passwd:
return None
logger.debug('LDAP Response: %s', found_response)
# Now check if we have the same username in our DB
username = found_response[self._uid_attr][0]
email = found_response[self._email_attr][0]
password = found_response[self._passwd_attr][0]
db_user = self._app_db.get_user(username)
logger.debug('Email: %s', email)
if not db_user:
# We must create the user in our db
db_user = self._app_db.create_user(username, 'password_from_ldap', email)
db_user.verified = True
else:
# Update the db attributes from ldap
db_user.email = email
db_user.save()
return db_user
class UserAuthentication(object):
def __init__(self, app=None, model=None):
self.app = app
if app is not None:
self.state = self.init_app(app, model)
else:
self.state = None
def init_app(self, app, model):
authentication_type = app.config.get('AUTHENTICATION_TYPE', 'Database')
if authentication_type == 'Database':
users = DatabaseUsers(model)
elif authentication_type == 'LDAP':
ldap_uri = app.config.get('LDAP_URI', 'ldap://localhost')
base_dn = app.config.get('LDAP_BASE_DN')
admin_dn = app.config.get('LDAP_ADMIN_DN')
admin_passwd = app.config.get('LDAP_ADMIN_PASSWD')
user_rdn = app.config.get('LDAP_USER_RDN', [])
uid_attr = app.config.get('LDAP_UID_ATTR', 'uid')
email_attr = app.config.get('LDAP_EMAIL_ATTR', 'mail')
passwd_attr = app.config.get('LDAP_PASSWD_ATTR', 'userPassword')
users = LDAPUsers(model, ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr,
email_attr, passwd_attr)
else:
raise RuntimeError('Unknown authentication type: %s' % authentication_type)
# register extension with app
app.extensions = getattr(app, 'extensions', {})
app.extensions['authentication'] = users
return users
def __getattr__(self, name):
return getattr(self.state, name, None)

View file

@ -5,7 +5,7 @@ from flask import request
from flask.ext.login import logout_user from flask.ext.login import logout_user
from flask.ext.principal import identity_changed, AnonymousIdentity from flask.ext.principal import identity_changed, AnonymousIdentity
from app import app, billing as stripe from app import app, billing as stripe, authentication
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error, from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
log_action, internal_only, NotFound, require_user_admin, log_action, internal_only, NotFound, require_user_admin,
InvalidToken, require_scope, format_date, hide_if, show_if) InvalidToken, require_scope, format_date, hide_if, show_if)
@ -227,7 +227,7 @@ def conduct_signin(username_or_email, password):
needs_email_verification = False needs_email_verification = False
invalid_credentials = False invalid_credentials = False
verified = model.verify_user(username_or_email, password) verified = authentication.verify_user(username_or_email, password)
if verified: if verified:
if common_login(verified): if common_login(verified):
return {'success': True} return {'success': True}
@ -289,7 +289,7 @@ class ConvertToOrganization(ApiResource):
# Ensure that the sign in credentials work. # Ensure that the sign in credentials work.
admin_password = convert_data['adminPassword'] admin_password = convert_data['adminPassword']
if not model.verify_user(admin_username, admin_password): if not authentication.verify_user(admin_username, admin_password):
raise request_error(reason='invaliduser', raise request_error(reason='invaliduser',
message='The admin user credentials are not valid') message='The admin user credentials are not valid')

View file

@ -32,3 +32,4 @@ python-magic
reportlab==2.7 reportlab==2.7
blinker blinker
raven raven
python-ldap