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.
- ldap_ui-0.10.3/LICENSE.txt +7 -0
- ldap_ui-0.10.3/PKG-INFO +167 -0
- ldap_ui-0.10.3/README.md +145 -0
- ldap_ui-0.10.3/backend/ldap_ui/__init__.py +1 -0
- ldap_ui-0.10.3/backend/ldap_ui/__main__.py +88 -0
- ldap_ui-0.10.3/backend/ldap_ui/app.py +90 -0
- ldap_ui-0.10.3/backend/ldap_ui/entities.py +50 -0
- ldap_ui-0.10.3/backend/ldap_ui/ldap_api.py +569 -0
- ldap_ui-0.10.3/backend/ldap_ui/ldap_helpers.py +162 -0
- ldap_ui-0.10.3/backend/ldap_ui/schema.py +158 -0
- ldap_ui-0.10.3/backend/ldap_ui/settings.py +108 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-B-jkhYfk.woff2 +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-CDK5bt4p.woff +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-CQDK8MU3.ttf +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-D13rzr4g.svg +2671 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/fontawesome-webfont-G5YE5S7X.eot +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-BxbE5Fxl.js +19 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-BxbE5Fxl.js.gz +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-sA8CL-9V.css +4 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/assets/index-sA8CL-9V.css.gz +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/favicon.ico +0 -0
- ldap_ui-0.10.3/backend/ldap_ui/statics/index.html +24 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/PKG-INFO +167 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/SOURCES.txt +28 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/dependency_links.txt +1 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/entry_points.txt +2 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/requires.txt +4 -0
- ldap_ui-0.10.3/backend/ldap_ui.egg-info/top_level.txt +1 -0
- ldap_ui-0.10.3/pyproject.toml +46 -0
- 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.
|
ldap_ui-0.10.3/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
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!
|
ldap_ui-0.10.3/README.md
ADDED
|
@@ -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
|
+

|
|
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
|