Skip to content

Commit 5abdae1

Browse files
author
Krishna Prasad ADHIKARI
authored
Merge pull request #3893 from hove-io/feat_avoid_pg_authentification_for_bad_token
Feat avoid pg authentification for bad token
2 parents 7b9fc70 + 22ad94b commit 5abdae1

File tree

5 files changed

+69
-19
lines changed

5 files changed

+69
-19
lines changed

source/jormungandr/jormungandr/api.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
from jormungandr.new_relic import record_custom_parameter
4343
from jormungandr.authentication import get_user, get_token, get_app_name, get_used_coverages
4444
from jormungandr._version import __version__
45-
from jormungandr.utils import can_connect_to_database
4645
import six
4746

4847

@@ -90,18 +89,22 @@ def add_info_newrelic(response, *args, **kwargs):
9089
try:
9190
record_custom_parameter('navitia-request-id', request.id)
9291

93-
if can_connect_to_database():
94-
token = get_token()
92+
token = get_token()
93+
# No log will be added in newrelic if token is absent in the request
94+
if token:
9595
user = get_user(token=token, abort_if_no_token=False)
96-
app_name = get_app_name(token)
9796
if user:
9897
record_custom_parameter('user_id', str(user.id))
99-
record_custom_parameter('token_name', app_name)
100-
101-
record_custom_parameter('version', __version__)
102-
coverages = get_used_coverages()
103-
if coverages:
104-
record_custom_parameter('coverage', coverages[0])
98+
# This method verifies database connection and gets object Key only once when cache expires.
99+
app_name = get_app_name(token)
100+
if app_name:
101+
record_custom_parameter('token_name', app_name)
102+
103+
record_custom_parameter('version', __version__)
104+
# No access to database required
105+
coverages = get_used_coverages()
106+
if coverages:
107+
record_custom_parameter('coverage', coverages[0])
105108
except:
106109
logger = logging.getLogger(__name__)
107110
logger.exception('error while reporting to newrelic:')

source/jormungandr/jormungandr/authentication.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def get_token():
107107
# Python3 Compatibility 2: Decode bytes to string in order to use split()
108108
if isinstance(decoded, bytes):
109109
decoded = decoded.decode()
110-
return decoded.split(':')[0]
110+
return decoded.split(':')[0].strip()
111111
except (binascii.Error, UnicodeDecodeError):
112112
logging.getLogger(__name__).exception('badly formated token %s', auth)
113113
flask_restful.abort(401, message="Unauthorized, invalid token", status=401)
@@ -141,6 +141,7 @@ def has_access(region, api, abort, user):
141141
# if jormungandr is on public mode or database is not accessible, we skip the authentication process
142142
logging.getLogger(__name__).debug('User "has_access" to region/api not cached')
143143

144+
# Connection to database verified only once when cache expires.
144145
if current_app.config.get('PUBLIC', False) or (not can_connect_to_database()):
145146
return True
146147

@@ -193,11 +194,13 @@ def cache_get_user(token):
193194

194195
def uncached_get_user(token):
195196
logging.getLogger(__name__).debug('Get User from token (uncached)')
196-
if not can_connect_to_database():
197-
logging.getLogger(__name__).debug('Cannot connect to database, we set User to None')
198-
return None
199197
try:
200198
user = User.get_from_token(token, datetime.datetime.now())
199+
200+
# if user doesn't exist for a token, get default token with user_type = no_access
201+
if not user:
202+
user = User.get_without_access()
203+
logging.getLogger(__name__).warning('Invalid token : {}'.format(token[0:10]))
201204
except Exception as e:
202205
logging.getLogger(__name__).error('No access to table User (error: {})'.format(e))
203206
g.can_connect_to_database = False
@@ -211,6 +214,7 @@ def uncached_get_user(token):
211214
)
212215
@cache.memoize(current_app.config[str('CACHE_CONFIGURATION')].get(str('TIMEOUT_AUTHENTICATION'), 300))
213216
def cache_get_key(token):
217+
# This verification is done only once when cache expires.
214218
if not can_connect_to_database():
215219
return None
216220
try:

source/jormungandr/jormungandr/instance_manager.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,9 @@ def stop(self):
222222
def _filter_authorized_instances(self, instances, api):
223223
if not instances:
224224
return []
225-
# During the period database is not accessible, all the instances are valid for the user.
226-
if not can_connect_to_database():
227-
return instances
228-
225+
# get_user is cached hence access to database only once when cache expires.
229226
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
230228
valid_instances = [
231229
i for i in instances if authentication.has_access(i.name, abort=False, user=user, api=api)
232230
]

source/navitiacommon/navitiacommon/models/__init__.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class User(db.Model, TimestampMixin): # type: ignore
129129
)
130130

131131
type = db.Column(
132-
db.Enum('with_free_instances', 'without_free_instances', 'super_user', name='user_type'),
132+
db.Enum('with_free_instances', 'without_free_instances', 'super_user', 'no_access', name='user_type'),
133133
default='with_free_instances',
134134
nullable=False,
135135
)
@@ -194,6 +194,11 @@ def get_from_key(cls, token, valid_until):
194194
)
195195
return query.first()
196196

197+
@classmethod
198+
def get_without_access(cls):
199+
query = cls.query.filter(cls.type == 'no_access' and cls.login == 'user_without_access')
200+
return query.first()
201+
197202
def has_access(self, instance_id, api_name):
198203
if self.is_super_user:
199204
return True
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Add a new type no_access in user_type
2+
3+
Revision ID: 04f9b89e3ef5
4+
Revises: 75681b9c74aa
5+
Create Date: 2022-12-30 11:23:58.436438
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = '04f9b89e3ef5'
11+
down_revision = '75681b9c74aa'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
16+
new_options = ('with_free_instances', 'without_free_instances', 'super_user', 'no_access')
17+
old_type = sa.Enum('with_free_instances', 'without_free_instances', 'super_user', name='user_type')
18+
new_type = sa.Enum(*new_options, name='user_type')
19+
tmp_type = sa.Enum(*new_options, name='_user_type')
20+
21+
22+
def upgrade():
23+
op.execute("COMMIT")
24+
op.execute("ALTER TYPE user_type ADD VALUE 'no_access'")
25+
op.execute(
26+
'INSERT INTO "user"(login, email, type) values(\'user_without_access\', \'[email protected]\', \'no_access\')'
27+
)
28+
29+
30+
def downgrade():
31+
op.execute('DELETE FROM "user" where type = \'no_access\'')
32+
tmp_type.create(op.get_bind(), checkfirst=False)
33+
op.execute('ALTER TABLE "user" ALTER COLUMN type DROP DEFAULT')
34+
op.execute('ALTER TABLE "user" ALTER COLUMN type TYPE _user_type USING type::text::_user_type')
35+
new_type.drop(op.get_bind(), checkfirst=False)
36+
37+
old_type.create(op.get_bind(), checkfirst=False)
38+
op.execute('ALTER TABLE "user" ALTER COLUMN type TYPE user_type USING type::text::user_type')
39+
op.execute('ALTER TABLE "user" ALTER COLUMN type SET DEFAULT \'with_free_instances\'')
40+
tmp_type.drop(op.get_bind(), checkfirst=False)

0 commit comments

Comments
 (0)