<commit_before># -*- encoding: utf-8 -*-
#
# Copyright © 2016 Red Hat, Inc.
# Copyright © 2014-2015 eNovance
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import uuid

from oslo_utils import strutils
import pecan
from pecan import rest
import six
from six.moves.urllib import parse as urllib_parse
from stevedore import extension
import voluptuous
import webob.exc
import werkzeug.http

from gnocchi import aggregates
from gnocchi import archive_policy
from gnocchi import indexer
from gnocchi import json
from gnocchi import storage
from gnocchi import utils


def arg_to_list(value):
    if isinstance(value, list):
        return value
    elif value:
        return [value]
    return []


def abort(status_code, detail='', headers=None, comment=None, **kw):
    """Like pecan.abort, but make sure detail is a string."""
    if status_code == 404 and not detail:
        raise RuntimeError("http code 404 must have 'detail' set")
    return pecan.abort(status_code, six.text_type(detail),
                       headers, comment, **kw)


def get_user_and_project():
    headers = pecan.request.headers
    user_id = headers.get("X-User-Id")
    project_id = headers.get("X-Project-Id")
    return (user_id, project_id)


# TODO(jd) Move this to oslo.utils as I stole it from Ceilometer
def recursive_keypairs(d, separator='.'):
    """Generator that produces sequence of keypairs for nested dictionaries."""
    for name, value in sorted(six.iteritems(d)):
        if isinstance(value, dict):
            for subname, subvalue in recursive_keypairs(value, separator):
                yield ('%s%s%s' % (name, separator, subname), subvalue)
        else:
            yield name, value


def enforce(rule, target):
    """Return the user and project the request should be limited to.

    :param rule: The rule name
    :param target: The target to enforce on.

    """
    headers = pecan.request.headers
    user_id, project_id = get_user_and_project()
    creds = {
        'roles': headers.get("X-Roles", "").split(","),
        'user_id': user_id,
        'project_id': project_id
    }

    if not isinstance(target, dict):
        target = target.__dict__

    # Flatten dict
    target = dict(recursive_keypairs(target))

    if not pecan.request.policy_enforcer.enforce(rule, target, creds):
        abort(403)


def _get_list_resource_policy_filter(rule, resource_type, user, project):
    try:
        # Check if the policy allows the user to list any resource
        enforce(rule, {
            "resource_type": resource_type,
        })
    except webob.exc.HTTPForbidden:
        policy_filter = []
        try:
            # Check if the policy allows the user to list resources linked
            # to their project
            enforce(rule, {
                "resource_type": resource_type,
                "project_id": project,
            })
        except webob.exc.HTTPForbidden:
            pass
        else:
            policy_filter.append({"=": {"project_id": project}})
        try:
            # Check if the policy allows the user to list resources linked
            # to their created_by_project
            enforce(rule, {
                "resource_type": resource_type,
                "created_by_project_id": project,
            })
        except webob.exc.HTTPForbidden:
            pass
        else:
            policy_filter.append({"=": {"created_by_project_id": project}})

        if not policy_filter:
            # We need to have at least one policy filter in place
            abort(403, "Insufficient privileges")

        return {"or": policy_filter}


def set_resp_location_hdr(location):
    location = '%s%s' % (pecan.request.script_name, location)
    # NOTE(sileht): according the pep-3333 the headers must be
    # str in py2 and py3 even this is not the same thing in both
    # version
    # see: http://legacy.python.org/dev/peps/pep-3333/#unicode-issues
    if six.PY2 and isinstance(location, six.text_type):
        location = location.encode('utf-8')
    location = urllib_parse.quote(location)
    pecan.response.headers['Location'] = location


def deserialize():
    mime_type, options = werkzeug.http.parse_options_header(
        pecan.request.headers.get('Content-Type'))
    if mime_type != "application/json":
        abort(415)
    try:
        params = json.load(pecan.request.body_file_raw,
                           encoding=options.get('charset', 'ascii'))
    except Exception as e:
        abort(400, "Unable to decode body: " + six.text_type(e))
    return params


def deserialize_and_validate(schema, required=True):
    try:
        return voluptuous.Schema(schema, required=required)(
            deserialize())
    except voluptuous.Error as e:
        abort(400, "Invalid input: %s" % e)


def Timestamp(v):
    if v is None:
        return v
    t = utils.to_timestamp(v)
    if t < utils.unix_universal_start:
        raise ValueError("Timestamp must be after Epoch")
    return t


def PositiveOrNullInt(value):
    value = int(value)
    if value < 0:
        raise ValueError("Value must be positive")
    return value


def PositiveNotNullInt(value):
    value = int(value)
    if value <= 0:
        raise ValueError("Value must be positive and not null")
    return value


def Timespan(value):
    return utils.to_timespan(value).total_seconds()


