ldap-ui 0.10.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. ldap_ui-0.10.3/LICENSE.txt +7 -0
  2. ldap_ui-0.10.3/PKG-INFO +167 -0
  3. ldap_ui-0.10.3/README.md +145 -0
  4. ldap_ui-0.10.3/backend/ldap_ui/__init__.py +1 -0
  5. ldap_ui-0.10.3/backend/ldap_ui/__main__.py +88 -0
  6. ldap_ui-0.10.3/backend/ldap_ui/app.py +90 -0
  7. ldap_ui-0.10.3/backend/ldap_ui/entities.py +50 -0
  8. ldap_ui-0.10.3/backend/ldap_ui/ldap_api.py +569 -0
  9. ldap_ui-0.10.3/backend/ldap_ui/ldap_helpers.py +162 -0
  10. ldap_ui-0.10.3/backend/ldap_ui/schema.py +158 -0
  11. ldap_ui-0.10.3/backend/ldap_ui/settings.py +108 -0
  12. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-B-jkhYfk.woff2 +0 -0
  13. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-CDK5bt4p.woff +0 -0
  14. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-CQDK8MU3.ttf +0 -0
  15. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-D13rzr4g.svg +2671 -0
  16. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-G5YE5S7X.eot +0 -0
  17. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-BxbE5Fxl.js +19 -0
  18. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-BxbE5Fxl.js.gz +0 -0
  19. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-sA8CL-9V.css +4 -0
  20. ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-sA8CL-9V.css.gz +0 -0
  21. ldap_ui-0.10.3/backend/ldap_ui/statics/favicon.ico +0 -0
  22. ldap_ui-0.10.3/backend/ldap_ui/statics/index.html +24 -0
  23. ldap_ui-0.10.3/backend/ldap_ui.egg-info/PKG-INFO +167 -0
  24. ldap_ui-0.10.3/backend/ldap_ui.egg-info/SOURCES.txt +28 -0
  25. ldap_ui-0.10.3/backend/ldap_ui.egg-info/dependency_links.txt +1 -0
  26. ldap_ui-0.10.3/backend/ldap_ui.egg-info/entry_points.txt +2 -0
  27. ldap_ui-0.10.3/backend/ldap_ui.egg-info/requires.txt +4 -0
  28. ldap_ui-0.10.3/backend/ldap_ui.egg-info/top_level.txt +1 -0
  29. ldap_ui-0.10.3/pyproject.toml +46 -0
  30. ldap_ui-0.10.3/setup.cfg +4 -0
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2023
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.4
2
+ Name: ldap-ui
3
+ Version: 0.10.3
4
+ Summary: A fast and versatile LDAP editor
5
+ Author: dnknth
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/dnknth/ldap-ui
8
+ Keywords: ldap,web-ui,python3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Software Development :: Libraries
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE.txt
17
+ Requires-Dist: fastapi>=0.115.12
18
+ Requires-Dist: python-ldap
19
+ Requires-Dist: python-multipart
20
+ Requires-Dist: uvicorn>=0.34.3
21
+ Dynamic: license-file
22
+
23
+ # Fast and versatile LDAP editor
24
+
25
+ This is a *minimal* web interface for LDAP directories. Docker images for `linux/amd64` and `linux/arm64/v8` are [available](https://hub.docker.com/r/dnknth/ldap-ui).
26
+
27
+ ![Screenshot](https://github.com/dnknth/ldap-ui/blob/main/screenshot.png?raw=true)
28
+
29
+ Features:
30
+
31
+ * Directory tree view
32
+ * Entry creation / modification / deletion
33
+ * LDIF import / export
34
+ * Image support for the `jpegPhoto` and `thumbnailPhoto` attributes
35
+ * Schema aware
36
+ * Simple search (configurable)
37
+ * Asynchronous LDAP backend with decent scalability
38
+ * Available as [Docker image](https://hub.docker.com/r/dnknth/ldap-ui/)
39
+
40
+ The app always requires authentication, even if the directory permits anonymous access. User credentials are validated through a simple `bind` on the directory (SASL is not supported). What a particular user can see (and edit) is governed entirely by directory access rules. The app shows the directory contents, nothing less and nothing more.
41
+
42
+ ## Usage
43
+
44
+ ### Environment variables
45
+
46
+ LDAP access is controlled by the following optional environment variables, possibly from a `.env` file:
47
+
48
+ * `LDAP_URL`: Connection URL, defaults to `ldap:///`.
49
+ * `BASE_DN`: Search base, e.g. `dc=example,dc=org`.
50
+ * `SCHEMA_DN`: # DN to obtain the directory schema, e.g. `cn=subSchema`.
51
+ * `LOGIN_ATTR`: User name attribute, defaults to `uid`.
52
+
53
+ * `USE_TLS`: Enable TLS, defaults to true for `ldaps` connections. Set it to a non-empty string to force `STARTTLS` on `ldap` connections.
54
+ * `INSECURE_TLS`: Do not require a valid server TLS certificate, defaults to false, implies `USE_TLS`.
55
+
56
+ if `BASE_DN` or `SCHEMA_DN` are not provided explicitly, auto-detection from the root DSE is attempted.
57
+ For this to work, the root DSE must be readable anonymously, e.g. with the following ACL line for OpenLDAP:
58
+
59
+ ```text
60
+ access to dn.base="" by * read
61
+ ```
62
+
63
+ For finer-grained control, see [settings.py](settings.py).
64
+
65
+ ### Docker
66
+
67
+ For the impatient: Run it with
68
+
69
+ ```shell
70
+ docker run -p 127.0.0.1:5000:5000 \
71
+ -e LDAP_URL=ldap://your.openldap.server/ \
72
+ dnknth/ldap-ui:latest
73
+ ```
74
+
75
+ For the even more impatient: Start a demo with
76
+
77
+ ```shell
78
+ docker compose up -d
79
+ ```
80
+
81
+ and go to <http://localhost:5000/>. You are automatically logged in as `Fred Flintstone`.
82
+
83
+ ### Pip
84
+
85
+ Install the `python-ldap` dependency with your system's package manager.
86
+ Otherwise, Pip will try to compile it from source and this will likely fail because it lacks a development environment.
87
+
88
+ Then install `ldap-ui` in a virtual environment:
89
+
90
+ ```shell
91
+ python3 -m venv --system-site-packages venv
92
+ . venv/bin/activate
93
+ pip3 install ldap-ui
94
+ ```
95
+
96
+ Possibly after a shell `rehash`, it is available as `ldap-ui`:
97
+
98
+ ```text
99
+ Usage: ldap-ui [OPTIONS]
100
+
101
+ Options:
102
+ -b, --base-dn TEXT LDAP base DN. Required unless the BASE_DN
103
+ environment variable is set.
104
+ -h, --host TEXT Bind socket to this host. [default:
105
+ 127.0.0.1]
106
+ -p, --port INTEGER Bind socket to this port. If 0, an available
107
+ port will be picked. [default: 5000]
108
+ -l, --log-level [critical|error|warning|info|debug|trace]
109
+ Log level. [default: info]
110
+ --version Display the current version and exit.
111
+ --help Show this message and exit.
112
+ ```
113
+
114
+ ## Development
115
+
116
+ Prerequisites:
117
+
118
+ * [GNU make](https://www.gnu.org/software/make/)
119
+ * [node.js](https://nodejs.dev) LTS version with NPM
120
+ * [Python3](https://www.python.org) ≥ 3.7
121
+ * [pip3](https://packaging.python.org/tutorials/installing-packages/)
122
+ * [python-ldap](https://pypi.org/project/python-ldap/); To compile the Python module:
123
+ * Debian / Ubuntu: `apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev`
124
+ * RedHat / CentOS: `yum install python-devel openldap-devel`
125
+
126
+ `ldap-ui` consists of a Vue frontend and a Python backend that roughly translates a subset of the LDAP protocol to a stateless ReST API.
127
+
128
+ For the frontend, `npm run build` assembles everything in `backend/ldap_ui/statics`.
129
+
130
+ Review the configuration in [settings.py](settings.py). It is short and mostly self-explaining.
131
+ Most settings can (and should) be overridden by environment variables or settings in a `.env` file; see [env.demo](env.demo) or [env.example](env.example).
132
+
133
+ The backend can be run locally with `make`, which will also install dependencies and build the frontend if needed.
134
+
135
+ ## Notes
136
+
137
+ ### Authentication methods
138
+
139
+ The UI always uses a simple `bind` operation to authenticate with the LDAP directory. How the `bind` DN is obtained from a given user name depends on a combination of OS environment variables, possibly from a `.env` file:
140
+
141
+ 1. Search by some attribute. By default, this is the `uid`, which can be overridden by the environment variable `LOGIN_ATTR`, e.g. `LOGIN_ATTR=cn`.
142
+ 2. If the environment variable `BIND_PATTERN` is set, then no search is performed. Login with a full DN can be configured with `BIND_PATTERN=%s`, which for example allows to login as user `cn=admin,dc=example,dc=org`. If a partial DN like `BIND_PATTERN=%s,dc=example,dc=org` is configured, the corresponding login would be `cn=admin`. If a specific pattern like `BIND_PATTERN=cn=%s,dc=example,dc=org` is configured, the login name is just `admin`.
143
+ 3. If security is no concern, then a fixed `BIND_DN` and `BIND_PASSWORD` can be set in the environment. This is for demo purposes only, and probably a very bad idea if access to the UI is not restricted by any other means.
144
+
145
+ ### Searching
146
+
147
+ Search uses a (configurable) set of criteria (`cn`, `gn`, `sn`, and `uid`) if the query does not contain `=`.
148
+ Wildcards are supported, e.g. `f*` will match all `cn`, `gn`, `sn`, and `uid` starting with `f`.
149
+ Additionally, arbitrary attributes can be searched with an LDAP filter specification, for example `sn=F*`.
150
+
151
+ ### Caveats
152
+
153
+ * The software works with [OpenLdap](http://www.openldap.org) using simple bind. Other directories have not been tested much, although [389 DS](https://www.port389.org) works to some extent.
154
+ * SASL authentication schemes are presently not supported.
155
+ * Passwords are transmitted as plain text. The LDAP server is expected to hash them (OpenLdap 2.4 does). I strongly recommend to expose the app through a TLS-enabled web server.
156
+ * HTTP *Basic Authentication* is triggered unless the `AUTHORIZATION` request variable is already set by some upstream HTTP server.
157
+
158
+ ## Q&A
159
+
160
+ * Q: Why are some fields not editable?
161
+ * A: The RDN of an entry is read-only. To change it, rename the entry with a different RDN, then change the old RDN and rename back. To change passwords, click on the question mark icon on the right side. Binary fields (as per schema) are read-only. You do not want to modify them accidentally.
162
+ * Q: Why did you write this?
163
+ * A: [PHPLdapAdmin](http://phpldapadmin.sf.net/) has not seen updates for ages. I needed a replacement, and wanted to try Vue.
164
+
165
+ ## Acknowledgements
166
+
167
+ The Python backend uses [FastAPI](https://fastapi.tiangolo.com). The UI is built with [Vue.js](https://vuejs.org) and [Tailwind CSS](https://tailwindcss.com/). Kudos to the authors of these elegant frameworks!
@@ -0,0 +1,145 @@
1
+ # Fast and versatile LDAP editor
2
+
3
+ This is a *minimal* web interface for LDAP directories. Docker images for `linux/amd64` and `linux/arm64/v8` are [available](https://hub.docker.com/r/dnknth/ldap-ui).
4
+
5
+ ![Screenshot](https://github.com/dnknth/ldap-ui/blob/main/screenshot.png?raw=true)
6
+
7
+ Features:
8
+
9
+ * Directory tree view
10
+ * Entry creation / modification / deletion
11
+ * LDIF import / export
12
+ * Image support for the `jpegPhoto` and `thumbnailPhoto` attributes
13
+ * Schema aware
14
+ * Simple search (configurable)
15
+ * Asynchronous LDAP backend with decent scalability
16
+ * Available as [Docker image](https://hub.docker.com/r/dnknth/ldap-ui/)
17
+
18
+ The app always requires authentication, even if the directory permits anonymous access. User credentials are validated through a simple `bind` on the directory (SASL is not supported). What a particular user can see (and edit) is governed entirely by directory access rules. The app shows the directory contents, nothing less and nothing more.
19
+
20
+ ## Usage
21
+
22
+ ### Environment variables
23
+
24
+ LDAP access is controlled by the following optional environment variables, possibly from a `.env` file:
25
+
26
+ * `LDAP_URL`: Connection URL, defaults to `ldap:///`.
27
+ * `BASE_DN`: Search base, e.g. `dc=example,dc=org`.
28
+ * `SCHEMA_DN`: # DN to obtain the directory schema, e.g. `cn=subSchema`.
29
+ * `LOGIN_ATTR`: User name attribute, defaults to `uid`.
30
+
31
+ * `USE_TLS`: Enable TLS, defaults to true for `ldaps` connections. Set it to a non-empty string to force `STARTTLS` on `ldap` connections.
32
+ * `INSECURE_TLS`: Do not require a valid server TLS certificate, defaults to false, implies `USE_TLS`.
33
+
34
+ if `BASE_DN` or `SCHEMA_DN` are not provided explicitly, auto-detection from the root DSE is attempted.
35
+ For this to work, the root DSE must be readable anonymously, e.g. with the following ACL line for OpenLDAP:
36
+
37
+ ```text
38
+ access to dn.base="" by * read
39
+ ```
40
+
41
+ For finer-grained control, see [settings.py](settings.py).
42
+
43
+ ### Docker
44
+
45
+ For the impatient: Run it with
46
+
47
+ ```shell
48
+ docker run -p 127.0.0.1:5000:5000 \
49
+ -e LDAP_URL=ldap://your.openldap.server/ \
50
+ dnknth/ldap-ui:latest
51
+ ```
52
+
53
+ For the even more impatient: Start a demo with
54
+
55
+ ```shell
56
+ docker compose up -d
57
+ ```
58
+
59
+ and go to <http://localhost:5000/>. You are automatically logged in as `Fred Flintstone`.
60
+
61
+ ### Pip
62
+
63
+ Install the `python-ldap` dependency with your system's package manager.
64
+ Otherwise, Pip will try to compile it from source and this will likely fail because it lacks a development environment.
65
+
66
+ Then install `ldap-ui` in a virtual environment:
67
+
68
+ ```shell
69
+ python3 -m venv --system-site-packages venv
70
+ . venv/bin/activate
71
+ pip3 install ldap-ui
72
+ ```
73
+
74
+ Possibly after a shell `rehash`, it is available as `ldap-ui`:
75
+
76
+ ```text
77
+ Usage: ldap-ui [OPTIONS]
78
+
79
+ Options:
80
+ -b, --base-dn TEXT LDAP base DN. Required unless the BASE_DN
81
+ environment variable is set.
82
+ -h, --host TEXT Bind socket to this host. [default:
83
+ 127.0.0.1]
84
+ -p, --port INTEGER Bind socket to this port. If 0, an available
85
+ port will be picked. [default: 5000]
86
+ -l, --log-level [critical|error|warning|info|debug|trace]
87
+ Log level. [default: info]
88
+ --version Display the current version and exit.
89
+ --help Show this message and exit.
90
+ ```
91
+
92
+ ## Development
93
+
94
+ Prerequisites:
95
+
96
+ * [GNU make](https://www.gnu.org/software/make/)
97
+ * [node.js](https://nodejs.dev) LTS version with NPM
98
+ * [Python3](https://www.python.org) ≥ 3.7
99
+ * [pip3](https://packaging.python.org/tutorials/installing-packages/)
100
+ * [python-ldap](https://pypi.org/project/python-ldap/); To compile the Python module:
101
+ * Debian / Ubuntu: `apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev`
102
+ * RedHat / CentOS: `yum install python-devel openldap-devel`
103
+
104
+ `ldap-ui` consists of a Vue frontend and a Python backend that roughly translates a subset of the LDAP protocol to a stateless ReST API.
105
+
106
+ For the frontend, `npm run build` assembles everything in `backend/ldap_ui/statics`.
107
+
108
+ Review the configuration in [settings.py](settings.py). It is short and mostly self-explaining.
109
+ Most settings can (and should) be overridden by environment variables or settings in a `.env` file; see [env.demo](env.demo) or [env.example](env.example).
110
+
111
+ The backend can be run locally with `make`, which will also install dependencies and build the frontend if needed.
112
+
113
+ ## Notes
114
+
115
+ ### Authentication methods
116
+
117
+ The UI always uses a simple `bind` operation to authenticate with the LDAP directory. How the `bind` DN is obtained from a given user name depends on a combination of OS environment variables, possibly from a `.env` file:
118
+
119
+ 1. Search by some attribute. By default, this is the `uid`, which can be overridden by the environment variable `LOGIN_ATTR`, e.g. `LOGIN_ATTR=cn`.
120
+ 2. If the environment variable `BIND_PATTERN` is set, then no search is performed. Login with a full DN can be configured with `BIND_PATTERN=%s`, which for example allows to login as user `cn=admin,dc=example,dc=org`. If a partial DN like `BIND_PATTERN=%s,dc=example,dc=org` is configured, the corresponding login would be `cn=admin`. If a specific pattern like `BIND_PATTERN=cn=%s,dc=example,dc=org` is configured, the login name is just `admin`.
121
+ 3. If security is no concern, then a fixed `BIND_DN` and `BIND_PASSWORD` can be set in the environment. This is for demo purposes only, and probably a very bad idea if access to the UI is not restricted by any other means.
122
+
123
+ ### Searching
124
+
125
+ Search uses a (configurable) set of criteria (`cn`, `gn`, `sn`, and `uid`) if the query does not contain `=`.
126
+ Wildcards are supported, e.g. `f*` will match all `cn`, `gn`, `sn`, and `uid` starting with `f`.
127
+ Additionally, arbitrary attributes can be searched with an LDAP filter specification, for example `sn=F*`.
128
+
129
+ ### Caveats
130
+
131
+ * The software works with [OpenLdap](http://www.openldap.org) using simple bind. Other directories have not been tested much, although [389 DS](https://www.port389.org) works to some extent.
132
+ * SASL authentication schemes are presently not supported.
133
+ * Passwords are transmitted as plain text. The LDAP server is expected to hash them (OpenLdap 2.4 does). I strongly recommend to expose the app through a TLS-enabled web server.
134
+ * HTTP *Basic Authentication* is triggered unless the `AUTHORIZATION` request variable is already set by some upstream HTTP server.
135
+
136
+ ## Q&A
137
+
138
+ * Q: Why are some fields not editable?
139
+ * A: The RDN of an entry is read-only. To change it, rename the entry with a different RDN, then change the old RDN and rename back. To change passwords, click on the question mark icon on the right side. Binary fields (as per schema) are read-only. You do not want to modify them accidentally.
140
+ * Q: Why did you write this?
141
+ * A: [PHPLdapAdmin](http://phpldapadmin.sf.net/) has not seen updates for ages. I needed a replacement, and wanted to try Vue.
142
+
143
+ ## Acknowledgements
144
+
145
+ The Python backend uses [FastAPI](https://fastapi.tiangolo.com). The UI is built with [Vue.js](https://vuejs.org) and [Tailwind CSS](https://tailwindcss.com/). Kudos to the authors of these elegant frameworks!
@@ -0,0 +1 @@
1
+ __version__ = "0.10.3"
@@ -0,0 +1,88 @@
1
+ import logging
2
+
3
+ import click
4
+ import uvicorn
5
+ from uvicorn.config import LOG_LEVELS
6
+ from uvicorn.logging import ColourizedFormatter
7
+ from uvicorn.main import LEVEL_CHOICES
8
+
9
+ import ldap_ui
10
+
11
+ from . import settings
12
+
13
+
14
+ def print_version(ctx: click.Context, param: click.Parameter, value: bool) -> None:
15
+ if value:
16
+ click.echo(ldap_ui.__version__)
17
+ ctx.exit()
18
+
19
+
20
+ @click.command()
21
+ @click.option(
22
+ "-b",
23
+ "--base-dn",
24
+ type=str,
25
+ default=settings.BASE_DN,
26
+ help="LDAP base DN. [default: Detect from root DSE]",
27
+ )
28
+ @click.option(
29
+ "-h",
30
+ "--host",
31
+ type=str,
32
+ default="127.0.0.1",
33
+ help="Bind socket to this IP.",
34
+ show_default=True,
35
+ )
36
+ @click.option(
37
+ "-p",
38
+ "--port",
39
+ type=int,
40
+ default=5000,
41
+ help="Bind socket to this port (or 0 for any available port).",
42
+ show_default=True,
43
+ )
44
+ @click.option(
45
+ "-u",
46
+ "--ldap-url",
47
+ type=str,
48
+ default=settings.LDAP_URL,
49
+ help="LDAP directory connection URL.",
50
+ show_default=True,
51
+ )
52
+ @click.option(
53
+ "-l",
54
+ "--log-level",
55
+ type=LEVEL_CHOICES,
56
+ default="info",
57
+ help="Log level.",
58
+ show_default=True,
59
+ )
60
+ @click.option(
61
+ "--reload",
62
+ is_flag=True,
63
+ help="Watch for changes and reload?",
64
+ )
65
+ @click.option(
66
+ "--version",
67
+ is_flag=True,
68
+ callback=print_version,
69
+ expose_value=False,
70
+ is_eager=True,
71
+ help="Display the current version and exit.",
72
+ )
73
+ def main(base_dn, host, port, ldap_url, log_level, reload):
74
+ logging.basicConfig(level=LOG_LEVELS[log_level])
75
+ rootHandler = logging.getLogger().handlers[0]
76
+ rootHandler.setFormatter(ColourizedFormatter(fmt="%(levelprefix)s %(message)s"))
77
+
78
+ if base_dn is not None:
79
+ settings.BASE_DN = base_dn
80
+
81
+ if ldap_url is not None:
82
+ settings.LDAP_URL = ldap_url
83
+
84
+ uvicorn.run("ldap_ui.app:app", host=host, port=port, reload=reload)
85
+
86
+
87
+ if __name__ == "__main__":
88
+ main()
@@ -0,0 +1,90 @@
1
+ """
2
+ Simplistic ReST proxy for LDAP access.
3
+
4
+ Authentication is either hard-wired in the settings,
5
+ or else only HTTP basic auth is supported.
6
+
7
+ The backend is stateless, it re-connects to the directory on every request.
8
+ No sessions, no cookies, nothing else.
9
+ """
10
+
11
+ import logging
12
+ from http import HTTPStatus
13
+
14
+ from fastapi import FastAPI, Request, Response
15
+ from fastapi.middleware.gzip import GZipMiddleware
16
+ from fastapi.responses import JSONResponse
17
+ from fastapi.staticfiles import StaticFiles
18
+ from ldap import ( # type: ignore
19
+ ALREADY_EXISTS, # type: ignore
20
+ INSUFFICIENT_ACCESS, # type: ignore
21
+ INVALID_CREDENTIALS, # type: ignore
22
+ NO_SUCH_OBJECT, # type: ignore
23
+ OBJECT_CLASS_VIOLATION, # type: ignore
24
+ UNWILLING_TO_PERFORM, # type: ignore # type: ignore
25
+ LDAPError, # type: ignore
26
+ )
27
+
28
+ from . import __version__, ldap_api, settings
29
+
30
+ # Main ASGI entry
31
+
32
+ app = FastAPI(debug=settings.DEBUG, title="LDAP UI", version=__version__)
33
+ app.include_router(ldap_api.api)
34
+ app.mount("/", StaticFiles(packages=["ldap_ui"], html=True))
35
+
36
+ app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)
37
+
38
+
39
+ @app.middleware("http")
40
+ async def cache_buster(request: Request, call_next) -> Response:
41
+ "Forbid caching of API responses"
42
+ response = await call_next(request)
43
+ if request.url.path.startswith("/api"):
44
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
45
+ response.headers["Pragma"] = "no-cache"
46
+ response.headers["Expires"] = "0"
47
+ return response
48
+
49
+
50
+ # API error handling
51
+
52
+ LDAP_ERROR_TO_STATUS = {
53
+ ALREADY_EXISTS: HTTPStatus.CONFLICT,
54
+ INSUFFICIENT_ACCESS: HTTPStatus.FORBIDDEN,
55
+ INVALID_CREDENTIALS: HTTPStatus.UNAUTHORIZED,
56
+ NO_SUCH_OBJECT: HTTPStatus.NOT_FOUND,
57
+ OBJECT_CLASS_VIOLATION: HTTPStatus.BAD_REQUEST,
58
+ UNWILLING_TO_PERFORM: HTTPStatus.FORBIDDEN,
59
+ }
60
+
61
+
62
+ @app.exception_handler(LDAPError)
63
+ def handle_ldap_error(request: Request, exc: LDAPError) -> Response:
64
+ "General handler for LDAP errors"
65
+
66
+ exc_type = type(exc)
67
+ if exc_type is UNWILLING_TO_PERFORM:
68
+ logging.critical("Need BIND_DN or BIND_PATTERN to authenticate")
69
+
70
+ if exc_type is INVALID_CREDENTIALS:
71
+ return Response(
72
+ status_code=HTTPStatus.UNAUTHORIZED,
73
+ headers={
74
+ "WWW-Authenticate": 'Basic realm="Please log in", charset="UTF-8"'
75
+ },
76
+ )
77
+
78
+ if exc_type not in LDAP_ERROR_TO_STATUS:
79
+ # Unknown error --> log it since FastApi won't do it for us
80
+ logging.exception("Error in %s %s:", request.method, request.url, exc_info=exc)
81
+
82
+ cause = exc.args[0] if exc.args else {}
83
+ desc = cause.get("desc", "LDAP error").capitalize()
84
+ msg = f"{desc}" if "info" not in cause else f"{desc}: {cause['info'].capitalize()}"
85
+ return JSONResponse(
86
+ {"detail": [msg]},
87
+ status_code=LDAP_ERROR_TO_STATUS.get(
88
+ exc_type, HTTPStatus.INTERNAL_SERVER_ERROR
89
+ ),
90
+ )
@@ -0,0 +1,50 @@
1
+ "Data types for ReST endpoints"
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class TreeItem(BaseModel):
7
+ "Entry in the navigation tree"
8
+
9
+ dn: str
10
+ structuralObjectClass: str
11
+ hasSubordinates: bool
12
+ level: int
13
+
14
+
15
+ Attributes = dict[str, list[str]]
16
+
17
+ AttributeNames = list[str] # Names of modified attributes
18
+
19
+
20
+ class Entry(BaseModel):
21
+ "Directory entry"
22
+
23
+ dn: str
24
+ attrs: Attributes
25
+ binary: AttributeNames
26
+ autoFilled: AttributeNames
27
+ changed: AttributeNames
28
+ isNew: bool = False
29
+
30
+
31
+ class ChangePasswordRequest(BaseModel):
32
+ "Change a password"
33
+
34
+ old: str
35
+ new1: str
36
+
37
+
38
+ class SearchResult(BaseModel):
39
+ "Search result"
40
+
41
+ dn: str
42
+ name: str
43
+
44
+
45
+ class Range(BaseModel):
46
+ "Numeric attribute range"
47
+
48
+ min: int
49
+ max: int
50
+ next: int