Evaluate decorator/Flask-like API
Some ideas for a cleaner API.
The current API is based on subclassing LDAPRequestHandler and implementing/overwriting the methods you need. In case of optional features (SASL auth, extension support), this requires setting both overwriting the method and setting a feature flag. A decorator-based approach for registering handlers seems more powerful and more intuitive:
server = ldapserver.Server(ldapserver.schema.RFC2307BIS_SCHEMA)
@server.bind_sasl_plain
def bind_sasl_plain(identity, password, authzid=None):
pass # ...
@server.bind_sasl('CUSTOM')
def bind_sasl_custom(mechanism, credentials=None, dn=None):
pass # ...
@server.extended('1.3.6.1.4.1.4203.1.11.3')
def who_am_i(value=None):
return b'foobar'
Decorators could also be used for entry objects to implement e.g. BIND locally for a specific object or a group of objects:
service = server.add_entry('cn=service,ou=system,dc=example,dc=com',
objectclass=['top', 'organizationalRole', 'simpleSecurityObject'],
structuralObjectClass=['organizationalRole'])
@service.bind
def service_bind(password):
if password == bind_password:
return
raise LDAPInvalidCredentials()
Templating could be extended with a custom DSL similar to Python format strings:
@server.template('uid={loginname},ou=users,dc=example,dc=com',
structuralObjectClass=['inetorgperson'],
objectClass=['top', 'inetorgperson', 'organizationalperson', 'person', 'posixaccount'],
sn=[' '],
cn=['{displayname}'],
displayname=['{displayname}'],
givenname=['{displayname}'],
homeDirectory=['/home/{loginname}'],
mail=['{email}'],
uid=['{loginname}'],
uidNumber=['{uid}'],
memberOf=['cn={*group_names},ou=groups,dc=example,dc=com'])
# Decorators could be used in a Flask-like fasion to add ACL checks to lookup functions
@ldapserver.disallow_unauthenticated
def users(loginname=None, uid=None, group_names=None, **kwargs):
request_params = {}
if not request_params and loginname is not None:
request_params = {'loginname': normalize_user_loginname(loginname)}
if not request_params and uid is not None:
request_params = {'id': uid}
if not request_params and group_names:
request_params = {'group': normalize_group_name(group_names[0])}
for user in api.get_users(**request_params):
yield users.create(loginname=user['loginname'], displayname=user['displayname'],
email=user['email'], uid=user['id'], groups=user['groups'])
@users.bind
def users_bind(entry, password):
if api.check_password(loginname=entry['uid'][0], password=password):
return
raise LDAPInvalidCredentials()