def get_header_option(name, params):
    type, options = werkzeug.http.parse_options_header(
        pecan.request.headers.get('Accept'))
    try:
        return strutils.bool_from_string(<commit_msg>rest: None is not a valid timestamp<commit_after># -*- encoding: utf-8 -*-
#
# Copyright © 2016 Red Hat, Inc.
# Copyright © 2014-2015 eNovance
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import uuid

from oslo_utils import strutils
import pecan
from pecan import rest
import six
from six.moves.urllib import parse as urllib_parse
from stevedore import extension
import voluptuous
import webob.exc
import werkzeug.http

from gnocchi import aggregates
from gnocchi import archive_policy
from gnocchi import indexer
from gnocchi import json
from gnocchi import storage
from gnocchi import utils


def arg_to_list(value):
    if isinstance(value, list):
        return value
    elif value:
        return [value]
    return []


def abort(status_code, detail='', headers=None, comment=None, **kw):
    """Like pecan.abort, but make sure detail is a string."""
    if status_code == 404 and not detail:
        raise RuntimeError("http code 404 must have 'detail' set")
    return pecan.abort(status_code, six.text_type(detail),
                       headers, comment, **kw)


def get_user_and_project():
    headers = pecan.request.headers
    user_id = headers.get("X-User-Id")
    project_id = headers.get("X-Project-Id")
    return (user_id, project_id)


# TODO(jd) Move this to oslo.utils as I stole it from Ceilometer
def recursive_keypairs(d, separator='.'):
    """Generator that produces sequence of keypairs for nested dictionaries."""
    for name, value in sorted(six.iteritems(d)):
        if isinstance(value, dict):
            for subname, subvalue in recursive_keypairs(value, separator):
                yield ('%s%s%s' % (name, separator, subname), subvalue)
        else:
            yield name, value


def enforce(rule, target):
    """Return the user and project the request should be limited to.

    :param rule: The rule name
    :param target: The target to enforce on.

    """
    headers = pecan.request.headers
    user_id, project_id = get_user_and_project()
    creds = {
        'roles': headers.get("X-Roles", "").split(","),
        'user_id': user_id,
        'project_id': project_id
    }

    if not isinstance(target, dict):
        target = target.__dict__

    # Flatten dict
    target = dict(recursive_keypairs(target))

    if not pecan.request.policy_enforcer.enforce(rule, target, creds):
        abort(403)


def _get_list_resource_policy_filter(rule, resource_type, user, project):
    try:
        # Check if the policy allows the user to list any resource
        enforce(rule, {
            "resource_type": resource_type,
        })
    except webob.exc.HTTPForbidden:
        policy_filter = []
        try:
            # Check if the policy allows the user to list resources linked
            # to their project
            enforce(rule, {
                "resource_type": resource_type,
                "project_id": project,
            })
        except webob.exc.HTTPForbidden:
            pass
        else:
            policy_filter.append({"=": {"project_id": project}})
        try:
            # Check if the policy allows the user to list resources linked
            # to their created_by_project
            enforce(rule, {
                "resource_type": resource_type,
                "created_by_project_id": project,
            })
        except webob.exc.HTTPForbidden:
            pass
        else:
            policy_filter.append({"=": {"created_by_project_id": project}})

        if not policy_filter:
            # We need to have at least one policy filter in place
            abort(403, "Insufficient privileges")

        return {"or": policy_filter}


def set_resp_location_hdr(location):
    location = '%s%s' % (pecan.request.script_name, location)
    # NOTE(sileht): according the pep-3333 the headers must be
    # str in py2 and py3 even this is not the same thing in both
    # version
    # see: http://legacy.python.org/dev/peps/pep-3333/#unicode-issues
    if six.PY2 and isinstance(location, six.text_type):
        location = location.encode('utf-8')
    location = urllib_parse.quote(location)
    pecan.response.headers['Location'] = location


def deserialize():
    mime_type, options = werkzeug.http.parse_options_header(
        pecan.request.headers.get('Content-Type'))
    if mime_type != "application/json":
        abort(415)
    try:
        params = json.load(pecan.request.body_file_raw,
                           encoding=options.get('charset', 'ascii'))
    except Exception as e:
        abort(400, "Unable to decode body: " + six.text_type(e))
    return params


def deserialize_and_validate(schema, required=True):
    try:
        return voluptuous.Schema(schema, required=required)(
            deserialize())
    except voluptuous.Error as e:
        abort(400, "Invalid input: %s" % e)


def Timestamp(v):
    t = utils.to_timestamp(v)
    if t < utils.unix_universal_start:
        raise ValueError("Timestamp must be after Epoch")
    return t


def PositiveOrNullInt(value):
    value = int(value)
    if value < 0:
        raise ValueError("Value must be positive")
    return value


def PositiveNotNullInt(value):
    value = int(value)
    if value <= 0:
        raise ValueError("Value must be positive and not null")
    return value


def Timespan(value):
    return utils.to_timespan(value).total_seconds()


def get_header_option(name, params):
    type, options = werkzeug.http.parse_options_header(
        pecan.request.headers.get('Accept'))
    try:
        return strutils.bool_from_string(
{'fraction_non_alphanumeric': 0.09475856376392901, 'fraction_numerical': 0.008584399504746182, 'mean_word_length': 2.7756310377064506, 'pattern_counts': {'":': 22, '<': 9, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '322745', 'n_tokens_mistral': 3591, 'n_tokens_neox': 3481, 'n_words': 1111}