tests workflow

This commit is contained in:
Éloi Rivard 2020-08-18 17:39:34 +02:00
parent 9e75432145
commit 531c34a689
15 changed files with 583 additions and 38 deletions

2
.gitignore vendored
View file

@ -1,7 +1,9 @@
*.sqlite *.sqlite
*.pyc *.pyc
venv/* venv/*
env
.*@neomake* .*@neomake*
.ash_history .ash_history
.python_history .python_history
config.toml config.toml
.tox

29
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,29 @@
---
image: python
stages:
- test
- build
- release
before_script:
- apt update
- env DEBIAN_FRONTEND=noninteractive apt install --yes slapd python3-dev libldap2-dev libsasl2-dev libssl-dev ldap-utils
- curl -O https://bootstrap.pypa.io/get-pip.py
- python get-pip.py
- pip install tox poetry coveralls pyyaml
python36:
image: python:3.6
stage: test
script: tox -e py36
python37:
image: python:3.7
stage: test
script: tox -e py37
python38:
image: python:3.8
stage: test
script: tox -e py38

View file

@ -3,3 +3,15 @@
oidc-ldap-bridge is a simple OpenID Connect provider based upon OpenLDAP. oidc-ldap-bridge is a simple OpenID Connect provider based upon OpenLDAP.
It authenticates your LDAP users, and do not need any additional database to work. Everything is stored in your OpenLDAP server. It authenticates your LDAP users, and do not need any additional database to work. Everything is stored in your OpenLDAP server.
## Contribute
Contributions are welcome!
To run the tests, you just need to run `tox`.
To try a development environment, you can run the docker image and then open https://127.0.0.1:5000
```bash
cp config.sample.toml config.toml
docker-compose up
```

View file

@ -6,6 +6,7 @@ NAME = "MyDomain"
LANGUAGE = "en" LANGUAGE = "en"
[LDAP] [LDAP]
URI = "ldaps://ldap.mydomain.tld" URI = "ldap://ldap"
BIND_USER = "cn=admin,dc=mydomain,dc=tld" ROOT_DN = "dc=mydomain,dc=tld"
BIND_DN = "cn=admin,dc=mydomain,dc=tld"
BIND_PW = "admin" BIND_PW = "admin"

View file

@ -8,8 +8,11 @@ services:
- LDAP_DOMAIN=mydomain.tld - LDAP_DOMAIN=mydomain.tld
volumes: volumes:
- ./docker/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-boostrap.ldif:ro - ./docker/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/50-boostrap.ldif:ro
- ./oauth2-openldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/40-oauth2.ldif:ro - ./schemas/oauth2-openldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/40-oauth2.ldif:ro
command: --copy-service command: --copy-service
ports:
- 5389:389
- 5636:636
oauth: oauth:
build: build:

View file

@ -3,4 +3,7 @@ flask
flask-babel flask-babel
flask-wtf flask-wtf
python-ldap python-ldap
pytest
pytest-flask
toml toml
pdbpp

View file

@ -0,0 +1,334 @@
attributetype ( 1.3.6.1.4.1.56207.1.1.1 NAME 'oauthCode'
DESC 'OAuth 2.0 Authorization Code'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.2 NAME 'oauthClientID'
DESC 'Authorized client'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.3 NAME 'oauthRedirectURI'
DESC 'Authorization Code Redirection URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.4 NAME 'oauthResponseType'
DESC 'OAuth 2.0 response type'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.5 NAME 'oauthScope'
DESC 'OAuth 2.0 scope value'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.6 NAME 'oauthNonce'
DESC 'OAuth 2.0 nonce'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.7 NAME 'oauthAuthorizationDate'
DESC 'Access token issue date'
EQUALITY generalizedTimeMatch
ORDERING generalizedTimeOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.8 NAME 'oauthCodeChallenge'
DESC 'OAuth 2.0 nonce'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.9 NAME 'oauthCodeChallengeMethod'
DESC 'OAuth 2.0 nonce'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.10 NAME 'oauthClientSecret'
DESC 'Client secret'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.11 NAME 'oauthClientSecretExpDate'
DESC 'Client secret expiration date/time'
EQUALITY generalizedTimeMatch
ORDERING generalizedTimeOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.12 NAME 'oauthIssueDate'
DESC 'Client identifier issue date/time'
EQUALITY generalizedTimeMatch
ORDERING generalizedTimeOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.13 NAME 'oauthGrantType'
DESC 'OAuth 2.0 grant type'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.14 NAME 'oauthTokenLifetime'
DESC 'OAuth 2.0 refresh token lifetime, in seconds'
EQUALITY integerMatch
ORDERING integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.15 NAME 'oauthClientName'
DESC 'Client name'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.16 NAME 'oauthClientContact'
DESC 'Client name'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.17 NAME 'oauthClientURI'
DESC 'Client URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.18 NAME 'oauthLogoURI'
DESC 'Logo URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.19 NAME 'oauthTermsOfServiceURI'
DESC 'Terms of service URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.20 NAME 'oauthPolicyURI'
DESC 'Policy URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.21 NAME 'oauthJWKURI'
DESC 'JWK set URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.22 NAME 'oauthJWK'
DESC 'JWK set JSON'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.23 NAME 'oauthSoftwareID'
DESC 'Software identifier'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.24 NAME 'oauthSoftwareVersion'
DESC 'Software version'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.25 NAME 'oauthToken'
DESC 'OAuth 2.0 Token'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.26 NAME 'oauthTokenType'
DESC 'OAuth 2.0 Token'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.27 NAME 'oauthAccessToken'
DESC 'OAuth 2.0 access token'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.28 NAME 'oauthRefreshToken'
DESC 'OAuth 2.0 refresh token'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.29 NAME 'oauthTokenEndpointAuthMethod'
DESC 'OAuth 2.0 Token endpoint authentication method'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0 Dynamic Client Registration Protocol' )
attributetype ( 1.3.6.1.4.1.56207.1.1.30 NAME 'oauthSubject'
DESC 'OAuth 2.0 Token subject'
EQUALITY caseExactMatch
ORDERING caseExactOrderingMatch
SUBSTR caseExactSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0 Dynamic Client Registration Protocol' )
attributetype ( 1.3.6.1.4.1.56207.1.1.31 NAME 'oauthRedirectURIs'
DESC 'Authorization Code Redirection URI'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
attributetype ( 1.3.6.1.4.1.56207.1.1.32 NAME 'oauthAuthorizationLifetime'
DESC 'OAuth 2.0 authorization code lifetime, in seconds'
EQUALITY integerMatch
ORDERING integerOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE
USAGE userApplications
X-ORIGIN 'OAuth 2.0' )
objectclass ( 1.3.6.1.4.1.56207.1.2.1 NAME 'oauthClient'
DESC 'OAuth 2.0 Authorization Code'
SUP top
STRUCTURAL
MUST oauthClientID
MAY ( description $
oauthClientName $
oauthClientContact $
oauthClientURI $
oauthRedirectURIs $
oauthLogoURI $
oauthIssueDate $
oauthClientSecret $
oauthClientSecretExpDate $
oauthGrantType $
oauthResponseType $
oauthScope $
oauthTermsOfServiceURI $
oauthPolicyURI $
oauthJWKURI $
oauthJWK $
oauthTokenEndpointAuthMethod $
oauthSoftwareID $
oauthSoftwareVersion )
)
X-ORIGIN 'OAuth 2.0' )
objectclass ( 1.3.6.1.4.1.56207.1.2.2 NAME 'oauthAuthorizationCode'
DESC 'OAuth 2.0 Authorization Code'
SUP top
STRUCTURAL
MUST oauthCode
MAY ( description $
oauthClientID $
oauthSubject $
oauthRedirectURI $
oauthResponseType $
oauthScope $
oauthNonce $
oauthAuthorizationDate $
oauthAuthorizationLifetime $
oauthCodeChallenge $
oauthCodeChallengeMethod )
X-ORIGIN 'OAuth 2.0' )
objectclass ( 1.3.6.1.4.1.56207.1.2.3 NAME 'oauthToken'
DESC 'OAuth 2.0 Token'
SUP top
STRUCTURAL
MUST oauthAccessToken
MAY ( description $
oauthClientID $
oauthSubject $
oauthTokenType $
oauthRefreshToken $
oauthScope $
oauthIssueDate $
oauthTokenLifetime )
X-ORIGIN 'OAuth 2.0' )

11
setup.cfg Normal file
View file

@ -0,0 +1,11 @@
[tox:tox]
envlist =
py36
py37
py38
skipsdist=True
[testenv]
install_command = pip install {packages}
commands = {envbindir}/pytest --showlocals {posargs}
deps = --requirement requirements.txt

0
tests/__init__.py Normal file
View file

117
tests/conftest.py Normal file
View file

@ -0,0 +1,117 @@
import datetime
import ldap.ldapobject
import os
import pytest
import slapdtest
from werkzeug.security import gen_salt
from web import create_app
from web.models import User, Client, Token, AuthorizationCode
from web.ldaputils import LDAPObjectHelper
class CustomSlapdObject(slapdtest.SlapdObject):
custom_schema_files = ("oauth2-openldap.schema",)
def _ln_schema_files(self, *args, **kwargs):
dir_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "schemas"
)
super()._ln_schema_files(*args, **kwargs)
super()._ln_schema_files(self.custom_schema_files, dir_path)
def gen_config(self):
previous = self.openldap_schema_files
self.openldap_schema_files += self.custom_schema_files
config = super().gen_config()
self.openldap_schema_files = previous
return config
@pytest.fixture(scope="session")
def slapd_server():
slapd = CustomSlapdObject()
try:
slapd.start()
suffix_dc = slapd.suffix.split(",")[0][3:]
slapd.ldapadd(
"\n".join(
[
"dn: " + slapd.suffix,
"objectClass: dcObject",
"objectClass: organization",
"dc: " + suffix_dc,
"o: " + suffix_dc,
"",
"dn: " + slapd.root_dn,
"objectClass: applicationProcess",
"cn: " + slapd.root_cn,
]
)
+ "\n"
)
yield slapd
finally:
slapd.stop()
@pytest.fixture
def slapd_connection(slapd_server):
conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri)
conn.protocol_version = 3
conn.simple_bind_s(slapd_server.root_dn, slapd_server.root_pw)
yield conn
conn.unbind_s()
@pytest.fixture
def app(slapd_server, slapd_connection):
LDAPObjectHelper.root_dn = slapd_server.suffix
Client.initialize(slapd_connection)
User.initialize(slapd_connection)
Token.initialize(slapd_connection)
AuthorizationCode.initialize(slapd_connection)
app = create_app(
{
"LDAP": {
"URI": slapd_server.ldap_uri,
"BIND_DN": slapd_server.root_dn,
"BIND_PW": slapd_server.root_pw,
}
}
)
return app
@pytest.fixture
def client(app, slapd_connection):
c = Client(
oauthClientID=gen_salt(24),
oauthClientName="Some client",
oauthClientContact="contact@mydomain.tld",
oauthClientURI="https://mydomain.tld",
oauthRedirectURIs=[
"https://mydomain.tld/redirect1",
"https://mydomain.tld/redirect2",
],
oauthLogoURI="https://mydomain.tld/logo.png",
oauthIssueDate=datetime.datetime.now().strftime("%Y%m%d%H%S%MZ"),
oauthClientSecret=gen_salt(48),
oauthGrantType=["password", "authorization_code"],
oauthResponseType=["code"],
oauthScope=["openid", "profile"],
oauthTermsOfServiceURI="https://mydomain.tld/tos",
oauthPolicyURI="https://mydomain.tld/policy",
oauthJWKURI="https://mydomain.tld/jwk",
oauthTokenEndpointAuthMethod="client_secret_basic",
)
c.save(slapd_connection)
return c
@pytest.fixture
def user(app, slapd_connection):
u = User(cn="John Doe", sn="Doe",)
u.save(slapd_connection)
return u

View file

@ -0,0 +1,2 @@
def test_foobar(slapd_connection, user, client):
assert True

View file

@ -7,35 +7,26 @@ from flask import Flask, g, request
from flask_babel import Babel from flask_babel import Babel
from .oauth2utils import config_oauth from .oauth2utils import config_oauth
from .ldaputils import LDAPObjectHelper
def create_app(config=None): def create_app(config=None):
app = Flask(__name__) app = Flask(__name__)
app.config.from_mapping( app.config.from_mapping({"OAUTH2_REFRESH_TOKEN_GENERATOR": True})
{"OAUTH2_REFRESH_TOKEN_GENERATOR": True,} if config:
) app.config.from_mapping(config)
app.config.from_mapping(toml.load(os.environ.get("CONFIG", "config.toml"))) elif "CONFIG" in os.environ:
app.config.from_mapping(toml.load(os.environ.get("CONFIG")))
app.url_map.strict_slashes = False elif os.path.exists("config.toml"):
app.config.from_mapping(toml.load("config.toml"))
setup_app(app) setup_app(app)
return app return app
def setup_app(app): def setup_app(app):
@app.before_request app.url_map.strict_slashes = False
def before_request():
g.ldap = ldap.initialize(app.config["LDAP"]["URI"])
g.ldap.simple_bind_s(
app.config["LDAP"]["BIND_USER"], app.config["LDAP"]["BIND_PW"]
)
@app.after_request
def after_request(response):
if "ldap" in g:
g.ldap.unbind_s()
return response
config_oauth(app) config_oauth(app)
app.register_blueprint(routes.bp) app.register_blueprint(routes.bp)
@ -44,6 +35,20 @@ def setup_app(app):
babel = Babel(app) babel = Babel(app)
@app.before_request
def before_request():
LDAPObjectHelper.root_dn = app.config["LDAP"]["ROOT_DN"]
g.ldap = ldap.initialize(app.config["LDAP"]["URI"])
g.ldap.simple_bind_s(
app.config["LDAP"]["BIND_DN"], app.config["LDAP"]["BIND_PW"]
)
@app.after_request
def after_request(response):
if "ldap" in g:
g.ldap.unbind_s()
return response
@app.context_processor @app.context_processor
def global_processor(): def global_processor():
return { return {

View file

@ -8,6 +8,7 @@ class LDAPObjectHelper:
may = None may = None
must = None must = None
base = None base = None
root_dn = None
id = None id = None
def __init__(self, dn=None, **kwargs): def __init__(self, dn=None, **kwargs):
@ -29,6 +30,10 @@ class LDAPObjectHelper:
self.__class__.__name__, self.id, getattr(self, self.id) self.__class__.__name__, self.id, getattr(self, self.id)
) )
@classmethod
def ldap(cls):
return g.ldap
def keys(self): def keys(self):
return self.must + self.may return self.must + self.may
@ -40,20 +45,37 @@ class LDAPObjectHelper:
self.__setattr__(k, v) self.__setattr__(k, v)
def delete(self): def delete(self):
g.ldap.delete_s(self.dn) self.ldap().delete_s(self.dn)
@property @property
def dn(self): def dn(self):
if not self.id in self.attrs: if not self.id in self.attrs:
return None return None
return f"{self.id}={self.attrs[self.id][0]},{self.base}" return f"{self.id}={self.attrs[self.id][0]},{self.base},{self.root_dn}"
@classmethod @classmethod
def ocs_by_name(cls): def initialize(cls, conn=None):
conn = conn or cls.ldap()
cls.ocs_by_name(conn)
cls.attr_type_by_name(conn)
dn = f"{cls.base},{cls.root_dn}"
conn.add_s(
dn,
[
("objectClass", [b"organizationalUnit"]),
("ou", [cls.base.encode("utf-8")]),
],
)
@classmethod
def ocs_by_name(cls, conn=None):
if cls._object_class_by_name: if cls._object_class_by_name:
return cls._object_class_by_name return cls._object_class_by_name
res = g.ldap.search_s( conn = conn or cls.ldap()
res = conn.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"] "cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
) )
subschema_entry = res[0] subschema_entry = res[0]
@ -69,11 +91,13 @@ class LDAPObjectHelper:
return cls._object_class_by_name return cls._object_class_by_name
@classmethod @classmethod
def attr_type_by_name(cls): def attr_type_by_name(cls, conn=None):
if cls._attribute_type_by_name: if cls._attribute_type_by_name:
return cls._attribute_type_by_name return cls._attribute_type_by_name
res = g.ldap.search_s( conn = conn or cls.ldap()
res = conn.search_s(
"cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"] "cn=subschema", ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]
) )
subschema_entry = res[0] subschema_entry = res[0]
@ -88,9 +112,10 @@ class LDAPObjectHelper:
return cls._attribute_type_by_name return cls._attribute_type_by_name
def save(self): def save(self, conn=None):
conn = conn or self.ldap()
try: try:
match = bool(g.ldap.search_s(self.dn, ldap.SCOPE_SUBTREE)) match = bool(conn.search_s(self.dn, ldap.SCOPE_SUBTREE))
except ldap.NO_SUCH_OBJECT: except ldap.NO_SUCH_OBJECT:
match = False match = False
@ -99,19 +124,19 @@ class LDAPObjectHelper:
(ldap.MOD_REPLACE, k, [elt.encode("utf-8") for elt in v]) (ldap.MOD_REPLACE, k, [elt.encode("utf-8") for elt in v])
for k, v in self.attrs.items() for k, v in self.attrs.items()
] ]
g.ldap.modify_s(self.dn, attributes) conn.modify_s(self.dn, attributes)
else: else:
attributes = [ attributes = [
(k, [elt.encode("utf-8") for elt in v]) for k, v in self.attrs.items() (k, [elt.encode("utf-8") for elt in v]) for k, v in self.attrs.items()
] ]
g.ldap.add_s(self.dn, attributes) conn.add_s(self.dn, attributes)
@classmethod @classmethod
def get(cls, dn): def get(cls, dn):
if "=" not in dn: if "=" not in dn:
dn = f"{cls.id}={dn},{cls.base}" dn = f"{cls.id}={dn},{cls.base},{cls.root_dn}"
result = g.ldap.search_s(dn, ldap.SCOPE_SUBTREE) result = cls.ldap().search_s(dn, ldap.SCOPE_SUBTREE)
if not result: if not result:
return None return None
@ -127,7 +152,8 @@ class LDAPObjectHelper:
class_filter = "".join([f"(objectClass={oc})" for oc in cls.objectClass]) class_filter = "".join([f"(objectClass={oc})" for oc in cls.objectClass])
arg_filter = "".join(f"({k}={v})" for k, v in kwargs.items()) arg_filter = "".join(f"({k}={v})" for k, v in kwargs.items())
ldapfilter = f"(&{class_filter}{arg_filter})" ldapfilter = f"(&{class_filter}{arg_filter})"
result = g.ldap.search_s(base or cls.base, ldap.SCOPE_SUBTREE, ldapfilter) base = base or f"{cls.base},{cls.root_dn}"
result = cls.ldap().search_s(base, ldap.SCOPE_SUBTREE, ldapfilter)
return [ return [
cls(**{k: [elt.decode("utf-8") for elt in v] for k, v in args.items()},) cls(**{k: [elt.decode("utf-8") for elt in v] for k, v in args.items()},)

View file

@ -11,7 +11,7 @@ from .ldaputils import LDAPObjectHelper
class User(LDAPObjectHelper): class User(LDAPObjectHelper):
objectClass = ["person"] objectClass = ["person"]
base = "ou=users,dc=mydomain,dc=tld" base = "ou=users"
id = "cn" id = "cn"
def check_password(self, password): def check_password(self, password):
@ -24,7 +24,7 @@ class User(LDAPObjectHelper):
class Client(LDAPObjectHelper, ClientMixin): class Client(LDAPObjectHelper, ClientMixin):
objectClass = ["oauthClient"] objectClass = ["oauthClient"]
base = "ou=clients,dc=mydomain,dc=tld" base = "ou=clients"
id = "oauthClientID" id = "oauthClientID"
@property @property
@ -93,7 +93,7 @@ class Client(LDAPObjectHelper, ClientMixin):
class AuthorizationCode(LDAPObjectHelper, AuthorizationCodeMixin): class AuthorizationCode(LDAPObjectHelper, AuthorizationCodeMixin):
objectClass = ["oauthAuthorizationCode"] objectClass = ["oauthAuthorizationCode"]
base = "ou=authorizations,dc=mydomain,dc=tld" base = "ou=authorizations"
id = "oauthCode" id = "oauthCode"
def get_redirect_uri(self): def get_redirect_uri(self):
@ -121,7 +121,7 @@ class AuthorizationCode(LDAPObjectHelper, AuthorizationCodeMixin):
class Token(LDAPObjectHelper, TokenMixin): class Token(LDAPObjectHelper, TokenMixin):
objectClass = ["oauthToken"] objectClass = ["oauthToken"]
base = "ou=tokens,dc=mydomain,dc=tld" base = "ou=tokens"
id = "oauthAccessToken" id = "oauthAccessToken"
def get_client_id(self): def get_client_id(self):