Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • nimrod/flask-simpleldap
1 result
Select Git revision
Show changes
Commits on Source (3)
Showing
with 600 additions and 462 deletions
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: mixed-line-ending
args: ['--fix=lf']
description: Forces to replace line ending by the UNIX 'lf' character.
- repo: https://github.com/psf/black
rev: 22.1.0
hooks:
- id: black
language_version: python3
args: [-t, py310]
language: python
sudo: required
dist: bionic
dist: focal
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"
env:
- FLASK=1.1.2
- FLASK=2.0.2
- FLASK=1.1.4
- FLASK=1.0.4
- FLASK=0.12.5
install:
- pip install Flask==$FLASK
- pip install -r dev_requirements.txt
......
The MIT License (MIT)
Copyright (c) 2019 Alexandre Ferland
Copyright (c) 2022 Alexandre Ferland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
......
.PHONY: help dev clean update test lint pre-commit
VENV_NAME?=venv
VENV_ACTIVATE=. $(VENV_NAME)/bin/activate
PYTHON=${VENV_NAME}/bin/python3
.DEFAULT: help
help:
@echo "make dev"
@echo " prepare development environment, use only once"
@echo "make clean"
@echo " delete development environment"
@echo "make update"
@echo " update dependencies"
@echo "make test"
@echo " run tests"
@echo "make lint"
@echo " run black"
@echo "make pre-commit"
@echo " run pre-commit hooks"
dev:
make venv
venv: $(VENV_NAME)/bin/activate
$(VENV_NAME)/bin/activate:
test -d $(VENV_NAME) || virtualenv -p python3 $(VENV_NAME)
${PYTHON} -m pip install -U pip
${PYTHON} -m pip install -r dev_requirements.txt
$(VENV_NAME)/bin/pre-commit install
touch $(VENV_NAME)/bin/activate
clean:
rm -rf venv
update:
${PYTHON} -m pip install -U -r dev_requirements.txt
$(VENV_NAME)/bin/pre-commit install
test: venv
${PYTHON} -m pytest
lint: venv
$(VENV_NAME)/bin/black -t py310 --exclude $(VENV_NAME) .
pre-commit: venv
$(VENV_NAME)/bin/pre-commit
Flask-SimpleLDAP
Flask-SimpleLDAP [![Build Status](https://app.travis-ci.com/alexferl/flask-simpleldap.svg?branch=master)](https://app.travis-ci.com/alexferl/flask-simpleldap)
================
[![Build Status](https://travis-ci.com/alexferl/flask-simpleldap.svg?branch=master)](https://travis-ci.com/alexferl/flask-simpleldap)
Flask-SimpleLDAP provides LDAP authentication for Flask.
Flask-SimpleLDAP is compatible with and tested on Python 3.5, 3.6 and 3.7.
Flask-SimpleLDAP is compatible with and tested on Python 3.7+.
Quickstart
----------
First, install Flask-SimpleLDAP:
$ pip install flask-simpleldap
```shell
pip install flask-simpleldap
```
Flask-SimpleLDAP depends, and will install for you, recent versions of Flask
(0.12.4 or later) and [python-ldap](https://python-ldap.org/).
Please consult the [python-ldap installation instructions](https://www.python-ldap.org/en/latest/installing.html) if you get an error during installation.
......@@ -27,19 +28,19 @@ from flask import Flask, g
from flask_simpleldap import LDAP
app = Flask(__name__)
#app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost
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'
# app.config["LDAP_HOST"] = "ldap.example.org" # defaults to localhost
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('/')
@app.route("/")
@ldap.basic_auth_required
def index():
return 'Welcome, {0}!'.format(g.ldap_username)
return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
```
......@@ -54,8 +55,8 @@ Once you get the basic example working, check out the more complex ones:
* [examples/groups](examples/groups) demostrates using:
* `@ldap.login_required` for form/cookie-based auth, instead of basic HTTP authentication.
* `@ldap.group_required()` to restrict access to pages based on the user's LDAP groups.
* [examples/blueprints](examples/blueprints) implements the same functionality, but uses Flask's
[application factories](http://flask.pocoo.org/docs/patterns/appfactories/)
* [examples/blueprints](examples/blueprints) implements the same functionality, but uses Flask's
[application factories](http://flask.pocoo.org/docs/patterns/appfactories/)
and [blueprints](http://flask.pocoo.org/docs/blueprints/).
......@@ -63,7 +64,7 @@ OpenLDAP
--------
Add the ``LDAP`` instance to your code and depending on your OpenLDAP
configuration, add the following at least LDAP_USER_OBJECT_FILTER and
configuration, add the following at least LDAP_USER_OBJECT_FILTER and
LDAP_USER_OBJECT_FILTER.
```python
......@@ -73,31 +74,31 @@ from flask_simpleldap import LDAP
app = Flask(__name__)
# Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication'
app.config['LDAP_HOST'] = 'openldap.example.org'
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=org'
app.config['LDAP_USERNAME'] = 'cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org'
app.config['LDAP_PASSWORD'] = 'password'
app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config["LDAP_HOST"] = "openldap.example.org"
app.config["LDAP_BASE_DN"] = "dc=users,dc=openldap,dc=org"
app.config["LDAP_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config["LDAP_PASSWORD"] = "password"
# OpenLDAP
app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_OPENLDAP'] = True
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))'
# OpenLDAP
app.config["LDAP_OBJECTS_DN"] = "dn"
app.config["LDAP_OPENLDAP"] = True
app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Groups
app.config['LDAP_GROUP_MEMBERS_FIELD'] = "uniquemember"
app.config['LDAP_GROUP_OBJECT_FILTER'] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config['LDAP_GROUP_MEMBER_FILTER'] = "(&(cn=*)(objectclass=groupOfUniqueNames)(uniquemember=%s))"
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn"
app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config["LDAP_GROUP_OBJECT_FILTER"] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER"] = "(&(cn=*)(objectclass=groupOfUniqueNames)(uniquemember=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app)
@app.route('/')
@app.route("/")
@ldap.basic_auth_required
def index():
return 'Welcome, {0}!'.format(g.ldap_username)
return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
```
......
black==22.1.0
pre-commit==2.17.0
python-ldap==3.2.0 # here instead of requirements.txt so rtfd can build
Sphinx==2.1.2
......
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
from pygments.token import (
Keyword,
Name,
Comment,
String,
Error,
Number,
Operator,
Generic,
Whitespace,
Punctuation,
Other,
Literal,
)
class FlaskyStyle(Style):
......@@ -10,77 +22,68 @@ class FlaskyStyle(Style):
styles = {
# No corresponding class for the following:
#Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd'
Keyword.Namespace: "bold #004461", # class: 'kn'
Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables.
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
Name: "#000000", # class: 'n'
Name.Attribute: "#c4a000", # class: 'na' - to be revised
Name.Builtin: "#004461", # class: 'nb'
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
Name.Class: "#000000", # class: 'nc' - to be revised
Name.Constant: "#000000", # class: 'no' - to be revised
Name.Decorator: "#888", # class: 'nd' - to be revised
Name.Entity: "#ce5c00", # class: 'ni'
Name.Exception: "bold #cc0000", # class: 'ne'
Name.Function: "#000000", # class: 'nf'
Name.Property: "#000000", # class: 'py'
Name.Label: "#f57900", # class: 'nl'
Name.Namespace: "#000000", # class: 'nn' - to be revised
Name.Other: "#000000", # class: 'nx'
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
Name.Variable: "#000000", # class: 'nv' - to be revised
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
String.Interpol: "#4e9a06", # class: 'si'
String.Other: "#4e9a06", # class: 'sx'
String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Generic.Output: "#888", # class: 'go'
Generic.Prompt: "#745334", # class: 'gp'
Generic.Strong: "bold #000000", # class: 'gs'
Generic.Subheading: "bold #800080", # class: 'gu'
Generic.Traceback: "bold #a40000", # class: 'gt'
}
......@@ -20,214 +20,218 @@ from mock import Mock as MagicMock
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return Mock()
return Mock()
MOCK_MODULES = ['ldap']
MOCK_MODULES = ["ldap"]
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
sys.path.insert(0, os.path.abspath(".."))
# -- General configuration -----------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = '.rst'
source_suffix = ".rst"
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = u'Flask-SimpleLDAP'
copyright = u'2019, Alexandre Ferland'
project = "Flask-SimpleLDAP"
copyright = "2022, Alexandre Ferland"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.4.0'
version = "1.4.0"
# The full version, including alpha/beta/rc tags.
release = '1.4.0'
release = "1.4.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# -- Options for HTML output ----------------------------------------------
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
html_theme = 'flask_small'
sys.path.append(os.path.abspath("_themes"))
html_theme_path = ["_themes"]
html_theme = "flask_small"
html_theme_options = {
'index_logo': '', #TODO
'github_fork': 'admiralobvious/flask-simpleldap',
}
"index_logo": "", # TODO
"github_fork": "alexferl/flask-simpleldap",
}
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_static_path = ["_static"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Flask-SimpleLDAPdoc'
htmlhelp_basename = "Flask-SimpleLDAPdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'Flask-SimpleLDAP.tex', u'Flask-SimpleLDAP Documentation',
u'Alexandre Ferland', 'manual'),
(
"index",
"Flask-SimpleLDAP.tex",
"Flask-SimpleLDAP Documentation",
"Alexandre Ferland",
"manual",
),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
......@@ -235,12 +239,17 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'flask-simpleldap', u'Flask-SimpleLDAP Documentation',
[u'Alexandre Ferland'], 1)
(
"index",
"flask-simpleldap",
"Flask-SimpleLDAP Documentation",
["Alexandre Ferland"],
1,
)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
......@@ -249,23 +258,29 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Flask-SimpleLDAP', u'Flask-SimpleLDAP Documentation',
u'Alexandre Ferland', 'Flask-SimpleLDAP', 'One line description of project.',
'Miscellaneous'),
(
"index",
"Flask-SimpleLDAP",
"Flask-SimpleLDAP Documentation",
"Alexandre Ferland",
"Flask-SimpleLDAP",
"One line description of project.",
"Miscellaneous",
),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
intersphinx_mapping = {"http://docs.python.org/": None}
......@@ -14,37 +14,38 @@ Quickstart
First, install Flask-SimpleLDAP:
.. code-block:: bash
.. code-block:: bash
$ pip install flask-simpleldap
pip install flask-simpleldap
Flask-SimpleLDAP depends, and will install for you, recent versions of Flask
(0.12.4 or later) and pyldap. Flask-SimpleLDAP is compatible
with and tested on Python 3.5, 3.6 and 3.7.
with and tested on Python 3.7+.
Next, add a :class:`~flask_simpleldap.LDAP` to your code and at least the three
required configuration options:
.. code-block:: python
.. code-block:: python
from flask import Flask
from flask_simpleldap import LDAP
from flask import Flask
from flask_simpleldap import LDAP
app = Flask(__name__)
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'
app = Flask(__name__)
# app.config["LDAP_HOST"] = "ldap.example.org" # defaults to localhost
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)
ldap = LDAP(app)
@app.route('/ldap')
@ldap.login_required
def ldap_protected():
return 'Success!'
if __name__ == '__main__':
app.run()
@app.route("/ldap")
@ldap.login_required
def ldap_protected():
return "Success!"
if __name__ == "__main__":
app.run()
Configuration
......@@ -122,40 +123,62 @@ History
Changes:
- 1.4.0 July 16, 2019
- This release drops support for `Python 2.7 <https://pythonclock.org/>`_. If you're still on Python 2.7, you can use `v1.3.3 <https://github.com/alexferl/flask-simpleldap/releases/tag/v1.3.3>`_.
- Fixes:
- `#62 <https://github.com/admiralobvious/flask-simpleldap/issues/62>`_ get_object_details returning None
- 1.3.0 July 14, 2019
- Thanks to the contributors, this release fixes issues related to bind_user and fixes some issues related to filtering.
`#51 <https://github.com/admiralobvious/flask-simpleldap/pull/51>`_ Referral chasing crash
`#54 <https://github.com/admiralobvious/flask-simpleldap/pull/54>`_ Fixes #44 - Error in bind_user method, also fixes #60 and #61
`#56 <https://github.com/admiralobvious/flask-simpleldap/pull/56>`_ OpenLDAP section has Incorrect LDAP_GROUP_OBJECT_FILTER
`#57 <https://github.com/admiralobvious/flask-simpleldap/pull/57>`_ next vaule: Priority use request.full_path
`#59 <https://github.com/admiralobvious/flask-simpleldap/pull/59>`_ get_object_details to take query_filter and fallback to LDAP_USER_OBJECT_FILTER or LDAP_GROUP_OBJECT_FILTER
- `#51 <https://github.com/admiralobvious/flask-simpleldap/pull/51>`_ Referral chasing crash
- `#54 <https://github.com/admiralobvious/flask-simpleldap/pull/54>`_ Fixes #44 - Error in bind_user method, also fixes #60 and #61
- `#56 <https://github.com/admiralobvious/flask-simpleldap/pull/56>`_ OpenLDAP section has Incorrect LDAP_GROUP_OBJECT_FILTER
- `#57 <https://github.com/admiralobvious/flask-simpleldap/pull/57>`_ next vaule: Priority use request.full_path
- `#59 <https://github.com/admiralobvious/flask-simpleldap/pull/59>`_ get_object_details to take query_filter and fallback to LDAP_USER_OBJECT_FILTER or LDAP_GROUP_OBJECT_FILTER
- 1.2.0 September 26, 2017
- Changed get_group_members() and get_user_groups() returning strings instead of bytes in PY3.
- 1.1.2 July 17, 2017
- Merge GitHub PR `#30 <https://github.com/admiralobvious/flask-simpleldap/pull/30>`_,
Fix for python3
- Fix decoding bytes in PY3 for @ldap.group_required.
- 1.1.1 April 10, 2017
- Merge GitHub pull `#26 <https://github.com/admiralobvious/flask-simpleldap/pull/26>`_,
Fix set_option call to LDAP for SSL CERT
- 1.1.0 June 7, 2016
- Add the ability the pass any valid pyldap config options via the LDAP_CUSTOM_OPTIONS configuration directive.
- 1.0.1 June 5, 2016
- Fix ldap filter import.
- 1.0.0 June 4, 2016
- Python 3.x support. Switched from python-ldap to pyldap which is a fork with Python 3.x support.
- 0.4.0: September 5, 2015
- Added support for OpenLDAP directories. Thanks to `@jm66 <https://github.com/jm66>`_ on GitHub.
- 0.3.0: January 21, 2015
- Fix Github issue `#10 <https://github.com/admiralobvious/flask-simpleldap/issues/10>`_,
Redirect users back to the page they originally requested after authenticating
......@@ -163,14 +186,17 @@ Changes:
Only trust .bind_user() with a non-empty password
- 0.2.0: December 7, 2014
- Added HTTP Basic Authentication. Thanks to `@OptiverTimAll <https://github.com/optivertimall>`_ on GitHub.
- Fix GitHub issue `#4 <https://github.com/admiralobvious/flask-simpleldap/issues/4>`_,
User or group queries are vulnerable to LDAP injection.
Make sure you update your filters to use '%s' instead of the old '{}'!
- 0.1.1: September 6, 2014
- Fix GitHub issue `#3 <https://github.com/admiralobvious/flask-simpleldap/issues/3>`_,
Not compatible with uppercase distinguished names.
- 0.1: August 9, 2014
- Initial Release
......@@ -2,17 +2,19 @@ from flask import Flask, g
from flask_simpleldap import LDAP
app = Flask(__name__)
#app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost
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'
# app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost
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('/')
@app.route("/")
@ldap.basic_auth_required
def index():
return 'Welcome, {0}!'.format(g.ldap_username)
return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
......@@ -4,31 +4,35 @@ from flask_simpleldap import LDAP
app = Flask(__name__)
# Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication'
app.config['LDAP_HOST'] = 'openldap.example.org'
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=org'
app.config['LDAP_USERNAME'] = 'cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org'
app.config['LDAP_PASSWORD'] = 'password'
app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config["LDAP_HOST"] = "openldap.example.org"
app.config["LDAP_BASE_DN"] = "dc=users,dc=openldap,dc=org"
app.config["LDAP_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config["LDAP_PASSWORD"] = "password"
# OpenLDAP
app.config['LDAP_OPENLDAP'] = True
app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))'
app.config["LDAP_OPENLDAP"] = True
app.config["LDAP_OBJECTS_DN"] = "dn"
app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Groups configuration
app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember'
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))'
app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames'
app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description']
app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))'
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn"
app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config["LDAP_GROUP_OBJECT_FILTER"] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config["LDAP_GROUPS_OBJECT_FILTER"] = "objectclass=groupOfUniqueNames"
app.config["LDAP_GROUP_FIELDS"] = ["cn", "entryDN", "member", "description"]
app.config[
"LDAP_GROUP_MEMBER_FILTER"
] = "(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app)
@app.route('/')
@app.route("/")
@ldap.basic_auth_required
def index():
return 'Welcome, {0}!'.format(g.ldap_username)
return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
......@@ -5,10 +5,7 @@ from .extensions import ldap
from .core import core
from .foo import foo
DEFAULT_BLUEPRINTS = (
core,
foo
)
DEFAULT_BLUEPRINTS = (core, foo)
def create_app(config=None, app_name=None, blueprints=None):
......@@ -33,11 +30,11 @@ def register_hooks(app):
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
if "user_id" in session:
# This is where you'd query your database to get the user info.
g.user = {}
# Create a global with the LDAP groups the user is a member of.
g.ldap_groups = ldap.get_user_groups(user=session['user_id'])
g.ldap_groups = ldap.get_user_groups(user=session["user_id"])
def register_blueprints(app, blueprints):
......
......@@ -2,14 +2,14 @@ import ldap
class BaseConfig(object):
PROJECT = 'foo'
SECRET_KEY = 'dev key'
PROJECT = "foo"
SECRET_KEY = "dev key"
DEBUG = True
# LDAP
LDAP_HOST = 'ldap.example.org'
LDAP_BASE_DN = 'OU=users,dc=example,dc=org'
LDAP_USERNAME = 'CN=user,OU=Users,DC=example,DC=org'
LDAP_PASSWORD = 'password'
LDAP_LOGIN_VIEW = 'core.login'
LDAP_HOST = "ldap.example.org"
LDAP_BASE_DN = "OU=users,dc=example,dc=org"
LDAP_USERNAME = "CN=user,OU=Users,DC=example,DC=org"
LDAP_PASSWORD = "password"
LDAP_LOGIN_VIEW = "core.login"
LDAP_CUSTOM_OPTIONS = {ldap.OPT_REFERRALS: 0}
from flask import Blueprint, g, request, session, redirect, url_for
from ..extensions import ldap
core = Blueprint('core', __name__)
core = Blueprint("core", __name__)
@core.route('/')
@core.route("/")
@ldap.login_required
def index():
return 'Successfully logged in!'
return "Successfully logged in!"
@core.route('/login', methods=['GET', 'POST'])
@core.route("/login", methods=["GET", "POST"])
def login():
if g.user:
return redirect(url_for('index'))
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
return redirect(url_for("index"))
if request.method == "POST":
user = request.form["user"]
passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
return 'Invalid credentials'
if test is None or passwd == "":
return "Invalid credentials"
else:
session['user_id'] = request.form['user']
return redirect('/')
session["user_id"] = request.form["user"]
return redirect("/")
return """<form action="" method="post">
user: <input name="user"><br>
password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>"""
@core.route('/group')
@ldap.group_required(groups=['Web Developers', 'QA'])
@core.route("/group")
@ldap.group_required(groups=["Web Developers", "QA"])
def group():
return 'Group restricted page'
return "Group restricted page"
@core.route('/logout')
@core.route("/logout")
def logout():
session.pop('user_id', None)
return redirect(url_for('index'))
session.pop("user_id", None)
return redirect(url_for("index"))
from flask_simpleldap import LDAP
ldap = LDAP()
from flask import Blueprint
from ..extensions import ldap
foo = Blueprint('foo', __name__, url_prefix='/foo')
foo = Blueprint("foo", __name__, url_prefix="/foo")
@foo.route('/group')
@ldap.group_required(groups=['Web Developers', 'QA'])
@foo.route("/group")
@ldap.group_required(groups=["Web Developers", "QA"])
def group():
return 'Group restricted page in foo module'
return "Group restricted page in foo module"
......@@ -3,14 +3,14 @@ from flask import Flask, g, request, session, redirect, url_for
from flask_simpleldap import LDAP
app = Flask(__name__)
app.secret_key = 'dev key'
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'
app.config['LDAP_CUSTOM_OPTIONS'] = {l.OPT_REFERRALS: 0}
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"
app.config["LDAP_CUSTOM_OPTIONS"] = {l.OPT_REFERRALS: 0}
ldap = LDAP(app)
......@@ -18,49 +18,49 @@ ldap = LDAP(app)
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
if "user_id" in session:
# This is where you'd query your database to get the user info.
g.user = {}
# Create a global with the LDAP groups the user is a member of.
g.ldap_groups = ldap.get_user_groups(user=session['user_id'])
g.ldap_groups = ldap.get_user_groups(user=session["user_id"])
@app.route('/')
@app.route("/")
@ldap.login_required
def index():
return 'Successfully logged in!'
return "Successfully logged in!"
@app.route('/login', methods=['GET', 'POST'])
@app.route("/login", methods=["GET", "POST"])
def login():
if g.user:
return redirect(url_for('index'))
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
return redirect(url_for("index"))
if request.method == "POST":
user = request.form["user"]
passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
return 'Invalid credentials'
if test is None or passwd == "":
return "Invalid credentials"
else:
session['user_id'] = request.form['user']
return redirect('/')
session["user_id"] = request.form["user"]
return redirect("/")
return """<form action="" method="post">
user: <input name="user"><br>
password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>"""
@app.route('/group')
@ldap.group_required(groups=['Web Developers', 'QA'])
@app.route("/group")
@ldap.group_required(groups=["Web Developers", "QA"])
def group():
return 'Group restricted page'
return "Group restricted page"
@app.route('/logout')
@app.route("/logout")
def logout():
session.pop('user_id', None)
return redirect(url_for('index'))
session.pop("user_id", None)
return redirect(url_for("index"))
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
......@@ -2,25 +2,27 @@ from flask import Flask, g, request, session, redirect, url_for
from flask_simpleldap import LDAP
app = Flask(__name__)
app.secret_key = 'dev key'
app.secret_key = "dev key"
app.debug = True
app.config['LDAP_OPENLDAP'] = True
app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication'
app.config['LDAP_HOST'] = 'openldap.example.org'
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=org'
app.config['LDAP_USERNAME'] = 'cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org'
app.config['LDAP_PASSWORD'] = 'password'
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))'
app.config["LDAP_OPENLDAP"] = True
app.config["LDAP_OBJECTS_DN"] = "dn"
app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config["LDAP_HOST"] = "openldap.example.org"
app.config["LDAP_BASE_DN"] = "dc=users,dc=openldap,dc=org"
app.config["LDAP_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config["LDAP_PASSWORD"] = "password"
app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Group configuration
app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember'
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))'
app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames'
app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description']
app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))'
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn"
app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config["LDAP_GROUP_OBJECT_FILTER"] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config["LDAP_GROUPS_OBJECT_FILTER"] = "objectclass=groupOfUniqueNames"
app.config["LDAP_GROUP_FIELDS"] = ["cn", "entryDN", "member", "description"]
app.config[
"LDAP_GROUP_MEMBER_FILTER"
] = "(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app)
......@@ -28,49 +30,49 @@ ldap = LDAP(app)
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
if "user_id" in session:
# This is where you'd query your database to get the user info.
g.user = {}
# Create a global with the LDAP groups the user is a member of.
g.ldap_groups = ldap.get_user_groups(user=session['user_id'])
g.ldap_groups = ldap.get_user_groups(user=session["user_id"])
@app.route('/')
@app.route("/")
@ldap.login_required
def index():
return 'Successfully logged in!'
return "Successfully logged in!"
@app.route('/login', methods=['GET', 'POST'])
@app.route("/login", methods=["GET", "POST"])
def login():
if g.user:
return redirect(url_for('index'))
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
return redirect(url_for("index"))
if request.method == "POST":
user = request.form["user"]
passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
return 'Invalid credentials'
if test is None or passwd == "":
return "Invalid credentials"
else:
session['user_id'] = request.form['user']
return redirect('/')
session["user_id"] = request.form["user"]
return redirect("/")
return """<form action="" method="post">
user: <input name="user"><br>
password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>"""
@app.route('/group')
@ldap.group_required(groups=['web-developers'])
@app.route("/group")
@ldap.group_required(groups=["web-developers"])
def group():
return 'Group restricted page'
return "Group restricted page"
@app.route('/logout')
@app.route("/logout")
def logout():
session.pop('user_id', None)
return redirect(url_for('index'))
session.pop("user_id", None)
return redirect(url_for("index"))
if __name__ == '__main__':
if __name__ == "__main__":
app.run()
......@@ -2,10 +2,9 @@ import re
from functools import wraps
import ldap
from ldap import filter as ldap_filter
from flask import abort, current_app, g, make_response, redirect, url_for, \
request
from flask import abort, current_app, g, make_response, redirect, url_for, request
__all__ = ['LDAP']
__all__ = ["LDAP"]
class LDAPException(RuntimeError):
......@@ -32,51 +31,50 @@ class LDAP(object):
:param flask.Flask app: the application to configure for use with
this :class:`~LDAP`
"""
app.config.setdefault('LDAP_HOST', 'localhost')
app.config.setdefault('LDAP_PORT', 389)
app.config.setdefault('LDAP_SCHEMA', 'ldap')
app.config.setdefault('LDAP_USERNAME', None)
app.config.setdefault('LDAP_PASSWORD', None)
app.config.setdefault('LDAP_TIMEOUT', 10)
app.config.setdefault('LDAP_USE_SSL', False)
app.config.setdefault('LDAP_USE_TLS', False)
app.config.setdefault('LDAP_REQUIRE_CERT', False)
app.config.setdefault('LDAP_CERT_PATH', '/path/to/cert')
app.config.setdefault('LDAP_BASE_DN', None)
app.config.setdefault('LDAP_OBJECTS_DN', 'distinguishedName')
app.config.setdefault('LDAP_USER_FIELDS', [])
app.config.setdefault('LDAP_USER_OBJECT_FILTER',
'(&(objectclass=Person)(userPrincipalName=%s))')
app.config.setdefault('LDAP_USER_GROUPS_FIELD', 'memberOf')
app.config.setdefault('LDAP_GROUP_FIELDS', [])
app.config.setdefault('LDAP_GROUPS_OBJECT_FILTER', 'objectclass=Group')
app.config.setdefault('LDAP_GROUP_OBJECT_FILTER',
'(&(objectclass=Group)(userPrincipalName=%s))')
app.config.setdefault('LDAP_GROUP_MEMBERS_FIELD', 'member')
app.config.setdefault('LDAP_LOGIN_VIEW', 'login')
app.config.setdefault('LDAP_REALM_NAME', 'LDAP authentication')
app.config.setdefault('LDAP_OPENLDAP', False)
app.config.setdefault('LDAP_GROUP_MEMBER_FILTER', '*')
app.config.setdefault('LDAP_GROUP_MEMBER_FILTER_FIELD', '*')
app.config.setdefault('LDAP_CUSTOM_OPTIONS', None)
if app.config['LDAP_USE_SSL'] or app.config['LDAP_USE_TLS']:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
ldap.OPT_X_TLS_NEVER)
if app.config['LDAP_REQUIRE_CERT']:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
ldap.OPT_X_TLS_DEMAND)
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,
app.config['LDAP_CERT_PATH'])
for option in ['USERNAME', 'PASSWORD', 'BASE_DN']:
if app.config['LDAP_{0}'.format(option)] is None:
raise LDAPException('LDAP_{0} cannot be None!'.format(option))
app.config.setdefault("LDAP_HOST", "localhost")
app.config.setdefault("LDAP_PORT", 389)
app.config.setdefault("LDAP_SCHEMA", "ldap")
app.config.setdefault("LDAP_USERNAME", None)
app.config.setdefault("LDAP_PASSWORD", None)
app.config.setdefault("LDAP_TIMEOUT", 10)
app.config.setdefault("LDAP_USE_SSL", False)
app.config.setdefault("LDAP_USE_TLS", False)
app.config.setdefault("LDAP_REQUIRE_CERT", False)
app.config.setdefault("LDAP_CERT_PATH", "/path/to/cert")
app.config.setdefault("LDAP_BASE_DN", None)
app.config.setdefault("LDAP_OBJECTS_DN", "distinguishedName")
app.config.setdefault("LDAP_USER_FIELDS", [])
app.config.setdefault(
"LDAP_USER_OBJECT_FILTER", "(&(objectclass=Person)(userPrincipalName=%s))"
)
app.config.setdefault("LDAP_USER_GROUPS_FIELD", "memberOf")
app.config.setdefault("LDAP_GROUP_FIELDS", [])
app.config.setdefault("LDAP_GROUPS_OBJECT_FILTER", "objectclass=Group")
app.config.setdefault(
"LDAP_GROUP_OBJECT_FILTER", "(&(objectclass=Group)(userPrincipalName=%s))"
)
app.config.setdefault("LDAP_GROUP_MEMBERS_FIELD", "member")
app.config.setdefault("LDAP_LOGIN_VIEW", "login")
app.config.setdefault("LDAP_REALM_NAME", "LDAP authentication")
app.config.setdefault("LDAP_OPENLDAP", False)
app.config.setdefault("LDAP_GROUP_MEMBER_FILTER", "*")
app.config.setdefault("LDAP_GROUP_MEMBER_FILTER_FIELD", "*")
app.config.setdefault("LDAP_CUSTOM_OPTIONS", None)
if app.config["LDAP_USE_SSL"] or app.config["LDAP_USE_TLS"]:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
if app.config["LDAP_REQUIRE_CERT"]:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, app.config["LDAP_CERT_PATH"])
for option in ["USERNAME", "PASSWORD", "BASE_DN"]:
if app.config["LDAP_{0}".format(option)] is None:
raise LDAPException("LDAP_{0} cannot be None!".format(option))
@staticmethod
def _set_custom_options(conn):
options = current_app.config['LDAP_CUSTOM_OPTIONS']
options = current_app.config["LDAP_CUSTOM_OPTIONS"]
if options:
for k, v in options.items():
conn.set_option(k, v)
......@@ -90,15 +88,19 @@ class LDAP(object):
"""
try:
conn = ldap.initialize('{0}://{1}:{2}'.format(
current_app.config['LDAP_SCHEMA'],
current_app.config['LDAP_HOST'],
current_app.config['LDAP_PORT']))
conn.set_option(ldap.OPT_NETWORK_TIMEOUT,
current_app.config['LDAP_TIMEOUT'])
conn = ldap.initialize(
"{0}://{1}:{2}".format(
current_app.config["LDAP_SCHEMA"],
current_app.config["LDAP_HOST"],
current_app.config["LDAP_PORT"],
)
)
conn.set_option(
ldap.OPT_NETWORK_TIMEOUT, current_app.config["LDAP_TIMEOUT"]
)
conn = self._set_custom_options(conn)
conn.protocol_version = ldap.VERSION3
if current_app.config['LDAP_USE_TLS']:
if current_app.config["LDAP_USE_TLS"]:
conn.start_tls_s()
return conn
except ldap.LDAPError as e:
......@@ -116,8 +118,8 @@ class LDAP(object):
conn = self.initialize
try:
conn.simple_bind_s(
current_app.config['LDAP_USERNAME'],
current_app.config['LDAP_PASSWORD'])
current_app.config["LDAP_USERNAME"], current_app.config["LDAP_PASSWORD"]
)
return conn
except ldap.LDAPError as e:
raise LDAPException(self.error(e.args))
......@@ -148,15 +150,17 @@ class LDAP(object):
return
try:
conn = self.initialize
_user_dn = user_dn.decode('utf-8') \
if isinstance(user_dn, bytes) else user_dn
_user_dn = (
user_dn.decode("utf-8") if isinstance(user_dn, bytes) else user_dn
)
conn.simple_bind_s(_user_dn, password)
return True
except ldap.LDAPError:
return
def get_object_details(self, user=None, group=None, query_filter=None,
dn_only=False):
def get_object_details(
self, user=None, group=None, query_filter=None, dn_only=False
):
"""Returns a ``dict`` with the object's (user or group) details.
:param str user: Username of the user object you want details for.
......@@ -169,33 +173,35 @@ class LDAP(object):
fields = None
if user is not None:
if not dn_only:
fields = current_app.config['LDAP_USER_FIELDS']
query_filter = query_filter or \
current_app.config['LDAP_USER_OBJECT_FILTER']
fields = current_app.config["LDAP_USER_FIELDS"]
query_filter = query_filter or current_app.config["LDAP_USER_OBJECT_FILTER"]
query = ldap_filter.filter_format(query_filter, (user,))
elif group is not None:
if not dn_only:
fields = current_app.config['LDAP_GROUP_FIELDS']
query_filter = query_filter or \
current_app.config['LDAP_GROUP_OBJECT_FILTER']
fields = current_app.config["LDAP_GROUP_FIELDS"]
query_filter = (
query_filter or current_app.config["LDAP_GROUP_OBJECT_FILTER"]
)
query = ldap_filter.filter_format(query_filter, (group,))
conn = self.bind
try:
records = conn.search_s(current_app.config['LDAP_BASE_DN'],
ldap.SCOPE_SUBTREE, query, fields)
records = conn.search_s(
current_app.config["LDAP_BASE_DN"], ldap.SCOPE_SUBTREE, query, fields
)
conn.unbind_s()
result = {}
if records and\
records[0][0] is not None and isinstance(records[0][1], dict):
if (
records
and records[0][0] is not None
and isinstance(records[0][1], dict)
):
if dn_only:
if current_app.config['LDAP_OPENLDAP']:
if current_app.config["LDAP_OPENLDAP"]:
if records:
return records[0][0]
else:
if current_app.config['LDAP_OBJECTS_DN'] \
in records[0][1]:
dn = records[0][1][
current_app.config['LDAP_OBJECTS_DN']]
if current_app.config["LDAP_OBJECTS_DN"] in records[0][1]:
dn = records[0][1][current_app.config["LDAP_OBJECTS_DN"]]
return dn[0]
for k, v in list(records[0][1].items()):
result[k] = v
......@@ -217,17 +223,21 @@ class LDAP(object):
"""
conn = self.bind
try:
fields = fields or current_app.config['LDAP_GROUP_FIELDS']
if current_app.config['LDAP_OPENLDAP']:
fields = fields or current_app.config["LDAP_GROUP_FIELDS"]
if current_app.config["LDAP_OPENLDAP"]:
records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
current_app.config['LDAP_GROUPS_OBJECT_FILTER'],
fields)
current_app.config["LDAP_BASE_DN"],
ldap.SCOPE_SUBTREE,
current_app.config["LDAP_GROUPS_OBJECT_FILTER"],
fields,
)
else:
records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
current_app.config['LDAP_GROUPS_OBJECT_FILTER'],
fields)
current_app.config["LDAP_BASE_DN"],
ldap.SCOPE_SUBTREE,
current_app.config["LDAP_GROUPS_OBJECT_FILTER"],
fields,
)
conn.unbind_s()
if records:
if dn_only:
......@@ -248,42 +258,51 @@ class LDAP(object):
conn = self.bind
try:
if current_app.config['LDAP_OPENLDAP']:
fields = \
[str(current_app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'])]
if current_app.config["LDAP_OPENLDAP"]:
fields = [str(current_app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"])]
records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
current_app.config["LDAP_BASE_DN"],
ldap.SCOPE_SUBTREE,
ldap_filter.filter_format(
current_app.config['LDAP_GROUP_MEMBER_FILTER'],
(self.get_object_details(user, dn_only=True),)),
fields)
current_app.config["LDAP_GROUP_MEMBER_FILTER"],
(self.get_object_details(user, dn_only=True),),
),
fields,
)
else:
records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
current_app.config["LDAP_BASE_DN"],
ldap.SCOPE_SUBTREE,
ldap_filter.filter_format(
current_app.config['LDAP_USER_OBJECT_FILTER'],
(user,)),
[current_app.config['LDAP_USER_GROUPS_FIELD']])
current_app.config["LDAP_USER_OBJECT_FILTER"], (user,)
),
[current_app.config["LDAP_USER_GROUPS_FIELD"]],
)
conn.unbind_s()
if records:
if current_app.config['LDAP_OPENLDAP']:
group_member_filter = \
current_app.config['LDAP_GROUP_MEMBER_FILTER_FIELD']
if current_app.config["LDAP_OPENLDAP"]:
group_member_filter = current_app.config[
"LDAP_GROUP_MEMBER_FILTER_FIELD"
]
record_list = [record[1] for record in records]
record_dicts = [
record for record in record_list if isinstance(record, dict)]
groups = [item.get([group_member_filter][0])[0]
for item in record_dicts]
record for record in record_list if isinstance(record, dict)
]
groups = [
item.get([group_member_filter][0])[0] for item in record_dicts
]
return groups
else:
if current_app.config['LDAP_USER_GROUPS_FIELD'] in \
records[0][1]:
if current_app.config["LDAP_USER_GROUPS_FIELD"] in records[0][1]:
groups = records[0][1][
current_app.config['LDAP_USER_GROUPS_FIELD']]
result = [re.findall(b'(?:cn=|CN=)(.*?),', group)[0]
for group in groups]
result = [r.decode('utf-8') for r in result]
current_app.config["LDAP_USER_GROUPS_FIELD"]
]
result = [
re.findall(b"(?:cn=|CN=)(.*?),", group)[0]
for group in groups
]
result = [r.decode("utf-8") for r in result]
return result
except ldap.LDAPError as e:
raise LDAPException(self.error(e.args))
......@@ -298,17 +317,20 @@ class LDAP(object):
conn = self.bind
try:
records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE,
current_app.config["LDAP_BASE_DN"],
ldap.SCOPE_SUBTREE,
ldap_filter.filter_format(
current_app.config['LDAP_GROUP_OBJECT_FILTER'], (group,)),
[current_app.config['LDAP_GROUP_MEMBERS_FIELD']])
current_app.config["LDAP_GROUP_OBJECT_FILTER"], (group,)
),
[current_app.config["LDAP_GROUP_MEMBERS_FIELD"]],
)
conn.unbind_s()
if records:
if current_app.config['LDAP_GROUP_MEMBERS_FIELD'] in \
records[0][1]:
if current_app.config["LDAP_GROUP_MEMBERS_FIELD"] in records[0][1]:
members = records[0][1][
current_app.config['LDAP_GROUP_MEMBERS_FIELD']]
members = [m.decode('utf-8') for m in members]
current_app.config["LDAP_GROUP_MEMBERS_FIELD"]
]
members = [m.decode("utf-8") for m in members]
return members
except ldap.LDAPError as e:
raise LDAPException(self.error(e.args))
......@@ -316,8 +338,8 @@ class LDAP(object):
@staticmethod
def error(e):
e = e[0]
if 'desc' in e:
return e['desc']
if "desc" in e:
return e["desc"]
else:
return e
......@@ -337,12 +359,12 @@ class LDAP(object):
@wraps(func)
def wrapped(*args, **kwargs):
if g.user is None:
next_path=request.full_path or request.path
if next_path == '/?':
return redirect(
url_for(current_app.config['LDAP_LOGIN_VIEW']))
return redirect(url_for(current_app.config['LDAP_LOGIN_VIEW'],
next=next_path))
next_path = request.full_path or request.path
if next_path == "/?":
return redirect(url_for(current_app.config["LDAP_LOGIN_VIEW"]))
return redirect(
url_for(current_app.config["LDAP_LOGIN_VIEW"], next=next_path)
)
return func(*args, **kwargs)
return wrapped
......@@ -367,8 +389,11 @@ class LDAP(object):
def wrapped(*args, **kwargs):
if g.user is None:
return redirect(
url_for(current_app.config['LDAP_LOGIN_VIEW'],
next=request.full_path or request.path))
url_for(
current_app.config["LDAP_LOGIN_VIEW"],
next=request.full_path or request.path,
)
)
match = [group for group in groups if group in g.ldap_groups]
if not match:
abort(403)
......@@ -395,9 +420,8 @@ class LDAP(object):
"""
def make_auth_required_response():
response = make_response('Unauthorized', 401)
response.www_authenticate.set_basic(
current_app.config['LDAP_REALM_NAME'])
response = make_response("Unauthorized", 401)
response.www_authenticate.set_basic(current_app.config["LDAP_REALM_NAME"])
return response
@wraps(func)
......@@ -412,13 +436,14 @@ class LDAP(object):
# with an empty password, even if you supply a non-anonymous user
# ID, causing .bind_user() to return True. Therefore, only accept
# non-empty passwords.
if req_username in ['', None] or req_password in ['', None]:
current_app.logger.debug('Got a request without auth data')
if req_username in ["", None] or req_password in ["", 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))
current_app.logger.debug(
"User {0!r} gave wrong " "password".format(req_username)
)
return make_auth_required_response()
g.ldap_username = req_username
......
Flask==1.1.1
mock==3.0.5 # for ci
Flask==2.0.2
mock==4.0.3 # for ci