<commit_before># -*- coding: utf-8 -*-
# ###
# Copyright (c) 2013, Rice University
# This software is subject to the provisions of the GNU Affero General
# Public License version 3 (AGPLv3).
# See LICENCE.txt for details.
# ###
import os
import logging
import json
import socket
import uuid
from urllib import urlencode
from urlparse import urlparse, urlunparse

import anykeystore
import velruse
from pyramid import security
from pyramid import httpexceptions
from pyramid.events import subscriber
from pyramid.response import Response
from pyramid.renderers import render_to_response
from pyramid.security import authenticated_userid, remember
from pyramid.threadlocal import get_current_registry
from pyramid.view import view_config
from sqlalchemy import or_
from sqlalchemy.exc import DBAPIError
from velruse.events import AfterLogin

from .utils import diffdict, discover_uid
from .models import (
    DBSession, User, Identity,
    user_schema,
    )
from ._velruse import IActiveIdentityProviders


logger = logging.getLogger('cnxauth')
here = os.path.abspath(os.path.dirname(__file__))
REFERRER_SESSION_KEY = 'referrer_info'


@view_config(route_name='index')
@view_config(route_name='catch-all')
def index(request):
    with open(os.path.join(here, 'assets', 'index.html'), 'r') as f:
        return render_to_response('string', f.read())


@view_config(route_name='identity-providers', renderer='json')
def identity_providers(request):
    """Produces a data structure of identity providers."""
    return request.registry.getUtility(IActiveIdentityProviders)


@view_config(route_name='get-users', request_method='GET', renderer='json')
def get_users(request):
    limit = request.params.get('limit', 10)
    offset = request.params.get('page', 0) * limit
    query = request.params.get('q', None)
    is_a_query = query is not None
    _tokenizer = lambda q: [t for t in q.split() if t]

    if is_a_query:
        user_query = DBSession.query(User)
        criteria = []
        for term in _tokenizer(query):
            term_set = (User.firstname.like('%{}%'.format(term)),
                        User.surname.like('%{}%'.format(term)),
                        User.email==term,
                        )
            criteria.extend(term_set)
        user_query = user_query.filter(or_(*criteria))
        try:
            users = user_query.order_by(User.surname) \
                .limit(limit).offset(offset) \
                .all()
        except DBAPIError:
            raise httpexceptions.HTTPServiceUnavailable(
                    connection_error_message,
                    content_type='text/plain',
                    )
    else:
        try:
            users = DBSession.query(User) \
                .order_by(User.surname) \
                .limit(limit).offset(offset) \
                .all()
        except DBAPIError:
            raise httpexceptions.HTTPServiceUnavailable(
                    connection_error_message,
                    content_type='text/plain',
                    )
    return users


@view_config(route_name='get-user', request_method='GET', renderer='json')
def get_user(request):
    id = request.matchdict['user_id']
    try:
        user = DBSession.query(User).filter(User.id==id).first()
    except DBAPIError:
        raise httpexceptions.HTTPServiceUnavailable(connection_error_message,
                                                    content_type='text/plain',
                                                    )
    if user is None:
        raise httpexceptions.HTTPNotFound()

    permissible = security.has_permission('view', user, request)
    if not permissible:
        raise httpexceptions.HTTPForbidden()
    return user


@view_config(route_name='put-user', request_method=('PUT', 'PATCH',),
             renderer='json')
def put_user(request):
    user = get_user(request)

    # Does the user have write access?
    permissible = security.has_permission('edit', user, request)
    if not permissible:
        raise httpexceptions.HTTPForbidden()

    # Update the user data.
    current_data = user_schema.dictify(user)
    posted_data = user_schema.deserialize(request.json)
    differences = diffdict(current_data, posted_data)
    for key, value in differences.iteritems():
        setattr(user, key, value)
    return user


@view_config(route_name='get-user-identities', request_method='GET',
             renderer='json')
def get_user_identities(request):
    user = get_user(request)
    # Since we are utilizing the another view to acquire the user object,
    #   that view does permissions checking and will prevent any
    #   unauthorized usage from moving forward.
    return user.identities


@view_config(route_name='delete-user-identity', request_method='DELETE')
def delete_user_identity(request):
    # Grab the identity
    id = request.matchdict['identity_id']
    try:
        identity = DBSession.query(Identity) \
            .filter(Identity.id==id) \
            .first()
    except DBAPIError:
        raise httpexceptions.HTTPServiceUnavailable(connection_error_message,
                                                    content_type='text/plain',
                                                    )
    if identity is None:
        raise httpexceptions.HTTPNotFound()
    elif len(identity.user.identities) <= 1:
        raise httpexceptions.HTTPForbidden("Cannot delete the only remaining "
                                           "identity connection.")
    # Check that this user has permission to remove an identity.
    permissible = security.has_permission('delete', identity, request)
    if not permissible:
        raise httpexceptions.HTTPUnauthorized()

    # Remove the identity
    DBSession.delete(identity)
    raise httpexceptions.HTTPNoContent()


def get_token_store():
    """Retrieve the key store for the service exchange tokens."""
    registry = get_current_registry()
    # FIXME The keystore is hardcoded against our existing sqla url.
    #       This should be a settings based connection utility lookup.
    sqla_url = registry.settings['sqlalchemy.url']
    token_storage = anykeystore.create_store('sqla', url=sqla_url)
    return token_storage


@subscriber(AfterLogin)
def capture_requesting_service(event_or_request):
    if isinstance(event_or_request, AfterLogin):
        # 'tis and event
        request = event_or_request.request
    else:
        request = event_or_request
    settings = request.registry.settings

    def parse_service_url(url):
        parsed_url = urlparse(url)
        netloc = parsed_url.netloc
        if netloc.find(':') >= 1:  # smalled possible, one char
            domain, port = netloc.split(':')
        else:
            domain = netloc
            port = parsed_url.scheme == 'http' and 80 or 443
        return domain, int(port)

    # Capture the referrer either through the login POST data
    #   or via the HTTP_REFERER environment variable.
    came_from = request.params.get('came_from', None)
    if came_from is not None:
        service_domain, service_port = parse_service_url(came_from)
    elif request.referrer is not None:
        came_from = request.referrer
        service_domain, service_port = parse_service_url(came_from)
    else:
        # Note, the following 'referer' is not miss-spelled.
        raise httpexceptions.HTTPBadRequest("Missing HTTP Referer")

    # Are local services enabled? This allows services running in the same
    #   address space to look remote.
    local_services_enabled = settings.get('allow-local-services', False)
    # Compare the referrer with the server host to see if they
    #   are the same. If they are, then we do not capture the referrer
    #   information, except when the 'allow-local-service' setting<commit_msg>Fixes an issue where the referrer information is overwritten on login captureing after the generic server-login view has already captured this information.<commit_after># -*- coding: utf-8 -*-
# ###
# Copyright (c) 2013, Rice University
# This software is subject to the provisions of the GNU Affero General
# Public License version 3 (AGPLv3).
# See LICENCE.txt for details.
# ###
import os
import logging
import json
import socket
import uuid
from urllib import urlencode
from urlparse import urlparse, urlunparse

import anykeystore
import velruse
from pyramid import security
from pyramid import httpexceptions
from pyramid.events import subscriber
from pyramid.response import Response
from pyramid.renderers import render_to_response
from pyramid.security import authenticated_userid, remember
from pyramid.threadlocal import get_current_registry
from pyramid.view import view_config
from sqlalchemy import or_
from sqlalchemy.exc import DBAPIError
from velruse.events import AfterLogin

from .utils import diffdict, discover_uid
from .models import (
    DBSession, User, Identity,
    user_schema,
    )
from ._velruse import IActiveIdentityProviders


logger = logging.getLogger('cnxauth')
here = os.path.abspath(os.path.dirname(__file__))
REFERRER_SESSION_KEY = 'referrer_info'


@view_config(route_name='index')
@view_config(route_name='catch-all')
def index(request):
    with open(os.path.join(here, 'assets', 'index.html'), 'r') as f:
        return render_to_response('string', f.read())


@view_config(route_name='identity-providers', renderer='json')
def identity_providers(request):
    """Produces a data structure of identity providers."""
    return request.registry.getUtility(IActiveIdentityProviders)


@view_config(route_name='get-users', request_method='GET', renderer='json')
def get_users(request):
    limit = request.params.get('limit', 10)
    offset = request.params.get('page', 0) * limit
    query = request.params.get('q', None)
    is_a_query = query is not None
    _tokenizer = lambda q: [t for t in q.split() if t]

    if is_a_query:
        user_query = DBSession.query(User)
        criteria = []
        for term in _tokenizer(query):
            term_set = (User.firstname.like('%{}%'.format(term)),
                        User.surname.like('%{}%'.format(term)),
                        User.email==term,
                        )
            criteria.extend(term_set)
        user_query = user_query.filter(or_(*criteria))
        try:
            users = user_query.order_by(User.surname) \
                .limit(limit).offset(offset) \
                .all()
        except DBAPIError:
            raise httpexceptions.HTTPServiceUnavailable(
                    connection_error_message,
                    content_type='text/plain',
                    )
    else:
        try:
            users = DBSession.query(User) \
                .order_by(User.surname) \
                .limit(limit).offset(offset) \
                .all()
        except DBAPIError:
            raise httpexceptions.HTTPServiceUnavailable(
                    connection_error_message,
                    content_type='text/plain',
                    )
    return users


@view_config(route_name='get-user', request_method='GET', renderer='json')
def get_user(request):
    id = request.matchdict['user_id']
    try:
        user = DBSession.query(User).filter(User.id==id).first()
    except DBAPIError:
        raise httpexceptions.HTTPServiceUnavailable(connection_error_message,
                                                    content_type='text/plain',
                                                    )
    if user is None:
        raise httpexceptions.HTTPNotFound()

    permissible = security.has_permission('view', user, request)
    if not permissible:
        raise httpexceptions.HTTPForbidden()
    return user


@view_config(route_name='put-user', request_method=('PUT', 'PATCH',),
             renderer='json')
def put_user(request):
    user = get_user(request)

    # Does the user have write access?
    permissible = security.has_permission('edit', user, request)
    if not permissible:
        raise httpexceptions.HTTPForbidden()

    # Update the user data.
    current_data = user_schema.dictify(user)
    posted_data = user_schema.deserialize(request.json)
    differences = diffdict(current_data, posted_data)
    for key, value in differences.iteritems():
        setattr(user, key, value)
    return user


@view_config(route_name='get-user-identities', request_method='GET',
             renderer='json')
def get_user_identities(request):
    user = get_user(request)
    # Since we are utilizing the another view to acquire the user object,
    #   that view does permissions checking and will prevent any
    #   unauthorized usage from moving forward.
    return user.identities


@view_config(route_name='delete-user-identity', request_method='DELETE')
def delete_user_identity(request):
    # Grab the identity
    id = request.matchdict['identity_id']
    try:
        identity = DBSession.query(Identity) \
            .filter(Identity.id==id) \
            .first()
    except DBAPIError:
        raise httpexceptions.HTTPServiceUnavailable(connection_error_message,
                                                    content_type='text/plain',
                                                    )
    if identity is None:
        raise httpexceptions.HTTPNotFound()
    elif len(identity.user.identities) <= 1:
        raise httpexceptions.HTTPForbidden("Cannot delete the only remaining "
                                           "identity connection.")
    # Check that this user has permission to remove an identity.
    permissible = security.has_permission('delete', identity, request)
    if not permissible:
        raise httpexceptions.HTTPUnauthorized()

    # Remove the identity
    DBSession.delete(identity)
    raise httpexceptions.HTTPNoContent()


def get_token_store():
    """Retrieve the key store for the service exchange tokens."""
    registry = get_current_registry()
    # FIXME The keystore is hardcoded against our existing sqla url.
    #       This should be a settings based connection utility lookup.
    sqla_url = registry.settings['sqlalchemy.url']
    token_storage = anykeystore.create_store('sqla', url=sqla_url)
    return token_storage


@subscriber(AfterLogin)
def capture_requesting_service(event_or_request):
    if isinstance(event_or_request, AfterLogin):
        # 'tis and event
        request = event_or_request.request
    else:
        request = event_or_request
    settings = request.registry.settings

    # We may have already captured the referrer information...
    if REFERRER_SESSION_KEY in request.session:
        return

    def parse_service_url(url):
        parsed_url = urlparse(url)
        netloc = parsed_url.netloc
        if netloc.find(':') >= 1:  # smalled possible, one char
            domain, port = netloc.split(':')
        else:
            domain = netloc
            port = parsed_url.scheme == 'http' and 80 or 443
        return domain, int(port)

    # Capture the referrer either through the login POST data
    #   or via the HTTP_REFERER environment variable.
    came_from = request.params.get('came_from', None)
    if came_from is not None:
        service_domain, service_port = parse_service_url(came_from)
    elif request.referrer is not None:
        came_from = request.referrer
        service_domain, service_port = parse_service_url(came_from)
    else:
        # Note, the following 'referer' is not miss-spelled.
        raise httpexceptions.HTTPBadRequest("Missing HTTP Referer")

    # Are local services enabled? This allows services running in the same
    #   address space to look remote.
    local_services_enabled = settings.get('allow-local-services', False)
    # Compare the referrer with the server host to see if they
    #   are the same. If they are, then we do not capture the referrer
    #   information, except when the 'allow-local-service' setting
{'fraction_non_alphanumeric': 0.08831890833280687, 'fraction_numerical': 0.0021479562827721272, 'mean_word_length': 2.7467455621301777, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1497244', 'n_tokens_mistral': 4425, 'n_tokens_neox': 4275, 'n_words': 1175}