Skip to content
Snippets Groups Projects
Commit 76b16954 authored by nd's avatar nd
Browse files

Merge branch 'mock-ldap' into 'master'

Resolve "mock ldap"

Closes #20

See merge request infra/uffd/uffd!1
parents 10e9e695 17892c4b
No related branches found
No related tags found
No related merge requests found
# uffd # uffd
This is the UserFrwaltungsFrontend. This is the UserFerwaltungsFrontend.
A web service to manage LDAP users, groups and permissions. A web service to manage LDAP users, groups and permissions.
## dependencies ## dependencies
...@@ -10,6 +10,13 @@ A web service to manage LDAP users, groups and permissions. ...@@ -10,6 +10,13 @@ A web service to manage LDAP users, groups and permissions.
- python3-flask-sqlalchemy - python3-flask-sqlalchemy
- git (cli utility, musst be in path) - git (cli utility, musst be in path)
## development
During development, you may want to enable LDAP mocking, as you otherwise need to have access to an actual LDAP server with the required schema.
You can do so by setting `LDAP_SERVICE_MOCK=True` in the config.
Afterwards you can login as a normal user with "testuser" and "userpassword", or as an admin with "testadmin" and "adminpassword".
Please note that the mocked LDAP functionality is very limited and many uffd features do not work correctly without a real LDAP server.
## deployment ## deployment
Use uwsgi. Use uwsgi.
......
{
"entries": [
{
"dn": "uid=testuser,ou=users,dc=example,dc=com",
"raw": {
"cn": [
"testuser"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"displayName": [
"Test User"
],
"entryDN": [
"uid=testuser,ou=users,dc=example,dc=com"
],
"entryUUID": [
"75e62c6a-03c2-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"givenName": [
"testuser"
],
"hasSubordinates": [
"FALSE"
],
"homeDirectory": [
"/home/testuser"
],
"mail": [
"testuser@example.com"
],
"memberOf": [
"cn=uffd_access,ou=groups,dc=example,dc=com",
"cn=users,ou=groups,dc=example,dc=com"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"top",
"inetOrgPerson",
"organizationalPerson",
"person",
"posixAccount"
],
"sn": [
" "
],
"structuralObjectClass": [
"inetOrgPerson"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uid": [
"testuser"
],
"uidNumber": [
"10000"
],
"userPassword": [
"userpassword"
]
}
},
{
"dn": "uid=testadmin,ou=users,dc=example,dc=com",
"raw": {
"cn": [
"testadmin"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"displayName": [
"Test Admin"
],
"entryDN": [
"uid=testadmin,ou=users,dc=example,dc=com"
],
"entryUUID": [
"678c8470-03c2-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"givenName": [
"Testadmin"
],
"hasSubordinates": [
"FALSE"
],
"homeDirectory": [
"/home/testadmin"
],
"mail": [
"testadmin@example.com"
],
"memberOf": [
"cn=users,ou=groups,dc=example,dc=com",
"cn=uffd_access,ou=groups,dc=example,dc=com",
"cn=uffd_admin,ou=groups,dc=example,dc=com"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"top",
"inetOrgPerson",
"organizationalPerson",
"person",
"posixAccount"
],
"sn": [
" "
],
"structuralObjectClass": [
"inetOrgPerson"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uid": [
"testadmin"
],
"uidNumber": [
"10001"
],
"userPassword": [
"adminpassword"
]
}
},
{
"dn": "cn=users,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"users"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"Base group for all users"
],
"entryDN": [
"cn=users,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"1aec0e8c-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20001"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"uid=testuser,ou=users,dc=example,dc=com",
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
},
{
"dn": "cn=uffd_access,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"uffd_access"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"User access to uffd selfservice"
],
"entryDN": [
"cn=uffd_access,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"4fc8dd60-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20002"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"cn=dummy,ou=system,dc=example,dc=com",
"uid=testuser,ou=users,dc=example,dc=com",
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
},
{
"dn": "cn=uffd_admin,ou=groups,dc=example,dc=com",
"raw": {
"cn": [
"uffd_admin"
],
"createTimestamp": [
"20200101000000Z"
],
"creatorsName": [
"cn=admin,dc=example,dc=com"
],
"description": [
"Admin access to uffd selfservice"
],
"entryDN": [
"cn=uffd_admin,ou=groups,dc=example,dc=com"
],
"entryUUID": [
"b5d869d6-03c3-11eb-adc1-0242ac120002"
],
"gidNumber": [
"20003"
],
"hasSubordinates": [
"FALSE"
],
"modifiersName": [
"cn=admin,dc=example,dc=com"
],
"modifyTimestamp": [
"20200101000000Z"
],
"objectClass": [
"posixGroup",
"groupOfUniqueNames",
"top"
],
"structuralObjectClass": [
"groupOfUniqueNames"
],
"subschemaSubentry": [
"cn=Subschema"
],
"uniqueMember": [
"uid=testadmin,ou=users,dc=example,dc=com"
]
}
}
]
}
{
"raw": {
"altServer": [],
"configContext": [
"cn=config"
],
"entryDN": [
""
],
"namingContexts": [
"dc=example,dc=com"
],
"objectClass": [
"top",
"OpenLDAProotDSE"
],
"structuralObjectClass": [
"OpenLDAProotDSE"
],
"subschemaSubentry": [
"cn=Subschema"
],
"supportedCapabilities": [],
"supportedControl": [
"2.16.840.1.113730.3.4.18",
"2.16.840.1.113730.3.4.2",
"1.3.6.1.4.1.4203.1.10.1",
"1.3.6.1.1.22",
"1.2.840.113556.1.4.319",
"1.2.826.0.1.3344810.2.3",
"1.3.6.1.1.13.2",
"1.3.6.1.1.13.1",
"1.3.6.1.1.12"
],
"supportedExtension": [
"1.3.6.1.4.1.1466.20037",
"1.3.6.1.4.1.4203.1.11.1",
"1.3.6.1.4.1.4203.1.11.3",
"1.3.6.1.1.8"
],
"supportedFeatures": [
"1.3.6.1.1.14",
"1.3.6.1.4.1.4203.1.5.1",
"1.3.6.1.4.1.4203.1.5.2",
"1.3.6.1.4.1.4203.1.5.3",
"1.3.6.1.4.1.4203.1.5.4",
"1.3.6.1.4.1.4203.1.5.5"
],
"supportedLDAPVersion": [
"3"
],
"supportedSASLMechanisms": [
"DIGEST-MD5",
"CRAM-MD5",
"NTLM"
],
"vendorName": [],
"vendorVersion": []
},
"type": "DsaInfo"
}
This diff is collapsed.
...@@ -26,7 +26,7 @@ def create_app(test_config=None): ...@@ -26,7 +26,7 @@ def create_app(test_config=None):
if not test_config: if not test_config:
# load the instance config, if it exists, when not testing # load the instance config, if it exists, when not testing
app.config.from_pyfile(os.path.join(app.instance_path, 'config.cfg')) app.config.from_pyfile(os.path.join(app.instance_path, 'config.cfg'), silent=True)
else: else:
# load the test config if passed in # load the test config if passed in
app.config.from_mapping(test_config) app.config.from_mapping(test_config)
......
...@@ -13,13 +13,12 @@ LDAP_USER_MAX_UID=18999 ...@@ -13,13 +13,12 @@ LDAP_USER_MAX_UID=18999
SESSION_LIFETIME_SECONDS=3600 SESSION_LIFETIME_SECONDS=3600
ACL_LDAP_GROUP_USEREDIT="admins" ACL_ADMIN_GROUP="uffd_admin"
ACL_ADMIN_GROUP="admin" ACL_SELFSERVICE_GROUP="uffd_access"
ACL_SELFSERVICE_GROUP="user"
MAIL_SERVER='smtp.gmail.com' MAIL_SERVER='' # e.g. example.com
MAIL_PORT=465 MAIL_PORT=465
MAIL_USERNAME='yourId@gmail.com' MAIL_USERNAME='yourId@example.com'
MAIL_PASSWORD='*****' MAIL_PASSWORD='*****'
MAIL_USE_STARTTLS=True MAIL_USE_STARTTLS=True
MAIL_FROM_ADDRESS='foo@bar.com' MAIL_FROM_ADDRESS='foo@bar.com'
...@@ -36,6 +35,7 @@ FOOTER_LINKS=[{"url": "https://example.com", "title": "example"}] ...@@ -36,6 +35,7 @@ FOOTER_LINKS=[{"url": "https://example.com", "title": "example"}]
#TEMPLATES_AUTO_RELOAD=True #TEMPLATES_AUTO_RELOAD=True
#SQLALCHEMY_ECHO=True #SQLALCHEMY_ECHO=True
#FLASK_ENV=development #FLASK_ENV=development
#LDAP_SERVICE_MOCK=True
# DO set in production # DO set in production
......
...@@ -4,7 +4,7 @@ from flask import Blueprint, current_app ...@@ -4,7 +4,7 @@ from flask import Blueprint, current_app
from ldap3.utils.conv import escape_filter_chars from ldap3.utils.conv import escape_filter_chars
from ldap3.core.exceptions import LDAPBindError, LDAPCursorError from ldap3.core.exceptions import LDAPBindError, LDAPCursorError
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, MOCK_SYNC
bp = Blueprint("ldap", __name__) bp = Blueprint("ldap", __name__)
...@@ -16,13 +16,38 @@ def fix_connection(conn): ...@@ -16,13 +16,38 @@ def fix_connection(conn):
conn.search = search conn.search = search
return conn return conn
def get_mock_conn():
if not current_app.debug:
raise Exception('LDAP_SERVICE_MOCK cannot be enabled on production instances')
# Entries are stored in-memory in the mocked `Connection` object. To make
# changes persistent across requests we reuse the same `Connection` object
# for all calls to `service_conn()` and `user_conn()`.
if not hasattr(current_app, 'ldap_mock'):
server = Server.from_definition('ldap_mock', 'ldap_server_info.json', 'ldap_server_schema.json')
current_app.ldap_mock = fix_connection(Connection(server, client_strategy=MOCK_SYNC))
current_app.ldap_mock.strategy.entries_from_json('ldap_server_entries.json')
current_app.ldap_mock.bind()
return current_app.ldap_mock
def service_conn(): def service_conn():
if current_app.config.get('LDAP_SERVICE_MOCK', False):
return get_mock_conn()
server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL) server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
return fix_connection(Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True)) return fix_connection(Connection(server, current_app.config["LDAP_SERVICE_BIND_DN"], current_app.config["LDAP_SERVICE_BIND_PASSWORD"], auto_bind=True))
def user_conn(loginname, password): def user_conn(loginname, password):
if not loginname_is_safe(loginname): if not loginname_is_safe(loginname):
return False return False
if current_app.config.get('LDAP_SERVICE_MOCK', False):
conn = get_mock_conn()
# Since we reuse the same conn for all calls to `user_conn()` we
# simulate the password check by rebinding. Note that ldap3's mocking
# implementation just compares the string in the objects's userPassword
# field with the password, no support for hashing or OpenLDAP-style
# password-prefixes ("{PLAIN}..." or "{ssha512}...").
if not conn.rebind(loginname_to_dn(loginname), password):
return False
return get_mock_conn()
server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL) server = Server(current_app.config["LDAP_SERVICE_URL"], get_info=ALL)
try: try:
return fix_connection(Connection(server, loginname_to_dn(loginname), password, auto_bind=True)) return fix_connection(Connection(server, loginname_to_dn(loginname), password, auto_bind=True))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment