<commit_before>import base64
import os
from operator import itemgetter
from datetime import datetime
import time
import jwt
from base64 import b64decode

from passlib.context import CryptContext
from pyramid.authentication import (
    BasicAuthAuthenticationPolicy as _BasicAuthAuthenticationPolicy,
    CallbackAuthenticationPolicy
)
import requests
from pyramid.path import (
    DottedNameResolver,
    caller_package,
)
from pyramid.security import (
    NO_PERMISSION_REQUIRED,
    remember,
    forget,
)
from pyramid.httpexceptions import (
    HTTPForbidden,
    HTTPFound
)
from pyramid.view import (
    view_config,
)
from pyramid.settings import asbool
from snovault import ROOT
from snovault.storage import User
from snovault import COLLECTIONS
from snovault.validation import ValidationFailure
from snovault.calculated import calculate_properties
from snovault.validators import no_validate_item_content_post

CRYPT_CONTEXT = __name__ + ':crypt_context'


def includeme(config):
    config.include('.edw_hash')
    setting_prefix = 'passlib.'
    passlib_settings = {
        k[len(setting_prefix):]: v
        for k, v in config.registry.settings.items()
        if k.startswith(setting_prefix)
    }
    if not passlib_settings:
        passlib_settings = {'schemes': 'edw_hash, unix_disabled'}
    crypt_context = CryptContext(**passlib_settings)
    config.registry[CRYPT_CONTEXT] = crypt_context

    # basic login route
    config.add_route('login', '/login')
    config.add_route('logout', '/logout')
    config.add_route('me', '/me')
    config.add_route('impersonate-user', '/impersonate-user')
    config.add_route('session-properties', '/session-properties')
    config.scan(__name__)


class NamespacedAuthenticationPolicy(object):
    """ Wrapper for authentication policy classes

    As userids are included in the list of principals, it seems good practice
    to namespace them to avoid clashes.

    Constructor Arguments

    ``namespace``

        The namespace used (string).

    ``base``

        The base authentication policy (class or dotted name).

    Remaining arguments are passed to the ``base`` constructor.

    Example

    To make a ``REMOTE_USER`` 'admin' be 'user.admin'

    .. code-block:: python

        policy = NamespacedAuthenticationPolicy('user',
            'pyramid.authentication.RemoteUserAuthenticationPolicy')
    """

    def __new__(cls, namespace, base, *args, **kw):
        # Dotted name support makes it easy to configure with pyramid_multiauth
        name_resolver = DottedNameResolver(caller_package())
        base = name_resolver.maybe_resolve(base)
        # Dynamically create a subclass
        name = 'Namespaced_%s_%s' % (namespace, base.__name__)
        klass = type(name, (cls, base), {'_namespace_prefix': namespace + '.'})
        return super(NamespacedAuthenticationPolicy, klass).__new__(klass)

    def __init__(self, namespace, base, *args, **kw):
        super(NamespacedAuthenticationPolicy, self).__init__(*args, **kw)

    def unauthenticated_userid(self, request):
        cls  = super(NamespacedAuthenticationPolicy, self)
        userid = super(NamespacedAuthenticationPolicy, self) \
            .unauthenticated_userid(request)
        if userid is not None:
            userid = self._namespace_prefix + userid
        return userid

    def remember(self, request, principal, **kw):
        if not principal.startswith(self._namespace_prefix):
            return []
        principal = principal[len(self._namespace_prefix):]
        return super(NamespacedAuthenticationPolicy, self) \
            .remember(request, principal, **kw)


class BasicAuthAuthenticationPolicy(_BasicAuthAuthenticationPolicy):
    def __init__(self, check, *args, **kw):
        # Dotted name support makes it easy to configure with pyramid_multiauth
        name_resolver = DottedNameResolver(caller_package())
        check = name_resolver.maybe_resolve(check)
        super(BasicAuthAuthenticationPolicy, self).__init__(check, *args, **kw)


class LoginDenied(HTTPForbidden):
    title = 'Login failure'


_fake_user = object()



class Auth0AuthenticationPolicy(CallbackAuthenticationPolicy):

    login_path = '/login'
    method = 'POST'

    def unauthenticated_userid(self, request):
        '''
        So basically this is used to do a login, instead of the actual
        login view... not sure why, but yeah..
        '''


        # we will cache it for the life of this request, cause pyramids does traversal
        cached = getattr(request, '_auth0_authenticated', _fake_user)
        if cached is not _fake_user:
            return cached

        # try to find the token in the request (should be in the header)
        id_token = get_jwt(request)
        if not id_token:
            # can I thrown an 403 here?
            #print('Missing assertion.', 'unauthenticated_userid', request)
            return None

        user_info = self.get_token_info(id_token, request)
        if not user_info:
            return None

        email = request._auth0_authenticated = user_info['email'].lower()

        # Allow us to access basic user credentials from request obj after authenticating & saving request....authenticated above
        def getUserInfo(request):
            userid = request.authenticated_userid.split('.', 1)[1]
            user_props = request.embed('/session-properties', as_user=userid)
            user_props.update({
                "details" : request.registry[COLLECTIONS]['user'][userid].properties,
                "id_token" : id_token
            })
            return user_props

        request.set_property(getUserInfo, "user_info", True)
        return email


    def get_token_info(self, token, request):
        '''
        given a jwt get token info from auth0, handle
        retrying and what not
        '''
        try:
            # lets see if we have an auth0 token or our own
            registry = request.registry
            auth0_client = registry.settings.get('auth0.client')
            auth0_secret = registry.settings.get('auth0.secret')
            if auth0_client and auth0_secret:
                # leeway accounts for clock drift between us and auth0
                payload = jwt.decode(token, b64decode(auth0_secret, '-_'),
                                     audience=auth0_client, leeway=30)
                if 'email' in payload and payload.get('email_verified') is True:
                    return payload

            else: # we don't have the key, let auth0 do the work for us
                try:
                    user_url = "https://{domain}/tokeninfo".format(domain='hms-dbmi.auth0.com')
                    resp  = requests.post(user_url, {'id_token':token})
                    payload = resp.json()
                    if 'email' in payload and payload.get('email_verified') is True:
                        request.set_property(lambda r: False, 'auth0_expired')
                        return payload
                except ValueError as e:
                    print("Bad or expired token: " + token)
                    request.set_property(lambda r: True, 'auth0_expired') # Allow us to return 403 code &or unset cookie in renderers.py
                    return None

        except ValueError as e:
            print('Invalid JWT assertion : %s (%s)', (e, type(e).__name__))
            return None

        print("didn't get email or email is not verified")
        return None


def get_jwt(request):
    token = None
    try:
        # ensure this is a jwt token not basic auth:
        auth_type = request.headers['Authorization'][:6]
        if auth_type.strip().lower() == 'bearer':
            token = request.headers['Authorization'][7:]
    except (ValueError, TypeError, KeyError):
        token = request.cookies.get('jwtToken')

    return token


@view_config(route_name='login', request_method='POST',
             permission=NO_PERMISSION_REQUIRED)
def login(request):
    '''check the auth0 assertion and remember the user'''

    user_info = request.user_info
    if not user_info:
        raise LoginDenied()

    return user_info

<commit_msg>login handler check for user_info attr (test fix)<commit_after>import base64
import os
from operator import itemgetter
from datetime import datetime
import time
import jwt
from base64 import b64decode

from passlib.context import CryptContext
from pyramid.authentication import (
    BasicAuthAuthenticationPolicy as _BasicAuthAuthenticationPolicy,
    CallbackAuthenticationPolicy
)
import requests
from pyramid.path import (
    DottedNameResolver,
    caller_package,
)
from pyramid.security import (
    NO_PERMISSION_REQUIRED,
    remember,
    forget,
)
from pyramid.httpexceptions import (
    HTTPForbidden,
    HTTPFound
)
from pyramid.view import (
    view_config,
)
from pyramid.settings import asbool
from snovault import ROOT
from snovault.storage import User
from snovault import COLLECTIONS
from snovault.validation import ValidationFailure
from snovault.calculated import calculate_properties
from snovault.validators import no_validate_item_content_post

CRYPT_CONTEXT = __name__ + ':crypt_context'


def includeme(config):
    config.include('.edw_hash')
    setting_prefix = 'passlib.'
    passlib_settings = {
        k[len(setting_prefix):]: v
        for k, v in config.registry.settings.items()
        if k.startswith(setting_prefix)
    }
    if not passlib_settings:
        passlib_settings = {'schemes': 'edw_hash, unix_disabled'}
    crypt_context = CryptContext(**passlib_settings)
    config.registry[CRYPT_CONTEXT] = crypt_context

    # basic login route
    config.add_route('login', '/login')
    config.add_route('logout', '/logout')
    config.add_route('me', '/me')
    config.add_route('impersonate-user', '/impersonate-user')
    config.add_route('session-properties', '/session-properties')
    config.scan(__name__)


class NamespacedAuthenticationPolicy(object):
    """ Wrapper for authentication policy classes

    As userids are included in the list of principals, it seems good practice
    to namespace them to avoid clashes.

    Constructor Arguments

    ``namespace``

        The namespace used (string).

    ``base``

        The base authentication policy (class or dotted name).

    Remaining arguments are passed to the ``base`` constructor.

    Example

    To make a ``REMOTE_USER`` 'admin' be 'user.admin'

    .. code-block:: python

        policy = NamespacedAuthenticationPolicy('user',
            'pyramid.authentication.RemoteUserAuthenticationPolicy')
    """

    def __new__(cls, namespace, base, *args, **kw):
        # Dotted name support makes it easy to configure with pyramid_multiauth
        name_resolver = DottedNameResolver(caller_package())
        base = name_resolver.maybe_resolve(base)
        # Dynamically create a subclass
        name = 'Namespaced_%s_%s' % (namespace, base.__name__)
        klass = type(name, (cls, base), {'_namespace_prefix': namespace + '.'})
        return super(NamespacedAuthenticationPolicy, klass).__new__(klass)

    def __init__(self, namespace, base, *args, **kw):
        super(NamespacedAuthenticationPolicy, self).__init__(*args, **kw)

    def unauthenticated_userid(self, request):
        cls  = super(NamespacedAuthenticationPolicy, self)
        userid = super(NamespacedAuthenticationPolicy, self) \
            .unauthenticated_userid(request)
        if userid is not None:
            userid = self._namespace_prefix + userid
        return userid

    def remember(self, request, principal, **kw):
        if not principal.startswith(self._namespace_prefix):
            return []
        principal = principal[len(self._namespace_prefix):]
        return super(NamespacedAuthenticationPolicy, self) \
            .remember(request, principal, **kw)


class BasicAuthAuthenticationPolicy(_BasicAuthAuthenticationPolicy):
    def __init__(self, check, *args, **kw):
        # Dotted name support makes it easy to configure with pyramid_multiauth
        name_resolver = DottedNameResolver(caller_package())
        check = name_resolver.maybe_resolve(check)
        super(BasicAuthAuthenticationPolicy, self).__init__(check, *args, **kw)


class LoginDenied(HTTPForbidden):
    title = 'Login failure'


_fake_user = object()



class Auth0AuthenticationPolicy(CallbackAuthenticationPolicy):

    login_path = '/login'
    method = 'POST'

    def unauthenticated_userid(self, request):
        '''
        So basically this is used to do a login, instead of the actual
        login view... not sure why, but yeah..
        '''


        # we will cache it for the life of this request, cause pyramids does traversal
        cached = getattr(request, '_auth0_authenticated', _fake_user)
        if cached is not _fake_user:
            return cached

        # try to find the token in the request (should be in the header)
        id_token = get_jwt(request)
        if not id_token:
            # can I thrown an 403 here?
            #print('Missing assertion.', 'unauthenticated_userid', request)
            return None

        user_info = self.get_token_info(id_token, request)
        if not user_info:
            return None

        email = request._auth0_authenticated = user_info['email'].lower()

        # Allow us to access basic user credentials from request obj after authenticating & saving request....authenticated above
        def getUserInfo(request):
            userid = request.authenticated_userid.split('.', 1)[1]
            user_props = request.embed('/session-properties', as_user=userid)
            user_props.update({
                "details" : request.registry[COLLECTIONS]['user'][userid].properties,
                "id_token" : id_token
            })
            return user_props

        request.set_property(getUserInfo, "user_info", True)
        return email


    def get_token_info(self, token, request):
        '''
        given a jwt get token info from auth0, handle
        retrying and what not
        '''
        try:
            # lets see if we have an auth0 token or our own
            registry = request.registry
            auth0_client = registry.settings.get('auth0.client')
            auth0_secret = registry.settings.get('auth0.secret')
            if auth0_client and auth0_secret:
                # leeway accounts for clock drift between us and auth0
                payload = jwt.decode(token, b64decode(auth0_secret, '-_'),
                                     audience=auth0_client, leeway=30)
                if 'email' in payload and payload.get('email_verified') is True:
                    return payload

            else: # we don't have the key, let auth0 do the work for us
                try:
                    user_url = "https://{domain}/tokeninfo".format(domain='hms-dbmi.auth0.com')
                    resp  = requests.post(user_url, {'id_token':token})
                    payload = resp.json()
                    if 'email' in payload and payload.get('email_verified') is True:
                        request.set_property(lambda r: False, 'auth0_expired')
                        return payload
                except ValueError as e:
                    print("Bad or expired token: " + token)
                    request.set_property(lambda r: True, 'auth0_expired') # Allow us to return 403 code &or unset cookie in renderers.py
                    return None

        except ValueError as e:
            print('Invalid JWT assertion : %s (%s)', (e, type(e).__name__))
            return None

        print("didn't get email or email is not verified")
        return None


def get_jwt(request):
    token = None
    try:
        # ensure this is a jwt token not basic auth:
        auth_type = request.headers['Authorization'][:6]
        if auth_type.strip().lower() == 'bearer':
            token = request.headers['Authorization'][7:]
    except (ValueError, TypeError, KeyError):
        token = request.cookies.get('jwtToken')

    return token


@view_config(route_name='login', request_method='POST',
             permission=NO_PERMISSION_REQUIRED)
def login(request):
    '''check the auth0 assertion and remember the user'''

    if hasattr(request, 'user_info'):
        user_info = request.user_info
        if not user_info:
            raise LoginDenied()
    else: 
        raise LoginDenied()

    return user_info


{'fraction_non_alphanumeric': 0.08792882822497106, 'fraction_numerical': 0.004752909633782219, 'mean_word_length': 2.6839506172839505, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5191017', 'n_tokens_mistral': 4581, 'n_tokens_neox': 4347, 'n_words': 1273}