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
  • ldapi-support
  • master
  • v0.1.0
  • v0.1.1
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v1.0.0
  • v1.0.1
  • v1.1.0
  • v1.1.1
  • v1.1.2
  • v1.3.0
  • v1.3.2
  • v1.3.3
  • v1.4.0
16 results

Target

Select target project
  • nimrod/flask-simpleldap
1 result
Select Git revision
  • ldapi-support
  • master
  • v0.1.0
  • v0.1.1
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v1.0.0
  • v1.0.1
  • v1.1.0
  • v1.1.1
  • v1.1.2
  • v1.3.0
  • v1.3.2
  • v1.3.3
  • v1.4.0
16 results
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 language: python
sudo: required sudo: required
dist: bionic dist: focal
python: python:
- "3.5"
- "3.6"
- "3.7" - "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
env: env:
- FLASK=1.1.2 - FLASK=2.0.2
- FLASK=1.1.4
- FLASK=1.0.4 - FLASK=1.0.4
- FLASK=0.12.5
install: install:
- pip install Flask==$FLASK - pip install Flask==$FLASK
- pip install -r dev_requirements.txt - pip install -r dev_requirements.txt
......
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 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 Quickstart
---------- ----------
First, install Flask-SimpleLDAP: 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 Flask-SimpleLDAP depends, and will install for you, recent versions of Flask
(0.12.4 or later) and [python-ldap](https://python-ldap.org/). (0.12.4 or later) and [python-ldap](https://python-ldap.org/).
...@@ -27,19 +28,19 @@ from flask import Flask, g ...@@ -27,19 +28,19 @@ from flask import Flask, g
from flask_simpleldap import LDAP from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
#app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost # app.config["LDAP_HOST"] = "ldap.example.org" # defaults to localhost
app.config['LDAP_BASE_DN'] = 'OU=users,dc=example,dc=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_USERNAME"] = "CN=user,OU=Users,DC=example,DC=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
ldap = LDAP(app) ldap = LDAP(app)
@app.route('/') @app.route("/")
@ldap.basic_auth_required @ldap.basic_auth_required
def index(): def index():
return 'Welcome, {0}!'.format(g.ldap_username) return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
``` ```
...@@ -73,31 +74,31 @@ from flask_simpleldap import LDAP ...@@ -73,31 +74,31 @@ from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
# Base # Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication' app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config['LDAP_HOST'] = 'openldap.example.org' app.config["LDAP_HOST"] = "openldap.example.org"
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=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_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
# OpenLDAP # OpenLDAP
app.config['LDAP_OBJECTS_DN'] = 'dn' app.config["LDAP_OBJECTS_DN"] = "dn"
app.config['LDAP_OPENLDAP'] = True app.config["LDAP_OPENLDAP"] = True
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))' app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Groups # Groups
app.config['LDAP_GROUP_MEMBERS_FIELD'] = "uniquemember" app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config['LDAP_GROUP_OBJECT_FILTER'] = "(&(objectclass=groupOfUniqueNames)(cn=%s))" 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"] = "(&(cn=*)(objectclass=groupOfUniqueNames)(uniquemember=%s))"
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn" app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app) ldap = LDAP(app)
@app.route('/') @app.route("/")
@ldap.basic_auth_required @ldap.basic_auth_required
def index(): def index():
return 'Welcome, {0}!'.format(g.ldap_username) return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__': if __name__ == "__main__":
app.run() 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 python-ldap==3.2.0 # here instead of requirements.txt so rtfd can build
Sphinx==2.1.2 Sphinx==2.1.2
......
# flasky extensions. flasky pygments style based on tango style # flasky extensions. flasky pygments style based on tango style
from pygments.style import Style from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \ from pygments.token import (
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal Keyword,
Name,
Comment,
String,
Error,
Number,
Operator,
Generic,
Whitespace,
Punctuation,
Other,
Literal,
)
class FlaskyStyle(Style): class FlaskyStyle(Style):
...@@ -14,10 +26,8 @@ class FlaskyStyle(Style): ...@@ -14,10 +26,8 @@ class FlaskyStyle(Style):
Whitespace: "underline #f8f8f8", # class: 'w' Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err' Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x' Other: "#000000", # class 'x'
Comment: "italic #8f5902", # class: 'c' Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp' Comment.Preproc: "noitalic", # class: 'cp'
Keyword: "bold #004461", # class: 'k' Keyword: "bold #004461", # class: 'k'
Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Constant: "bold #004461", # class: 'kc'
Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Declaration: "bold #004461", # class: 'kd'
...@@ -25,12 +35,9 @@ class FlaskyStyle(Style): ...@@ -25,12 +35,9 @@ class FlaskyStyle(Style):
Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Pseudo: "bold #004461", # class: 'kp'
Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Reserved: "bold #004461", # class: 'kr'
Keyword.Type: "bold #004461", # class: 'kt' Keyword.Type: "bold #004461", # class: 'kt'
Operator: "#582800", # class: 'o' Operator: "#582800", # class: 'o'
Operator.Word: "bold #004461", # class: 'ow' - like keywords Operator.Word: "bold #004461", # class: 'ow' - like keywords
Punctuation: "bold #000000", # class: 'p' Punctuation: "bold #000000", # class: 'p'
# because special names such as Name.Class, Name.Function, etc. # because special names such as Name.Class, Name.Function, etc.
# are not recognized as such later in the parsing, we choose them # are not recognized as such later in the parsing, we choose them
# to look the same as ordinary variables. # to look the same as ordinary variables.
...@@ -53,12 +60,9 @@ class FlaskyStyle(Style): ...@@ -53,12 +60,9 @@ class FlaskyStyle(Style):
Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised
Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
Number: "#990000", # class: 'm' Number: "#990000", # class: 'm'
Literal: "#000000", # class: 'l' Literal: "#000000", # class: 'l'
Literal.Date: "#000000", # class: 'ld' Literal.Date: "#000000", # class: 'ld'
String: "#4e9a06", # class: 's' String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb' String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc' String.Char: "#4e9a06", # class: 'sc'
...@@ -71,7 +75,6 @@ class FlaskyStyle(Style): ...@@ -71,7 +75,6 @@ class FlaskyStyle(Style):
String.Regex: "#4e9a06", # class: 'sr' String.Regex: "#4e9a06", # class: 'sr'
String.Single: "#4e9a06", # class: 's1' String.Single: "#4e9a06", # class: 's1'
String.Symbol: "#4e9a06", # class: 'ss' String.Symbol: "#4e9a06", # class: 'ss'
Generic: "#000000", # class: 'g' Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd' Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge' Generic.Emph: "italic #000000", # class: 'ge'
......
...@@ -22,13 +22,14 @@ class Mock(MagicMock): ...@@ -22,13 +22,14 @@ class Mock(MagicMock):
def __getattr__(cls, name): 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) sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # 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 ----------- # -- General configuration -----------
...@@ -39,34 +40,34 @@ sys.path.insert(0, os.path.abspath('..')) ...@@ -39,34 +40,34 @@ sys.path.insert(0, os.path.abspath('..'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
# source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'Flask-SimpleLDAP' project = "Flask-SimpleLDAP"
copyright = u'2019, Alexandre Ferland' copyright = "2022, Alexandre Ferland"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '1.4.0' version = "1.4.0"
# The full version, including alpha/beta/rc tags. # 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 # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
...@@ -80,7 +81,7 @@ release = '1.4.0' ...@@ -80,7 +81,7 @@ release = '1.4.0'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # 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 # The reST default role (used for this markup: `text`) to use for all
# documents. # documents.
...@@ -98,7 +99,7 @@ exclude_patterns = ['_build'] ...@@ -98,7 +99,7 @@ exclude_patterns = ['_build']
# show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # 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. # A list of ignored prefixes for module index sorting.
# modindex_common_prefix = [] # modindex_common_prefix = []
...@@ -109,12 +110,12 @@ pygments_style = 'sphinx' ...@@ -109,12 +110,12 @@ pygments_style = 'sphinx'
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------
sys.path.append(os.path.abspath('_themes')) sys.path.append(os.path.abspath("_themes"))
html_theme_path = ['_themes'] html_theme_path = ["_themes"]
html_theme = 'flask_small' html_theme = "flask_small"
html_theme_options = { html_theme_options = {
'index_logo': '', #TODO "index_logo": "", # TODO
'github_fork': 'admiralobvious/flask-simpleldap', "github_fork": "alexferl/flask-simpleldap",
} }
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
...@@ -136,7 +137,7 @@ html_theme_options = { ...@@ -136,7 +137,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # 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 # Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied # .htaccess) here, relative to this directory. These files are copied
...@@ -185,7 +186,7 @@ html_static_path = ['_static'] ...@@ -185,7 +186,7 @@ html_static_path = ['_static']
# html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'Flask-SimpleLDAPdoc' htmlhelp_basename = "Flask-SimpleLDAPdoc"
# -- Options for LaTeX output --------------------------------------------- # -- Options for LaTeX output ---------------------------------------------
...@@ -193,10 +194,8 @@ htmlhelp_basename = 'Flask-SimpleLDAPdoc' ...@@ -193,10 +194,8 @@ htmlhelp_basename = 'Flask-SimpleLDAPdoc'
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt', #'pointsize': '10pt',
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#'preamble': '', #'preamble': '',
} }
...@@ -205,8 +204,13 @@ latex_elements = { ...@@ -205,8 +204,13 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ 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 name of an image file (relative to this directory) to place at the top of
...@@ -235,8 +239,13 @@ latex_documents = [ ...@@ -235,8 +239,13 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ 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. # If true, show URL addresses after external links.
...@@ -249,9 +258,15 @@ man_pages = [ ...@@ -249,9 +258,15 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'Flask-SimpleLDAP', u'Flask-SimpleLDAP Documentation', (
u'Alexandre Ferland', 'Flask-SimpleLDAP', 'One line description of project.', "index",
'Miscellaneous'), "Flask-SimpleLDAP",
"Flask-SimpleLDAP Documentation",
"Alexandre Ferland",
"Flask-SimpleLDAP",
"One line description of project.",
"Miscellaneous",
),
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
...@@ -268,4 +283,4 @@ texinfo_documents = [ ...@@ -268,4 +283,4 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library. # Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None} intersphinx_mapping = {"http://docs.python.org/": None}
...@@ -16,11 +16,11 @@ First, install Flask-SimpleLDAP: ...@@ -16,11 +16,11 @@ 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 Flask-SimpleLDAP depends, and will install for you, recent versions of Flask
(0.12.4 or later) and pyldap. Flask-SimpleLDAP is compatible (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 Next, add a :class:`~flask_simpleldap.LDAP` to your code and at least the three
required configuration options: required configuration options:
...@@ -31,19 +31,20 @@ required configuration options: ...@@ -31,19 +31,20 @@ required configuration options:
from flask_simpleldap import LDAP from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
app.config['LDAP_BASE_DN'] = 'OU=users,dc=example,dc=org' # app.config["LDAP_HOST"] = "ldap.example.org" # defaults to localhost
app.config['LDAP_USERNAME'] = 'CN=user,OU=Users,DC=example,DC=org' app.config["LDAP_BASE_DN"] = "OU=users,dc=example,dc=org"
app.config['LDAP_PASSWORD'] = 'password' 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') @app.route("/ldap")
@ldap.login_required @ldap.login_required
def ldap_protected(): def ldap_protected():
return 'Success!' return "Success!"
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
...@@ -122,40 +123,62 @@ History ...@@ -122,40 +123,62 @@ History
Changes: 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 - 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. - 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 - `#51 <https://github.com/admiralobvious/flask-simpleldap/pull/51>`_ Referral chasing crash
`#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 - `#54 <https://github.com/admiralobvious/flask-simpleldap/pull/54>`_ Fixes #44 - Error in bind_user method, also fixes #60 and #61
`#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
- `#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 - 1.2.0 September 26, 2017
- Changed get_group_members() and get_user_groups() returning strings instead of bytes in PY3. - Changed get_group_members() and get_user_groups() returning strings instead of bytes in PY3.
- 1.1.2 July 17, 2017 - 1.1.2 July 17, 2017
- Merge GitHub PR `#30 <https://github.com/admiralobvious/flask-simpleldap/pull/30>`_, - Merge GitHub PR `#30 <https://github.com/admiralobvious/flask-simpleldap/pull/30>`_,
Fix for python3 Fix for python3
- Fix decoding bytes in PY3 for @ldap.group_required. - Fix decoding bytes in PY3 for @ldap.group_required.
- 1.1.1 April 10, 2017 - 1.1.1 April 10, 2017
- Merge GitHub pull `#26 <https://github.com/admiralobvious/flask-simpleldap/pull/26>`_, - Merge GitHub pull `#26 <https://github.com/admiralobvious/flask-simpleldap/pull/26>`_,
Fix set_option call to LDAP for SSL CERT Fix set_option call to LDAP for SSL CERT
- 1.1.0 June 7, 2016 - 1.1.0 June 7, 2016
- Add the ability the pass any valid pyldap config options via the LDAP_CUSTOM_OPTIONS configuration directive. - Add the ability the pass any valid pyldap config options via the LDAP_CUSTOM_OPTIONS configuration directive.
- 1.0.1 June 5, 2016 - 1.0.1 June 5, 2016
- Fix ldap filter import. - Fix ldap filter import.
- 1.0.0 June 4, 2016 - 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. - 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 - 0.4.0: September 5, 2015
- Added support for OpenLDAP directories. Thanks to `@jm66 <https://github.com/jm66>`_ on GitHub. - Added support for OpenLDAP directories. Thanks to `@jm66 <https://github.com/jm66>`_ on GitHub.
- 0.3.0: January 21, 2015 - 0.3.0: January 21, 2015
- Fix Github issue `#10 <https://github.com/admiralobvious/flask-simpleldap/issues/10>`_, - Fix Github issue `#10 <https://github.com/admiralobvious/flask-simpleldap/issues/10>`_,
Redirect users back to the page they originally requested after authenticating Redirect users back to the page they originally requested after authenticating
...@@ -163,14 +186,17 @@ Changes: ...@@ -163,14 +186,17 @@ Changes:
Only trust .bind_user() with a non-empty password Only trust .bind_user() with a non-empty password
- 0.2.0: December 7, 2014 - 0.2.0: December 7, 2014
- Added HTTP Basic Authentication. Thanks to `@OptiverTimAll <https://github.com/optivertimall>`_ on GitHub. - 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>`_, - Fix GitHub issue `#4 <https://github.com/admiralobvious/flask-simpleldap/issues/4>`_,
User or group queries are vulnerable to LDAP injection. User or group queries are vulnerable to LDAP injection.
Make sure you update your filters to use '%s' instead of the old '{}'! Make sure you update your filters to use '%s' instead of the old '{}'!
- 0.1.1: September 6, 2014 - 0.1.1: September 6, 2014
- Fix GitHub issue `#3 <https://github.com/admiralobvious/flask-simpleldap/issues/3>`_, - Fix GitHub issue `#3 <https://github.com/admiralobvious/flask-simpleldap/issues/3>`_,
Not compatible with uppercase distinguished names. Not compatible with uppercase distinguished names.
- 0.1: August 9, 2014 - 0.1: August 9, 2014
- Initial Release - Initial Release
...@@ -3,16 +3,18 @@ from flask_simpleldap import LDAP ...@@ -3,16 +3,18 @@ from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
# app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost # app.config['LDAP_HOST'] = 'ldap.example.org' # defaults to localhost
app.config['LDAP_BASE_DN'] = 'OU=users,dc=example,dc=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_USERNAME"] = "CN=user,OU=Users,DC=example,DC=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
ldap = LDAP(app) ldap = LDAP(app)
@app.route('/')
@app.route("/")
@ldap.basic_auth_required @ldap.basic_auth_required
def index(): def index():
return 'Welcome, {0}!'.format(g.ldap_username) return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
...@@ -4,31 +4,35 @@ from flask_simpleldap import LDAP ...@@ -4,31 +4,35 @@ from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
# Base # Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication' app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config['LDAP_HOST'] = 'openldap.example.org' app.config["LDAP_HOST"] = "openldap.example.org"
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=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_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
# OpenLDAP # OpenLDAP
app.config['LDAP_OPENLDAP'] = True app.config["LDAP_OPENLDAP"] = True
app.config['LDAP_OBJECTS_DN'] = 'dn' app.config["LDAP_OBJECTS_DN"] = "dn"
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))' app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Groups configuration # Groups configuration
app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember' app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))' app.config["LDAP_GROUP_OBJECT_FILTER"] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames' app.config["LDAP_GROUPS_OBJECT_FILTER"] = "objectclass=groupOfUniqueNames"
app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description'] app.config["LDAP_GROUP_FIELDS"] = ["cn", "entryDN", "member", "description"]
app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))' app.config[
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn" "LDAP_GROUP_MEMBER_FILTER"
] = "(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app) ldap = LDAP(app)
@app.route('/')
@app.route("/")
@ldap.basic_auth_required @ldap.basic_auth_required
def index(): def index():
return 'Welcome, {0}!'.format(g.ldap_username) return "Welcome, {0}!".format(g.ldap_username)
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
...@@ -5,10 +5,7 @@ from .extensions import ldap ...@@ -5,10 +5,7 @@ from .extensions import ldap
from .core import core from .core import core
from .foo import foo from .foo import foo
DEFAULT_BLUEPRINTS = ( DEFAULT_BLUEPRINTS = (core, foo)
core,
foo
)
def create_app(config=None, app_name=None, blueprints=None): def create_app(config=None, app_name=None, blueprints=None):
...@@ -33,11 +30,11 @@ def register_hooks(app): ...@@ -33,11 +30,11 @@ def register_hooks(app):
@app.before_request @app.before_request
def before_request(): def before_request():
g.user = None 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. # This is where you'd query your database to get the user info.
g.user = {} g.user = {}
# Create a global with the LDAP groups the user is a member of. # 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): def register_blueprints(app, blueprints):
......
...@@ -2,14 +2,14 @@ import ldap ...@@ -2,14 +2,14 @@ import ldap
class BaseConfig(object): class BaseConfig(object):
PROJECT = 'foo' PROJECT = "foo"
SECRET_KEY = 'dev key' SECRET_KEY = "dev key"
DEBUG = True DEBUG = True
# LDAP # LDAP
LDAP_HOST = 'ldap.example.org' LDAP_HOST = "ldap.example.org"
LDAP_BASE_DN = 'OU=users,dc=example,dc=org' LDAP_BASE_DN = "OU=users,dc=example,dc=org"
LDAP_USERNAME = 'CN=user,OU=Users,DC=example,DC=org' LDAP_USERNAME = "CN=user,OU=Users,DC=example,DC=org"
LDAP_PASSWORD = 'password' LDAP_PASSWORD = "password"
LDAP_LOGIN_VIEW = 'core.login' LDAP_LOGIN_VIEW = "core.login"
LDAP_CUSTOM_OPTIONS = {ldap.OPT_REFERRALS: 0} LDAP_CUSTOM_OPTIONS = {ldap.OPT_REFERRALS: 0}
from flask import Blueprint, g, request, session, redirect, url_for from flask import Blueprint, g, request, session, redirect, url_for
from ..extensions import ldap from ..extensions import ldap
core = Blueprint('core', __name__) core = Blueprint("core", __name__)
@core.route('/') @core.route("/")
@ldap.login_required @ldap.login_required
def index(): def index():
return 'Successfully logged in!' return "Successfully logged in!"
@core.route('/login', methods=['GET', 'POST']) @core.route("/login", methods=["GET", "POST"])
def login(): def login():
if g.user: if g.user:
return redirect(url_for('index')) return redirect(url_for("index"))
if request.method == 'POST': if request.method == "POST":
user = request.form['user'] user = request.form["user"]
passwd = request.form['passwd'] passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd) test = ldap.bind_user(user, passwd)
if test is None or passwd == '': if test is None or passwd == "":
return 'Invalid credentials' return "Invalid credentials"
else: else:
session['user_id'] = request.form['user'] session["user_id"] = request.form["user"]
return redirect('/') return redirect("/")
return """<form action="" method="post"> return """<form action="" method="post">
user: <input name="user"><br> user: <input name="user"><br>
password:<input type="password" name="passwd"><br> password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>""" <input type="submit" value="Submit"></form>"""
@core.route('/group') @core.route("/group")
@ldap.group_required(groups=['Web Developers', 'QA']) @ldap.group_required(groups=["Web Developers", "QA"])
def group(): def group():
return 'Group restricted page' return "Group restricted page"
@core.route('/logout') @core.route("/logout")
def logout(): def logout():
session.pop('user_id', None) session.pop("user_id", None)
return redirect(url_for('index')) return redirect(url_for("index"))
from flask_simpleldap import LDAP from flask_simpleldap import LDAP
ldap = LDAP() ldap = LDAP()
from flask import Blueprint from flask import Blueprint
from ..extensions import ldap from ..extensions import ldap
foo = Blueprint('foo', __name__, url_prefix='/foo') foo = Blueprint("foo", __name__, url_prefix="/foo")
@foo.route('/group') @foo.route("/group")
@ldap.group_required(groups=['Web Developers', 'QA']) @ldap.group_required(groups=["Web Developers", "QA"])
def group(): 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 ...@@ -3,14 +3,14 @@ from flask import Flask, g, request, session, redirect, url_for
from flask_simpleldap import LDAP from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'dev key' app.secret_key = "dev key"
app.debug = True app.debug = True
app.config['LDAP_HOST'] = 'ldap.example.org' app.config["LDAP_HOST"] = "ldap.example.org"
app.config['LDAP_BASE_DN'] = 'OU=users,dc=example,dc=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_USERNAME"] = "CN=user,OU=Users,DC=example,DC=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
app.config['LDAP_CUSTOM_OPTIONS'] = {l.OPT_REFERRALS: 0} app.config["LDAP_CUSTOM_OPTIONS"] = {l.OPT_REFERRALS: 0}
ldap = LDAP(app) ldap = LDAP(app)
...@@ -18,49 +18,49 @@ ldap = LDAP(app) ...@@ -18,49 +18,49 @@ ldap = LDAP(app)
@app.before_request @app.before_request
def before_request(): def before_request():
g.user = None 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. # This is where you'd query your database to get the user info.
g.user = {} g.user = {}
# Create a global with the LDAP groups the user is a member of. # 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 @ldap.login_required
def index(): def index():
return 'Successfully logged in!' return "Successfully logged in!"
@app.route('/login', methods=['GET', 'POST']) @app.route("/login", methods=["GET", "POST"])
def login(): def login():
if g.user: if g.user:
return redirect(url_for('index')) return redirect(url_for("index"))
if request.method == 'POST': if request.method == "POST":
user = request.form['user'] user = request.form["user"]
passwd = request.form['passwd'] passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd) test = ldap.bind_user(user, passwd)
if test is None or passwd == '': if test is None or passwd == "":
return 'Invalid credentials' return "Invalid credentials"
else: else:
session['user_id'] = request.form['user'] session["user_id"] = request.form["user"]
return redirect('/') return redirect("/")
return """<form action="" method="post"> return """<form action="" method="post">
user: <input name="user"><br> user: <input name="user"><br>
password:<input type="password" name="passwd"><br> password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>""" <input type="submit" value="Submit"></form>"""
@app.route('/group') @app.route("/group")
@ldap.group_required(groups=['Web Developers', 'QA']) @ldap.group_required(groups=["Web Developers", "QA"])
def group(): def group():
return 'Group restricted page' return "Group restricted page"
@app.route('/logout') @app.route("/logout")
def logout(): def logout():
session.pop('user_id', None) session.pop("user_id", None)
return redirect(url_for('index')) return redirect(url_for("index"))
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
...@@ -2,25 +2,27 @@ from flask import Flask, g, request, session, redirect, url_for ...@@ -2,25 +2,27 @@ from flask import Flask, g, request, session, redirect, url_for
from flask_simpleldap import LDAP from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'dev key' app.secret_key = "dev key"
app.debug = True app.debug = True
app.config['LDAP_OPENLDAP'] = True app.config["LDAP_OPENLDAP"] = True
app.config['LDAP_OBJECTS_DN'] = 'dn' app.config["LDAP_OBJECTS_DN"] = "dn"
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication' app.config["LDAP_REALM_NAME"] = "OpenLDAP Authentication"
app.config['LDAP_HOST'] = 'openldap.example.org' app.config["LDAP_HOST"] = "openldap.example.org"
app.config['LDAP_BASE_DN'] = 'dc=users,dc=openldap,dc=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_USERNAME"] = "cn=user,ou=servauth-users,dc=users,dc=openldap,dc=org"
app.config['LDAP_PASSWORD'] = 'password' app.config["LDAP_PASSWORD"] = "password"
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)(uid=%s))' app.config["LDAP_USER_OBJECT_FILTER"] = "(&(objectclass=inetOrgPerson)(uid=%s))"
# Group configuration # Group configuration
app.config['LDAP_GROUP_MEMBERS_FIELD'] = 'uniquemember' app.config["LDAP_GROUP_MEMBERS_FIELD"] = "uniquemember"
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(&(objectclass=groupOfUniqueNames)(cn=%s))' app.config["LDAP_GROUP_OBJECT_FILTER"] = "(&(objectclass=groupOfUniqueNames)(cn=%s))"
app.config['LDAP_GROUPS_OBJECT_FILTER'] = 'objectclass=groupOfUniqueNames' app.config["LDAP_GROUPS_OBJECT_FILTER"] = "objectclass=groupOfUniqueNames"
app.config['LDAP_GROUP_FIELDS'] = ['cn', 'entryDN', 'member', 'description'] app.config["LDAP_GROUP_FIELDS"] = ["cn", "entryDN", "member", "description"]
app.config['LDAP_GROUP_MEMBER_FILTER'] = '(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))' app.config[
app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] = "cn" "LDAP_GROUP_MEMBER_FILTER"
] = "(&(cn=*)(objectclass=groupOfUniqueNames)(member=%s))"
app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"] = "cn"
ldap = LDAP(app) ldap = LDAP(app)
...@@ -28,49 +30,49 @@ ldap = LDAP(app) ...@@ -28,49 +30,49 @@ ldap = LDAP(app)
@app.before_request @app.before_request
def before_request(): def before_request():
g.user = None 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. # This is where you'd query your database to get the user info.
g.user = {} g.user = {}
# Create a global with the LDAP groups the user is a member of. # 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 @ldap.login_required
def index(): def index():
return 'Successfully logged in!' return "Successfully logged in!"
@app.route('/login', methods=['GET', 'POST']) @app.route("/login", methods=["GET", "POST"])
def login(): def login():
if g.user: if g.user:
return redirect(url_for('index')) return redirect(url_for("index"))
if request.method == 'POST': if request.method == "POST":
user = request.form['user'] user = request.form["user"]
passwd = request.form['passwd'] passwd = request.form["passwd"]
test = ldap.bind_user(user, passwd) test = ldap.bind_user(user, passwd)
if test is None or passwd == '': if test is None or passwd == "":
return 'Invalid credentials' return "Invalid credentials"
else: else:
session['user_id'] = request.form['user'] session["user_id"] = request.form["user"]
return redirect('/') return redirect("/")
return """<form action="" method="post"> return """<form action="" method="post">
user: <input name="user"><br> user: <input name="user"><br>
password:<input type="password" name="passwd"><br> password:<input type="password" name="passwd"><br>
<input type="submit" value="Submit"></form>""" <input type="submit" value="Submit"></form>"""
@app.route('/group') @app.route("/group")
@ldap.group_required(groups=['web-developers']) @ldap.group_required(groups=["web-developers"])
def group(): def group():
return 'Group restricted page' return "Group restricted page"
@app.route('/logout') @app.route("/logout")
def logout(): def logout():
session.pop('user_id', None) session.pop("user_id", None)
return redirect(url_for('index')) return redirect(url_for("index"))
if __name__ == '__main__': if __name__ == "__main__":
app.run() app.run()
...@@ -2,10 +2,9 @@ import re ...@@ -2,10 +2,9 @@ import re
from functools import wraps from functools import wraps
import ldap import ldap
from ldap import filter as ldap_filter from ldap import filter as ldap_filter
from flask import abort, current_app, g, make_response, redirect, url_for, \ from flask import abort, current_app, g, make_response, redirect, url_for, request
request
__all__ = ['LDAP'] __all__ = ["LDAP"]
class LDAPException(RuntimeError): class LDAPException(RuntimeError):
...@@ -32,51 +31,50 @@ class LDAP(object): ...@@ -32,51 +31,50 @@ class LDAP(object):
:param flask.Flask app: the application to configure for use with :param flask.Flask app: the application to configure for use with
this :class:`~LDAP` this :class:`~LDAP`
""" """
app.config.setdefault('LDAP_HOST', 'localhost') app.config.setdefault("LDAP_HOST", "localhost")
app.config.setdefault('LDAP_PORT', 389) app.config.setdefault("LDAP_PORT", 389)
app.config.setdefault('LDAP_SCHEMA', 'ldap') app.config.setdefault("LDAP_SCHEMA", "ldap")
app.config.setdefault('LDAP_USERNAME', None) app.config.setdefault("LDAP_USERNAME", None)
app.config.setdefault('LDAP_PASSWORD', None) app.config.setdefault("LDAP_PASSWORD", None)
app.config.setdefault('LDAP_TIMEOUT', 10) app.config.setdefault("LDAP_TIMEOUT", 10)
app.config.setdefault('LDAP_USE_SSL', False) app.config.setdefault("LDAP_USE_SSL", False)
app.config.setdefault('LDAP_USE_TLS', False) app.config.setdefault("LDAP_USE_TLS", False)
app.config.setdefault('LDAP_REQUIRE_CERT', False) app.config.setdefault("LDAP_REQUIRE_CERT", False)
app.config.setdefault('LDAP_CERT_PATH', '/path/to/cert') app.config.setdefault("LDAP_CERT_PATH", "/path/to/cert")
app.config.setdefault('LDAP_BASE_DN', None) app.config.setdefault("LDAP_BASE_DN", None)
app.config.setdefault('LDAP_OBJECTS_DN', 'distinguishedName') app.config.setdefault("LDAP_OBJECTS_DN", "distinguishedName")
app.config.setdefault('LDAP_USER_FIELDS', []) app.config.setdefault("LDAP_USER_FIELDS", [])
app.config.setdefault('LDAP_USER_OBJECT_FILTER', app.config.setdefault(
'(&(objectclass=Person)(userPrincipalName=%s))') "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_USER_GROUPS_FIELD", "memberOf")
app.config.setdefault('LDAP_GROUPS_OBJECT_FILTER', 'objectclass=Group') app.config.setdefault("LDAP_GROUP_FIELDS", [])
app.config.setdefault('LDAP_GROUP_OBJECT_FILTER', app.config.setdefault("LDAP_GROUPS_OBJECT_FILTER", "objectclass=Group")
'(&(objectclass=Group)(userPrincipalName=%s))') app.config.setdefault(
app.config.setdefault('LDAP_GROUP_MEMBERS_FIELD', 'member') "LDAP_GROUP_OBJECT_FILTER", "(&(objectclass=Group)(userPrincipalName=%s))"
app.config.setdefault('LDAP_LOGIN_VIEW', 'login') )
app.config.setdefault('LDAP_REALM_NAME', 'LDAP authentication') app.config.setdefault("LDAP_GROUP_MEMBERS_FIELD", "member")
app.config.setdefault('LDAP_OPENLDAP', False) app.config.setdefault("LDAP_LOGIN_VIEW", "login")
app.config.setdefault('LDAP_GROUP_MEMBER_FILTER', '*') app.config.setdefault("LDAP_REALM_NAME", "LDAP authentication")
app.config.setdefault('LDAP_GROUP_MEMBER_FILTER_FIELD', '*') app.config.setdefault("LDAP_OPENLDAP", False)
app.config.setdefault('LDAP_CUSTOM_OPTIONS', None) app.config.setdefault("LDAP_GROUP_MEMBER_FILTER", "*")
app.config.setdefault("LDAP_GROUP_MEMBER_FILTER_FIELD", "*")
if app.config['LDAP_USE_SSL'] or app.config['LDAP_USE_TLS']: app.config.setdefault("LDAP_CUSTOM_OPTIONS", None)
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
ldap.OPT_X_TLS_NEVER) 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, if app.config["LDAP_REQUIRE_CERT"]:
ldap.OPT_X_TLS_DEMAND) ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, app.config["LDAP_CERT_PATH"])
app.config['LDAP_CERT_PATH'])
for option in ["USERNAME", "PASSWORD", "BASE_DN"]:
for option in ['USERNAME', 'PASSWORD', 'BASE_DN']: if app.config["LDAP_{0}".format(option)] is None:
if app.config['LDAP_{0}'.format(option)] is None: raise LDAPException("LDAP_{0} cannot be None!".format(option))
raise LDAPException('LDAP_{0} cannot be None!'.format(option))
@staticmethod @staticmethod
def _set_custom_options(conn): def _set_custom_options(conn):
options = current_app.config['LDAP_CUSTOM_OPTIONS'] options = current_app.config["LDAP_CUSTOM_OPTIONS"]
if options: if options:
for k, v in options.items(): for k, v in options.items():
conn.set_option(k, v) conn.set_option(k, v)
...@@ -90,15 +88,19 @@ class LDAP(object): ...@@ -90,15 +88,19 @@ class LDAP(object):
""" """
try: try:
conn = ldap.initialize('{0}://{1}:{2}'.format( conn = ldap.initialize(
current_app.config['LDAP_SCHEMA'], "{0}://{1}:{2}".format(
current_app.config['LDAP_HOST'], current_app.config["LDAP_SCHEMA"],
current_app.config['LDAP_PORT'])) current_app.config["LDAP_HOST"],
conn.set_option(ldap.OPT_NETWORK_TIMEOUT, current_app.config["LDAP_PORT"],
current_app.config['LDAP_TIMEOUT']) )
)
conn.set_option(
ldap.OPT_NETWORK_TIMEOUT, current_app.config["LDAP_TIMEOUT"]
)
conn = self._set_custom_options(conn) conn = self._set_custom_options(conn)
conn.protocol_version = ldap.VERSION3 conn.protocol_version = ldap.VERSION3
if current_app.config['LDAP_USE_TLS']: if current_app.config["LDAP_USE_TLS"]:
conn.start_tls_s() conn.start_tls_s()
return conn return conn
except ldap.LDAPError as e: except ldap.LDAPError as e:
...@@ -116,8 +118,8 @@ class LDAP(object): ...@@ -116,8 +118,8 @@ class LDAP(object):
conn = self.initialize conn = self.initialize
try: try:
conn.simple_bind_s( conn.simple_bind_s(
current_app.config['LDAP_USERNAME'], current_app.config["LDAP_USERNAME"], current_app.config["LDAP_PASSWORD"]
current_app.config['LDAP_PASSWORD']) )
return conn return conn
except ldap.LDAPError as e: except ldap.LDAPError as e:
raise LDAPException(self.error(e.args)) raise LDAPException(self.error(e.args))
...@@ -148,15 +150,17 @@ class LDAP(object): ...@@ -148,15 +150,17 @@ class LDAP(object):
return return
try: try:
conn = self.initialize conn = self.initialize
_user_dn = user_dn.decode('utf-8') \ _user_dn = (
if isinstance(user_dn, bytes) else user_dn user_dn.decode("utf-8") if isinstance(user_dn, bytes) else user_dn
)
conn.simple_bind_s(_user_dn, password) conn.simple_bind_s(_user_dn, password)
return True return True
except ldap.LDAPError: except ldap.LDAPError:
return return
def get_object_details(self, user=None, group=None, query_filter=None, def get_object_details(
dn_only=False): self, user=None, group=None, query_filter=None, dn_only=False
):
"""Returns a ``dict`` with the object's (user or group) details. """Returns a ``dict`` with the object's (user or group) details.
:param str user: Username of the user object you want details for. :param str user: Username of the user object you want details for.
...@@ -169,33 +173,35 @@ class LDAP(object): ...@@ -169,33 +173,35 @@ class LDAP(object):
fields = None fields = None
if user is not None: if user is not None:
if not dn_only: if not dn_only:
fields = current_app.config['LDAP_USER_FIELDS'] fields = current_app.config["LDAP_USER_FIELDS"]
query_filter = query_filter or \ query_filter = query_filter or current_app.config["LDAP_USER_OBJECT_FILTER"]
current_app.config['LDAP_USER_OBJECT_FILTER']
query = ldap_filter.filter_format(query_filter, (user,)) query = ldap_filter.filter_format(query_filter, (user,))
elif group is not None: elif group is not None:
if not dn_only: if not dn_only:
fields = current_app.config['LDAP_GROUP_FIELDS'] fields = current_app.config["LDAP_GROUP_FIELDS"]
query_filter = query_filter or \ query_filter = (
current_app.config['LDAP_GROUP_OBJECT_FILTER'] query_filter or current_app.config["LDAP_GROUP_OBJECT_FILTER"]
)
query = ldap_filter.filter_format(query_filter, (group,)) query = ldap_filter.filter_format(query_filter, (group,))
conn = self.bind conn = self.bind
try: try:
records = conn.search_s(current_app.config['LDAP_BASE_DN'], records = conn.search_s(
ldap.SCOPE_SUBTREE, query, fields) current_app.config["LDAP_BASE_DN"], ldap.SCOPE_SUBTREE, query, fields
)
conn.unbind_s() conn.unbind_s()
result = {} result = {}
if records and\ if (
records[0][0] is not None and isinstance(records[0][1], dict): records
and records[0][0] is not None
and isinstance(records[0][1], dict)
):
if dn_only: if dn_only:
if current_app.config['LDAP_OPENLDAP']: if current_app.config["LDAP_OPENLDAP"]:
if records: if records:
return records[0][0] return records[0][0]
else: else:
if current_app.config['LDAP_OBJECTS_DN'] \ if current_app.config["LDAP_OBJECTS_DN"] in records[0][1]:
in records[0][1]: dn = records[0][1][current_app.config["LDAP_OBJECTS_DN"]]
dn = records[0][1][
current_app.config['LDAP_OBJECTS_DN']]
return dn[0] return dn[0]
for k, v in list(records[0][1].items()): for k, v in list(records[0][1].items()):
result[k] = v result[k] = v
...@@ -217,17 +223,21 @@ class LDAP(object): ...@@ -217,17 +223,21 @@ class LDAP(object):
""" """
conn = self.bind conn = self.bind
try: try:
fields = fields or current_app.config['LDAP_GROUP_FIELDS'] fields = fields or current_app.config["LDAP_GROUP_FIELDS"]
if current_app.config['LDAP_OPENLDAP']: if current_app.config["LDAP_OPENLDAP"]:
records = conn.search_s( records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE, current_app.config["LDAP_BASE_DN"],
current_app.config['LDAP_GROUPS_OBJECT_FILTER'], ldap.SCOPE_SUBTREE,
fields) current_app.config["LDAP_GROUPS_OBJECT_FILTER"],
fields,
)
else: else:
records = conn.search_s( records = conn.search_s(
current_app.config['LDAP_BASE_DN'], ldap.SCOPE_SUBTREE, current_app.config["LDAP_BASE_DN"],
current_app.config['LDAP_GROUPS_OBJECT_FILTER'], ldap.SCOPE_SUBTREE,
fields) current_app.config["LDAP_GROUPS_OBJECT_FILTER"],
fields,
)
conn.unbind_s() conn.unbind_s()
if records: if records:
if dn_only: if dn_only:
...@@ -248,42 +258,51 @@ class LDAP(object): ...@@ -248,42 +258,51 @@ class LDAP(object):
conn = self.bind conn = self.bind
try: try:
if current_app.config['LDAP_OPENLDAP']: if current_app.config["LDAP_OPENLDAP"]:
fields = \ fields = [str(current_app.config["LDAP_GROUP_MEMBER_FILTER_FIELD"])]
[str(current_app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'])]
records = conn.search_s( 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( ldap_filter.filter_format(
current_app.config['LDAP_GROUP_MEMBER_FILTER'], current_app.config["LDAP_GROUP_MEMBER_FILTER"],
(self.get_object_details(user, dn_only=True),)), (self.get_object_details(user, dn_only=True),),
fields) ),
fields,
)
else: else:
records = conn.search_s( 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( ldap_filter.filter_format(
current_app.config['LDAP_USER_OBJECT_FILTER'], current_app.config["LDAP_USER_OBJECT_FILTER"], (user,)
(user,)), ),
[current_app.config['LDAP_USER_GROUPS_FIELD']]) [current_app.config["LDAP_USER_GROUPS_FIELD"]],
)
conn.unbind_s() conn.unbind_s()
if records: if records:
if current_app.config['LDAP_OPENLDAP']: if current_app.config["LDAP_OPENLDAP"]:
group_member_filter = \ group_member_filter = current_app.config[
current_app.config['LDAP_GROUP_MEMBER_FILTER_FIELD'] "LDAP_GROUP_MEMBER_FILTER_FIELD"
]
record_list = [record[1] for record in records] record_list = [record[1] for record in records]
record_dicts = [ record_dicts = [
record for record in record_list if isinstance(record, dict)] record for record in record_list if isinstance(record, dict)
groups = [item.get([group_member_filter][0])[0] ]
for item in record_dicts] groups = [
item.get([group_member_filter][0])[0] for item in record_dicts
]
return groups return groups
else: else:
if current_app.config['LDAP_USER_GROUPS_FIELD'] in \ if current_app.config["LDAP_USER_GROUPS_FIELD"] in records[0][1]:
records[0][1]:
groups = records[0][1][ groups = records[0][1][
current_app.config['LDAP_USER_GROUPS_FIELD']] current_app.config["LDAP_USER_GROUPS_FIELD"]
result = [re.findall(b'(?:cn=|CN=)(.*?),', group)[0] ]
for group in groups] result = [
result = [r.decode('utf-8') for r in result] re.findall(b"(?:cn=|CN=)(.*?),", group)[0]
for group in groups
]
result = [r.decode("utf-8") for r in result]
return result return result
except ldap.LDAPError as e: except ldap.LDAPError as e:
raise LDAPException(self.error(e.args)) raise LDAPException(self.error(e.args))
...@@ -298,17 +317,20 @@ class LDAP(object): ...@@ -298,17 +317,20 @@ class LDAP(object):
conn = self.bind conn = self.bind
try: try:
records = conn.search_s( 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( ldap_filter.filter_format(
current_app.config['LDAP_GROUP_OBJECT_FILTER'], (group,)), current_app.config["LDAP_GROUP_OBJECT_FILTER"], (group,)
[current_app.config['LDAP_GROUP_MEMBERS_FIELD']]) ),
[current_app.config["LDAP_GROUP_MEMBERS_FIELD"]],
)
conn.unbind_s() conn.unbind_s()
if records: if records:
if current_app.config['LDAP_GROUP_MEMBERS_FIELD'] in \ if current_app.config["LDAP_GROUP_MEMBERS_FIELD"] in records[0][1]:
records[0][1]:
members = records[0][1][ members = records[0][1][
current_app.config['LDAP_GROUP_MEMBERS_FIELD']] current_app.config["LDAP_GROUP_MEMBERS_FIELD"]
members = [m.decode('utf-8') for m in members] ]
members = [m.decode("utf-8") for m in members]
return members return members
except ldap.LDAPError as e: except ldap.LDAPError as e:
raise LDAPException(self.error(e.args)) raise LDAPException(self.error(e.args))
...@@ -316,8 +338,8 @@ class LDAP(object): ...@@ -316,8 +338,8 @@ class LDAP(object):
@staticmethod @staticmethod
def error(e): def error(e):
e = e[0] e = e[0]
if 'desc' in e: if "desc" in e:
return e['desc'] return e["desc"]
else: else:
return e return e
...@@ -338,11 +360,11 @@ class LDAP(object): ...@@ -338,11 +360,11 @@ class LDAP(object):
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
if g.user is None: if g.user is None:
next_path = request.full_path or request.path next_path = request.full_path or request.path
if next_path == '/?': if next_path == "/?":
return redirect(url_for(current_app.config["LDAP_LOGIN_VIEW"]))
return redirect( return redirect(
url_for(current_app.config['LDAP_LOGIN_VIEW'])) url_for(current_app.config["LDAP_LOGIN_VIEW"], next=next_path)
return redirect(url_for(current_app.config['LDAP_LOGIN_VIEW'], )
next=next_path))
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapped return wrapped
...@@ -367,8 +389,11 @@ class LDAP(object): ...@@ -367,8 +389,11 @@ class LDAP(object):
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
if g.user is None: if g.user is None:
return redirect( return redirect(
url_for(current_app.config['LDAP_LOGIN_VIEW'], url_for(
next=request.full_path or request.path)) 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] match = [group for group in groups if group in g.ldap_groups]
if not match: if not match:
abort(403) abort(403)
...@@ -395,9 +420,8 @@ class LDAP(object): ...@@ -395,9 +420,8 @@ class LDAP(object):
""" """
def make_auth_required_response(): def make_auth_required_response():
response = make_response('Unauthorized', 401) response = make_response("Unauthorized", 401)
response.www_authenticate.set_basic( response.www_authenticate.set_basic(current_app.config["LDAP_REALM_NAME"])
current_app.config['LDAP_REALM_NAME'])
return response return response
@wraps(func) @wraps(func)
...@@ -412,13 +436,14 @@ class LDAP(object): ...@@ -412,13 +436,14 @@ class LDAP(object):
# with an empty password, even if you supply a non-anonymous user # with an empty password, even if you supply a non-anonymous user
# ID, causing .bind_user() to return True. Therefore, only accept # ID, causing .bind_user() to return True. Therefore, only accept
# non-empty passwords. # non-empty passwords.
if req_username in ['', None] or req_password in ['', None]: if req_username in ["", None] or req_password in ["", None]:
current_app.logger.debug('Got a request without auth data') current_app.logger.debug("Got a request without auth data")
return make_auth_required_response() return make_auth_required_response()
if not self.bind_user(req_username, req_password): if not self.bind_user(req_username, req_password):
current_app.logger.debug('User {0!r} gave wrong ' current_app.logger.debug(
'password'.format(req_username)) "User {0!r} gave wrong " "password".format(req_username)
)
return make_auth_required_response() return make_auth_required_response()
g.ldap_username = req_username g.ldap_username = req_username
......
Flask==1.1.1 Flask==2.0.2
mock==3.0.5 # for ci mock==4.0.3 # for ci