{"id":13609044,"url":"https://github.com/jupyterhub/ldapauthenticator","last_synced_at":"2025-05-14T04:07:33.160Z","repository":{"id":44746290,"uuid":"49804028","full_name":"jupyterhub/ldapauthenticator","owner":"jupyterhub","description":"LDAP Authenticator Plugin for Jupyter","archived":false,"fork":false,"pushed_at":"2025-04-07T20:36:01.000Z","size":318,"stargazers_count":211,"open_issues_count":19,"forks_count":180,"subscribers_count":18,"default_branch":"main","last_synced_at":"2025-05-13T10:16:25.616Z","etag":null,"topics":["authenticator","jupyter","jupyterhub","ldap-authentication"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jupyterhub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-01-17T05:11:49.000Z","updated_at":"2025-04-25T03:34:15.000Z","dependencies_parsed_at":"2024-04-02T08:45:58.374Z","dependency_job_id":"4e77929b-ff87-4f85-98dd-2d62fe9ff986","html_url":"https://github.com/jupyterhub/ldapauthenticator","commit_stats":{"total_commits":112,"total_committers":30,"mean_commits":"3.7333333333333334","dds":0.8303571428571428,"last_synced_commit":"c11e26d9d649efa7849291285f74fd9eecdcf1bb"},"previous_names":["yuvipanda/ldapauthenticator"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fldapauthenticator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fldapauthenticator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fldapauthenticator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fldapauthenticator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jupyterhub","download_url":"https://codeload.github.com/jupyterhub/ldapauthenticator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254069513,"owners_count":22009558,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authenticator","jupyter","jupyterhub","ldap-authentication"],"created_at":"2024-08-01T19:01:32.024Z","updated_at":"2025-05-14T04:07:33.116Z","avatar_url":"https://github.com/jupyterhub.png","language":"Python","funding_links":[],"categories":["JupyterHub认证","JupyterHub Authenticators"],"sub_categories":[],"readme":"# ldapauthenticator\n\n[![Latest PyPI version](https://img.shields.io/pypi/v/jupyterhub-ldapauthenticator?logo=pypi)](https://pypi.python.org/pypi/jupyterhub-ldapauthenticator)\n[![Latest conda-forge version](https://img.shields.io/conda/vn/conda-forge/jupyterhub-ldapauthenticator?logo=conda-forge)](https://anaconda.org/conda-forge/jupyterhub-ldapauthenticator)\n[![GitHub Workflow Status - Test](https://img.shields.io/github/actions/workflow/status/jupyterhub/ldapauthenticator/test.yaml?logo=github\u0026label=tests)](https://github.com/jupyterhub/ldapauthenticator/actions)\n[![Test coverage of code](https://codecov.io/gh/jupyterhub/ldapauthenticator/branch/main/graph/badge.svg)](https://codecov.io/gh/jupyterhub/ldapauthenticator)\n[![Issue tracking - GitHub](https://img.shields.io/badge/issue_tracking-github-blue?logo=github)](https://github.com/jupyterhub/ldapauthenticator/issues)\n[![Help forum - Discourse](https://img.shields.io/badge/help_forum-discourse-blue?logo=discourse)](https://discourse.jupyter.org/c/jupyterhub)\n\nSimple LDAP Authenticator Plugin for JupyterHub\n\n## Installation\n\nYou can install it from pip with:\n\n```\npip install jupyterhub-ldapauthenticator\n```\n\n...or using conda with:\n\n```\nconda install -c conda-forge jupyterhub-ldapauthenticator\n```\n\n## Logging people out\n\nIf you make any changes to JupyterHub's authentication setup that changes\nwhich group of users is allowed to login (such as changing `allowed_groups`\nor even just turning on LDAPAuthenticator), you **must** change the\njupyterhub cookie secret, or users who were previously logged in and did\nnot log out would continue to be able to log in!\n\nYou can do this by deleting the `jupyterhub_cookie_secret` file. Note\nthat this will log out _all_ users who are currently logged in.\n\n## Usage\n\nYou can enable this authenticator by adding lines to your `jupyterhub_config.py`.\n\n**Note: This file may not exist in your current installation! In TLJH, it\nis located in /opt/tljh/config/jupyterhub_config.d. Create it there if you\ndon't already have one.**\n\n```python\nc.JupyterHub.authenticator_class = 'ldap'\n```\n\n### Required configuration\n\nAt minimum, the following two configuration options must be set before\nthe LDAP Authenticator can be used:\n\n#### `LDAPAuthenticator.server_address`\n\nAddress of the LDAP Server to contact. Just use a bare hostname or IP,\nwithout a port name or protocol prefix.\n\n#### `LDAPAuthenticator.lookup_dn` or `LDAPAuthenticator.bind_dn_template`\n\nTo authenticate a user we need the corresponding DN to bind against the LDAP server. The DN can be acquired by either:\n\n1. setting `bind_dn_template`, which is a list of string template used to\n   generate the full DN for a user from the human readable username, or\n2. setting `lookup_dn` to `True`, which does a reverse lookup to obtain the\n   user's DN. This is because some LDAP servers, such as Active Directory, don't\n   always bind with the true DN.\n\n##### `lookup_dn = False`\n\nIf `lookup_dn = False`, then `bind_dn_template` is required to be a non-empty\nlist of templates the users belong to. For example, if some of the users in your\nLDAP database have DN of the form `uid=Yuvipanda,ou=people,dc=wikimedia,dc=org`\nand some other users have DN like `uid=Mike,ou=developers,dc=wikimedia,dc=org`\nwhere `Yuvipanda` and `Mike` are the usernames, you would set this config item\nto be:\n\n```python\nc.LDAPAuthenticator.bind_dn_template = [\n    \"uid={username},ou=people,dc=wikimedia,dc=org\",\n    \"uid={username},ou=developers,dc=wikimedia,dc=org\",\n]\n```\n\nDon't forget the preceeding `c.` for setting configuration parameters! JupyterHub\nuses [traitlets](https://traitlets.readthedocs.io) for configuration, and the\n`c` represents the [config object](https://traitlets.readthedocs.io/en/stable/config.html).\n\nThe `{username}` is expanded into the username the user provides.\n\n##### `lookup_dn = True`\n\n```python\nc.LDAPAuthenticator.lookup_dn = True\n```\n\nIf `bind_dn_template` isn't explicitly configured, i.e. the empty list, the\ndynamically acquired value for DN from the username lookup will be used\ninstead. If `bind_dn_template` is configured it will be used just like in the\n`lookup_dn = False` case.\n\nThe `{username}` is expanded to the full path to the LDAP object returned by the\nLDAP lookup. For example, on an Active Directory system `{username}` might\nexpand to something like `CN=First M. Last,OU=An Example Organizational\nUnit,DC=EXAMPLE,DC=COM`.\n\nAlso, when using `lookup_dn = True` the options `user_search_base`,\n`user_attribute`, `lookup_dn_user_dn_attribute` and `lookup_dn_search_filter`\nare required, although their defaults might be sufficient for your use case.\n\n### Optional configuration\n\n#### `LDAPAuthenticator.allowed_groups`\n\nLDAP groups whose members are allowed to log in. This must be\nset to either empty `[]` (the default, to disable) or to a list of\nfull DNs that have a `member` attribute that includes the current\nuser attempting to log in.\n\nAs an example, to restrict access only to people in groups\n`researcher` or `operations`,\n\n```python\nc.LDAPAuthenticator.allowed_groups = [\n    \"cn=researcher,ou=groups,dc=wikimedia,dc=org\",\n    \"cn=operations,ou=groups,dc=wikimedia,dc=org\",\n]\n```\n\n#### `LDAPAuthenticator.group_search_filter`\n\nThe LDAP group search filter.\n\nThe default value is an LDAP OR search that looks like the following:\n\n```\n(|(member={userdn})(uniqueMember={userdn})(memberUid={uid}))\n```\n\nSo it basically compares the `userdn` attribute against the `member` attribute,\nthen against the `uniqueMember`, and finally checks the `memberUid` against\nthe `uid`.\n\nIf you modify this value, you probably want to change `group_attributes` too.\nHere is an example that should work with OpenLDAP servers.\n\n```\n(member={userdn})\n```\n\n#### `LDAPAuthenticator.group_attributes`\n\nA list of attributes used when searching for LDAP groups.\n\nBy default, it uses `member`, `uniqueMember`, and `memberUid`. Certain\nservers may reject invalid values causing exceptions during\nauthentication.\n\n#### `LDAPAuthenticator.valid_username_regex`\n\nAll usernames will be checked against this before being sent\nto LDAP. This acts as both an easy way to filter out invalid\nusernames as well as protection against LDAP injection attacks.\n\nBy default it looks for the regex `^[a-z][.a-z0-9_-]*$` which\nis what most shell username validators do.\n\n#### `LDAPAuthenticator.use_ssl`\n\n`use_ssl` is deprecated since 2.0. `use_ssl=True` translates to configuring\n`tls_strategy=\"on_connect\"`, but `use_ssl=False` (previous default) doesn't\ntranslate to anything.\n\n#### `LDAPAuthenticator.tls_strategy`\n\nWhen LDAPAuthenticator connects to the LDAP server, it can establish a\nSSL/TLS connection directly, or do it before binding, which is LDAP\nterminology for authenticating and sending sensitive credentials.\n\nThe LDAP v3 protocol deprecated establishing a SSL/TLS connection\ndirectly (`tls_strategy=\"on_connect\"`) in favor of upgrading the\nconnection to SSL/TLS before binding (`tls_strategy=\"before_bind\"`).\n\nSupported `tls_strategy` values are:\n\n- \"before_bind\" (default)\n- \"on_connect\" (deprecated in LDAP v3, associated with use of port 636)\n- \"insecure\"\n\nWhen configuring `tls_strategy=\"on_connect\"`, the default value of\n`server_port` becomes 636.\n\n#### `LDAPAuthenticator.tls_kwargs`\n\nA dictionary that will be used as keyword arguments for the constructor\nof the ldap3 package's Tls object, influencing encrypted connections to\nthe LDAP server.\n\nFor details on what can be configured and its effects, refer to the\nldap3 package's documentation and code:\n\n- ldap3 documentation: https://ldap3.readthedocs.io/en/latest/ssltls.html#the-tls-object\n- ldap3 code: https://github.com/cannatag/ldap3/blob/v2.9.1/ldap3/core/tls.py#L59-L82\n\nYou can for example configure this like:\n\n```python\nc.LDAPAuthenticator.tls_kwargs = {\n    \"ca_certs_file\": \"file/path.here\",\n}\n```\n\n#### `LDAPAuthenticator.server_port`\n\nPort on which to contact the LDAP server.\n\nDefaults to `636` if `tls_strategy=\"on_connect\"` is set, `389`\notherwise.\n\n#### `LDAPAuthenticator.user_search_base`\n\nOnly used with `lookup_dn=True` or with a configured `search_filter`.\n\nDefines the search base for looking up users in the directory.\n\n```python\nc.LDAPAuthenticator.user_search_base = 'ou=People,dc=example,dc=com'\n```\n\nLDAPAuthenticator will search all objects matching under this base where\nthe `user_attribute` is set to the current username to form the userdn.\n\nFor example, if all users objects existed under the base\n`ou=people,dc=wikimedia,dc=org`, and the username users use is set with\nthe attribute `uid`, you can use the following config:\n\n```python\nc.LDAPAuthenticator.lookup_dn = True\nc.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'\nc.LDAPAuthenticator.lookup_dn_search_user = 'ldap_search_user_technical_account'\nc.LDAPAuthenticator.lookup_dn_search_password = 'secret'\nc.LDAPAuthenticator.user_search_base = 'ou=people,dc=wikimedia,dc=org'\nc.LDAPAuthenticator.user_attribute = 'uid'\nc.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'\n```\n\n#### `LDAPAuthenticator.user_attribute`\n\nOnly used with `lookup_dn=True` or with a configured `search_filter`.\n\nTogether with `user_search_base`, this attribute will be searched to\ncontain the username provided by the user in JupyterHub's login form.\n\n```python\n# Active Directory\nc.LDAPAuthenticator.user_attribute = 'sAMAccountName'\n\n# OpenLDAP\nc.LDAPAuthenticator.user_attribute = 'uid'\n```\n\n#### `LDAPAuthenticator.lookup_dn_search_filter`\n\nOnly used with `lookup_dn=True`.\n\nHow to query LDAP for user name lookup.\n\nDefault value `'({login_attr}={login})'` should be good enough for most\nuse cases.\n\n#### `LDAPAuthenticator.lookup_dn_search_user`, `LDAPAuthenticator.lookup_dn_search_password`\n\nOnly used with `lookup_dn=True`.\n\nTechnical account for user lookup. If both `lookup_dn_search_user` and\n`lookup_dn_search_password` are None, then anonymous LDAP query will be\ndone.\n\n#### `LDAPAuthenticator.lookup_dn_user_dn_attribute`\n\nOnly used with `lookup_dn=True`.\n\nAttribute containing user's name needed for building DN string.\nSee `user_search_base` for info on how this attribute is used.\nFor most LDAP servers, this is username. For Active Directory, it is cn.\n\n#### `LDAPAuthenticator.auth_state_attributes`\n\nAn optional list of attributes to be fetched for a user after login.\nIf found, these will be available as `auth_state[\"user_attributes\"]`.\n\n#### `LDAPAuthenticator.use_lookup_dn_username`\n\nOnly used with `lookup_dn=True`.\n\nIf configured True, the `lookup_dn_user_dn_attribute` value used to\nbuild the LDAP user's DN string is also used as the authenticated user's\nJuptyerHub username.\n\nIf this is configured True, its important to ensure that the values of\n`lookup_dn_user_dn_attribute` are unique even after the are normalized\nto be lowercase, otherwise two LDAP users could end up sharing the same\nJupyterHub username.\n\nWith ldapauthenticator 2, the default value was changed to False.\n\n#### `LDAPAuthenticator.search_filter`\n\nLDAP3 Search Filter to limit allowed users.\n\nThat a unique LDAP user is identified with the search_filter is\nnecessary but not sufficient to grant access. Grant access by setting\none or more of `allowed_users`, `allow_all`, `allowed_groups`, etc.\n\nUsers who do not match this filter cannot be allowed\nby any other configuration.\n\nThe search filter string will be expanded, so that:\n\n- `{userattr}` is replaced with the `user_attribute` config's value.\n- `{username}` is replaced with an escaped username, either provided\n  directly or previously looked up with `lookup_dn` configured.\n\n#### `LDAPAuthenticator.attributes`\n\nList of attributes to be passed in the LDAP search with `search_filter`.\n\n## Compatibility\n\nThis has been tested against an OpenLDAP server, with the client\nrunning Python 3.4. Verifications of this code working well with\nother LDAP setups are welcome, as are bug reports and patches to make\nit work with other LDAP setups!\n\n## Active Directory integration\n\nPlease use following options for AD integration. This is useful especially in two cases:\n\n- LDAP Search requires valid user account in order to query user database\n- DN does not contain login but some other field, like CN (actual login is present in sAMAccountName, and we need to lookup CN)\n\n```python\nc.LDAPAuthenticator.lookup_dn = True\nc.LDAPAuthenticator.lookup_dn_search_filter = '({login_attr}={login})'\nc.LDAPAuthenticator.lookup_dn_search_user = 'ldap_search_user_technical_account'\nc.LDAPAuthenticator.lookup_dn_search_password = 'secret'\nc.LDAPAuthenticator.user_search_base = 'ou=people,dc=wikimedia,dc=org'\nc.LDAPAuthenticator.user_attribute = 'sAMAccountName'\nc.LDAPAuthenticator.lookup_dn_user_dn_attribute = 'cn'\n```\n\nIn setup above, first LDAP will be searched (with account ldap_search_user_technical_account) for users that have sAMAccountName=login\nThen DN will be constructed using found CN value.\n\n## Configuration note on local user creation\n\nCurrently, local user creation by the LDAPAuthenticator is unsupported as\nthis is insecure since there's no cleanup method for these created users. As a\nresult, users who are disabled in LDAP will have access to this for far longer.\n\nAlternatively, there's good support in Linux for integrating LDAP into the\nsystem user setup directly, and users can just use PAM (which is supported in\nnot just JupyterHub, but ssh and a lot of other tools) to log in. You can see\nhttp://www.tldp.org/HOWTO/archived/LDAP-Implementation-HOWTO/pamnss.html and\nlots of other documentation on the web on how to set up LDAP to provide user\naccounts for your system. Those methods are very widely used, much more secure\nand more widely documented. We recommend you use them rather than have\nJupyterHub create local accounts using the LDAPAuthenticator.\n\nIssue [#19](https://github.com/jupyterhub/ldapauthenticator/issues/19) provides\nadditional discussion on local user creation.\n\n## Handling SSL/TLS handshake errors\n\nIf you have received a SSL/TLS handshake error, it could be that no [cipher\nsuite] accepted by LDAPAuthenticator is also accepted by the LDAP server. This\nis likely because LDAPAuthenticator is stricter than the LDAP server and only\naccepts modern cipher suites than the LDAP server doesn't accept. Due to this,\nyou should from a security perspective ideally modernize the LDAP server's\naccepted cipher suites rather than expand the LDAPAuthenticator accepted cipher\nsuites to include older cipher suites.\n\nThe cipher suites that LDAPAuthenticator accepted by default come from\n[ssl.create_default_context().get_ciphers()], which in turn can change with\nPython version. Upgrading Python from 3.7 - 3.9 to 3.10 - 3.13 is known to\nstrictly reduce the set of accepted cipher suites from 30 to 17 for example. Due\nto this, upgrading Python could lead to observing a handshake error previously\nnot observed.\n\nIf you want to configure LDAPAuthenticator to accept older cipher suites instead\nof updating the LDAP server to accept modern cipher suites, you can do it using\n`LDAPAuthenticator.tls_kwargs` as demonstrated below.\n\n```python\n# default cipher suites accepted by LDAPAuthenticator in Python 3.7 - 3.9\n# it includes 30 cipher suites, where 13 of them were considered less secure\n# and removed as default cipher suites in Python 3.10\nold_ciphers_list_considered_less_secure = \"AES128-SHA:AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES128-SHA256:AES256-GCM-SHA384:AES256-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-CHACHA20-POLY1305:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\"\n\n# default cipher suites accepted by LDAPAuthenticator in Python 3.10 - 3.13\n# this list includes 17 cipher suites out of the 30 in the old list, with no\n# new additions\nnew_ciphers_list = \"DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-CHACHA20-POLY1305:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256\"\n\nc.LDAPAuthenticator.tls_kwargs = {\n    \"ciphers\": old_ciphers_list_considered_less_secure,\n}\n```\n\nFor reference, you can use a command like below to see what the default cipher\nsuites LDAPAuthenticator will use in various Python versions.\n\n```shell\ndocker run -it --rm python:3.13 python -c 'import ssl; c = ssl.create_default_context(); print(\":\".join(sorted([c[\"name\"] for c in c.get_ciphers()])))'\n```\n\n[cipher suite]: https://en.wikipedia.org/wiki/Cipher_suite#Full_handshake:_coordinating_cipher_suites\n[ssl.create_default_context().get_ciphers()]: https://docs.python.org/3/library/ssl.html#ssl.create_default_context\n\n## Testing LDAPAuthenticator without JupyterHub\n\nThis script can be written to a file such as `test_ldap_auth.py`, and run with\n`python test_ldap_auth.py`, to test use of LDAPAuthenticator with a given config\nwithout involving JupyterHub.\n\nIf the authenticator works, this script should print either None or a username\ndepending if the user was considered allowed access.\n\n```python\nimport asyncio\nimport getpass\n\nfrom traitlets.config import Config\nfrom ldapauthenticator import LDAPAuthenticator\n\n# Configure LDAPAuthenticator below to work against your ldap server\nc = Config()\nc.LDAPAuthenticator.server_address = \"ldap.organisation.org\"\nc.LDAPAuthenticator.server_port = 636\nc.LDAPAuthenticator.bind_dn_template = \"uid={username},ou=people,dc=organisation,dc=org\"\nc.LDAPAuthenticator.user_attribute = \"uid\"\nc.LDAPAuthenticator.user_search_base = \"ou=people,dc=organisation,dc=org\"\nc.LDAPAuthenticator.attributes = [\"uid\", \"cn\", \"mail\", \"ou\", \"o\"]\n# The following is an example of a search_filter which is build on LDAP AND and OR operations\n# here in this example as a combination of the LDAP attributes 'ou', 'mail' and 'uid'\nsf = \"(\u0026(o={o})(ou={ou}))\".format(o=\"yourOrganisation\", ou=\"yourOrganisationalUnit\")\nsf += \"(\u0026(o={o})(mail={mail}))\".format(o=\"yourOrganisation\", mail=\"yourMailAddress\")\nc.LDAPAuthenticator.search_filter = f\"(\u0026({{userattr}}={{username}})(|{sf}))\"\n\n# Run test\nauthenticator = LDAPAuthenticator(config=c)\nusername = input(\"Username: \")\npassword = getpass.getpass()\ndata = dict(username=username, password=password)\nreturn_value = asyncio.run(authenticator.authenticate(None, data))\nprint(return_value)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupyterhub%2Fldapauthenticator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjupyterhub%2Fldapauthenticator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupyterhub%2Fldapauthenticator/lists"}