ositah 25.6.dev1__py3-none-any.whl → 25.9.dev1__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.
Potentially problematic release.
This version of ositah might be problematic. Click here for more details.
- ositah/app.py +17 -17
- ositah/apps/analysis.py +785 -785
- ositah/apps/configuration/callbacks.py +916 -916
- ositah/apps/configuration/main.py +546 -546
- ositah/apps/configuration/parameters.py +74 -74
- ositah/apps/configuration/tools.py +112 -112
- ositah/apps/export.py +1208 -1191
- ositah/apps/validation/callbacks.py +240 -240
- ositah/apps/validation/main.py +89 -89
- ositah/apps/validation/parameters.py +25 -25
- ositah/apps/validation/tables.py +646 -646
- ositah/apps/validation/tools.py +552 -552
- ositah/assets/arrow_down_up.svg +3 -3
- ositah/assets/ositah.css +53 -53
- ositah/assets/sort_ascending.svg +4 -4
- ositah/assets/sort_descending.svg +5 -5
- ositah/assets/sorttable.js +499 -499
- ositah/main.py +449 -449
- ositah/ositah.example.cfg +229 -229
- ositah/static/style.css +53 -53
- ositah/templates/base.html +22 -22
- ositah/templates/bootstrap_login.html +38 -38
- ositah/templates/login_form.html +26 -26
- ositah/utils/agents.py +124 -124
- ositah/utils/authentication.py +287 -287
- ositah/utils/cache.py +19 -19
- ositah/utils/core.py +13 -13
- ositah/utils/exceptions.py +64 -64
- ositah/utils/hito_db.py +51 -51
- ositah/utils/hito_db_model.py +253 -253
- ositah/utils/menus.py +339 -339
- ositah/utils/period.py +139 -139
- ositah/utils/projects.py +1178 -1178
- ositah/utils/teams.py +42 -42
- ositah/utils/utils.py +474 -474
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/METADATA +149 -150
- ositah-25.9.dev1.dist-info/RECORD +46 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/licenses/LICENSE +29 -29
- ositah-25.6.dev1.dist-info/RECORD +0 -46
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/WHEEL +0 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/entry_points.txt +0 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/top_level.txt +0 -0
ositah/utils/authentication.py
CHANGED
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
# Module to handle user login with flask-multipass, a multi-backend authentication module for Flask
|
|
2
|
-
# The code is largely based on a simplified version of what Indico is doing and is focused on using
|
|
3
|
-
# LDAP (IJCLab ActiveDirectory) as the backend.
|
|
4
|
-
#
|
|
5
|
-
# There is no attempt to store session data in a database.
|
|
6
|
-
|
|
7
|
-
import functools
|
|
8
|
-
import json
|
|
9
|
-
from urllib.parse import urlparse
|
|
10
|
-
from uuid import uuid1
|
|
11
|
-
|
|
12
|
-
from flask import flash, redirect, request, session
|
|
13
|
-
from flask_multipass import InvalidCredentials, Multipass, NoSuchUser
|
|
14
|
-
|
|
15
|
-
from ositah.utils.utils import GlobalParams
|
|
16
|
-
|
|
17
|
-
# Redirect URL for login and logout
|
|
18
|
-
LOGIN_URL = "/login"
|
|
19
|
-
LOGOUT_URL = "/logout"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Session validity duration (in hours), i.e. max time since the last use
|
|
23
|
-
SESSION_MAX_DURATION = 4
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# List of authenticated users
|
|
27
|
-
user_list = {}
|
|
28
|
-
identity_list = {}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class User:
|
|
32
|
-
def __init__(self, first_name=None, last_name=None, email=None):
|
|
33
|
-
self._id = uuid1()
|
|
34
|
-
self._firstname = first_name
|
|
35
|
-
self._lastname = last_name
|
|
36
|
-
self._email = email
|
|
37
|
-
self._identities = []
|
|
38
|
-
|
|
39
|
-
def add_identity(self, identity):
|
|
40
|
-
self._identities.append(identity)
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def identities(self):
|
|
44
|
-
return self._identities
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def email(self):
|
|
48
|
-
return self._email
|
|
49
|
-
|
|
50
|
-
def get_first_identity(self):
|
|
51
|
-
if len(self._identities) >= 1:
|
|
52
|
-
return self._identities[0]
|
|
53
|
-
else:
|
|
54
|
-
return Exception(f"No identity defined for user '{self.email}'")
|
|
55
|
-
|
|
56
|
-
@property
|
|
57
|
-
def id(self):
|
|
58
|
-
return self._id
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class Identity:
|
|
62
|
-
def __init__(self, provider=None, identifier=None):
|
|
63
|
-
self._id = identifier
|
|
64
|
-
self._provider = provider
|
|
65
|
-
self._multipass_data = None
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def id(self):
|
|
69
|
-
return self._id
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def multipass_data(self):
|
|
73
|
-
return self._multipass_data
|
|
74
|
-
|
|
75
|
-
@multipass_data.setter
|
|
76
|
-
def multipass_data(self, data):
|
|
77
|
-
self._multipass_data = data
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def provider(self):
|
|
81
|
-
return self._provider
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class OSITAHMultipass(Multipass):
|
|
85
|
-
def init_app(self, app):
|
|
86
|
-
super(OSITAHMultipass, self).init_app(app)
|
|
87
|
-
with app.app_context():
|
|
88
|
-
self._check_default_provider()
|
|
89
|
-
|
|
90
|
-
def _check_default_provider(self):
|
|
91
|
-
# Ensure that there is maximum one sync provider
|
|
92
|
-
sync_providers = [
|
|
93
|
-
p for p in self.identity_providers.values() if p.settings.get("synced_fields")
|
|
94
|
-
]
|
|
95
|
-
if len(sync_providers) > 1:
|
|
96
|
-
raise ValueError("There can only be one sync provider.")
|
|
97
|
-
# Ensure that there is exactly one form-based default auth provider
|
|
98
|
-
auth_providers = list(self.auth_providers.values())
|
|
99
|
-
external_providers = [p for p in auth_providers if p.is_external]
|
|
100
|
-
local_providers = [p for p in auth_providers if not p.is_external]
|
|
101
|
-
if any(p.settings.get("default") for p in external_providers):
|
|
102
|
-
raise ValueError("The default provider cannot be external")
|
|
103
|
-
if all(p.is_external for p in auth_providers):
|
|
104
|
-
return
|
|
105
|
-
default_providers = [p for p in auth_providers if p.settings.get("default")]
|
|
106
|
-
if len(default_providers) > 1:
|
|
107
|
-
raise ValueError("There can only be one default auth provider")
|
|
108
|
-
elif not default_providers:
|
|
109
|
-
if len(local_providers) == 1:
|
|
110
|
-
local_providers[0].settings["default"] = True
|
|
111
|
-
else:
|
|
112
|
-
raise ValueError("There is no default auth provider")
|
|
113
|
-
|
|
114
|
-
def handle_auth_error(self, exc, redirect_to_login=False):
|
|
115
|
-
if isinstance(exc, (NoSuchUser, InvalidCredentials)):
|
|
116
|
-
print("Invalid credentials")
|
|
117
|
-
else:
|
|
118
|
-
exc_str = str(exc)
|
|
119
|
-
print(
|
|
120
|
-
"Authentication via %s failed: %s (%r)",
|
|
121
|
-
exc.provider.name if exc.provider else None,
|
|
122
|
-
exc_str,
|
|
123
|
-
exc.details,
|
|
124
|
-
)
|
|
125
|
-
return super(OSITAHMultipass, self).handle_auth_error(
|
|
126
|
-
exc, redirect_to_login=redirect_to_login
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def configure_multipass_ldap(app, provider_title):
|
|
131
|
-
"""
|
|
132
|
-
Configure Flask_multipass from configuration. Inspired from Indico and its configuration file.
|
|
133
|
-
Required flask_multipass with PR #42.
|
|
134
|
-
|
|
135
|
-
:param app: Flask app
|
|
136
|
-
:param provider_title: text associated with the auth/identity provider
|
|
137
|
-
:return: none
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
global_params = GlobalParams()
|
|
141
|
-
config = global_params.ldap
|
|
142
|
-
|
|
143
|
-
if not config or len(config) == 0:
|
|
144
|
-
raise Exception("Missing LDAP configuration")
|
|
145
|
-
|
|
146
|
-
ldap_config = {
|
|
147
|
-
"uri": config["uri"],
|
|
148
|
-
"bind_dn": config["bind_dn"],
|
|
149
|
-
"bind_password": config["password"],
|
|
150
|
-
"timeout": 30,
|
|
151
|
-
"verify_cert": True,
|
|
152
|
-
"page_size": 10000,
|
|
153
|
-
"uid": "sAMAccountName",
|
|
154
|
-
"user_base": config["base_dn"],
|
|
155
|
-
"user_filter": "(objectcategory=user)",
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
auth_provider = {
|
|
159
|
-
"ldap": {
|
|
160
|
-
"type": "ldap",
|
|
161
|
-
"title": provider_title,
|
|
162
|
-
"ldap": ldap_config,
|
|
163
|
-
},
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
identity_provider = {
|
|
167
|
-
"ldap": {
|
|
168
|
-
"type": "ldap",
|
|
169
|
-
"title": provider_title,
|
|
170
|
-
"ldap": ldap_config,
|
|
171
|
-
"identifier_field": "mail",
|
|
172
|
-
"accepted_users": "all",
|
|
173
|
-
"mapping": {
|
|
174
|
-
"first_name": "givenName",
|
|
175
|
-
"last_name": "sn",
|
|
176
|
-
"email": "mail",
|
|
177
|
-
"affiliation": "company",
|
|
178
|
-
"phone": "telephoneNumber",
|
|
179
|
-
},
|
|
180
|
-
"trusted_email": True,
|
|
181
|
-
},
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
app.config["MULTIPASS_AUTH_PROVIDERS"] = auth_provider
|
|
185
|
-
app.config["MULTIPASS_IDENTITY_PROVIDERS"] = identity_provider
|
|
186
|
-
app.config["MULTIPASS_PROVIDER_MAP"] = {"ldap": "ldap"}
|
|
187
|
-
app.config["MULTIPASS_IDENTITY_INFO_KEYS"] = {"first_name", "last_name", "email"}
|
|
188
|
-
app.config["MULTIPASS_LOGIN_FORM_TEMPLATE"] = "login_form.html"
|
|
189
|
-
app.config["MULTIPASS_SUCCESS_ENDPOINT"] = "/"
|
|
190
|
-
app.config["MULTIPASS_FAILURE_MESSAGE"] = "Login failed: {error}"
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
multipass = OSITAHMultipass()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@multipass.identity_handler
|
|
197
|
-
def identity_handler(identity_info):
|
|
198
|
-
if identity_info.identifier in identity_list:
|
|
199
|
-
user = identity_list[identity_info.identifier]
|
|
200
|
-
identity = user.get_first_identity()
|
|
201
|
-
else:
|
|
202
|
-
if identity_info.data["email"] in user_list:
|
|
203
|
-
user = user_list[identity_info.data["email"]]
|
|
204
|
-
else:
|
|
205
|
-
user = User(**identity_info.data.to_dict())
|
|
206
|
-
user_list[user.email] = user
|
|
207
|
-
identity = Identity(
|
|
208
|
-
provider=identity_info.provider.name, identifier=identity_info.identifier
|
|
209
|
-
)
|
|
210
|
-
user.add_identity(identity)
|
|
211
|
-
identity_list[identity.id] = user
|
|
212
|
-
identity.multipass_data = json.dumps(identity_info.multipass_data)
|
|
213
|
-
session["user_id"] = identity.id
|
|
214
|
-
flash("Received IdentityInfo: {}".format(identity_info), "success")
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def login_required(view):
|
|
218
|
-
"""
|
|
219
|
-
A decorator to require login on Flask views
|
|
220
|
-
|
|
221
|
-
:param view: a function
|
|
222
|
-
:return: decorated function
|
|
223
|
-
"""
|
|
224
|
-
|
|
225
|
-
@functools.wraps(view)
|
|
226
|
-
def wrapped_view(**kwargs):
|
|
227
|
-
redirect_path = urlparse(request.base_url).path
|
|
228
|
-
if len(redirect_path) == 0:
|
|
229
|
-
redirect_path = "/"
|
|
230
|
-
if "user_id" not in session:
|
|
231
|
-
if redirect_path != "/favicon.ico":
|
|
232
|
-
return redirect(f"{LOGIN_URL}?next={redirect_path}")
|
|
233
|
-
elif redirect_path == LOGOUT_URL:
|
|
234
|
-
remove_session()
|
|
235
|
-
return multipass.logout("/", clear_session=True)
|
|
236
|
-
|
|
237
|
-
return view(**kwargs)
|
|
238
|
-
|
|
239
|
-
return wrapped_view
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def protect_views(app):
|
|
243
|
-
for view_func in app.server.view_functions:
|
|
244
|
-
if view_func.startswith("/<path:path>"):
|
|
245
|
-
app.server.view_functions[view_func] = login_required(
|
|
246
|
-
app.server.view_functions[view_func]
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
return app
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def remove_session():
|
|
253
|
-
"""
|
|
254
|
-
Remove a session from the database and do additional session cleanup.
|
|
255
|
-
|
|
256
|
-
:return: None
|
|
257
|
-
"""
|
|
258
|
-
|
|
259
|
-
from sqlalchemy import delete
|
|
260
|
-
|
|
261
|
-
from ositah.utils.hito_db import get_db
|
|
262
|
-
from ositah.utils.hito_db_model import OSITAHSession
|
|
263
|
-
|
|
264
|
-
global_params = GlobalParams()
|
|
265
|
-
|
|
266
|
-
if "uid" in session:
|
|
267
|
-
del global_params.session_data
|
|
268
|
-
|
|
269
|
-
if session["user_id"] in identity_list:
|
|
270
|
-
del user_list[identity_list[session["user_id"]].email]
|
|
271
|
-
del identity_list[session["user_id"]]
|
|
272
|
-
else:
|
|
273
|
-
print(
|
|
274
|
-
(
|
|
275
|
-
f"WARNING: attempt to delete a non-existing user/identity"
|
|
276
|
-
f" {session['uid']} (user={session['user_id']})"
|
|
277
|
-
)
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
if "user_email" in session:
|
|
281
|
-
sql_cmd = delete(OSITAHSession).where(
|
|
282
|
-
OSITAHSession.id == session["uid"],
|
|
283
|
-
OSITAHSession.email == session["user_email"],
|
|
284
|
-
)
|
|
285
|
-
db = get_db()
|
|
286
|
-
db.session.execute(sql_cmd)
|
|
287
|
-
db.session.commit()
|
|
1
|
+
# Module to handle user login with flask-multipass, a multi-backend authentication module for Flask
|
|
2
|
+
# The code is largely based on a simplified version of what Indico is doing and is focused on using
|
|
3
|
+
# LDAP (IJCLab ActiveDirectory) as the backend.
|
|
4
|
+
#
|
|
5
|
+
# There is no attempt to store session data in a database.
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import json
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
from uuid import uuid1
|
|
11
|
+
|
|
12
|
+
from flask import flash, redirect, request, session
|
|
13
|
+
from flask_multipass import InvalidCredentials, Multipass, NoSuchUser
|
|
14
|
+
|
|
15
|
+
from ositah.utils.utils import GlobalParams
|
|
16
|
+
|
|
17
|
+
# Redirect URL for login and logout
|
|
18
|
+
LOGIN_URL = "/login"
|
|
19
|
+
LOGOUT_URL = "/logout"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Session validity duration (in hours), i.e. max time since the last use
|
|
23
|
+
SESSION_MAX_DURATION = 4
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# List of authenticated users
|
|
27
|
+
user_list = {}
|
|
28
|
+
identity_list = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class User:
|
|
32
|
+
def __init__(self, first_name=None, last_name=None, email=None):
|
|
33
|
+
self._id = uuid1()
|
|
34
|
+
self._firstname = first_name
|
|
35
|
+
self._lastname = last_name
|
|
36
|
+
self._email = email
|
|
37
|
+
self._identities = []
|
|
38
|
+
|
|
39
|
+
def add_identity(self, identity):
|
|
40
|
+
self._identities.append(identity)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def identities(self):
|
|
44
|
+
return self._identities
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def email(self):
|
|
48
|
+
return self._email
|
|
49
|
+
|
|
50
|
+
def get_first_identity(self):
|
|
51
|
+
if len(self._identities) >= 1:
|
|
52
|
+
return self._identities[0]
|
|
53
|
+
else:
|
|
54
|
+
return Exception(f"No identity defined for user '{self.email}'")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def id(self):
|
|
58
|
+
return self._id
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Identity:
|
|
62
|
+
def __init__(self, provider=None, identifier=None):
|
|
63
|
+
self._id = identifier
|
|
64
|
+
self._provider = provider
|
|
65
|
+
self._multipass_data = None
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def id(self):
|
|
69
|
+
return self._id
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def multipass_data(self):
|
|
73
|
+
return self._multipass_data
|
|
74
|
+
|
|
75
|
+
@multipass_data.setter
|
|
76
|
+
def multipass_data(self, data):
|
|
77
|
+
self._multipass_data = data
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def provider(self):
|
|
81
|
+
return self._provider
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class OSITAHMultipass(Multipass):
|
|
85
|
+
def init_app(self, app):
|
|
86
|
+
super(OSITAHMultipass, self).init_app(app)
|
|
87
|
+
with app.app_context():
|
|
88
|
+
self._check_default_provider()
|
|
89
|
+
|
|
90
|
+
def _check_default_provider(self):
|
|
91
|
+
# Ensure that there is maximum one sync provider
|
|
92
|
+
sync_providers = [
|
|
93
|
+
p for p in self.identity_providers.values() if p.settings.get("synced_fields")
|
|
94
|
+
]
|
|
95
|
+
if len(sync_providers) > 1:
|
|
96
|
+
raise ValueError("There can only be one sync provider.")
|
|
97
|
+
# Ensure that there is exactly one form-based default auth provider
|
|
98
|
+
auth_providers = list(self.auth_providers.values())
|
|
99
|
+
external_providers = [p for p in auth_providers if p.is_external]
|
|
100
|
+
local_providers = [p for p in auth_providers if not p.is_external]
|
|
101
|
+
if any(p.settings.get("default") for p in external_providers):
|
|
102
|
+
raise ValueError("The default provider cannot be external")
|
|
103
|
+
if all(p.is_external for p in auth_providers):
|
|
104
|
+
return
|
|
105
|
+
default_providers = [p for p in auth_providers if p.settings.get("default")]
|
|
106
|
+
if len(default_providers) > 1:
|
|
107
|
+
raise ValueError("There can only be one default auth provider")
|
|
108
|
+
elif not default_providers:
|
|
109
|
+
if len(local_providers) == 1:
|
|
110
|
+
local_providers[0].settings["default"] = True
|
|
111
|
+
else:
|
|
112
|
+
raise ValueError("There is no default auth provider")
|
|
113
|
+
|
|
114
|
+
def handle_auth_error(self, exc, redirect_to_login=False):
|
|
115
|
+
if isinstance(exc, (NoSuchUser, InvalidCredentials)):
|
|
116
|
+
print("Invalid credentials")
|
|
117
|
+
else:
|
|
118
|
+
exc_str = str(exc)
|
|
119
|
+
print(
|
|
120
|
+
"Authentication via %s failed: %s (%r)",
|
|
121
|
+
exc.provider.name if exc.provider else None,
|
|
122
|
+
exc_str,
|
|
123
|
+
exc.details,
|
|
124
|
+
)
|
|
125
|
+
return super(OSITAHMultipass, self).handle_auth_error(
|
|
126
|
+
exc, redirect_to_login=redirect_to_login
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def configure_multipass_ldap(app, provider_title):
|
|
131
|
+
"""
|
|
132
|
+
Configure Flask_multipass from configuration. Inspired from Indico and its configuration file.
|
|
133
|
+
Required flask_multipass with PR #42.
|
|
134
|
+
|
|
135
|
+
:param app: Flask app
|
|
136
|
+
:param provider_title: text associated with the auth/identity provider
|
|
137
|
+
:return: none
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
global_params = GlobalParams()
|
|
141
|
+
config = global_params.ldap
|
|
142
|
+
|
|
143
|
+
if not config or len(config) == 0:
|
|
144
|
+
raise Exception("Missing LDAP configuration")
|
|
145
|
+
|
|
146
|
+
ldap_config = {
|
|
147
|
+
"uri": config["uri"],
|
|
148
|
+
"bind_dn": config["bind_dn"],
|
|
149
|
+
"bind_password": config["password"],
|
|
150
|
+
"timeout": 30,
|
|
151
|
+
"verify_cert": True,
|
|
152
|
+
"page_size": 10000,
|
|
153
|
+
"uid": "sAMAccountName",
|
|
154
|
+
"user_base": config["base_dn"],
|
|
155
|
+
"user_filter": "(objectcategory=user)",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
auth_provider = {
|
|
159
|
+
"ldap": {
|
|
160
|
+
"type": "ldap",
|
|
161
|
+
"title": provider_title,
|
|
162
|
+
"ldap": ldap_config,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
identity_provider = {
|
|
167
|
+
"ldap": {
|
|
168
|
+
"type": "ldap",
|
|
169
|
+
"title": provider_title,
|
|
170
|
+
"ldap": ldap_config,
|
|
171
|
+
"identifier_field": "mail",
|
|
172
|
+
"accepted_users": "all",
|
|
173
|
+
"mapping": {
|
|
174
|
+
"first_name": "givenName",
|
|
175
|
+
"last_name": "sn",
|
|
176
|
+
"email": "mail",
|
|
177
|
+
"affiliation": "company",
|
|
178
|
+
"phone": "telephoneNumber",
|
|
179
|
+
},
|
|
180
|
+
"trusted_email": True,
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
app.config["MULTIPASS_AUTH_PROVIDERS"] = auth_provider
|
|
185
|
+
app.config["MULTIPASS_IDENTITY_PROVIDERS"] = identity_provider
|
|
186
|
+
app.config["MULTIPASS_PROVIDER_MAP"] = {"ldap": "ldap"}
|
|
187
|
+
app.config["MULTIPASS_IDENTITY_INFO_KEYS"] = {"first_name", "last_name", "email"}
|
|
188
|
+
app.config["MULTIPASS_LOGIN_FORM_TEMPLATE"] = "login_form.html"
|
|
189
|
+
app.config["MULTIPASS_SUCCESS_ENDPOINT"] = "/"
|
|
190
|
+
app.config["MULTIPASS_FAILURE_MESSAGE"] = "Login failed: {error}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
multipass = OSITAHMultipass()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@multipass.identity_handler
|
|
197
|
+
def identity_handler(identity_info):
|
|
198
|
+
if identity_info.identifier in identity_list:
|
|
199
|
+
user = identity_list[identity_info.identifier]
|
|
200
|
+
identity = user.get_first_identity()
|
|
201
|
+
else:
|
|
202
|
+
if identity_info.data["email"] in user_list:
|
|
203
|
+
user = user_list[identity_info.data["email"]]
|
|
204
|
+
else:
|
|
205
|
+
user = User(**identity_info.data.to_dict())
|
|
206
|
+
user_list[user.email] = user
|
|
207
|
+
identity = Identity(
|
|
208
|
+
provider=identity_info.provider.name, identifier=identity_info.identifier
|
|
209
|
+
)
|
|
210
|
+
user.add_identity(identity)
|
|
211
|
+
identity_list[identity.id] = user
|
|
212
|
+
identity.multipass_data = json.dumps(identity_info.multipass_data)
|
|
213
|
+
session["user_id"] = identity.id
|
|
214
|
+
flash("Received IdentityInfo: {}".format(identity_info), "success")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def login_required(view):
|
|
218
|
+
"""
|
|
219
|
+
A decorator to require login on Flask views
|
|
220
|
+
|
|
221
|
+
:param view: a function
|
|
222
|
+
:return: decorated function
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
@functools.wraps(view)
|
|
226
|
+
def wrapped_view(**kwargs):
|
|
227
|
+
redirect_path = urlparse(request.base_url).path
|
|
228
|
+
if len(redirect_path) == 0:
|
|
229
|
+
redirect_path = "/"
|
|
230
|
+
if "user_id" not in session:
|
|
231
|
+
if redirect_path != "/favicon.ico":
|
|
232
|
+
return redirect(f"{LOGIN_URL}?next={redirect_path}")
|
|
233
|
+
elif redirect_path == LOGOUT_URL:
|
|
234
|
+
remove_session()
|
|
235
|
+
return multipass.logout("/", clear_session=True)
|
|
236
|
+
|
|
237
|
+
return view(**kwargs)
|
|
238
|
+
|
|
239
|
+
return wrapped_view
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def protect_views(app):
|
|
243
|
+
for view_func in app.server.view_functions:
|
|
244
|
+
if view_func.startswith("/<path:path>"):
|
|
245
|
+
app.server.view_functions[view_func] = login_required(
|
|
246
|
+
app.server.view_functions[view_func]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return app
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def remove_session():
|
|
253
|
+
"""
|
|
254
|
+
Remove a session from the database and do additional session cleanup.
|
|
255
|
+
|
|
256
|
+
:return: None
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
from sqlalchemy import delete
|
|
260
|
+
|
|
261
|
+
from ositah.utils.hito_db import get_db
|
|
262
|
+
from ositah.utils.hito_db_model import OSITAHSession
|
|
263
|
+
|
|
264
|
+
global_params = GlobalParams()
|
|
265
|
+
|
|
266
|
+
if "uid" in session:
|
|
267
|
+
del global_params.session_data
|
|
268
|
+
|
|
269
|
+
if session["user_id"] in identity_list:
|
|
270
|
+
del user_list[identity_list[session["user_id"]].email]
|
|
271
|
+
del identity_list[session["user_id"]]
|
|
272
|
+
else:
|
|
273
|
+
print(
|
|
274
|
+
(
|
|
275
|
+
f"WARNING: attempt to delete a non-existing user/identity"
|
|
276
|
+
f" {session['uid']} (user={session['user_id']})"
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if "user_email" in session:
|
|
281
|
+
sql_cmd = delete(OSITAHSession).where(
|
|
282
|
+
OSITAHSession.id == session["uid"],
|
|
283
|
+
OSITAHSession.email == session["user_email"],
|
|
284
|
+
)
|
|
285
|
+
db = get_db()
|
|
286
|
+
db.session.execute(sql_cmd)
|
|
287
|
+
db.session.commit()
|
ositah/utils/cache.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# Helper functions to manage the data cache
|
|
2
|
-
|
|
3
|
-
from ositah.utils.exceptions import SessionDataMissing
|
|
4
|
-
from ositah.utils.utils import GlobalParams, no_session_id_jumbotron
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def clear_cached_data():
|
|
8
|
-
"""
|
|
9
|
-
Clear the data cached by the previous requests
|
|
10
|
-
|
|
11
|
-
:return: None
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
global_params = GlobalParams()
|
|
15
|
-
try:
|
|
16
|
-
session_data = global_params.session_data
|
|
17
|
-
session_data.reset_caches()
|
|
18
|
-
except SessionDataMissing:
|
|
19
|
-
return no_session_id_jumbotron()
|
|
1
|
+
# Helper functions to manage the data cache
|
|
2
|
+
|
|
3
|
+
from ositah.utils.exceptions import SessionDataMissing
|
|
4
|
+
from ositah.utils.utils import GlobalParams, no_session_id_jumbotron
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def clear_cached_data():
|
|
8
|
+
"""
|
|
9
|
+
Clear the data cached by the previous requests
|
|
10
|
+
|
|
11
|
+
:return: None
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
global_params = GlobalParams()
|
|
15
|
+
try:
|
|
16
|
+
session_data = global_params.session_data
|
|
17
|
+
session_data.reset_caches()
|
|
18
|
+
except SessionDataMissing:
|
|
19
|
+
return no_session_id_jumbotron()
|
ositah/utils/core.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
# Module with utility functions
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
# Singleton decorator definition
|
|
5
|
-
def singleton(cls):
|
|
6
|
-
instances = {}
|
|
7
|
-
|
|
8
|
-
def getinstance(*args, **kwargs):
|
|
9
|
-
if cls not in instances:
|
|
10
|
-
instances[cls] = cls(*args, **kwargs)
|
|
11
|
-
return instances[cls]
|
|
12
|
-
|
|
13
|
-
return getinstance
|
|
1
|
+
# Module with utility functions
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Singleton decorator definition
|
|
5
|
+
def singleton(cls):
|
|
6
|
+
instances = {}
|
|
7
|
+
|
|
8
|
+
def getinstance(*args, **kwargs):
|
|
9
|
+
if cls not in instances:
|
|
10
|
+
instances[cls] = cls(*args, **kwargs)
|
|
11
|
+
return instances[cls]
|
|
12
|
+
|
|
13
|
+
return getinstance
|