ldap-ui 0.10.0__py3-none-any.whl → 0.10.2__py3-none-any.whl
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/__init__.py +1 -1
- ldap_ui/app.py +35 -77
- ldap_ui/entities.py +8 -20
- ldap_ui/ldap_api.py +170 -160
- ldap_ui/ldap_helpers.py +40 -23
- ldap_ui/schema.py +0 -2
- ldap_ui/statics/assets/index-0D2GdpZj.js +19 -0
- ldap_ui/statics/assets/index-0D2GdpZj.js.gz +0 -0
- ldap_ui/statics/assets/{index-4LadlPh6.css → index-CAWiQJyn.css} +1 -1
- ldap_ui/statics/assets/index-CAWiQJyn.css.gz +0 -0
- ldap_ui/statics/index.html +2 -2
- {ldap_ui-0.10.0.dist-info → ldap_ui-0.10.2.dist-info}/METADATA +1 -1
- ldap_ui-0.10.2.dist-info/RECORD +25 -0
- ldap_ui/statics/assets/index-4LadlPh6.css.gz +0 -0
- ldap_ui/statics/assets/index-CVDJWmXN.js +0 -19
- ldap_ui/statics/assets/index-CVDJWmXN.js.gz +0 -0
- ldap_ui-0.10.0.dist-info/RECORD +0 -25
- {ldap_ui-0.10.0.dist-info → ldap_ui-0.10.2.dist-info}/WHEEL +0 -0
- {ldap_ui-0.10.0.dist-info → ldap_ui-0.10.2.dist-info}/entry_points.txt +0 -0
- {ldap_ui-0.10.0.dist-info → ldap_ui-0.10.2.dist-info}/licenses/LICENSE.txt +0 -0
- {ldap_ui-0.10.0.dist-info → ldap_ui-0.10.2.dist-info}/top_level.txt +0 -0
ldap_ui/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.10.
|
|
1
|
+
__version__ = "0.10.2"
|
ldap_ui/app.py
CHANGED
|
@@ -9,8 +9,6 @@ No sessions, no cookies, nothing else.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
|
-
import sys
|
|
13
|
-
from contextlib import contextmanager
|
|
14
12
|
from http import HTTPStatus
|
|
15
13
|
|
|
16
14
|
from fastapi import FastAPI, Request, Response
|
|
@@ -23,67 +21,16 @@ from ldap import ( # type: ignore
|
|
|
23
21
|
INVALID_CREDENTIALS, # type: ignore
|
|
24
22
|
NO_SUCH_OBJECT, # type: ignore
|
|
25
23
|
OBJECT_CLASS_VIOLATION, # type: ignore
|
|
26
|
-
|
|
27
|
-
UNWILLING_TO_PERFORM, # type: ignore
|
|
24
|
+
UNWILLING_TO_PERFORM, # type: ignore # type: ignore
|
|
28
25
|
LDAPError, # type: ignore
|
|
29
26
|
)
|
|
30
27
|
|
|
31
|
-
from . import __version__, settings
|
|
32
|
-
from .ldap_api import api
|
|
33
|
-
from .ldap_helpers import WITH_OPERATIONAL_ATTRS, ldap_connect
|
|
34
|
-
|
|
35
|
-
LOG = logging.getLogger("ldap-ui")
|
|
36
|
-
|
|
37
|
-
LDAP_ERROR_TO_STATUS = {
|
|
38
|
-
ALREADY_EXISTS: HTTPStatus.CONFLICT,
|
|
39
|
-
INSUFFICIENT_ACCESS: HTTPStatus.FORBIDDEN,
|
|
40
|
-
INVALID_CREDENTIALS: HTTPStatus.UNAUTHORIZED,
|
|
41
|
-
NO_SUCH_OBJECT: HTTPStatus.NOT_FOUND,
|
|
42
|
-
OBJECT_CLASS_VIOLATION: HTTPStatus.BAD_REQUEST,
|
|
43
|
-
UNWILLING_TO_PERFORM: HTTPStatus.FORBIDDEN,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def format_ldap_error(exc: LDAPError) -> str:
|
|
48
|
-
cause = exc.args[0] if exc.args else {}
|
|
49
|
-
desc = cause.get("desc", "LDAP error").strip().capitalize()
|
|
50
|
-
if "info" in cause:
|
|
51
|
-
return f"{desc}: {cause['info'].strip().capitalize()}"
|
|
52
|
-
return f"{desc}"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if not settings.BASE_DN or not settings.SCHEMA_DN:
|
|
56
|
-
try: # Auto-detection from root DSE
|
|
57
|
-
connect = contextmanager(ldap_connect)
|
|
58
|
-
with connect() as connection:
|
|
59
|
-
_dn, attrs = connection.search_s( # type: ignore
|
|
60
|
-
"",
|
|
61
|
-
SCOPE_BASE,
|
|
62
|
-
attrlist=WITH_OPERATIONAL_ATTRS,
|
|
63
|
-
)[0]
|
|
64
|
-
base_dns = attrs.get("namingContexts", [])
|
|
65
|
-
if len(base_dns) == 1:
|
|
66
|
-
settings.BASE_DN = settings.BASE_DN or base_dns[0].decode()
|
|
67
|
-
else:
|
|
68
|
-
LOG.warning("No unique base DN: %s", base_dns)
|
|
69
|
-
schema_dns = attrs.get("subschemaSubentry", [])
|
|
70
|
-
settings.SCHEMA_DN = settings.SCHEMA_DN or schema_dns[0].decode()
|
|
71
|
-
except LDAPError as err:
|
|
72
|
-
LOG.error(format_ldap_error(err), exc_info=err)
|
|
73
|
-
|
|
74
|
-
if not settings.BASE_DN:
|
|
75
|
-
LOG.critical("An LDAP base DN is required!")
|
|
76
|
-
sys.exit(1)
|
|
77
|
-
|
|
78
|
-
if not settings.SCHEMA_DN:
|
|
79
|
-
LOG.critical("An LDAP schema DN is required!")
|
|
80
|
-
sys.exit(1)
|
|
81
|
-
|
|
28
|
+
from . import __version__, ldap_api, settings
|
|
82
29
|
|
|
83
30
|
# Main ASGI entry
|
|
84
31
|
|
|
85
32
|
app = FastAPI(debug=settings.DEBUG, title="LDAP UI", version=__version__)
|
|
86
|
-
app.include_router(api)
|
|
33
|
+
app.include_router(ldap_api.api)
|
|
87
34
|
app.mount("/", StaticFiles(packages=["ldap_ui"], html=True))
|
|
88
35
|
|
|
89
36
|
app.add_middleware(GZipMiddleware, minimum_size=1000, compresslevel=5)
|
|
@@ -102,31 +49,42 @@ async def cache_buster(request: Request, call_next) -> Response:
|
|
|
102
49
|
|
|
103
50
|
# API error handling
|
|
104
51
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
}
|
|
110
60
|
|
|
111
61
|
|
|
112
|
-
@app.exception_handler(
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
exc,
|
|
116
|
-
HTTPStatus.UNAUTHORIZED,
|
|
117
|
-
headers={"WWW-Authenticate": 'Basic realm="Please log in", charset="UTF-8"'},
|
|
118
|
-
)
|
|
62
|
+
@app.exception_handler(LDAPError)
|
|
63
|
+
def handle_ldap_error(request: Request, exc: LDAPError) -> Response:
|
|
64
|
+
"General handler for LDAP errors"
|
|
119
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")
|
|
120
69
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
)
|
|
125
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)
|
|
126
81
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
"
|
|
130
|
-
return
|
|
131
|
-
|
|
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
|
+
),
|
|
132
90
|
)
|
ldap_ui/entities.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"Data types for ReST endpoints"
|
|
2
2
|
|
|
3
|
-
from pydantic import BaseModel
|
|
3
|
+
from pydantic import BaseModel
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class TreeItem(BaseModel):
|
|
@@ -12,32 +12,20 @@ class TreeItem(BaseModel):
|
|
|
12
12
|
level: int
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class Meta(BaseModel):
|
|
16
|
-
"Attribute classification for an entry"
|
|
17
|
-
|
|
18
|
-
dn: str
|
|
19
|
-
required: list[str]
|
|
20
|
-
aux: list[str]
|
|
21
|
-
binary: list[str]
|
|
22
|
-
autoFilled: list[str]
|
|
23
|
-
isNew: bool = False
|
|
24
|
-
|
|
25
|
-
|
|
26
15
|
Attributes = dict[str, list[str]]
|
|
27
16
|
|
|
17
|
+
AttributeNames = list[str] # Names of modified attributes
|
|
18
|
+
|
|
28
19
|
|
|
29
20
|
class Entry(BaseModel):
|
|
30
21
|
"Directory entry"
|
|
31
22
|
|
|
23
|
+
dn: str
|
|
32
24
|
attrs: Attributes
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class ChangedAttributes(BaseModel):
|
|
38
|
-
"List of modified attributes"
|
|
39
|
-
|
|
40
|
-
changed: list[str]
|
|
25
|
+
binary: AttributeNames
|
|
26
|
+
autoFilled: AttributeNames
|
|
27
|
+
changed: AttributeNames
|
|
28
|
+
isNew: bool = False
|
|
41
29
|
|
|
42
30
|
|
|
43
31
|
class ChangePasswordRequest(BaseModel):
|