Skip to content

Commit 762cf9d

Browse files
author
Patrick Qian
authored
Merge pull request #3916 from hove-io/optimize_verif_instance_for_user
Optimize while fetching authorized coverages for an user
2 parents a75b427 + 3c39b5d commit 762cf9d

File tree

5 files changed

+72
-61
lines changed

5 files changed

+72
-61
lines changed

source/jormungandr/jormungandr/authentication.py

+5
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ def get_user(token, abort_if_no_token=True):
281281
g.user.id = 0
282282
else:
283283
g.user = cache_get_user(token)
284+
if g.user.type == 'no_access':
285+
flask_restful.abort(
286+
401,
287+
message='Token absent in the database You can get one at http://www.navitia.io or contact your support if you’re using the opensource version of Navitia https://github.com/hove-io/navitia',
288+
)
284289

285290
return g.user
286291

source/jormungandr/jormungandr/instance_manager.py

+52-47
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from jormungandr.exceptions import ApiNotFound, RegionNotFound, DeadSocketException, InvalidArguments
4242
from jormungandr import authentication, cache, app
4343
from jormungandr.instance import Instance
44-
from jormungandr.utils import can_connect_to_database
4544
import gevent
4645
import os
4746

@@ -154,7 +153,7 @@ def initialization(self):
154153
def _clear_cache(self):
155154
logging.getLogger(__name__).info('clear cache')
156155
try:
157-
cache.delete_memoized(self._all_keys_of_id)
156+
cache.delete_memoized(self._exists_id_in_instance)
158157
except:
159158
# if there is an error with cache, flask want to access to the app, this will fail at startup
160159
# with a "working outside of application context"
@@ -219,21 +218,19 @@ def stop(self):
219218
if not self.thread_event.is_set():
220219
self.thread_event.set()
221220

222-
def _filter_authorized_instances(self, instances, api):
223-
if not instances:
224-
return []
225-
# get_user is cached hence access to database only once when cache expires.
226-
user = authentication.get_user(token=authentication.get_token())
227-
# has_access returns true if can_connect_to_database = false when cache expires for each coverage
228-
valid_instances = [
229-
i for i in instances if authentication.has_access(i.name, abort=False, user=user, api=api)
221+
def _get_authorized_instances(self, user, api):
222+
authorized_instances = [
223+
i
224+
for name, i in self.instances.items()
225+
if authentication.has_access(name, abort=False, user=user, api=api)
230226
]
231-
if not valid_instances:
227+
228+
if not authorized_instances:
232229
context = 'User has no access to any instance'
233230
authentication.abort_request(user, context)
234-
return valid_instances
231+
return authorized_instances
235232

236-
def _find_coverage_by_object_id(self, object_id):
233+
def _find_coverage_by_object_id_in_instances(self, instances, object_id):
237234
if object_id.count(";") == 1 or object_id[:6] == "coord:":
238235
if object_id.count(";") == 1:
239236
lon, lat = object_id.split(";")
@@ -244,32 +241,33 @@ def _find_coverage_by_object_id(self, object_id):
244241
flat = float(lat)
245242
except:
246243
raise InvalidArguments(object_id)
247-
return self._all_keys_of_coord(flon, flat)
248-
return self._all_keys_of_id(object_id)
244+
return self._all_keys_of_coord_in_instances(instances, flon, flat)
249245

250-
@cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('TIMEOUT_PTOBJECTS'), None))
251-
def _all_keys_of_id(self, object_id):
252-
instances = []
253-
futures = {}
254-
for name, instance in self.instances.items():
255-
futures[name] = gevent.spawn(instance.has_id, object_id)
256-
for name, future in futures.items():
257-
if future.get():
258-
instances.append(name)
246+
return self._all_keys_of_id_in_instances(instances, object_id)
259247

260-
if not instances:
248+
def _all_keys_of_id_in_instances(self, instances, object_id):
249+
valid_instances = []
250+
for instance in instances:
251+
if self._exists_id_in_instance(instance, object_id):
252+
valid_instances.append(instance)
253+
if not valid_instances:
261254
raise RegionNotFound(object_id=object_id)
262-
return instances
263255

