Skip to content
Snippets Groups Projects
Commit 79fd47c0 authored by Alexandre Ferland's avatar Alexandre Ferland
Browse files

Merge pull request #8 from OptiverTimAll/basic-auth-decorator

Basic auth decorator
parents a067a985 dfadbe61
Branches
No related tags found
No related merge requests found
...@@ -82,8 +82,14 @@ directives: ...@@ -82,8 +82,14 @@ directives:
Default: '(&(objectclass=Group)(userPrincipalName={}))' Default: '(&(objectclass=Group)(userPrincipalName={}))'
``LDAP_GROUP_MEMBERS_FIELD`` The field to return when searching for a group's members. ``LDAP_GROUP_MEMBERS_FIELD`` The field to return when searching for a group's members.
Default: 'member' Default: 'member'
``LDAP_LOGIN_VIEW`` The view to redirect to when a user needs to log-in. ``LDAP_LOGIN_VIEW`` Views decorated with :meth:`.login_required()` or
Default: 'login'. :meth:`.group_required()` will redirect
unauthenticated requests to this view. Default:
'login'.
``LDAP_REALM_NAME`` Views decorated with
:meth:`.basic_auth_required()` will use this as
the "realm" part of HTTP Basic Authentication when
responding to unauthenticated requests.
============================ =================================================== ============================ ===================================================
......
from flask import Flask, g, request, session, redirect, url_for
from flask.ext.simpleldap import LDAP
app = Flask(__name__)
app.secret_key = 'dev key'
app.debug = True
app.config['LDAP_HOST'] = 'ldap.example.org'
app.config['LDAP_BASE_DN'] = 'OU=users,dc=example,dc=org'
app.config['LDAP_USERNAME'] = 'CN=user,OU=Users,DC=example,DC=org'
app.config['LDAP_PASSWORD'] = 'password'
ldap = LDAP(app)
@app.route('/')
@ldap.basic_auth_required
def index():
return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__':
app.run()
...@@ -5,7 +5,8 @@ from functools import wraps ...@@ -5,7 +5,8 @@ from functools import wraps
import re import re
import ldap import ldap
from flask import abort, current_app, g, redirect, url_for from flask import abort, current_app, g, redirect, url_for, request
from flask import make_response
try: try:
from flask import _app_ctx_stack as stack from flask import _app_ctx_stack as stack
...@@ -62,6 +63,7 @@ class LDAP(object): ...@@ -62,6 +63,7 @@ class LDAP(object):
'(&(objectclass=Group)(userPrincipalName={0}))') '(&(objectclass=Group)(userPrincipalName={0}))')
app.config.setdefault('LDAP_GROUP_MEMBERS_FIELD', 'member') app.config.setdefault('LDAP_GROUP_MEMBERS_FIELD', 'member')
app.config.setdefault('LDAP_LOGIN_VIEW', 'login') app.config.setdefault('LDAP_LOGIN_VIEW', 'login')
app.config.setdefault('LDAP_REALM_NAME', 'LDAP authentication')
if app.config['LDAP_USE_SSL'] or app.config['LDAP_USE_TLS']: if app.config['LDAP_USE_SSL'] or app.config['LDAP_USE_TLS']:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
...@@ -222,8 +224,13 @@ class LDAP(object): ...@@ -222,8 +224,13 @@ class LDAP(object):
@staticmethod @staticmethod
def login_required(func): def login_required(func):
"""Used to decorate a view function to require LDAP login but does NOT """When applied to a view function, any unauthenticated requests will
require membership from a specific group. be redirected to the view named in LDAP_LOGIN_VIEW. Authenticated
requests do NOT require membership from a specific group.
The login view is responsible for asking for credentials, checking
them, and setting ``flask.g.user`` to the name of the authenticated
user if the credentials are acceptable.
:param func: The view function to decorate. :param func: The view function to decorate.
""" """
...@@ -237,8 +244,14 @@ class LDAP(object): ...@@ -237,8 +244,14 @@ class LDAP(object):
@staticmethod @staticmethod
def group_required(groups=None): def group_required(groups=None):
"""Used to decorate a view function to require LDAP login AND membership """When applied to a view function, any unauthenticated requests will
from one of the groups within the groups list. be redirected to the view named in LDAP_LOGIN_VIEW. Authenticated
requests are only permitted if they belong to one of the listed groups.
The login view is responsible for asking for credentials, checking
them, and setting ``flask.g.user`` to the name of the authenticated
user and ``flask.g.ldap_groups`` to the authenticated's user's groups
if the credentials are acceptable.
:param list groups: List of groups that should be able to access the view :param list groups: List of groups that should be able to access the view
function. function.
...@@ -255,3 +268,48 @@ class LDAP(object): ...@@ -255,3 +268,48 @@ class LDAP(object):
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapped return wrapped
return wrapper return wrapper
def basic_auth_required(self, func):
"""When applied to a view function, any unauthenticated requests are
asked to authenticate via HTTP's standard Basic Authentication system.
Requests with credentials are checked with :meth:`.bind_user()`.
The user's browser will typically show them the contents of
LDAP_REALM_NAME as a prompt for which username and password to enter.
If the request's credentials are accepted by the LDAP server, the
username is stored in ``flask.g.ldap_username`` and the password in
``flask.g.ldap_password``.
:param func: The view function to decorate.
"""
def make_auth_required_response():
response = make_response("Authorization required", 401)
response.www_authenticate.set_basic(
current_app.config['LDAP_REALM_NAME'])
return response
@wraps(func)
def wrapped(*args, **kwargs):
if request.authorization is None:
req_username = None
req_password = None
else:
req_username = request.authorization.username
req_password = request.authorization.password
if req_username is None or req_password is None:
current_app.logger.debug("Got a request without auth data")
return make_auth_required_response()
if not self.bind_user(req_username, req_password):
current_app.logger.debug("User {0!r} gave wrong "
"password".format(req_username))
return make_auth_required_response()
g.ldap_username = req_username
g.ldap_password = req_password
return func(*args, **kwargs)
return wrapped
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment