Skip to content
This repository has been archived by the owner on Feb 21, 2019. It is now read-only.

Commit

Permalink
Merge pull request #67 from tracim/fix/953_refactor_auth_to_rely_on_i…
Browse files Browse the repository at this point in the history
…d_instead_of_email

WIP: refactor auth mecanism ot rely on id
  • Loading branch information
Damien Accorsi committed Oct 5, 2018
2 parents 08358ef + 70a9180 commit 0b601ab
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 47 deletions.
8 changes: 2 additions & 6 deletions backend/tracim_backend/__init__.py
Expand Up @@ -7,17 +7,14 @@
from http import client as HTTPStatus

from pyramid.config import Configurator
from pyramid.authentication import BasicAuthAuthenticationPolicy
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authentication import AuthTktAuthenticationPolicy
from hapic.ext.pyramid import PyramidContext
from sqlalchemy.exc import OperationalError

from tracim_backend.extensions import hapic
from tracim_backend.config import CFG
from tracim_backend.lib.utils.request import TracimRequest
from tracim_backend.lib.utils.authentification import basic_auth_check_credentials
from tracim_backend.lib.utils.authentification import CookieSessionAuthentificationPolicy
from tracim_backend.lib.utils.authentification import TracimBasicAuthAuthenticationPolicy
from tracim_backend.lib.utils.authentification import ApiTokenAuthentificationPolicy
from tracim_backend.lib.utils.authentification import TRACIM_API_KEY_HEADER
from tracim_backend.lib.utils.authentification import TRACIM_API_USER_EMAIL_LOGIN_HEADER
Expand Down Expand Up @@ -74,8 +71,7 @@ def web(global_config, **local_settings):
api_key_header=TRACIM_API_KEY_HEADER,
api_user_email_login_header=TRACIM_API_USER_EMAIL_LOGIN_HEADER
),
BasicAuthAuthenticationPolicy(
basic_auth_check_credentials,
TracimBasicAuthAuthenticationPolicy(
realm=BASIC_AUTH_WEBUI_REALM
),
]
Expand Down
85 changes: 46 additions & 39 deletions backend/tracim_backend/lib/utils/authentification.py
@@ -1,52 +1,27 @@
import datetime
import typing

from pyramid.authentication import BasicAuthAuthenticationPolicy
from pyramid.authentication import CallbackAuthenticationPolicy
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authentication import extract_http_basic_credentials
from pyramid.interfaces import IAuthenticationPolicy
from pyramid.request import Request
from zope.interface import implementer

from tracim_backend.exceptions import UserDoesNotExist
from tracim_backend.lib.core.user import UserApi
from tracim_backend.lib.utils.request import TracimRequest
from tracim_backend.models import User

BASIC_AUTH_WEBUI_REALM = "tracim"
TRACIM_API_KEY_HEADER = "Tracim-Api-Key"
TRACIM_API_USER_EMAIL_LOGIN_HEADER = "Tracim-Api-Login"

###
# Pyramid HTTP Basic Auth
###


def basic_auth_check_credentials(
login: str,
cleartext_password: str,
request: TracimRequest
) -> typing.Optional[list]:
"""
Check credential for pyramid basic_auth
:param login: login of user
:param cleartext_password: user password in cleartext
:param request: Pyramid request
:return: None if auth failed, list of permissions if auth succeed
"""

# Do not accept invalid user
user = _get_auth_unsafe_user(request)
if not user \
or user.email != login \
or not user.is_active \
or user.is_deleted \
or not user.validate_password(cleartext_password):
return None
return []


def _get_auth_unsafe_user(
request: Request,
email: str=None,
user_id: int=None,
) -> typing.Optional[User]:
"""
:param request: pyramid request
Expand All @@ -55,13 +30,43 @@ def _get_auth_unsafe_user(
app_config = request.registry.settings['CFG']
uapi = UserApi(None, session=request.dbsession, config=app_config)
try:
login = request.unauthenticated_userid
if not login:
return None
user = uapi.get_one_by_email(login)
_, user = uapi.find(user_id=user_id, email=email)
return user
except UserDoesNotExist:
return None
return user

###
# Pyramid HTTP Basic Auth
###


@implementer(IAuthenticationPolicy)
class TracimBasicAuthAuthenticationPolicy(BasicAuthAuthenticationPolicy):

def __init__(self, realm):
BasicAuthAuthenticationPolicy.__init__(self, check=None, realm=realm)
# TODO - G.M - 2018-09-21 - Disable callback is needed to have BasicAuth
# correctly working, if enabled, callback method will try check method
# who is now disabled (uneeded because we use directly
# authenticated_user_id) and failed.
self.callback = None

def authenticated_userid(self, request):
# check if user is correct
credentials = extract_http_basic_credentials(request)
user = _get_auth_unsafe_user(
request,
email=request.unauthenticated_userid
)
if not user \
or user.email != request.unauthenticated_userid \
or not user.is_active \
or user.is_deleted \
or not credentials \
or not user.validate_password(credentials.password):
return None
return user.user_id


###
# Pyramid cookie auth policy
Expand All @@ -74,22 +79,23 @@ class CookieSessionAuthentificationPolicy(SessionAuthenticationPolicy):
def __init__(self, reissue_time: int, debug: bool = False):
SessionAuthenticationPolicy.__init__(self, debug=debug, callback=None)
self._reissue_time = reissue_time
self.callback = None

def authenticated_userid(self, request):
# check if user is correct
user = _get_auth_unsafe_user(request)
user = _get_auth_unsafe_user(request, user_id=request.unauthenticated_userid) # nopep8
# do not allow invalid_user + ask for cleanup of session cookie
if not user or not user.is_active or user.is_deleted:
request.session.delete()
return None
# recreate session if need renew
if not request.session.new:
now = datetime.datetime.now()
last_access_datetime = datetime.datetime.utcfromtimestamp(request.session.last_accessed)
last_access_datetime = datetime.datetime.utcfromtimestamp(request.session.last_accessed) # nopep8
reissue_limit = last_access_datetime + datetime.timedelta(seconds=self._reissue_time) # nopep8
if now > reissue_limit: # nopep8
request.session.regenerate_id()
return request.unauthenticated_userid
return user.user_id

def forget(self, request):
""" Remove the stored userid from the session."""
Expand All @@ -109,6 +115,7 @@ class ApiTokenAuthentificationPolicy(CallbackAuthenticationPolicy):
def __init__(self, api_key_header: str, api_user_email_login_header: str):
self.api_key_header = api_key_header
self.api_user_email_login_header = api_user_email_login_header
self.callback = None

def authenticated_userid(self, request):
app_config = request.registry.settings['CFG'] # type:'CFG'
Expand All @@ -119,10 +126,10 @@ def authenticated_userid(self, request):
if valid_api_key != api_key:
return None
# check if user is correct
user = _get_auth_unsafe_user(request)
user = _get_auth_unsafe_user(request, email=request.unauthenticated_userid)
if not user or not user.is_active or user.is_deleted:
return None
return request.unauthenticated_userid
return user.user_id

def unauthenticated_userid(self, request):
return request.headers.get(self.api_user_email_login_header)
Expand Down
2 changes: 1 addition & 1 deletion backend/tracim_backend/lib/utils/request.py
Expand Up @@ -326,7 +326,7 @@ def _get_auth_safe_user(
login = request.authenticated_userid
if not login:
raise UserNotFoundInTracimRequest('You request a current user but the context not permit to found one') # nopep8
user = uapi.get_one_by_email(login)
user = uapi.get_one(login)
if not user.is_active:
raise UserNotActive('User {} is not active'.format(login))
except (UserDoesNotExist, UserNotFoundInTracimRequest) as exc:
Expand Down
Expand Up @@ -42,7 +42,7 @@ def login(self, context, request: TracimRequest, hapic_data=None):
config=app_config,
)
user = uapi.authenticate_user(login.email, login.password)
remember(request, user.email)
remember(request, user.user_id)
return uapi.get_user_with_context(user)

@hapic.with_api_doc(tags=[SWAGGER_TAG__SESSION_ENDPOINTS])
Expand Down

0 comments on commit 0b601ab

Please sign in to comment.