264-
def _all_keys_of_coord(self, lon, lat):
256+
return valid_instances
257+
258+
@cache.memoize(app.config[str('CACHE_CONFIGURATION')].get(str('TIMEOUT_PTOBJECTS'), None))
259+
def _exists_id_in_instance(self, instance, object_id):
260+
return instance.has_id(object_id)
261+
262+
def _all_keys_of_coord_in_instances(self, instances, lon, lat):
265263
p = geometry.Point(lon, lat)
266-
instances = [i.name for i in self.instances.values() if i.has_point(p)]
264+
valid_instances = [i for i in instances if i.has_point(p)]
267265
logging.getLogger(__name__).debug(
268-
"all_keys_of_coord(self, {}, {}) returns {}".format(lon, lat, instances)
266+
"_all_keys_of_coord_in_instances(self, {}, {}) returns {}".format(lon, lat, instances)
269267
)
270-
if not instances:
268+
if not valid_instances:
271269
raise RegionNotFound(lon=lon, lat=lat)
272-
return instances
270+
return valid_instances
273271

274272
def get_region(self, region_str=None, lon=None, lat=None, object_id=None, api='ALL'):
275273
return self.get_regions(region_str, lon, lat, object_id, api, only_one=True)
@@ -284,27 +282,34 @@ def get_regions(self, region_str=None, lon=None, lat=None, object_id=None, api='
284282
return [i.name for i in valid_instances]
285283

286284
def get_instances(self, name=None, lon=None, lat=None, object_id=None, api='ALL'):
287-
available_instances = []
285+
if name and name not in self.instances:
286+
raise RegionNotFound(region=name)
287+
288+
# Request without token or bad token makes a request exception and exits with a message
289+
# get_user is cached hence access to database only once when cache expires.
290+
user = authentication.get_user(token=authentication.get_token())
291+
292+
# fetch all the authorized instances (free + private) using cached function has_access()
293+
authorized_instances = self._get_authorized_instances(user, api)
294+
if not authorized_instances:
295+
# user doesn't have access to any of the instances
296+
context = 'User has no access to any instance'
297+
authentication.abort_request(user=user, context=context)
298+
299+
# Filter instances among instances in authorized_instances
288300
if name:
289-
if name in self.instances:
290-
available_instances = [self.instances[name]]
301+
valid_instances = [i for i in authorized_instances if i.name == name]
291302
elif lon and lat:
292-
available_instances = [
293-
self.instances[k] for k in self._all_keys_of_coord(lon, lat) if k in self.instances
294-
]
303+
valid_instances = self._all_keys_of_coord_in_instances(authorized_instances, lon, lat)
295304
elif object_id:
296-
instance_keys = self._find_coverage_by_object_id(object_id)
297-
if instance_keys is None:
298-
available_instances = []
299-
else:
300-
available_instances = [self.instances[k] for k in instance_keys if k in self.instances]
305+
valid_instances = self._find_coverage_by_object_id_in_instances(authorized_instances, object_id)
301306
else:
302-
available_instances = list(self.instances.values())
303-
valid_instances = self._filter_authorized_instances(available_instances, api)
304-
if available_instances and not valid_instances:
307+
valid_instances = authorized_instances
308+
309+
if not valid_instances:
305310
# user doesn't have access to any of the instances
306-
context = 'User does not have access to any of the instances'
307-
authentication.abort_request(user=authentication.get_user(None), context=context)
311+
context = "User has no access to any instance or instance doesn't exist"
312+
authentication.abort_request(user=user, context=context)
308313
else:
309314
return valid_instances
310315

source/jormungandr/jormungandr/tests/instance_manager_tests.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ def get_instances_test(manager):
6262
assert len(instances) == 1
6363
assert 'paris' == instances[0].name
6464

65-
assert manager.get_instances('foo') == []
66-
6765

6866
def get_instances_by_coord_test(manager, mocker):
69-
mock = mocker.patch.object(manager, '_all_keys_of_coord', return_value=['paris'])
67+
mock = mocker.patch.object(
68+
manager, '_all_keys_of_coord_in_instances', return_value=[manager.instances['paris']]
69+
)
7070
with app.test_request_context('/'):
7171
instances = manager.get_instances(lon=4, lat=3)
7272
assert len(instances) == 1
@@ -75,7 +75,7 @@ def get_instances_by_coord_test(manager, mocker):
7575

7676

7777
def get_instances_by_object_id_test(manager, mocker):
78-
mock = mocker.patch.object(manager, '_all_keys_of_id', return_value=['pdl'])
78+
mock = mocker.patch.object(manager, '_all_keys_of_id_in_instances', return_value=[manager.instances['pdl']])
7979
with app.test_request_context('/'):
8080
instances = manager.get_instances(object_id='sa:pdl')
8181
assert len(instances) == 1

source/jormungandr/tests/authentication_tests.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,9 @@ def test_status_code(self):
251251
# stopA and stopB and in main routing test, all is ok
252252
('/v1/journeys?from=stopA&to=stopB&datetime=20120614T080000', 200),
253253
# stop1 is in departure board -> KO
254-
('/v1/journeys?from=stopA&to=stop2&datetime=20120614T080000', 403),
254+
('/v1/journeys?from=stopA&to=stop2&datetime=20120614T080000', 404),
255255
# stop1 and stop2 are in departure board -> KO
256-
('/v1/journeys?from=stop1&to=stop2&datetime=20120614T080000', 403),
256+
('/v1/journeys?from=stop1&to=stop2&datetime=20120614T080000', 404),
257257
]
258258

