<commit_before># -*- coding: utf-8 -*-

import pandas as pd
from altair import Chart, X, Y
from blessings import Terminal
from .models import Chart as ChartFlo, Number

COLOR = Terminal()
OK = "[" + COLOR.bold_green("ok") + "] "

class ChartController():
    """
    Charts builder: handles serialization into Vega Lite format
    """

    def serialize_count(self, dataset, xfield, yfield, chart_type="bar",
                        width=800, height=300, color=None, size=None):
        """
        Serialize a chart from a count dataset:
        Ex: {"users":200, "groups":30}
        """
        x = []
        y = []
        xfieldtype = xfield[1]
        yfieldtype = yfield[1]
        for datapoint in dataset:
            x.append(datapoint)
            y.append(dataset[datapoint])
        df = pd.DataFrame({xfield[0]: x, yfield[0]: y})
        xencode, yencode = self._encode_fields(xfieldtype, yfieldtype)
        chart = self._chart_class(df, chart_type).encode(
            x=xencode,
            y=yencode,
            color=color,
            size=size
        ).configure_cell(
            width=width,
            height=height
        )
        return chart

    def serialize_timeseries(self, query, xfield, yfield, time_unit,
                             chart_type="line", width=800,
                             height=300, color=None, size=None):
        """
        Serialize a timeseries chart from a query
        """
        xfieldname = xfield[0]
        xfieldtype = xfield[1]
        dates = []
        vals = []
        yfieldname = yfield[0]
        yfieldtype = yfield[1]
        for row in query:
            # date
            has_date = False
            d = getattr(row, xfieldname)
            if d is not None:
                dstr = d.strftime("%Y-%m-%d %H:%M:%S")
                dates.append(dstr)
                has_date = True
            if has_date is True:
                v = getattr(row, yfieldname)
                vals.append(v)
        df = pd.DataFrame({xfieldname: dates, yfieldname: vals})
        # print(df)
        xencode, yencode = self._encode_fields(
            xfieldtype, yfieldtype, time_unit)
        if chart_type != "tick":
            chart = self._chart_class(df, chart_type).encode(
                x=xencode,
                y=yencode,
                color=color,
                size=size,
            ).configure_cell(
                width=width,
                height=height
            )
        else:
            chart = self._chart_class(df, chart_type).encode(
                x=xencode,
                color=color,
                size=size,
            ).configure_cell(
                width=width,
                height=height
            ).configure_scale(
                bandSize=30
            )
        return chart

    def count(self, query, field=None, func=None):
        """
        Count values for a query doing custom checks on fields
        """
        pack = {}
        if field is not None:
            pack = {field: func}
        return self._count_for_query(query, pack)

    def generate(self, slug, name, chart_type, query, x, y,
                 width, height, time_unit=None, color=None,
                 size=None, verbose=False):
        """
        Generates or update a chart instance from a query
        """
        global OK, COLOR
        if verbose is True:
            print("Serializing", slug, "chart...")
        chart = ChartController()
        if time_unit is not None:
            dataset = chart.serialize_timeseries(
                query, x, y, time_unit=time_unit, chart_type=chart_type,
                width=width, height=height, size=size, color=color
            )
        else:
            dataset = chart.serialize_count(
                query, x, y, chart_type=chart_type,
                width=width, height=height, size=size, color=color
            )
        chart, _ = ChartFlo.objects.get_or_create(slug=slug)
        chart.generate(chart, slug, name, dataset)
        if verbose is True:
            print(OK + "Chart", COLOR.bold(slug), "saved")

    def _chart_class(self, df, chart_type):
        """
        Get the right chart class from a string
        """
        if chart_type == "bar":
            return Chart(df).mark_bar()
        elif chart_type == "circle":
            return Chart(df).mark_circle()
        elif chart_type == "line":
            return Chart(df).mark_line()
        elif chart_type == "point":
            return Chart(df).mark_point()
        elif chart_type == "area":
            return Chart(df).mark_area()
        elif chart_type == "tick":
            return Chart(df).mark_tick()
        elif chart_type == "text":
            return Chart(df).mark_text()
        elif chart_type == "square":
            return Chart(df).mark_square()
        elif chart_type == "rule":
            return Chart(df).mark_rule()
        return None

    def _encode_fields(self, xfieldtype, yfieldtype, time_unit=None):
        """
        Encode the fields in Altair format
        """
        if time_unit is not None:
            xencode = X(xfieldtype, timeUnit=time_unit)
        else:
            xencode = X(xfieldtype)
        yencode = Y(yfieldtype)
        return xencode, yencode

    def _count_for_query(self, query, fieldchecks):
        """
        Do custom checks on fields and returns a count
        """
        counter = 0
        for obj in query:
            commit = True
            if len(fieldchecks.keys()) > 0:
                for fieldname in fieldchecks.keys():
                    fieldval = str(getattr(obj, fieldname))
                    func = fieldchecks[fieldname]
                    if func is not None:
                        if func(fieldval) is False:
                            commit = False
            if commit is True:
                counter += 1
        return counter

class NumberController():

    def generate(self, slug, legend, value, unit=None):
        """
        Create or update a number instance from a value
        """
        defaults = {"legend": legend, "value": value, "unit": unit}
        num, _ = Number.objects.get_or_create(slug=slug, defaults=defaults)
        num.generate()
<commit_msg>Add a serialize_date function to ChartController<commit_after># -*- coding: utf-8 -*-

import pandas as pd
from altair import Chart, X, Y
from blessings import Terminal
from .models import Chart as ChartFlo, Number

COLOR = Terminal()
OK = "[" + COLOR.bold_green("ok") + "] "

class ChartController():
    """
    Charts builder: handles serialization into Vega Lite format
    """

    def serialize_count(self, dataset, xfield, yfield, chart_type="bar",
                        width=800, height=300, color=None, size=None):
        """
        Serialize a chart from a count dataset:
        Ex: {"users":200, "groups":30}
        """
        x = []
        y = []
        xfieldtype = xfield[1]
        yfieldtype = yfield[1]
        for datapoint in dataset:
            x.append(datapoint)
            y.append(dataset[datapoint])
        df = pd.DataFrame({xfield[0]: x, yfield[0]: y})
        xencode, yencode = self._encode_fields(xfieldtype, yfieldtype)
        chart = self._chart_class(df, chart_type).encode(
            x=xencode,
            y=yencode,
            color=color,
            size=size
        ).configure_cell(
            width=width,
            height=height
        )
        return chart

    def serialize_timeseries(self, query, xfield, yfield, time_unit,
                             chart_type="line", width=800,
                             height=300, color=None, size=None):
        """
        Serialize a timeseries chart from a query
        """
        xfieldname = xfield[0]
        xfieldtype = xfield[1]
        dates = []
        vals = []
        yfieldname = yfield[0]
        yfieldtype = yfield[1]
        for row in query:
            # date
            has_date = False
            d = getattr(row, xfieldname)
            if d is not None:
                dstr = self.serialize_date(d)
                dates.append(dstr)
                has_date = True
            if has_date is True:
                v = getattr(row, yfieldname)
                vals.append(v)
        df = pd.DataFrame({xfieldname: dates, yfieldname: vals})
        # print(df)
        xencode, yencode = self._encode_fields(
            xfieldtype, yfieldtype, time_unit)
        if chart_type != "tick":
            chart = self._chart_class(df, chart_type).encode(
                x=xencode,
                y=yencode,
                color=color,
                size=size,
            ).configure_cell(
                width=width,
                height=height
            )
        else:
            chart = self._chart_class(df, chart_type).encode(
                x=xencode,
                color=color,
                size=size,
            ).configure_cell(
                width=width,
                height=height
            ).configure_scale(
                bandSize=30
            )
        return chart

    def serialize_date(self, date):
        """
        Serializes a datetime object to Vega Lite format
        """
        return date.strftime("%Y-%m-%d %H:%M:%S")

    def count(self, query, field=None, func=None):
        """
        Count values for a query doing custom checks on fields
        """
        pack = {}
        if field is not None:
            pack = {field: func}
        return self._count_for_query(query, pack)

    def generate(self, slug, name, chart_type, query, x, y,
                 width, height, time_unit=None, color=None,
                 size=None, verbose=False):
        """
        Generates or update a chart instance from a query
        """
        global OK, COLOR
        if verbose is True:
            print("Serializing", slug, "chart...")
        chart = ChartController()
        if time_unit is not None:
            dataset = chart.serialize_timeseries(
                query, x, y, time_unit=time_unit, chart_type=chart_type,
                width=width, height=height, size=size, color=color
            )
        else:
            dataset = chart.serialize_count(
                query, x, y, chart_type=chart_type,
                width=width, height=height, size=size, color=color
            )
        chart, _ = ChartFlo.objects.get_or_create(slug=slug)
        chart.generate(chart, slug, name, dataset)
        if verbose is True:
            print(OK + "Chart", COLOR.bold(slug), "saved")

    def _chart_class(self, df, chart_type):
        """
        Get the right chart class from a string
        """
        if chart_type == "bar":
            return Chart(df).mark_bar()
        elif chart_type == "circle":
            return Chart(df).mark_circle()
        elif chart_type == "line":
            return Chart(df).mark_line()
        elif chart_type == "point":
            return Chart(df).mark_point()
        elif chart_type == "area":
            return Chart(df).mark_area()
        elif chart_type == "tick":
            return Chart(df).mark_tick()
        elif chart_type == "text":
            return Chart(df).mark_text()
        elif chart_type == "square":
            return Chart(df).mark_square()
        elif chart_type == "rule":
            return Chart(df).mark_rule()
        return None

    def _encode_fields(self, xfieldtype, yfieldtype, time_unit=None):
        """
        Encode the fields in Altair format
        """
        if time_unit is not None:
            xencode = X(xfieldtype, timeUnit=time_unit)
        else:
            xencode = X(xfieldtype)
        yencode = Y(yfieldtype)
        return xencode, yencode

    def _count_for_query(self, query, fieldchecks):
        """
        Do custom checks on fields and returns a count
        """
        counter = 0
        for obj in query:
            commit = True
            if len(fieldchecks.keys()) > 0:
                for fieldname in fieldchecks.keys():
                    fieldval = str(getattr(obj, fieldname))
                    func = fieldchecks[fieldname]
                    if func is not None:
                        if func(fieldval) is False:
                            commit = False
            if commit is True:
                counter += 1
        return counter

class NumberController():

    def generate(self, slug, legend, value, unit="", verbose=False):
        """
        Create or update a number instance from a value
        """
        defaults = {"legend": legend, "value": value, "unit": unit}
        num, _ = Number.objects.get_or_create(slug=slug, defaults=defaults)
        num.generate()
        if verbose is True:
            print("[x] Generated number", legend)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import datetime
import werkzeug

from collections import OrderedDict
from werkzeug.exceptions import NotFound

from odoo import fields
from odoo import http
from odoo.http import request
from odoo.addons.http_routing.models.ir_http import slug, unslug
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.website_partner.controllers.main import WebsitePartnerPage

from odoo.tools.translate import _

class WebsiteAccount(CustomerPortal):

    def get_domain_my_lead(self, user):
        return [
            ('partner_assigned_id', 'child_of', user.commercial_partner_id.id),
            ('type', '=', 'lead')
        ]

    def get_domain_my_opp(self, user):
        return [
            ('partner_assigned_id', 'child_of', user.commercial_partner_id.id),
            ('type', '=', 'opportunity')
        ]

    def _prepare_portal_layout_values(self):
        values = super(WebsiteAccount, self)._prepare_portal_layout_values()
        lead_count = request.env['crm.lead'].search_count(self.get_domain_my_lead(request.env.user))
        opp_count = request.env['crm.lead'].search_count(self.get_domain_my_opp(request.env.user))
        values.update({
            'lead_count': lead_count,
            'opp_count': opp_count,
        })
        return values

    @http.route(['/my/leads', '/my/leads/page/<int:page>'], type='http', auth="user", website=True)
    def portal_my_leads(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
        values = self._prepare_portal_layout_values()
        CrmLead = request.env['crm.lead']
        domain = self.get_domain_my_lead(request.env.user)

        searchbar_sortings = {
            'date': {'label': _('Newest'), 'order': 'create_date desc'},
            'name': {'label': _('Name'), 'order': 'name'},
            'contact_name': {'label': _('Contact Name'), 'order': 'contact_name'},
        }

        # default sort by value
        if not sortby:
            sortby = 'date'
        order = searchbar_sortings[sortby]['order']

        # archive groups - Default Group By 'create_date'
        archive_groups = self._get_archive_groups('crm.lead', domain)
        if date_begin and date_end:
            domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
        # pager
        lead_count = CrmLead.search_count(domain)
        pager = request.website.pager(
            url="/my/leads",
            url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
            total=lead_count,
            page=page,
            step=self._items_per_page
        )
        # content according to pager and archive selected
        leads = CrmLead.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])

        values.update({
            'date': date_begin,
            'leads': leads,
            'page_name': 'lead',
            'archive_groups': archive_groups,
            'default_url': '/my/leads',
            'pager': pager,
            'searchbar_sortings': searchbar_sortings,
            'sortby': sortby,
        })
        return request.render("website_crm_partner_assign.portal_my_leads", values)

    @http.route(['/my/opportunities', '/my/opportunities/page/<int:page>'], type='http', auth="user", website=True)
    def portal_my_opportunities(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw):
        values = self._prepare_portal_layout_values()
        CrmLead = request.env['crm.lead']
        domain = self.get_domain_my_opp(request.env.user)

        today = fields.Date.today()
        this_week_end_date = fields.Date.to_string(fields.Date.from_string(today) + datetime.timedelta(days=7))

        searchbar_filters = {
            'all': {'label': _('Active'), 'domain': []},
            'today': {'label': _('Today Activities'), 'domain': [('activity_date_deadline', '=', today)]},
            'week': {'label': _('This Week Activities'),
                     'domain': [('activity_date_deadline', '>=', today), ('activity_date_deadline', '<=', this_week_end_date)]},
            'overdue': {'label': _('Overdue Activities'), 'domain': [('activity_date_deadline', '<', today)]},
            'won': {'label': _('Won'), 'domain': [('stage_id.probability', '=', 100), ('stage_id.on_change', '=', True)]},
            'lost': {'label': _('Lost'), 'domain': [('active', '=', False), ('probability', '=', 0)]},
        }
        searchbar_sortings = {
            'date': {'label': _('Newest'), 'order': 'create_date desc'},
            'name': {'label': _('Name'), 'order': 'name'},
            'contact_name': {'label': _('Contact Name'), 'order': 'contact_name'},
            'revenue': {'label': _('Expected Revenue'), 'order': 'planned_revenue desc'},
            'probability': {'label': _('Probability'), 'order': 'probability desc'},
            'stage': {'label': _('Stage'), 'order': 'stage_id'},
        }

        # default sort by value
        if not sortby:
            sortby = 'date'
        order = searchbar_sortings[sortby]['order']
        # default filter by value
        if not filterby:
            filterby = 'all'
        domain += searchbar_filters[filterby]['domain']
        if filterby == 'lost':
            CrmLead = CrmLead.with_context(active_test=False)

        # archive groups - Default Group By 'create_date'
        archive_groups = self._get_archive_groups('crm.lead', domain)
        if date_begin and date_end:
            domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
        # pager
        opp_count = CrmLead.search_count(domain)
        pager = request.website.pager(
            url="/my/opportunities",
            url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
            total=opp_count,
            page=page,
            step=self._items_per_page
        )
        # content according to pager and archive selected
        opportunities = CrmLead.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])

        values.update({
            'date': date_begin,
            'opportunities': opportunities,
            'page_name': 'opportunity',
            'archive_groups': archive_groups,
            'default_url': '/my/opportunities',
            'pager': pager,
            'searchbar_sortings': searchbar_sortings,
            'sortby': sortby,
            'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
            'filterby': filterby,
        })
        return request.render("website_crm_partner_assign.portal_my_opportunities", values)

    @http.route(['''/my/lead/<model('crm.lead', "[('type','=', 'lead')]"):lead>'''], type='http', auth="user", website=True)
    def portal_my_lead(self, lead, **kw):
        if lead.type != 'lead':
            raise NotFound()
        return request.render("website_crm_partner_assign.portal_my_lead", {'lead': lead})

    @http.route(['''/my/opportunity/<model('crm.lead', "[('type','=', 'opportunity')]"):opp>'''], type='http', auth="user", website=True)
    def portal_my_opportunity(self, opp, **kw):
        if opp.type != 'opportunity':
            raise NotFound()

        return request.render(
            "website_crm_partner_assign.portal_my_opportunity", {
                'opportunity': opp,
                'user_activity': opp.sudo().activity_ids.filtered(lambda activity: activity.user_id == request.env.user)[:1],
                'stages': request.env['crm.stage'].search([('probability', '!=', '100')], order='sequence desc, name desc, id desc'),
                'activity_types': request.env['mail.activity.type'].sudo().search([]),
                'states': request.env['res.country.state'].sudo().search([]),
                'countries': request.env['res.country'].sudo().search([]),
            })

class WebsiteCrmPartnerAssign(WebsitePartnerPage):
    _references_per_page = 40

    def sitemap_partners(env, rule, qs):
        if not qs or qs.lower() in '/partners':
            yield {'loc': '/partners'}

        Grade = env['res.partner.grade']
        dom = [('website_published', '=', True)]
        dom += sitemap_qs2dom(qs=qs, route='/partners/grade/', field=Grade._rec_name)
        for grade in env['res.partner.grade'].search(dom):
            loc = '/partners/grade/%s' % slug(grade)
            if not qs or qs.lower() in loc:
                yield {'loc': loc}

        partners_dom = [('is_company', '=', True), ('grade_id', '!=', False), ('website_published', '=', True), ('grade_id.website_published', '=', True)]
        dom += sitemap_qs2dom(qs=qs, route='/partners/country/')
        countries = env['res.partner'].sudo().read_group(partners_dom, fields=['id', 'country_id'], groupby='country_id')
        for country in countries:
            loc = '/partners/country/%s' % slug(country['country_id'])
            if not qs or qs.lower() in loc:
                yield {'loc': loc}

    @http.route([
        '/partners',
        '/partners/page/<int:page>',

        '/partners/grade/<model("res.partner.grade"):grade>',
        '/partners/grade/<model("res.partner.grade"):grade>/page/<int:page>',

        '/partners/country/<model("res.country"):country>',
        '/partners/country/<model("res.country"):country>/page/<int:page>',

        '/partners/grade/<model("res.partner.grade"):grade>/country/<model("res.country"):country>',
        '/partners/grade/<model("res.partner.grade"):grade>/country/<model("res.country"):country>/page/<int:page>',
    ], type='http', auth="public", website=True, sitemap=sitemap_partners)
    def partners(self, country=None, grade=None, page=0, **post):
        country_all = post.pop('country_all', False)
        partner_obj = request.env['res.partner']
        country_obj = request.env['res.country']
        search = post.get('search', '')

        base_partner_domain = [('is_company', '=', True), ('grade_id', '!=', False), ('website_published', '=', True)]
        if not request.env['res.users'].has_group('website.group_website_publisher'):
            base_partner_domain += [('grade_id.website_published', '=', True)]
        if search:
            base_partner_domain += ['|', ('name', 'ilike', search), ('website_description', 'ilike', search)]

        # group by grade
        grade_domain = list(base_partner_domain)
        if not country and not country_all:
            country_code = request.session['geoip'].get('country_code')
            if country_code:
                country = country_obj.search([('code', '=', country_code)], limit=1)
        if country:
            grade_domain += [('country_id', '=', country.id)]
        grades = partner_obj.sudo().read_group(
            grade_domain, ["id", "grade_id"],
            groupby="grade_id")
        grades_partners = partner_obj.sudo().search_count(grade_domain)
        # flag active grade
        for grade_dict in grades:
            grade_dict['active'] = grade and grade_dict['grade_id'][0] == grade.id
        grades.insert(0, {
            'grade_id_count': grades_partners,
            'grade_id': (0, _("All Categories")),
            'active': bool(grade is None),
        })

        # group by country
        country_domain = list(base_partner_domain)
        if grade:
            country_domain += [('grade_id', '=', grade.id)]
        countries = partner_obj.sudo().read_group(
            country_domain, ["id", "country_id"],
            groupby="country_id", orderby="country_id")
        countries_partners = partner_obj.sudo().search_count(country_domain)
        # flag active country
        for country_dict in countries:
            country_dict['active'] = country and country_dict['country_id'] and country_dict['country_id'][0] == country.id
        countries.insert(0, {
            'country_id_count': countries_partners,
            'country_id': (0, _("All Countries")),
            'active': bool(country is None),
        })

        # current search
        if grade:
            base_partner_domain += [('grade_id', '=', grade.id)]
        if country:
            base_partner_domain += [('country_id', '=', country.id)]

        # format pager
        if grade and not country:
            url = '/partners/grade/' + slug(grade)
        elif country and not grade:
            url = '/partners/country/' + slug(country)
        elif country and grade:
            url = '/partners/grade/' + slug(grade) + '/country/' + slug(country)
        else:
            url = '/partners'
        url_args = {}
        if search:
            url_args['search'] = search
        if country_all:
            url_args['country_all'] = True

        partner_count = partner_obj.sudo().search_count(base_partner_domain)
        pager = request.website.pager(
            url=url, total=partner_count, page=page, step=self._references_per_page, scope=7,
            url_args=url_args)

        # search partners matching current search parameters
        partner_ids = partner_obj.sudo().search(
            base_partner_domain, order="grade_sequence DESC, implemented_count DESC, display_name ASC, id ASC",
            offset=pager['offset'], limit=self._references_per_page)
        partners = partner_ids.sudo()

        google_map_partner_ids = ','.join(str(p.id) for p in partners)
        google_maps_api_key = request.website.google_maps_api_key

        values = {
            'countries': countries,
            'current_country': country,
            'grades': grades,
            'current_grade': grade,
            'partners': partners,
            'google_map_partner_ids': google_map_partner_ids,
            'pager': pager,
            'searches': post,
            'search_path': "%s" % werkzeug.url_encode(post),
            'google_maps_api_key': google_maps_api_key,
        }
        return request.render("website_crm_partner_assign.index", values, status=partners and 200 or 404)

    # Do not use semantic controller due to sudo()
    @http.route(['/partners/<partner_id>'], type='http', auth="public", website=True)
    def partners_detail(self, partner_id, **post):
        _, partner_id = unslug(partner_id)
        current_grade, current_country = None, None
        grade_id = post.get('grade_id')
        country_id = post.get('country_id')
        if grade_id:
            current_grade = request.env['res.partner.grade'].browse(int(grade_id)).exists()
        if country_id:
            current_country = request.env['res.country'].browse(int(country_id)).exists()
        if partner_id:
            partner = request.env['res.partner'].sudo().browse(partner_id)
            is_website_publisher = request.env['res.users'].has_group('website.group_website_publisher')
            if partner.exists() and (partner.website_published or is_website_publisher):
                values = {
                    'main_object': partner,
                    'partner': partner,
                    'current_grade': current_grade,
                    'current_country': current_country
                }
                return request.render("website_crm_partner_assign.partner", values)
        return self.partners(**post)
<commit_msg>[FIX] website_crm_partner_assign: fix sitemap partner no country<commit_after># -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

import datetime
import werkzeug

from collections import OrderedDict
from werkzeug.exceptions import NotFound

from odoo import fields
from odoo import http
from odoo.http import request
from odoo.addons.http_routing.models.ir_http import slug, unslug
from odoo.addons.website.models.ir_http import sitemap_qs2dom
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.website_partner.controllers.main import WebsitePartnerPage

from odoo.tools.translate import _

class WebsiteAccount(CustomerPortal):

    def get_domain_my_lead(self, user):
        return [
            ('partner_assigned_id', 'child_of', user.commercial_partner_id.id),
            ('type', '=', 'lead')
        ]

    def get_domain_my_opp(self, user):
        return [
            ('partner_assigned_id', 'child_of', user.commercial_partner_id.id),
            ('type', '=', 'opportunity')
        ]

    def _prepare_portal_layout_values(self):
        values = super(WebsiteAccount, self)._prepare_portal_layout_values()
        lead_count = request.env['crm.lead'].search_count(self.get_domain_my_lead(request.env.user))
        opp_count = request.env['crm.lead'].search_count(self.get_domain_my_opp(request.env.user))
        values.update({
            'lead_count': lead_count,
            'opp_count': opp_count,
        })
        return values

    @http.route(['/my/leads', '/my/leads/page/<int:page>'], type='http', auth="user", website=True)
    def portal_my_leads(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
        values = self._prepare_portal_layout_values()
        CrmLead = request.env['crm.lead']
        domain = self.get_domain_my_lead(request.env.user)

        searchbar_sortings = {
            'date': {'label': _('Newest'), 'order': 'create_date desc'},
            'name': {'label': _('Name'), 'order': 'name'},
            'contact_name': {'label': _('Contact Name'), 'order': 'contact_name'},
        }

        # default sort by value
        if not sortby:
            sortby = 'date'
        order = searchbar_sortings[sortby]['order']

        # archive groups - Default Group By 'create_date'
        archive_groups = self._get_archive_groups('crm.lead', domain)
        if date_begin and date_end:
            domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
        # pager
        lead_count = CrmLead.search_count(domain)
        pager = request.website.pager(
            url="/my/leads",
            url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
            total=lead_count,
            page=page,
            step=self._items_per_page
        )
        # content according to pager and archive selected
        leads = CrmLead.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])

        values.update({
            'date': date_begin,
            'leads': leads,
            'page_name': 'lead',
            'archive_groups': archive_groups,
            'default_url': '/my/leads',
            'pager': pager,
            'searchbar_sortings': searchbar_sortings,
            'sortby': sortby,
        })
        return request.render("website_crm_partner_assign.portal_my_leads", values)

    @http.route(['/my/opportunities', '/my/opportunities/page/<int:page>'], type='http', auth="user", website=True)
    def portal_my_opportunities(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw):
        values = self._prepare_portal_layout_values()
        CrmLead = request.env['crm.lead']
        domain = self.get_domain_my_opp(request.env.user)

        today = fields.Date.today()
        this_week_end_date = fields.Date.to_string(fields.Date.from_string(today) + datetime.timedelta(days=7))

        searchbar_filters = {
            'all': {'label': _('Active'), 'domain': []},
            'today': {'label': _('Today Activities'), 'domain': [('activity_date_deadline', '=', today)]},
            'week': {'label': _('This Week Activities'),
                     'domain': [('activity_date_deadline', '>=', today), ('activity_date_deadline', '<=', this_week_end_date)]},
            'overdue': {'label': _('Overdue Activities'), 'domain': [('activity_date_deadline', '<', today)]},
            'won': {'label': _('Won'), 'domain': [('stage_id.probability', '=', 100), ('stage_id.on_change', '=', True)]},
            'lost': {'label': _('Lost'), 'domain': [('active', '=', False), ('probability', '=', 0)]},
        }
        searchbar_sortings = {
            'date': {'label': _('Newest'), 'order': 'create_date desc'},
            'name': {'label': _('Name'), 'order': 'name'},
            'contact_name': {'label': _('Contact Name'), 'order': 'contact_name'},
            'revenue': {'label': _('Expected Revenue'), 'order': 'planned_revenue desc'},
            'probability': {'label': _('Probability'), 'order': 'probability desc'},
            'stage': {'label': _('Stage'), 'order': 'stage_id'},
        }

        # default sort by value
        if not sortby:
            sortby = 'date'
        order = searchbar_sortings[sortby]['order']
        # default filter by value
        if not filterby:
            filterby = 'all'
        domain += searchbar_filters[filterby]['domain']
        if filterby == 'lost':
            CrmLead = CrmLead.with_context(active_test=False)

        # archive groups - Default Group By 'create_date'
        archive_groups = self._get_archive_groups('crm.lead', domain)
        if date_begin and date_end:
            domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
        # pager
        opp_count = CrmLead.search_count(domain)
        pager = request.website.pager(
            url="/my/opportunities",
            url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
            total=opp_count,
            page=page,
            step=self._items_per_page
        )
        # content according to pager and archive selected
        opportunities = CrmLead.search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])

        values.update({
            'date': date_begin,
            'opportunities': opportunities,
            'page_name': 'opportunity',
            'archive_groups': archive_groups,
            'default_url': '/my/opportunities',
            'pager': pager,
            'searchbar_sortings': searchbar_sortings,
            'sortby': sortby,
            'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
            'filterby': filterby,
        })
        return request.render("website_crm_partner_assign.portal_my_opportunities", values)

    @http.route(['''/my/lead/<model('crm.lead', "[('type','=', 'lead')]"):lead>'''], type='http', auth="user", website=True)
    def portal_my_lead(self, lead, **kw):
        if lead.type != 'lead':
            raise NotFound()
        return request.render("website_crm_partner_assign.portal_my_lead", {'lead': lead})

    @http.route(['''/my/opportunity/<model('crm.lead', "[('type','=', 'opportunity')]"):opp>'''], type='http', auth="user", website=True)
    def portal_my_opportunity(self, opp, **kw):
        if opp.type != 'opportunity':
            raise NotFound()

        return request.render(
            "website_crm_partner_assign.portal_my_opportunity", {
                'opportunity': opp,
                'user_activity': opp.sudo().activity_ids.filtered(lambda activity: activity.user_id == request.env.user)[:1],
                'stages': request.env['crm.stage'].search([('probability', '!=', '100')], order='sequence desc, name desc, id desc'),
                'activity_types': request.env['mail.activity.type'].sudo().search([]),
                'states': request.env['res.country.state'].sudo().search([]),
                'countries': request.env['res.country'].sudo().search([]),
            })

class WebsiteCrmPartnerAssign(WebsitePartnerPage):
    _references_per_page = 40

    def sitemap_partners(env, rule, qs):
        if not qs or qs.lower() in '/partners':
            yield {'loc': '/partners'}

        Grade = env['res.partner.grade']
        dom = [('website_published', '=', True)]
        dom += sitemap_qs2dom(qs=qs, route='/partners/grade/', field=Grade._rec_name)
        for grade in env['res.partner.grade'].search(dom):
            loc = '/partners/grade/%s' % slug(grade)
            if not qs or qs.lower() in loc:
                yield {'loc': loc}

        partners_dom = [('is_company', '=', True), ('grade_id', '!=', False), ('website_published', '=', True),
                        ('grade_id.website_published', '=', True), ('country_id', '!=', False)]
        dom += sitemap_qs2dom(qs=qs, route='/partners/country/')
        countries = env['res.partner'].sudo().read_group(partners_dom, fields=['id', 'country_id'], groupby='country_id')
        for country in countries:
            loc = '/partners/country/%s' % slug(country['country_id'])
            if not qs or qs.lower() in loc:
                yield {'loc': loc}

    @http.route([
        '/partners',
        '/partners/page/<int:page>',

        '/partners/grade/<model("res.partner.grade"):grade>',
        '/partners/grade/<model("res.partner.grade"):grade>/page/<int:page>',

        '/partners/country/<model("res.country"):country>',
        '/partners/country/<model("res.country"):country>/page/<int:page>',

        '/partners/grade/<model("res.partner.grade"):grade>/country/<model("res.country"):country>',
        '/partners/grade/<model("res.partner.grade"):grade>/country/<model("res.country"):country>/page/<int:page>',
    ], type='http', auth="public", website=True, sitemap=sitemap_partners)
    def partners(self, country=None, grade=None, page=0, **post):
        country_all = post.pop('country_all', False)
        partner_obj = request.env['res.partner']
        country_obj = request.env['res.country']
        search = post.get('search', '')

        base_partner_domain = [('is_company', '=', True), ('grade_id', '!=', False), ('website_published', '=', True)]
        if not request.env['res.users'].has_group('website.group_website_publisher'):
            base_partner_domain += [('grade_id.website_published', '=', True)]
        if search:
            base_partner_domain += ['|', ('name', 'ilike', search), ('website_description', 'ilike', search)]

        # group by grade
        grade_domain = list(base_partner_domain)
        if not country and not country_all:
            country_code = request.session['geoip'].get('country_code')
            if country_code:
                country = country_obj.search([('code', '=', country_code)], limit=1)
        if country:
            grade_domain += [('country_id', '=', country.id)]
        grades = partner_obj.sudo().read_group(
            grade_domain, ["id", "grade_id"],
            groupby="grade_id")
        grades_partners = partner_obj.sudo().search_count(grade_domain)
        # flag active grade
        for grade_dict in grades:
            grade_dict['active'] = grade and grade_dict['grade_id'][0] == grade.id
        grades.insert(0, {
            'grade_id_count': grades_partners,
            'grade_id': (0, _("All Categories")),
            'active': bool(grade is None),
        })

        # group by country
        country_domain = list(base_partner_domain)
        if grade:
            country_domain += [('grade_id', '=', grade.id)]
        countries = partner_obj.sudo().read_group(
            country_domain, ["id", "country_id"],
            groupby="country_id", orderby="country_id")
        countries_partners = partner_obj.sudo().search_count(country_domain)
        # flag active country
        for country_dict in countries:
            country_dict['active'] = country and country_dict['country_id'] and country_dict['country_id'][0] == country.id
        countries.insert(0, {
            'country_id_count': countries_partners,
            'country_id': (0, _("All Countries")),
            'active': bool(country is None),
        })

        # current search
        if grade:
            base_partner_domain += [('grade_id', '=', grade.id)]
        if country:
            base_partner_domain += [('country_id', '=', country.id)]

        # format pager
        if grade and not country:
            url = '/partners/grade/' + slug(grade)
        elif country and not grade:
            url = '/partners/country/' + slug(country)
        elif country and grade:
            url = '/partners/grade/' + slug(grade) + '/country/' + slug(country)
        else:
            url = '/partners'
        url_args = {}
        if search:
            url_args['search'] = search
        if country_all:
            url_args['country_all'] = True

        partner_count = partner_obj.sudo().search_count(base_partner_domain)
        pager = request.website.pager(
            url=url, total=partner_count, page=page, step=self._references_per_page, scope=7,
            url_args=url_args)

        # search partners matching current search parameters
        partner_ids = partner_obj.sudo().search(
            base_partner_domain, order="grade_sequence DESC, implemented_count DESC, display_name ASC, id ASC",
            offset=pager['offset'], limit=self._references_per_page)
        partners = partner_ids.sudo()

        google_map_partner_ids = ','.join(str(p.id) for p in partners)
        google_maps_api_key = request.website.google_maps_api_key

        values = {
            'countries': countries,
            'current_country': country,
            'grades': grades,
            'current_grade': grade,
            'partners': partners,
            'google_map_partner_ids': google_map_partner_ids,
            'pager': pager,
            'searches': post,
            'search_path': "%s" % werkzeug.url_encode(post),
            'google_maps_api_key': google_maps_api_key,
        }
        return request.render("website_crm_partner_assign.index", values, status=partners and 200 or 404)

    # Do not use semantic controller due to sudo()
    @http.route(['/partners/<partner_id>'], type='http', auth="public", website=True)
    def partners_detail(self, partner_id, **post):
        _, partner_id = unslug(partner_id)
        current_grade, current_country = None, None
        grade_id = post.get('grade_id')
        country_id = post.get('country_id')
        if grade_id:
            current_grade = request.env['res.partner.grade'].browse(int(grade_id)).exists()
        if country_id:
            current_country = request.env['res.country'].browse(int(country_id)).exists()
        if partner_id:
            partner = request.env['res.partner'].sudo().browse(partner_id)
            is_website_publisher = request.env['res.users'].has_group('website.group_website_publisher')
            if partner.exists() and (partner.website_published or is_website_publisher):
                values = {
                    'main_object': partner,
                    'partner': partner,
                    'current_grade': current_grade,
                    'current_country': current_country
                }
                return request.render("website_crm_partner_assign.partner", values)
        return self.partners(**post)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import pytest

import formation.atomic_template
from formation.exception import InvalidPropertyError
from formation.parameter import Parameter

@pytest.mark.parametrize("properties,resource_title,expected_output", [
    # Flat dict of parameters.
    (
        {"A": Parameter("A"), "B": Parameter("B"), "C": "C"},
        "Title",
        {"TitleA": {"Type": "String"}, "TitleB": {"Type": "String"}}
    ),
    # List of parameters
    (
        {"A": [Parameter("B"), Parameter("C")]},
        "Title",
        {"TitleB": {"Type": "String"}, "TitleC": {"Type": "String"}}
    ),
    # Nested dict of parameters
    (
        {"A": {"B": Parameter("B"), "C": Parameter("C")}},
        "Title",
        {"TitleB": {"Type": "String"}, "TitleC": {"Type": "String"}}
    ),
    # Nested list and dict
    (
        {"A": {"B": [Parameter("C")], "D": {"E": Parameter("E")}}},
        "Title",
        {"TitleC": {"Type": "String"}, "TitleE": {"Type": "String"}}
    )
])
def test_get_parameters(properties, resource_title, expected_output):
    output = formation.atomic_template._get_parameters(
        properties, resource_title
    )
    assert output == expected_output

@pytest.mark.parametrize(
    "required_properties,user_properties,expected_output",
    [
        (
            ["CidrBlock"],
            {},
            {
                "CidrBlock": Parameter("CidrBlock")
            }
        ),
        (
            ["CidrBlock"],
            {"CidrBlock": "10.0.0.0/24"},
            {
                "CidrBlock": "10.0.0.0/24"
            }
        ),
        (
            ["CidrBlock"],
            {"EnableDnsSupport": False},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "EnableDnsSupport": False
            }
        ),
        (
            ["CidrBlock"],
            {"EnableDnsSupport": Parameter("MyEnableDnsSupportParam")},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "EnableDnsSupport": Parameter("MyEnableDnsSupportParam")
            }
        ),
        (
            ["CidrBlock"],
            {"Tags": [{"Key": "Name", "Value": Parameter("VpcName")}]},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "Tags": [{"Key": "Name", "Value": Parameter("VpcName")}]
            }
        ),
    ]
)
def test_get_properties(required_properties, user_properties, expected_output):
    output = formation.atomic_template._get_properties(
        required_properties,
        user_properties
    )
    assert output == expected_output

@pytest.mark.parametrize(
    "required_properties,user_properties,expected_output",
    [
        (
            {
                "Code": {
                    "Documentation": "<truncated>",
                    "Required": True,
                    "PrimitiveType": "String",
                    "UpdateType": "Immutable"
                }
            },
            {},
            None
        ),
        (
            {
                "Code": {
                    "Documentation": "<truncated>",
                    "Required": True,
                    "Type": "Code",
                    "UpdateType": "Mutable"
                }
            },
            {},
            InvalidPropertyError
        )
    ]
)
def test_validate_properties(
        required_properties, user_properties, expected_output
):
    if expected_output and issubclass(expected_output, Exception):
        with pytest.raises(expected_output):
            formation.atomic_template._validate_properties(
                required_properties, user_properties
            )
    else:
        output = formation.atomic_template._validate_properties(
            required_properties, user_properties
        )
        assert output == expected_output
<commit_msg>Add _resolve_parameterised_properties test<commit_after># -*- coding: utf-8 -*-

import pytest

import formation.atomic_template
from formation.exception import InvalidPropertyError
from formation.parameter import Parameter

@pytest.mark.parametrize("properties,resource_title,expected_output", [
    # Flat dict of parameters.
    (
        {"A": Parameter("A"), "B": Parameter("B"), "C": "C"},
        "Title",
        {"TitleA": {"Type": "String"}, "TitleB": {"Type": "String"}}
    ),
    # List of parameters
    (
        {"A": [Parameter("B"), Parameter("C")]},
        "Title",
        {"TitleB": {"Type": "String"}, "TitleC": {"Type": "String"}}
    ),
    # Nested dict of parameters
    (
        {"A": {"B": Parameter("B"), "C": Parameter("C")}},
        "Title",
        {"TitleB": {"Type": "String"}, "TitleC": {"Type": "String"}}
    ),
    # Nested list and dict
    (
        {"A": {"B": [Parameter("C")], "D": {"E": Parameter("E")}}},
        "Title",
        {"TitleC": {"Type": "String"}, "TitleE": {"Type": "String"}}
    )
])
def test_get_parameters(properties, resource_title, expected_output):
    output = formation.atomic_template._get_parameters(
        properties, resource_title
    )
    assert output == expected_output

@pytest.mark.parametrize(
    "required_properties,user_properties,expected_output",
    [
        (
            ["CidrBlock"],
            {},
            {
                "CidrBlock": Parameter("CidrBlock")
            }
        ),
        (
            ["CidrBlock"],
            {"CidrBlock": "10.0.0.0/24"},
            {
                "CidrBlock": "10.0.0.0/24"
            }
        ),
        (
            ["CidrBlock"],
            {"EnableDnsSupport": False},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "EnableDnsSupport": False
            }
        ),
        (
            ["CidrBlock"],
            {"EnableDnsSupport": Parameter("MyEnableDnsSupportParam")},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "EnableDnsSupport": Parameter("MyEnableDnsSupportParam")
            }
        ),
        (
            ["CidrBlock"],
            {"Tags": [{"Key": "Name", "Value": Parameter("VpcName")}]},
            {
                "CidrBlock": Parameter("CidrBlock"),
                "Tags": [{"Key": "Name", "Value": Parameter("VpcName")}]
            }
        ),
    ]
)
def test_get_properties(required_properties, user_properties, expected_output):
    output = formation.atomic_template._get_properties(
        required_properties,
        user_properties
    )
    assert output == expected_output

@pytest.mark.parametrize("properties,resource_title,expected_output", [
    # Flat dict of parameters.
    (
        {"A": Parameter("A"), "B": Parameter("B"), "C": "C"},
        "Title",
        {"A": {"Ref": "TitleA"}, "B": {"Ref": "TitleB"}, "C": "C"}
    ),
    # List of parameters
    (
        {"A": [Parameter("B"), Parameter("C")]},
        "Title",
        {"A": [{"Ref": "TitleB"}, {"Ref": "TitleC"}]}
    ),
    # Nested dict of parameters
    (
        {"A": {"B": Parameter("B"), "C": Parameter("C")}},
        "Title",
        {"A": {"B": {"Ref": "TitleB"}, "C": {"Ref": "TitleC"}}}
    ),
    # Nested list and dict
    (
        {"A": {"B": [Parameter("C")], "D": {"E": Parameter("E")}}},
        "Title",
        {"A": {"B": [{"Ref": "TitleC"}], "D": {"E": {"Ref": "TitleE"}}}}
    )
])
def test_resolve_parameterised_properties(
        properties, resource_title, expected_output
):
    output = formation.atomic_template._resolve_parameterised_properties(
        properties, resource_title
    )
    assert output == expected_output

@pytest.mark.parametrize(
    "required_properties,user_properties,expected_output",
    [
        (
            {
                "Code": {
                    "Documentation": "<truncated>",
                    "Required": True,
                    "PrimitiveType": "String",
                    "UpdateType": "Immutable"
                }
            },
            {},
            None
        ),
        (
            {
                "Code": {
                    "Documentation": "<truncated>",
                    "Required": True,
                    "Type": "Code",
                    "UpdateType": "Mutable"
                }
            },
            {},
            InvalidPropertyError
        )
    ]
)
def test_validate_properties(
        required_properties, user_properties, expected_output
):
    if expected_output and issubclass(expected_output, Exception):
        with pytest.raises(expected_output):
            formation.atomic_template._validate_properties(
                required_properties, user_properties
            )
    else:
        output = formation.atomic_template._validate_properties(
            required_properties, user_properties
        )
        assert output == expected_output
<|endoftext|>


<commit_before>## Copyright (c) 2013 Edward Weston Hunter
## See the file license.txt for copying permission.

from bitmath import BinInt, Context, Environment

def random_tests():
    i1 = BinInt(8)
    i1.bit_on(1)
    i1.bit_on(5)
    print i1
    i1.invert()
    print i1
    i1.left_shift(2)
    print i1
    i1.right_shift()
    print i1
    i1.invert()
    print i1
    i2 = i1.clone()

    i1.bit_on(1)
    i2.bit_off(2)
    print
    print i1
    print i2

    i1.all_on()
    i2.all_off()
    print
    print i1
    print i2

    i3 = BinInt(8)
    i3.bit_on(0)
    i3.bit_on(1)
    i4 = BinInt(8)
    i4.bit_on(1)
    i4.bit_on(2)
    i4.bit_on(4)
    i5 = i3.clone()
    print
    print i3
    print i4
    i5.add(i4)
    print i5

    print
    print 'Test zero:'
    print i2, i2.is_zero()
    print i3, i3.is_zero()
    print i4, i4.is_zero()
    print i5, i5.is_zero()

def environment_tests():
    # env = Environment()
    # print env

    ctx = Context(bit_depth=8)
    x = ctx.new_int()
    x.bit_on(3)
    x.bit_on(1)
    print x
    x.increment()
    print x
    x.decrement()
    x.decrement()
    print x

def main():
    # random_tests()
    environment_tests()

main()
<commit_msg>Test changes.<commit_after>## Copyright (c) 2013 Edward Weston Hunter
## See the file license.txt for copying permission.

from bitmath import BinInt, Context, Environment

def random_tests():
    i1 = BinInt(8)
    i1.bit_on(1)
    i1.bit_on(5)
    print i1
    i1.invert()
    print i1
    i1.left_shift(2)
    print i1
    i1.right_shift()
    print i1
    i1.invert()
    print i1
    i2 = i1.clone()

    i1.bit_on(1)
    i2.bit_off(2)
    print
    print i1
    print i2

    i1.all_on()
    i2.all_off()
    print
    print i1
    print i2

    i3 = BinInt(8)
    i3.bit_on(0)
    i3.bit_on(1)
    i4 = BinInt(8)
    i4.bit_on(1)
    i4.bit_on(2)
    i4.bit_on(4)
    i5 = i3.clone()
    print
    print i3
    print i4
    i5.add(i4)
    print i5

    print
    print 'Test zero:'
    print i2, i2.is_zero()
    print i3, i3.is_zero()
    print i4, i4.is_zero()
    print i5, i5.is_zero()

def environment_tests():
    # env = Environment()
    # print env

    ctx = Context(bit_depth=8)
    x = ctx.new_int()
    x.bit_on(3)
    x.bit_on(1)
    print x
    print x.increment_inplace()
    print x
    x.decrement_inplace(2)
    print x
    y = x.clone()
    y.bit_off(3)
    print y
    y.copy_from(x)
    print y
    print y.invert()
    print y.negate()

    x.increment_inplace()
    z = ctx.new_int()
    z.bit_on(1)
    z.bit_on(0)
    print x, z
    print x.subtract(z)
    t = z.subtract(x)
    print t, t.is_negative(), t.abs()

def main():
    # random_tests()
    environment_tests()

main()
<|endoftext|>


<commit_before>import base64
import os
import re
import time

import util.cryptography
from linkr import db

def _generate_api_key():
    """
    Generate a random API key.

    :return: A random alphanumeric string.
    """
    return re.sub('(\W\D)+', '', base64.b64encode(os.urandom(32)))

class User(db.Model):
    """
    Model representing a registered user.
    """

    __tablename__ = 'user'

    user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    is_admin = db.Column(db.Boolean, index=True)
    signup_time = db.Column(db.Integer)
    signup_ip = db.Column(db.Text)
    username = db.Column(db.String(64), index=True, unique=True)
    password_hash = db.Column(db.Text)
    api_key = db.Column(db.String(64), index=True)

    def __init__(
        self,
        username,
        password,
        signup_ip,
        is_admin=False,
    ):
        self.signup_time = int(time.time())
        self.signup_ip = signup_ip
        self.username = username
        self.password_hash = util.cryptography.secure_hash(password)
        self.generate_new_api_key()
        self.is_admin = is_admin

    def validate_password(self, password):
        """
        Validate that the supplied password is correct for this user account.

        :param password: The supplied password.
        :return: True if the supplied password matches the user's password; False otherwise.
        """
        return util.cryptography.secure_hash(password) == self.password_hash

    def update_password(self, new_password):
        """
        Set a new password for the user.

        :param new_password: The new plain-text password.
        """
        self.password_hash = util.cryptography.secure_hash(new_password)

    def generate_new_api_key(self):
        """
        Generate a new API key for this user.
        """
        self.api_key = _generate_api_key()

    def as_dict(self):
        """
        Represent this user as a API-friendly, JSON-formatted dictionary.

        :return: A representation of this user's data as a dictionary.
        """
        return {
            'user_id': self.user_id,
            'is_admin': self.is_admin,
            'signup_time': self.signup_time,
            'signup_ip': self.signup_ip,
            'username': self.username,
            'api_key': self.api_key,
        }

    @staticmethod
    def is_authenticated():
        return True

    @staticmethod
    def is_anonymous():
        return False

    @staticmethod
    def is_active():
        return True

    def get_id(self):
        return unicode(self.user_id)
<commit_msg>Default User.is_admin to False<commit_after>import base64
import os
import re
import time

import util.cryptography
from linkr import db

def _generate_api_key():
    """
    Generate a random API key.

    :return: A random alphanumeric string.
    """
    return re.sub('(\W\D)+', '', base64.b64encode(os.urandom(32)))

class User(db.Model):
    """
    Model representing a registered user.
    """

    __tablename__ = 'user'

    user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    is_admin = db.Column(db.Boolean, index=True, default=False)
    signup_time = db.Column(db.Integer)
    signup_ip = db.Column(db.Text)
    username = db.Column(db.String(64), index=True, unique=True)
    password_hash = db.Column(db.Text)
    api_key = db.Column(db.String(64), index=True)

    def __init__(
        self,
        username,
        password,
        signup_ip,
        is_admin=False,
    ):
        self.signup_time = int(time.time())
        self.signup_ip = signup_ip
        self.username = username
        self.password_hash = util.cryptography.secure_hash(password)
        self.generate_new_api_key()
        self.is_admin = is_admin

    def validate_password(self, password):
        """
        Validate that the supplied password is correct for this user account.

        :param password: The supplied password.
        :return: True if the supplied password matches the user's password; False otherwise.
        """
        return util.cryptography.secure_hash(password) == self.password_hash

    def update_password(self, new_password):
        """
        Set a new password for the user.

        :param new_password: The new plain-text password.
        """
        self.password_hash = util.cryptography.secure_hash(new_password)

    def generate_new_api_key(self):
        """
        Generate a new API key for this user.
        """
        self.api_key = _generate_api_key()

    def as_dict(self):
        """
        Represent this user as a API-friendly, JSON-formatted dictionary.

        :return: A representation of this user's data as a dictionary.
        """
        return {
            'user_id': self.user_id,
            'is_admin': self.is_admin,
            'signup_time': self.signup_time,
            'signup_ip': self.signup_ip,
            'username': self.username,
            'api_key': self.api_key,
        }

    @staticmethod
    def is_authenticated():
        return True

    @staticmethod
    def is_anonymous():
        return False

    @staticmethod
    def is_active():
        return True

    def get_id(self):
        return unicode(self.user_id)
<|endoftext|>


<commit_before>def git(self, message):
    import requests
    matches = re.findall("github.com/([\w/-]+[^\s])", message)
    for match in matches:
        match = match.split("/")
        if match[-1] == "":
            del match[-1]
        if len(match) == 1:
            req = requests.get("http://osrc.dfm.io/%s.json" % match[0]).json()
            if not "message" in req.keys():
                self.conman.gen_send("Github: %s - %s repositories - %s contributions - Favourite language: %s (%s contributions)" % (req["name"], len(req["repositories"]), req["usage"]["total"], req["usage"]["languages"][0]["language"], req["usage"]["languages"][0]["count"]))
            else:
                req = requests.get("https://api.github.com/users/%s" % match[0]).json()
                if not "message" in req.keys():
                    self.conman.gen_send("Github: %s" % req["login"])
                else:
                    self.conman.gen_send("Github: User %s doesn't appear to exist" % match[0])
        elif len(match) == 2:
            req = requests.get("https://api.github.com/repos/%s/%s" % tuple(match)).json()
            tosend = "Github: %s - %s - by %s - Last push: %s" % (match[1], req["description"], match[0], req["pushed_at"].split("T")[0])
            if not req["homepage"] == None and not req["homepage"] == "":
                tosend += " - %s" % req["homepage"]
            self.conman.gen_send(tosend)
        elif match[2] == "issues" or match[2] == "pull":
            match[2] = "issues"
            if len(match) == 4:
                req = requests.get("https://api.github.com/repos/%s/%s/%s/%s" % tuple(match)).json()
                self.conman.gen_send("Github: %s/%s#%s - %s - by %s" % (match[0], match[1], req["number"], req["title"], req["user"]["login"]))
            else:
                req = requests.get("https://api.github.com/repos/%s/%s/%s" % tuple(match)).json()
                numofiss = len(req)
                self.conman.gen_send("Github: %s - by %s - Open issues: %s" % (match[1], match[0], numofiss))
        elif match[2] == "tree":
            branch = match[3]
            filepath = "/".join(match[4:])
            self.conman.gen_send("Github: %s on branch %s - %s - by %s" % (filepath, branch, match[1], match[0]))

self._map("regex", ".*https?://(www\.)?github.com/.*", git)
<commit_msg>Added github commit API reader thingo<commit_after>def git(self, message):
    import requests
    matches = re.findall("github.com/([\w/-]+[^\s])", message)
    for match in matches:
        match = match.split("/")
        if match[-1] == "":
            del match[-1]
        if len(match) == 1:
            req = requests.get("http://osrc.dfm.io/%s.json" % match[0]).json()
            if not "message" in req.keys():
                self.conman.gen_send("Github: %s - %s repositories - %s contributions - Favourite language: %s (%s contributions)" % (req["name"], len(req["repositories"]), req["usage"]["total"], req["usage"]["languages"][0]["language"], req["usage"]["languages"][0]["count"]))
            else:
                req = requests.get("https://api.github.com/users/%s" % match[0]).json()
                if not "message" in req.keys():
                    self.conman.gen_send("Github: %s" % req["login"])
                else:
                    self.conman.gen_send("Github: User %s doesn't appear to exist" % match[0])
        elif len(match) == 2:
            req = requests.get("https://api.github.com/repos/%s/%s" % tuple(match)).json()
            tosend = "Github: %s - %s - by %s - Last push: %s" % (match[1], req["description"], match[0], req["pushed_at"].split("T")[0])
            if not req["homepage"] == None and not req["homepage"] == "":
                tosend += " - %s" % req["homepage"]
            self.conman.gen_send(tosend)
        elif match[2] == "issues" or match[2] == "pull":
            match[2] = "issues"
            if len(match) == 4:
                req = requests.get("https://api.github.com/repos/%s/%s/%s/%s" % tuple(match)).json()
                self.conman.gen_send("Github: %s/%s#%s - %s - by %s - Created: %s - Updated: %s" % (match[0], match[1], req["number"], req["title"], req["user"]["login"], req["created_at"].split("T")[0], req["updated_at"].split("T")[0]))
            else:
                req = requests.get("https://api.github.com/repos/%s/%s/%s" % tuple(match)).json()
                numofiss = len(req)
                self.conman.gen_send("Github: %s - by %s - Open issues: %s" % (match[1], match[0], numofiss))
        elif match[2] == "tree":
            branch = match[3]
            filepath = "/".join(match[4:])
            self.conman.gen_send("Github: %s on branch %s - %s - by %s" % (filepath, branch, match[1], match[0]))
        elif match[2] == "commit":
            req = requests.get("https://api.github.com/repos/%s/%s/%ss/%s" % tuple(match)).json()
            self.conman.gen_send("Github: Commit %s on %s/%s - %s - by %s - %s" % (req["sha"], match[0], match[1], req["message"], req["author"]["name"], req["commiter"]["date"].split("T")[0]))

self._map("regex", ".*https?://(www\.)?github.com/.*", git)
<|endoftext|>


<commit_before>from base64 import b64encode, urlsafe_b64encode
import calendar
import hashlib
import hmac
import logging
import math
import os
import pprint
import re
import sys
import time

import six

from .exc import BadHeaderValue, HawkFail, InvalidCredentials

HAWK_VER = 1
log = logging.getLogger(__name__)
allowable_header_keys = set(['id', 'ts', 'tsm', 'nonce', 'hash',
                             'error', 'ext', 'mac', 'app', 'dlg'])

def validate_credentials(creds):
    if not isinstance(creds, dict):
        raise InvalidCredentials('credentials must be a dict')
    try:
        creds['id']
        creds['key']
        creds['algorithm']
    except KeyError:
        etype, val, tb = sys.exc_info()
        raise InvalidCredentials('{etype}: {val}'
                                 .format(etype=etype, val=val))

def random_string(length):
    """Generates a random string for a given length."""
    # this conservatively gets 8*length bits and then returns 6*length of
    # them. Grabbing (6/8)*length bits could lose some entropy off the ends.
    return urlsafe_b64encode(os.urandom(length))[:length]

def calculate_payload_hash(payload, algorithm, content_type):
    """Calculates a hash for a given payload."""
    p_hash = hashlib.new(algorithm)

    parts = []
    parts.append('hawk.' + str(HAWK_VER) + '.payload\n')
    parts.append(parse_content_type(content_type) + '\n')
    parts.append(payload or '')
    parts.append('\n')

    for i, p in enumerate(parts):
        # Make sure we are about to hash binary strings.
        if not isinstance(p, six.binary_type):
            p = p.encode('utf8')
        p_hash.update(p)
        parts[i] = p

    log.debug('calculating payload hash from:\n{parts}'
              .format(parts=pprint.pformat(parts)))

    return b64encode(p_hash.digest())

def calculate_mac(mac_type, resource, content_hash):
    """Calculates a message authorization code (MAC)."""
    normalized = normalize_string(mac_type, resource, content_hash)
    log.debug(u'normalized resource for mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, resource.credentials['algorithm'])

    # Make sure we are about to hash binary strings.

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = resource.credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def calculate_ts_mac(ts, credentials):
    """Calculates a message authorization code (MAC) for a timestamp."""
    normalized = ('hawk.{hawk_ver}.ts\n{ts}\n'
                  .format(hawk_ver=HAWK_VER, ts=ts))
    log.debug(u'normalized resource for ts mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, credentials['algorithm'])

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def normalize_string(mac_type, resource, content_hash):
    """Serializes mac_type and resource into a HAWK string."""

    normalized = [
        'hawk.' + str(HAWK_VER) + '.' + mac_type,
        normalize_header_attr(resource.timestamp),
        normalize_header_attr(resource.nonce),
        normalize_header_attr(resource.method or ''),
        normalize_header_attr(resource.name or ''),
        normalize_header_attr(resource.host),
        normalize_header_attr(resource.port),
        normalize_header_attr(content_hash or '')
    ]

    # The blank lines are important. They follow what the Node Hawk lib does.

    normalized.append(normalize_header_attr(resource.ext or ''))

    if resource.app:
        normalized.append(normalize_header_attr(resource.app))
        normalized.append(normalize_header_attr(resource.dlg or ''))

    # Add trailing new line.
    normalized.append('')

    normalized = '\n'.join(normalized)

    return normalized

def parse_content_type(content_type):
    """Cleans up content_type."""
    if content_type:
        return content_type.split(';')[0].strip().lower()
    else:
        return ''

def parse_authorization_header(auth_header):
    """
    Example Authorization header:

        'Hawk id="dh37fgj492je", ts="1367076201", nonce="NPHgnG", ext="and
        welcome!", mac="CeWHy4d9kbLGhDlkyw2Nh3PJ7SDOdZDa267KH4ZaNMY="'
    """
    attributes = {}

    # Make sure we have a unicode object for consistency.
    if isinstance(auth_header, six.binary_type):
        auth_header = auth_header.decode('utf8')

    parts = auth_header.split(',')
    auth_scheme_parts = parts[0].split(' ')
    if 'hawk' != auth_scheme_parts[0].lower():
        raise HawkFail("Unknown scheme '{scheme}' when parsing header"
                       .format(scheme=auth_scheme_parts[0].lower()))

    # Replace 'Hawk key: value' with 'key: value'
    # which matches the rest of parts
    parts[0] = auth_scheme_parts[1]

    for part in parts:
        attr_parts = part.split('=')
        key = attr_parts[0].strip()
        if key not in allowable_header_keys:
            raise HawkFail("Unknown Hawk key '{key}' when parsing header"
                           .format(key=key))

        # TODO we don't do a good job of parsing, '=' should work for more =.
        # hash or mac value includes '=' character... fixup
        if len(attr_parts) == 3:
            attr_parts[1] += '=' + attr_parts[2]

        # Chop of quotation marks
        value = attr_parts[1]

        if attr_parts[1].find('"') == 0:
            value = attr_parts[1][1:]

        if value.find('"') > -1:
            value = value[0:-1]

        validate_header_attr(value, name=key)
        value = unescape_header_attr(value)
        attributes[key] = value

    log.debug('parsed Hawk header: {header} into: \n{parsed}'
              .format(header=auth_header, parsed=pprint.pformat(attributes)))
    return attributes

def strings_match(a, b):
    # Constant time string comparision, mitigates side channel attacks.
    if len(a) != len(b):
        return False
    result = 0

    def byte_ints(buf):
        for ch in buf:
            # In Python 3, if we have a bytes object, iterating it will
            # already get the integer value. In older pythons, we need
            # to use ord().
            if not isinstance(ch, int):
                ch = ord(ch)
            yield ch

    for x, y in zip(byte_ints(a), byte_ints(b)):
        result |= x ^ y
    return result == 0

def utc_now(offset_in_seconds=0.0):
    # TODO: add support for SNTP server? See ntplib module.
    return int(math.floor(calendar.timegm(time.gmtime()) +
                          float(offset_in_seconds)))

# Allowed value characters:
# !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
_header_attribute_chars = re.compile(
    r"^[ \w\!#\$%&'\(\)\*\+,\-\./\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$",
    # Here we want to make sure non-ascii chars are excluded from headers per
    # the Node lib.
    re.LOCALE)

def validate_header_attr(val, name=None):
    if not _header_attribute_chars.match(val):
        raise BadHeaderValue('header value name={name} value={val} '
                             'contained an illegal character'
                             .format(name=name or '?', val=repr(val)))

def escape_header_attr(val):

    # Ensure we are working with Unicode for consistency.
    if isinstance(val, six.binary_type):
        val = val.decode('utf8')

    # Escape quotes and slash like the hawk reference code.
    val = val.replace('\\', '\\\\')
    val = val.replace('"', '\\"')
    val = val.replace('\n', '\\n')
    return val

def unescape_header_attr(val):
    # Un-do the hawk escaping.
    val = val.replace('\\n', '\n')
    val = val.replace('\\\\', '\\').replace('\\"', '"')
    return val

def prepare_header_val(val):
    val = escape_header_attr(val)
    validate_header_attr(val)
    return val

def normalize_header_attr(val):
    if not val:
        val = ''

    # Normalize like the hawk reference code.
    val = escape_header_attr(val)
    return val
<commit_msg>add initial bewit generation<commit_after>from base64 import b64encode, urlsafe_b64encode
import calendar
import hashlib
import hmac
import logging
import math
import os
import pprint
import re
import sys
import time

import six

from .exc import BadHeaderValue, HawkFail, InvalidCredentials

HAWK_VER = 1
log = logging.getLogger(__name__)
allowable_header_keys = set(['id', 'ts', 'tsm', 'nonce', 'hash',
                             'error', 'ext', 'mac', 'app', 'dlg'])

def validate_credentials(creds):
    if not isinstance(creds, dict):
        raise InvalidCredentials('credentials must be a dict')
    try:
        creds['id']
        creds['key']
        creds['algorithm']
    except KeyError:
        etype, val, tb = sys.exc_info()
        raise InvalidCredentials('{etype}: {val}'
                                 .format(etype=etype, val=val))

def random_string(length):
    """Generates a random string for a given length."""
    # this conservatively gets 8*length bits and then returns 6*length of
    # them. Grabbing (6/8)*length bits could lose some entropy off the ends.
    return urlsafe_b64encode(os.urandom(length))[:length]

def calculate_payload_hash(payload, algorithm, content_type):
    """Calculates a hash for a given payload."""
    p_hash = hashlib.new(algorithm)

    parts = []
    parts.append('hawk.' + str(HAWK_VER) + '.payload\n')
    parts.append(parse_content_type(content_type) + '\n')
    parts.append(payload or '')
    parts.append('\n')

    for i, p in enumerate(parts):
        # Make sure we are about to hash binary strings.
        if not isinstance(p, six.binary_type):
            p = p.encode('utf8')
        p_hash.update(p)
        parts[i] = p

    log.debug('calculating payload hash from:\n{parts}'
              .format(parts=pprint.pformat(parts)))

    return b64encode(p_hash.digest())

def calculate_mac(mac_type, resource, content_hash):
    """Calculates a message authorization code (MAC)."""
    normalized = normalize_string(mac_type, resource, content_hash)
    log.debug(u'normalized resource for mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, resource.credentials['algorithm'])

    # Make sure we are about to hash binary strings.

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = resource.credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def calculate_ts_mac(ts, credentials):
    """Calculates a message authorization code (MAC) for a timestamp."""
    normalized = ('hawk.{hawk_ver}.ts\n{ts}\n'
                  .format(hawk_ver=HAWK_VER, ts=ts))
    log.debug(u'normalized resource for ts mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, credentials['algorithm'])

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def normalize_string(mac_type, resource, content_hash):
    """Serializes mac_type and resource into a HAWK string."""

    normalized = [
        'hawk.' + str(HAWK_VER) + '.' + mac_type,
        normalize_header_attr(resource.timestamp),
        normalize_header_attr(resource.nonce),
        normalize_header_attr(resource.method or ''),
        normalize_header_attr(resource.name or ''),
        normalize_header_attr(resource.host),
        normalize_header_attr(resource.port),
        normalize_header_attr(content_hash or '')
    ]

    # The blank lines are important. They follow what the Node Hawk lib does.

    normalized.append(normalize_header_attr(resource.ext or ''))

    if resource.app:
        normalized.append(normalize_header_attr(resource.app))
        normalized.append(normalize_header_attr(resource.dlg or ''))

    # Add trailing new line.
    normalized.append('')

    normalized = '\n'.join(normalized)

    return normalized

def parse_content_type(content_type):
    """Cleans up content_type."""
    if content_type:
        return content_type.split(';')[0].strip().lower()
    else:
        return ''

def parse_authorization_header(auth_header):
    """
    Example Authorization header:

        'Hawk id="dh37fgj492je", ts="1367076201", nonce="NPHgnG", ext="and
        welcome!", mac="CeWHy4d9kbLGhDlkyw2Nh3PJ7SDOdZDa267KH4ZaNMY="'
    """
    attributes = {}

    # Make sure we have a unicode object for consistency.
    if isinstance(auth_header, six.binary_type):
        auth_header = auth_header.decode('utf8')

    parts = auth_header.split(',')
    auth_scheme_parts = parts[0].split(' ')
    if 'hawk' != auth_scheme_parts[0].lower():
        raise HawkFail("Unknown scheme '{scheme}' when parsing header"
                       .format(scheme=auth_scheme_parts[0].lower()))

    # Replace 'Hawk key: value' with 'key: value'
    # which matches the rest of parts
    parts[0] = auth_scheme_parts[1]

    for part in parts:
        attr_parts = part.split('=')
        key = attr_parts[0].strip()
        if key not in allowable_header_keys:
            raise HawkFail("Unknown Hawk key '{key}' when parsing header"
                           .format(key=key))

        # TODO we don't do a good job of parsing, '=' should work for more =.
        # hash or mac value includes '=' character... fixup
        if len(attr_parts) == 3:
            attr_parts[1] += '=' + attr_parts[2]

        # Chop of quotation marks
        value = attr_parts[1]

        if attr_parts[1].find('"') == 0:
            value = attr_parts[1][1:]

        if value.find('"') > -1:
            value = value[0:-1]

        validate_header_attr(value, name=key)
        value = unescape_header_attr(value)
        attributes[key] = value

    log.debug('parsed Hawk header: {header} into: \n{parsed}'
              .format(header=auth_header, parsed=pprint.pformat(attributes)))
    return attributes

def strings_match(a, b):
    # Constant time string comparision, mitigates side channel attacks.
    if len(a) != len(b):
        return False
    result = 0

    def byte_ints(buf):
        for ch in buf:
            # In Python 3, if we have a bytes object, iterating it will
            # already get the integer value. In older pythons, we need
            # to use ord().
            if not isinstance(ch, int):
                ch = ord(ch)
            yield ch

    for x, y in zip(byte_ints(a), byte_ints(b)):
        result |= x ^ y
    return result == 0

def utc_now(offset_in_seconds=0.0):
    # TODO: add support for SNTP server? See ntplib module.
    return int(math.floor(calendar.timegm(time.gmtime()) +
                          float(offset_in_seconds)))

# Allowed value characters:
# !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
_header_attribute_chars = re.compile(
    r"^[ \w\!#\$%&'\(\)\*\+,\-\./\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$",
    # Here we want to make sure non-ascii chars are excluded from headers per
    # the Node lib.
    re.LOCALE)

def validate_header_attr(val, name=None):
    if not _header_attribute_chars.match(val):
        raise BadHeaderValue('header value name={name} value={val} '
                             'contained an illegal character'
                             .format(name=name or '?', val=repr(val)))

def escape_header_attr(val):

    # Ensure we are working with Unicode for consistency.
    if isinstance(val, six.binary_type):
        val = val.decode('utf8')

    # Escape quotes and slash like the hawk reference code.
    val = val.replace('\\', '\\\\')
    val = val.replace('"', '\\"')
    val = val.replace('\n', '\\n')
    return val

def unescape_header_attr(val):
    # Un-do the hawk escaping.
    val = val.replace('\\n', '\n')
    val = val.replace('\\\\', '\\').replace('\\"', '"')
    return val

def prepare_header_val(val):
    val = escape_header_attr(val)
    validate_header_attr(val)
    return val

def normalize_header_attr(val):
    if not val:
        val = ''

    # Normalize like the hawk reference code.
    val = escape_header_attr(val)
    return val

def get_bewit(resource):
    """Returns a bewit identifier for the resource"""
    assert resource.method == 'GET'
    mac = calculate_mac(
        'bewit',
        resource,
        None,
    )

    if resource.ext is None:
        ext = ''
    else:
        ext = resource.ext
    return urlsafe_b64encode(r"{id}\{exp}\{mac}\{ext}".format(id=resource.credentials['id'], exp=resource.timestamp, ext=ext, mac=mac))
<|endoftext|>


<commit_before>from base64 import b64encode, urlsafe_b64encode
import calendar
import hashlib
import hmac
import logging
import math
import os
import pprint
import re
import sys
import time

import six

from .exc import (
    BadHeaderValue,
    HawkFail,
    InvalidCredentials)

HAWK_VER = 1
log = logging.getLogger(__name__)
allowable_header_keys = set(['id', 'ts', 'tsm', 'nonce', 'hash',
                             'error', 'ext', 'mac', 'app', 'dlg'])

def validate_credentials(creds):
    if not hasattr(creds, '__getitem__'):
        raise InvalidCredentials('credentials must be a dict-like object')
    try:
        creds['id']
        creds['key']
        creds['algorithm']
    except KeyError:
        etype, val, tb = sys.exc_info()
        raise InvalidCredentials('{etype}: {val}'
                                 .format(etype=etype, val=val))

def random_string(length):
    """Generates a random string for a given length."""
    # this conservatively gets 8*length bits and then returns 6*length of
    # them. Grabbing (6/8)*length bits could lose some entropy off the ends.
    return urlsafe_b64encode(os.urandom(length))[:length]

def calculate_payload_hash(payload, algorithm, content_type):
    """Calculates a hash for a given payload."""
    p_hash = hashlib.new(algorithm)

    parts = []
    parts.append('hawk.' + str(HAWK_VER) + '.payload\n')
    parts.append(parse_content_type(content_type) + '\n')
    parts.append(payload or '')
    parts.append('\n')

    for i, p in enumerate(parts):
        # Make sure we are about to hash binary strings.
        if not isinstance(p, six.binary_type):
            p = p.encode('utf8')
        p_hash.update(p)
        parts[i] = p

    log.debug('calculating payload hash from:\n{parts}'
              .format(parts=pprint.pformat(parts)))

    return b64encode(p_hash.digest())

def calculate_mac(mac_type, resource, content_hash):
    """Calculates a message authorization code (MAC)."""
    normalized = normalize_string(mac_type, resource, content_hash)
    log.debug(u'normalized resource for mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, resource.credentials['algorithm'])

    # Make sure we are about to hash binary strings.

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = resource.credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def calculate_ts_mac(ts, credentials):
    """Calculates a message authorization code (MAC) for a timestamp."""
    normalized = ('hawk.{hawk_ver}.ts\n{ts}\n'
                  .format(hawk_ver=HAWK_VER, ts=ts))
    log.debug(u'normalized resource for ts mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, credentials['algorithm'])

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def normalize_string(mac_type, resource, content_hash):
    """Serializes mac_type and resource into a HAWK string."""

    normalized = [
        'hawk.' + str(HAWK_VER) + '.' + mac_type,
        normalize_header_attr(resource.timestamp),
        normalize_header_attr(resource.nonce),
        normalize_header_attr(resource.method or ''),
        normalize_header_attr(resource.name or ''),
        normalize_header_attr(resource.host),
        normalize_header_attr(resource.port),
        normalize_header_attr(content_hash or '')
    ]

    # The blank lines are important. They follow what the Node Hawk lib does.

    normalized.append(normalize_header_attr(resource.ext or ''))

    if resource.app:
        normalized.append(normalize_header_attr(resource.app))
        normalized.append(normalize_header_attr(resource.dlg or ''))

    # Add trailing new line.
    normalized.append('')

    normalized = '\n'.join(normalized)

    return normalized

def parse_content_type(content_type):
    """Cleans up content_type."""
    if content_type:
        return content_type.split(';')[0].strip().lower()
    else:
        return ''

def parse_authorization_header(auth_header):
    """
    Example Authorization header:

        'Hawk id="dh37fgj492je", ts="1367076201", nonce="NPHgnG", ext="and
        welcome!", mac="CeWHy4d9kbLGhDlkyw2Nh3PJ7SDOdZDa267KH4ZaNMY="'
    """
    attributes = {}

    # Make sure we have a unicode object for consistency.
    if isinstance(auth_header, six.binary_type):
        auth_header = auth_header.decode('utf8')

    parts = auth_header.split(',')
    auth_scheme_parts = parts[0].split(' ')
    if 'hawk' != auth_scheme_parts[0].lower():
        raise HawkFail("Unknown scheme '{scheme}' when parsing header"
                       .format(scheme=auth_scheme_parts[0].lower()))

    # Replace 'Hawk key: value' with 'key: value'
    # which matches the rest of parts
    parts[0] = auth_scheme_parts[1]

    for part in parts:
        attr_parts = part.split('=')
        key = attr_parts[0].strip()
        if key not in allowable_header_keys:
            raise HawkFail("Unknown Hawk key '{key}' when parsing header"
                           .format(key=key))

        if len(attr_parts) > 2:
            attr_parts[1] = '='.join(attr_parts[1:])

        # Chop of quotation marks
        value = attr_parts[1]

        if attr_parts[1].find('"') == 0:
            value = attr_parts[1][1:]

        if value.find('"') > -1:
            value = value[0:-1]

        validate_header_attr(value, name=key)
        value = unescape_header_attr(value)
        attributes[key] = value

    log.debug('parsed Hawk header: {header} into: \n{parsed}'
              .format(header=auth_header, parsed=pprint.pformat(attributes)))
    return attributes

def strings_match(a, b):
    # Constant time string comparision, mitigates side channel attacks.
    if len(a) != len(b):
        return False
    result = 0

    def byte_ints(buf):
        for ch in buf:
            # In Python 3, if we have a bytes object, iterating it will
            # already get the integer value. In older pythons, we need
            # to use ord().
            if not isinstance(ch, int):
                ch = ord(ch)
            yield ch

    for x, y in zip(byte_ints(a), byte_ints(b)):
        result |= x ^ y
    return result == 0

def utc_now(offset_in_seconds=0.0):
    # TODO: add support for SNTP server? See ntplib module.
    return int(math.floor(calendar.timegm(time.gmtime()) +
                          float(offset_in_seconds)))

# Allowed value characters:
# !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
_header_attribute_chars = re.compile(
    r"^[ \w\!#\$%&'\(\)\*\+,\-\./\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$",
    # Here we want to make sure non-ascii chars are excluded from headers per
    # the Node lib.
    re.LOCALE)

def validate_header_attr(val, name=None):
    if not _header_attribute_chars.match(val):
        raise BadHeaderValue('header value name={name} value={val} '
                             'contained an illegal character'
                             .format(name=name or '?', val=repr(val)))

def escape_header_attr(val):

    # Ensure we are working with Unicode for consistency.
    if isinstance(val, six.binary_type):
        val = val.decode('utf8')

    # Escape quotes and slash like the hawk reference code.
    val = val.replace('\\', '\\\\')
    val = val.replace('"', '\\"')
    val = val.replace('\n', '\\n')
    return val

def unescape_header_attr(val):
    # Un-do the hawk escaping.
    val = val.replace('\\n', '\n')
    val = val.replace('\\\\', '\\').replace('\\"', '"')
    return val

def prepare_header_val(val):
    val = escape_header_attr(val)
    validate_header_attr(val)
    return val

def normalize_header_attr(val):
    if not val:
        val = ''

    # Normalize like the hawk reference code.
    val = escape_header_attr(val)
    return val
<commit_msg>Use explicit matching for a-z, A-Z, 0-9 and _<commit_after>from base64 import b64encode, urlsafe_b64encode
import calendar
import hashlib
import hmac
import logging
import math
import os
import pprint
import re
import sys
import time

import six

from .exc import (
    BadHeaderValue,
    HawkFail,
    InvalidCredentials)

HAWK_VER = 1
log = logging.getLogger(__name__)
allowable_header_keys = set(['id', 'ts', 'tsm', 'nonce', 'hash',
                             'error', 'ext', 'mac', 'app', 'dlg'])

def validate_credentials(creds):
    if not hasattr(creds, '__getitem__'):
        raise InvalidCredentials('credentials must be a dict-like object')
    try:
        creds['id']
        creds['key']
        creds['algorithm']
    except KeyError:
        etype, val, tb = sys.exc_info()
        raise InvalidCredentials('{etype}: {val}'
                                 .format(etype=etype, val=val))

def random_string(length):
    """Generates a random string for a given length."""
    # this conservatively gets 8*length bits and then returns 6*length of
    # them. Grabbing (6/8)*length bits could lose some entropy off the ends.
    return urlsafe_b64encode(os.urandom(length))[:length]

def calculate_payload_hash(payload, algorithm, content_type):
    """Calculates a hash for a given payload."""
    p_hash = hashlib.new(algorithm)

    parts = []
    parts.append('hawk.' + str(HAWK_VER) + '.payload\n')
    parts.append(parse_content_type(content_type) + '\n')
    parts.append(payload or '')
    parts.append('\n')

    for i, p in enumerate(parts):
        # Make sure we are about to hash binary strings.
        if not isinstance(p, six.binary_type):
            p = p.encode('utf8')
        p_hash.update(p)
        parts[i] = p

    log.debug('calculating payload hash from:\n{parts}'
              .format(parts=pprint.pformat(parts)))

    return b64encode(p_hash.digest())

def calculate_mac(mac_type, resource, content_hash):
    """Calculates a message authorization code (MAC)."""
    normalized = normalize_string(mac_type, resource, content_hash)
    log.debug(u'normalized resource for mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, resource.credentials['algorithm'])

    # Make sure we are about to hash binary strings.

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = resource.credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def calculate_ts_mac(ts, credentials):
    """Calculates a message authorization code (MAC) for a timestamp."""
    normalized = ('hawk.{hawk_ver}.ts\n{ts}\n'
                  .format(hawk_ver=HAWK_VER, ts=ts))
    log.debug(u'normalized resource for ts mac calc: {norm}'
              .format(norm=normalized))
    digestmod = getattr(hashlib, credentials['algorithm'])

    if not isinstance(normalized, six.binary_type):
        normalized = normalized.encode('utf8')
    key = credentials['key']
    if not isinstance(key, six.binary_type):
        key = key.encode('ascii')

    result = hmac.new(key, normalized, digestmod)
    return b64encode(result.digest())

def normalize_string(mac_type, resource, content_hash):
    """Serializes mac_type and resource into a HAWK string."""

    normalized = [
        'hawk.' + str(HAWK_VER) + '.' + mac_type,
        normalize_header_attr(resource.timestamp),
        normalize_header_attr(resource.nonce),
        normalize_header_attr(resource.method or ''),
        normalize_header_attr(resource.name or ''),
        normalize_header_attr(resource.host),
        normalize_header_attr(resource.port),
        normalize_header_attr(content_hash or '')
    ]

    # The blank lines are important. They follow what the Node Hawk lib does.

    normalized.append(normalize_header_attr(resource.ext or ''))

    if resource.app:
        normalized.append(normalize_header_attr(resource.app))
        normalized.append(normalize_header_attr(resource.dlg or ''))

    # Add trailing new line.
    normalized.append('')

    normalized = '\n'.join(normalized)

    return normalized

def parse_content_type(content_type):
    """Cleans up content_type."""
    if content_type:
        return content_type.split(';')[0].strip().lower()
    else:
        return ''

def parse_authorization_header(auth_header):
    """
    Example Authorization header:

        'Hawk id="dh37fgj492je", ts="1367076201", nonce="NPHgnG", ext="and
        welcome!", mac="CeWHy4d9kbLGhDlkyw2Nh3PJ7SDOdZDa267KH4ZaNMY="'
    """
    attributes = {}

    # Make sure we have a unicode object for consistency.
    if isinstance(auth_header, six.binary_type):
        auth_header = auth_header.decode('utf8')

    parts = auth_header.split(',')
    auth_scheme_parts = parts[0].split(' ')
    if 'hawk' != auth_scheme_parts[0].lower():
        raise HawkFail("Unknown scheme '{scheme}' when parsing header"
                       .format(scheme=auth_scheme_parts[0].lower()))

    # Replace 'Hawk key: value' with 'key: value'
    # which matches the rest of parts
    parts[0] = auth_scheme_parts[1]

    for part in parts:
        attr_parts = part.split('=')
        key = attr_parts[0].strip()
        if key not in allowable_header_keys:
            raise HawkFail("Unknown Hawk key '{key}' when parsing header"
                           .format(key=key))

        if len(attr_parts) > 2:
            attr_parts[1] = '='.join(attr_parts[1:])

        # Chop of quotation marks
        value = attr_parts[1]

        if attr_parts[1].find('"') == 0:
            value = attr_parts[1][1:]

        if value.find('"') > -1:
            value = value[0:-1]

        validate_header_attr(value, name=key)
        value = unescape_header_attr(value)
        attributes[key] = value

    log.debug('parsed Hawk header: {header} into: \n{parsed}'
              .format(header=auth_header, parsed=pprint.pformat(attributes)))
    return attributes

def strings_match(a, b):
    # Constant time string comparision, mitigates side channel attacks.
    if len(a) != len(b):
        return False
    result = 0

    def byte_ints(buf):
        for ch in buf:
            # In Python 3, if we have a bytes object, iterating it will
            # already get the integer value. In older pythons, we need
            # to use ord().
            if not isinstance(ch, int):
                ch = ord(ch)
            yield ch

    for x, y in zip(byte_ints(a), byte_ints(b)):
        result |= x ^ y
    return result == 0

def utc_now(offset_in_seconds=0.0):
    # TODO: add support for SNTP server? See ntplib module.
    return int(math.floor(calendar.timegm(time.gmtime()) +
                          float(offset_in_seconds)))

# Allowed value characters:
# !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
_header_attribute_chars = re.compile(
    r"^[ a-zA-Z0-9_\!#\$%&'\(\)\*\+,\-\./\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$")

def validate_header_attr(val, name=None):
    if not _header_attribute_chars.match(val):
        raise BadHeaderValue('header value name={name} value={val} '
                             'contained an illegal character'
                             .format(name=name or '?', val=repr(val)))

def escape_header_attr(val):

    # Ensure we are working with Unicode for consistency.
    if isinstance(val, six.binary_type):
        val = val.decode('utf8')

    # Escape quotes and slash like the hawk reference code.
    val = val.replace('\\', '\\\\')
    val = val.replace('"', '\\"')
    val = val.replace('\n', '\\n')
    return val

def unescape_header_attr(val):
    # Un-do the hawk escaping.
    val = val.replace('\\n', '\n')
    val = val.replace('\\\\', '\\').replace('\\"', '"')
    return val

def prepare_header_val(val):
    val = escape_header_attr(val)
    validate_header_attr(val)
    return val

def normalize_header_attr(val):
    if not val:
        val = ''

    # Normalize like the hawk reference code.
    val = escape_header_attr(val)
    return val
<|endoftext|>