259259
with user_set(app, FakeUserAuth, 'bob'):
@@ -262,7 +262,7 @@ def test_status_code(self):
262262

263263
def test_unkown_region(self):
264264
"""
265-
the authentication process must not mess if the region is not found
265+
the authentication process prevails even if the region is not found
266266
"""
267267
with user_set(app, FakeUserAuth, 'bob'):
268268
r, status = self.query_no_assert('/v1/coverage/the_marvelous_unknown_region/stop_areas')
@@ -419,7 +419,7 @@ def test_journeys_for_tgv(self):
419419
response = self.query('/v1/journeys?from=stopA&to=stopB&datetime=20120614T080000')
420420
assert 'error' not in response
421421
_, status = self.query_no_assert('/v1/journeys?from=stop1&to=stop2&datetime=20120614T080000')
422-
assert status == 403
422+
assert status == 404
423423

424424
_, status = self.query_no_assert(
425425
'/v1/coverage/empty_routing_test/journeys?from=stop1&to=stop2&datetime=20120614T080000'
@@ -444,9 +444,9 @@ def test_journeys_for_bobette(self):
444444
"""
445445
with user_set(app, FakeUserAuth, 'bobette'):
446446
response, status = self.query_no_assert('/v1/journeys?from=stopA&to=stopB&datetime=20120614T080000')
447-
assert status == 403
447+
assert status == 404
448448
response, status = self.query_no_assert('/v1/journeys?from=stop1&to=stop2&datetime=20120614T080000')
449-
assert status == 403
449+
assert status == 404
450450

451451
response, status = self.query_no_assert(
452452
'/v1/journeys?from={from_coord}&to={to_coord}&datetime={d}'.format(

source/jormungandr/tests/journey_common_tests.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1472,10 +1472,11 @@ def test_no_region(self):
14721472

14731473
assert status != 200, "the response should not be valid"
14741474

1475-
assert response['error']['id'] == "unknown_object"
1476-
1477-
error_regexp = re.compile('^No region available for the coordinates.*')
1478-
assert error_regexp.match(response['error']['message'])
1475+
assert 'message' in response
1476+
assert (
1477+
response['message']
1478+
== "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server."
1479+
)
14791480

14801481

14811482
@dataset({"basic_routing_test": {}})

0 commit comments

Comments
 (0)