<commit_before># -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
# Copyright (c) 2011, Evan Leis
#
# Distributed under the terms of the Lesser GNU General Public License (LGPL)
#-----------------------------------------------------------------------------
'''
Created on May 9, 2011

@author: evan
'''

from lxml import etree

from kayako.core.object import KayakoObject

__all__ = [
    'User',
    'UserGroup',
    'UserOrganization',
]

class User(KayakoObject):
    '''
    Kayako User API Object.

    fullname              The full name of User.
    usergroupid           The User Group ID to assign to this User.
    email                 The Email Address of User, you can send multiple email addresses by using "email[]" as the key
    userorganizationid    The User Organization ID
    salutation            The User Salutation, available options are: Mr., Ms., Mrs., Dr.
    designation           The User Designation/Title
    phone                 The Phone Number
    isenabled             Toggle whether the user is to be enabled/disabled
    userrole              The User Role, available options are: user, manager. Default: user
    timezone              The Time Zone
    enabledst             Enable Daylight Savings?
    slaplanid             The SLA Plan ID to assign to the user
    slaplanexpiry         The SLA Plan Expiry, 0 = never expires
    userexpiry            The User Expiry, 0 = never expires
    dateline
    lastvisit
    sendwelcomeemail
    '''

    controller = '/Base/User'

    __parameters__ = [
        'id',
        'fullname',
        'usergroupid',
        'email',
        'password',
        'userorganizationid',
        'salutation',
        'designation',
        'phone',
        'isenabled',
        'userrole',
        'timezone',
        'enabledst',
        'slaplanid',
        'slaplanexpiry',
        'userexpiry',
        'dateline',
        'lastvisit',
        'sendwelcomeemail',
    ]

    __required_add_parameters__ = ['fullname', 'usergroupid', 'password', 'email']
    __add_parameters__ = ['fullname', 'usergroupid', 'password', 'email', 'userorganizationid', 'salutation', 'designation', 'phone', 'isenabled', 'userrole', 'timezone', 'enabledst', 'slaplanid', 'slaplanexpiry', 'userexpiry', 'sendwelcomeemail']

    __required_save_parameters__ = ['fullname']
    __save_parameters__ = ['fullname', 'usergroupid', 'email', 'userorganizationid', 'salutation', 'designation', 'phone', 'isenabled', 'userrole', 'timezone', 'enabledst', 'slaplanid', 'slaplanexpiry', 'userexpiry']

    @classmethod
    def _parse_user(cls, user_tree):
        emails = [cls._get_string(email_node) for email_node in user_tree.findall('email')]
        params = dict(
            id=cls._get_int(user_tree.find('id')),
            fullname=cls._get_string(user_tree.find('fullname')),
            usergroupid=cls._get_int(user_tree.find('usergroupid')),
            email=emails,
            userorganizationid=cls._get_int(user_tree.find('userorganizationid'), required=False),
            salutation=cls._get_string(user_tree.find('salutation')),
            designation=cls._get_string(user_tree.find('designation')),
            phone=cls._get_string(user_tree.find('phone')),
            isenabled=cls._get_boolean(user_tree.find('isenabled')),
            userrole=cls._get_string(user_tree.find('userrole')),
            timezone=cls._get_string(user_tree.find('timezone')),
            enabledst=cls._get_boolean(user_tree.find('enabledst')),
            slaplanid=cls._get_int(user_tree.find('slaplanid')),
            slaplanexpiry=cls._get_date(user_tree.find('slaplanexpiry')),
            userexpiry=cls._get_date(user_tree.find('userexpiry')),
            dateline=cls._get_date(user_tree.find('dateline')),
            lastvisit=cls._get_date(user_tree.find('lastvisit')),
        )
        return params

    def _update_from_response(self, user_tree):

        self.emails = [self._get_string(email_node) for email_node in user_tree.findall('email')]

        for int_node in ['id', 'usergroupid', 'userorganizationid', 'slaplanid']:
            node = user_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['fullname', 'salutation', 'designation', 'phone', 'userrole', 'timezone']:
            node = user_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for bool_node in ['isenabled', 'enabledst']:
            node = user_tree.find(bool_node)
            if node is not None:
                setattr(self, bool_node, self._get_boolean(node, required=False))

        for date_node in ['slaplanexpiry', 'userexpiry', 'dateline', 'lastvisit']:
            node = user_tree.find(date_node)
            if node is not None:
                setattr(self, date_node, self._get_date(node))

    @classmethod
    def get_all(cls, api, marker=0, maxitems=1000):
        '''
        Returns the users starting at User ID ``marker`` pulling in a maximum
        ``maxitems`` number of Users.
        '''
        response = api._request('%s/%s/%s/' % (cls.controller, marker, maxitems), 'GET')
        tree = etree.parse(response)
        return [User(api, **cls._parse_user(user_tree)) for user_tree in tree.findall('user')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('user')
        if node is None:
            return None
        params = cls._parse_user(node)
        return User(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('user')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('user')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<User (%s): %s %s>' % (self.id, self.fullname, self.email)

class UserGroup(KayakoObject):
    '''
    Kayako UserGroup API Object.

    title      The title of the user group.
    grouptype  The type of user group ('guest' or 'registered').
    ismaster
    '''

    controller = '/Base/UserGroup'

    __parameters__ = [
        'id',
        'title',
        'grouptype',
        'ismaster',
    ]

    __required_add_parameters__ = ['title', 'grouptype']
    __add_parameters__ = ['title', 'grouptype']

    __required_save_parameters__ = ['title']
    __save_parameters__ = ['title']

    @classmethod
    def _parse_user_group(cls, user_group_tree):
        params = dict(
            id=cls._get_int(user_group_tree.find('id')),
            title=cls._get_string(user_group_tree.find('title')),
            grouptype=cls._get_string(user_group_tree.find('grouptype')),
            ismaster=cls._get_boolean(user_group_tree.find('ismaster')),
        )
        return params

    def _update_from_response(self, user_group_tree):
        for int_node in ['id']:
            node = user_group_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['title', 'grouptype']:
            node = user_group_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for bool_node in ['ismaster']:
            node = user_group_tree.find(bool_node)
            if node is not None:
                setattr(self, bool_node, self._get_boolean(node, required=False))

    @classmethod
    def get_all(cls, api):
        response = api._request(cls.controller, 'GET')
        tree = etree.parse(response)
        return [UserGroup(api, **cls._parse_user_group(user_group_tree)) for user_group_tree in tree.findall('usergroup')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('usergroup')
        if node is None:
            return None
        params = cls._parse_user_group(node)
        return UserGroup(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('usergroup')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('usergroup')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<UserGroup (%s): %s (%s)>' % (self.id, self.title, self.grouptype)

class UserOrganization(KayakoObject):
    '''
    Kayako UserOrganization API Object.

    name               The name of the user organization.
    organizationtype   The type of user organization ('restricted' or 'shared').
    address
    city
    state
    postalcode
    country
    phone
    fax
    website
    slaplanid          The SLA Plan ID to link with this organization
    slaplanexpiry      The UNIX timestamp by which to ignore the SLA Plan, 0 = never expires
    '''

    controller = '/Base/UserOrganization'

    __parameters__ = [
        'id',
        'name',
        'organizationtype',
        'address',
        'city',
        'state',
        'postalcode',
        'country',
        'phone',
        'fax',
        'website',
        'slaplanid',
        'slaplanexpiry',
        'dateline',
        'lastupdate',
    ]

    __required_add_parameters__ = ['name', 'organizationtype']
    __add_parameters__ = ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website', 'slaplanid', 'slaplanexpiry']

    __required_save_parameters__ = ['name']
    __save_parameters__ = ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website', 'slaplanid', 'slaplanexpiry']

    @classmethod
    def _parse_user_organization(cls, user_organization_tree):
        params = dict(
            id=cls._get_int(user_organization_tree.find('id')),
            name=cls._get_string(user_organization_tree.find('name')),
            organizationtype=cls._get_string(user_organization_tree.find('organizationtype')),
            address=cls._get_string(user_organization_tree.find('address')),
            city=cls._get_string(user_organization_tree.find('city')),
            state=cls._get_string(user_organization_tree.find('state')),
            postalcode=cls._get_string(user_organization_tree.find('postalcode')),
            country=cls._get_string(user_organization_tree.find('country')),
            phone=cls._get_string(user_organization_tree.find('phone')),
            fax=cls._get_string(user_organization_tree.find('fax')),
            website=cls._get_string(user_organization_tree.find('website')),
            dateline=cls._get_date(user_organization_tree.find('dateline')),
            lastupdate=cls._get_date(user_organization_tree.find('lastupdate')),
            slaplanid=cls._get_int(user_organization_tree.find('slaplanid')),
            slaplanexpiry=cls._get_date(user_organization_tree.find('slaplanexpiry')),
        )
        return params

    def _update_from_response(self, user_tree):
        for int_node in ['id', 'slaplanid']:
            node = user_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website']:
            node = user_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for date_node in ['dateline', 'lastupdate', 'slaplanexpiry']:
            node = user_tree.find(date_node)
            if node is not None:
                setattr(self, date_node, self._get_date(node, required=False))

    @classmethod
    def get_all(cls, api):
        response = api._request(cls.controller, 'GET')
        tree = etree.parse(response)
        return [UserOrganization(api, **cls._parse_user_organization(user_organization_tree)) for user_organization_tree in tree.findall('userorganization')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('userorganization')
        if node is None:
            return node
        params = cls._parse_user_organization(node)
        return UserOrganization(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('userorganization')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('userorganization')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<UserOrganization (%s): %s>' % (self.id, self.name)

<commit_msg>bug fix: marker and maxitems for User objects working now.<commit_after># -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
# Copyright (c) 2011, Evan Leis
#
# Distributed under the terms of the Lesser GNU General Public License (LGPL)
#-----------------------------------------------------------------------------
'''
Created on May 9, 2011

@author: evan
'''

from lxml import etree

from kayako.core.object import KayakoObject

__all__ = [
    'User',
    'UserGroup',
    'UserOrganization',
]

class User(KayakoObject):
    '''
    Kayako User API Object.

    fullname              The full name of User.
    usergroupid           The User Group ID to assign to this User.
    email                 The Email Address of User, you can send multiple email addresses by using "email[]" as the key
    userorganizationid    The User Organization ID
    salutation            The User Salutation, available options are: Mr., Ms., Mrs., Dr.
    designation           The User Designation/Title
    phone                 The Phone Number
    isenabled             Toggle whether the user is to be enabled/disabled
    userrole              The User Role, available options are: user, manager. Default: user
    timezone              The Time Zone
    enabledst             Enable Daylight Savings?
    slaplanid             The SLA Plan ID to assign to the user
    slaplanexpiry         The SLA Plan Expiry, 0 = never expires
    userexpiry            The User Expiry, 0 = never expires
    dateline
    lastvisit
    sendwelcomeemail
    '''

    controller = '/Base/User'

    __parameters__ = [
        'id',
        'fullname',
        'usergroupid',
        'email',
        'password',
        'userorganizationid',
        'salutation',
        'designation',
        'phone',
        'isenabled',
        'userrole',
        'timezone',
        'enabledst',
        'slaplanid',
        'slaplanexpiry',
        'userexpiry',
        'dateline',
        'lastvisit',
        'sendwelcomeemail',
    ]

    __required_add_parameters__ = ['fullname', 'usergroupid', 'password', 'email']
    __add_parameters__ = ['fullname', 'usergroupid', 'password', 'email', 'userorganizationid', 'salutation', 'designation', 'phone', 'isenabled', 'userrole', 'timezone', 'enabledst', 'slaplanid', 'slaplanexpiry', 'userexpiry', 'sendwelcomeemail']

    __required_save_parameters__ = ['fullname']
    __save_parameters__ = ['fullname', 'usergroupid', 'email', 'userorganizationid', 'salutation', 'designation', 'phone', 'isenabled', 'userrole', 'timezone', 'enabledst', 'slaplanid', 'slaplanexpiry', 'userexpiry']

    @classmethod
    def _parse_user(cls, user_tree):
        emails = [cls._get_string(email_node) for email_node in user_tree.findall('email')]
        params = dict(
            id=cls._get_int(user_tree.find('id')),
            fullname=cls._get_string(user_tree.find('fullname')),
            usergroupid=cls._get_int(user_tree.find('usergroupid')),
            email=emails,
            userorganizationid=cls._get_int(user_tree.find('userorganizationid'), required=False),
            salutation=cls._get_string(user_tree.find('salutation')),
            designation=cls._get_string(user_tree.find('designation')),
            phone=cls._get_string(user_tree.find('phone')),
            isenabled=cls._get_boolean(user_tree.find('isenabled')),
            userrole=cls._get_string(user_tree.find('userrole')),
            timezone=cls._get_string(user_tree.find('timezone')),
            enabledst=cls._get_boolean(user_tree.find('enabledst')),
            slaplanid=cls._get_int(user_tree.find('slaplanid')),
            slaplanexpiry=cls._get_date(user_tree.find('slaplanexpiry')),
            userexpiry=cls._get_date(user_tree.find('userexpiry')),
            dateline=cls._get_date(user_tree.find('dateline')),
            lastvisit=cls._get_date(user_tree.find('lastvisit')),
        )
        return params

    def _update_from_response(self, user_tree):

        self.emails = [self._get_string(email_node) for email_node in user_tree.findall('email')]

        for int_node in ['id', 'usergroupid', 'userorganizationid', 'slaplanid']:
            node = user_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['fullname', 'salutation', 'designation', 'phone', 'userrole', 'timezone']:
            node = user_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for bool_node in ['isenabled', 'enabledst']:
            node = user_tree.find(bool_node)
            if node is not None:
                setattr(self, bool_node, self._get_boolean(node, required=False))

        for date_node in ['slaplanexpiry', 'userexpiry', 'dateline', 'lastvisit']:
            node = user_tree.find(date_node)
            if node is not None:
                setattr(self, date_node, self._get_date(node))

    @classmethod
    def get_all(cls, api, marker=0, maxitems=1000):
        '''
        Returns the users starting at User ID ``marker`` pulling in a maximum
        ``maxitems`` number of Users.
        '''
        response = api._request('%s/Filter/%s/%s/' % (cls.controller, marker, maxitems), 'GET')
        tree = etree.parse(response)
        return [User(api, **cls._parse_user(user_tree)) for user_tree in tree.findall('user')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('user')
        if node is None:
            return None
        params = cls._parse_user(node)
        return User(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('user')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('user')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<User (%s): %s %s>' % (self.id, self.fullname, self.email)

class UserGroup(KayakoObject):
    '''
    Kayako UserGroup API Object.

    title      The title of the user group.
    grouptype  The type of user group ('guest' or 'registered').
    ismaster
    '''

    controller = '/Base/UserGroup'

    __parameters__ = [
        'id',
        'title',
        'grouptype',
        'ismaster',
    ]

    __required_add_parameters__ = ['title', 'grouptype']
    __add_parameters__ = ['title', 'grouptype']

    __required_save_parameters__ = ['title']
    __save_parameters__ = ['title']

    @classmethod
    def _parse_user_group(cls, user_group_tree):
        params = dict(
            id=cls._get_int(user_group_tree.find('id')),
            title=cls._get_string(user_group_tree.find('title')),
            grouptype=cls._get_string(user_group_tree.find('grouptype')),
            ismaster=cls._get_boolean(user_group_tree.find('ismaster')),
        )
        return params

    def _update_from_response(self, user_group_tree):
        for int_node in ['id']:
            node = user_group_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['title', 'grouptype']:
            node = user_group_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for bool_node in ['ismaster']:
            node = user_group_tree.find(bool_node)
            if node is not None:
                setattr(self, bool_node, self._get_boolean(node, required=False))

    @classmethod
    def get_all(cls, api):
        response = api._request(cls.controller, 'GET')
        tree = etree.parse(response)
        return [UserGroup(api, **cls._parse_user_group(user_group_tree)) for user_group_tree in tree.findall('usergroup')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('usergroup')
        if node is None:
            return None
        params = cls._parse_user_group(node)
        return UserGroup(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('usergroup')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('usergroup')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<UserGroup (%s): %s (%s)>' % (self.id, self.title, self.grouptype)

class UserOrganization(KayakoObject):
    '''
    Kayako UserOrganization API Object.

    name               The name of the user organization.
    organizationtype   The type of user organization ('restricted' or 'shared').
    address
    city
    state
    postalcode
    country
    phone
    fax
    website
    slaplanid          The SLA Plan ID to link with this organization
    slaplanexpiry      The UNIX timestamp by which to ignore the SLA Plan, 0 = never expires
    '''

    controller = '/Base/UserOrganization'

    __parameters__ = [
        'id',
        'name',
        'organizationtype',
        'address',
        'city',
        'state',
        'postalcode',
        'country',
        'phone',
        'fax',
        'website',
        'slaplanid',
        'slaplanexpiry',
        'dateline',
        'lastupdate',
    ]

    __required_add_parameters__ = ['name', 'organizationtype']
    __add_parameters__ = ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website', 'slaplanid', 'slaplanexpiry']

    __required_save_parameters__ = ['name']
    __save_parameters__ = ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website', 'slaplanid', 'slaplanexpiry']

    @classmethod
    def _parse_user_organization(cls, user_organization_tree):
        params = dict(
            id=cls._get_int(user_organization_tree.find('id')),
            name=cls._get_string(user_organization_tree.find('name')),
            organizationtype=cls._get_string(user_organization_tree.find('organizationtype')),
            address=cls._get_string(user_organization_tree.find('address')),
            city=cls._get_string(user_organization_tree.find('city')),
            state=cls._get_string(user_organization_tree.find('state')),
            postalcode=cls._get_string(user_organization_tree.find('postalcode')),
            country=cls._get_string(user_organization_tree.find('country')),
            phone=cls._get_string(user_organization_tree.find('phone')),
            fax=cls._get_string(user_organization_tree.find('fax')),
            website=cls._get_string(user_organization_tree.find('website')),
            dateline=cls._get_date(user_organization_tree.find('dateline')),
            lastupdate=cls._get_date(user_organization_tree.find('lastupdate')),
            slaplanid=cls._get_int(user_organization_tree.find('slaplanid')),
            slaplanexpiry=cls._get_date(user_organization_tree.find('slaplanexpiry')),
        )
        return params

    def _update_from_response(self, user_tree):
        for int_node in ['id', 'slaplanid']:
            node = user_tree.find(int_node)
            if node is not None:
                setattr(self, int_node, self._get_int(node, required=False))

        for str_node in ['name', 'organizationtype', 'address', 'city', 'state', 'postalcode', 'country', 'phone', 'fax', 'website']:
            node = user_tree.find(str_node)
            if node is not None:
                setattr(self, str_node, self._get_string(node))

        for date_node in ['dateline', 'lastupdate', 'slaplanexpiry']:
            node = user_tree.find(date_node)
            if node is not None:
                setattr(self, date_node, self._get_date(node, required=False))

    @classmethod
    def get_all(cls, api):
        response = api._request(cls.controller, 'GET')
        tree = etree.parse(response)
        return [UserOrganization(api, **cls._parse_user_organization(user_organization_tree)) for user_organization_tree in tree.findall('userorganization')]

    @classmethod
    def get(cls, api, id):
        response = api._request('%s/%s/' % (cls.controller, id), 'GET')
        tree = etree.parse(response)
        node = tree.find('userorganization')
        if node is None:
            return node
        params = cls._parse_user_organization(node)
        return UserOrganization(api, **params)

    def add(self):
        response = self._add(self.controller)
        tree = etree.parse(response)
        node = tree.find('userorganization')
        self._update_from_response(node)

    def save(self):
        response = self._save('%s/%s/' % (self.controller, self.id))
        tree = etree.parse(response)
        node = tree.find('userorganization')
        self._update_from_response(node)

    def delete(self):
        self._delete('%s/%s/' % (self.controller, self.id))

    def __str__(self):
        return '<UserOrganization (%s): %s>' % (self.id, self.name)

<|endoftext|>


<commit_before>#!/usr/bin/env python3

"""
        Copyright 2016 Stuart Freeman Licensed under the
	Educational Community License, Version 2.0 (the "License"); you may
	not use this file except in compliance with the License. You may
	obtain a copy of the License at

http://www.osedu.org/licenses/ECL-2.0

	Unless required by applicable law or agreed to in writing,
	software distributed under the License is distributed on an "AS IS"
	BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
	or implied. See the License for the specific language governing
	permissions and limitations under the License.
"""

import csv, datetime, errno, getopt, io, os, re, sqlite3, sys, zipfile

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def main(argv):
    # If start and end date aren't specified we'll use the first and last days of the previous month
    startDate = (datetime.date.today().replace(day=1) - datetime.timedelta(days=1)).replace(day=1).strftime('%Y-%m-%d')
    endDate = (datetime.date.today().replace(day=1) - datetime.timedelta(days=1)).strftime('%Y-%m-%d')

    # Set up command line parsing
    usage = sys.argv[0] + ' -i <inputfile> -s <YYYY-MM-DD> -e <YYYY-MM-DD>'
    try:
        opts, args = getopt.getopt(argv, "hi:s:e:", ['ifile', 'startDate', 'endDate'])
    except getopt.GetoptError:
        print(usage)
        sys.exit(2)
    if len(opts) == 0:
        print(usage)
        sys.exit()
    for opt, arg in opts:
        if opt == '-h':
            print(usage)
            sys.exit()
        elif opt == '-i':
            inputfile = arg
        elif opt == '-s':
            if re.fullmatch('\d{4}-\d{2}-\d{2}', arg):
                startDate = arg
            else:
                eprint('Dates should be in the format YYYY-MM-DD')
                sys.exit(2)
        elif opt == '-e':
            if re.fullmatch('\d{4}-\d{2}-\d{2}', arg):
                endDate = arg
            else:
                eprint('Dates should be in the format YYYY-MM-DD')
                sys.exit(2)

    # Make the results dir
    resultspath = os.path.dirname(os.path.realpath(__file__)) + '/results'
    try:
        os.makedirs(resultspath)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise

    dbfile = resultspath + '/coursera.sqlite3'
    dbExists = os.path.isfile(dbfile)
    dbOld = getFileModDate(inputfile) > getFileModDate(dbfile) if dbExists else False

    # Our cached DB is old, delete it so we can rebuild
    if dbOld:
        os.remove(dbfile)

    # Connect to the cached DB or create an empty one
    db = sqlite3.connect(dbfile)
    cur = db.cursor()

    # If we just created an empty DB populate it from the zip file
    if not dbExists or dbOld:
        with zipfile.ZipFile(inputfile) as myzip:
            parse('courses', cur, myzip)
            parse('course_memberships', cur, myzip)
            parse('course_grades', cur, myzip)
            parse('users_courses__certificate_payments', cur, myzip)

        cur.execute('CREATE INDEX course_idx1 ON courses(course_id);')
        cur.execute('CREATE INDEX course_idx2 ON course_memberships(course_id);')
        cur.execute('CREATE INDEX course_idx3 ON course_grades(course_id);')
        cur.execute('CREATE INDEX date_idx1 ON course_memberships(course_membership_ts);')
        db.commit()

    with open('{0}/report.{1}.{2}.csv'.format(resultspath, startDate, endDate), 'w') as reportfile:
        writer = csv.writer(reportfile)
        cur.execute("""SELECT courses.course_name,
        count(course_memberships.gatech_user_id) AS members,
        count(CASE course_memberships.course_membership_role WHEN 'LEARNER' THEN 1 ELSE null END) AS active,
        payments.paid AS paid,
        completed.completed AS completed
        FROM courses
                JOIN course_memberships ON courses.course_id = course_memberships.course_id
                JOIN (
                    SELECT course_memberships.course_id, count(course_memberships.course_id) AS paid FROM users_courses__certificate_payments JOIN course_memberships ON users_courses__certificate_payments.course_id = course_memberships.course_id AND users_courses__certificate_payments.gatech_user_id = course_memberships.gatech_user_id WHERE course_membership_ts BETWEEN ? AND ? GROUP BY course_memberships.course_id
                ) AS payments ON courses.course_id = payments.course_id
                JOIN (
                    SELECT course_grades.course_id, count(course_memberships.course_id) AS completed FROM course_grades JOIN course_memberships ON course_grades.course_id = course_memberships.course_id AND course_grades.gatech_user_id = course_memberships.gatech_user_id WHERE (course_grades.course_passing_state_id = '1' OR course_grades.course_passing_state_id = '2') AND course_memberships.course_membership_ts BETWEEN ? AND ? GROUP BY course_memberships.course_id
                ) AS completed ON courses.course_id = completed.course_id
                WHERE course_memberships.course_membership_ts BETWEEN ? AND ? GROUP BY courses.course_id;""", (startDate, endDate, startDate, endDate, startDate, endDate))
        writer.writerow([d[0] for d in cur.description])
        writer.writerows(cur.fetchall())

def parse(name, cur, zipfile):
    """Extract a csv from the zip and read it into a table"""
    csvfile = io.TextIOWrapper(zipfile.open(name + '.csv'), encoding='utf-8')
    dr = csv.DictReader(csvfile)
    cur.execute('CREATE TABLE {0} ({1});'.format(name, ', '.join(dr.fieldnames)))
    for row in dr:
        columns = ', '.join([':' + key for key in filter(None, row.keys())])
        cur.execute('INSERT INTO {0} ({1}) VALUES ({2});'.format(name, ', '.join(filter(None,row.keys())), columns), row)

def getFileModDate(filename):
    """Get the datetime when a file was modified"""
    t = os.path.getmtime(filename)
    return datetime.datetime.fromtimestamp(t)

if __name__ == '__main__':
    main(sys.argv[1:])
<commit_msg>only count each member once<commit_after>#!/usr/bin/env python3

"""
        Copyright 2016 Stuart Freeman Licensed under the
	Educational Community License, Version 2.0 (the "License"); you may
	not use this file except in compliance with the License. You may
	obtain a copy of the License at

http://www.osedu.org/licenses/ECL-2.0

	Unless required by applicable law or agreed to in writing,
	software distributed under the License is distributed on an "AS IS"
	BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
	or implied. See the License for the specific language governing
	permissions and limitations under the License.
"""

import csv, datetime, errno, getopt, io, os, re, sqlite3, sys, zipfile

def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)

def main(argv):
    # If start and end date aren't specified we'll use the first and last days of the previous month
    startDate = (datetime.date.today().replace(day=1) - datetime.timedelta(days=1)).replace(day=1).strftime('%Y-%m-%d')
    endDate = (datetime.date.today().replace(day=1) - datetime.timedelta(days=1)).strftime('%Y-%m-%d')

    # Set up command line parsing
    usage = sys.argv[0] + ' -i <inputfile> -s <YYYY-MM-DD> -e <YYYY-MM-DD>'
    try:
        opts, args = getopt.getopt(argv, "hi:s:e:", ['ifile', 'startDate', 'endDate'])
    except getopt.GetoptError:
        print(usage)
        sys.exit(2)
    if len(opts) == 0:
        print(usage)
        sys.exit()
    for opt, arg in opts:
        if opt == '-h':
            print(usage)
            sys.exit()
        elif opt == '-i':
            inputfile = arg
        elif opt == '-s':
            if re.fullmatch('\d{4}-\d{2}-\d{2}', arg):
                startDate = arg
            else:
                eprint('Dates should be in the format YYYY-MM-DD')
                sys.exit(2)
        elif opt == '-e':
            if re.fullmatch('\d{4}-\d{2}-\d{2}', arg):
                endDate = arg
            else:
                eprint('Dates should be in the format YYYY-MM-DD')
                sys.exit(2)

    # Make the results dir
    resultspath = os.path.dirname(os.path.realpath(__file__)) + '/results'
    try:
        os.makedirs(resultspath)
    except OSError as exception:
        if exception.errno != errno.EEXIST:
            raise

    dbfile = resultspath + '/coursera.sqlite3'
    dbExists = os.path.isfile(dbfile)
    dbOld = getFileModDate(inputfile) > getFileModDate(dbfile) if dbExists else False

    # Our cached DB is old, delete it so we can rebuild
    if dbOld:
        os.remove(dbfile)

    # Connect to the cached DB or create an empty one
    db = sqlite3.connect(dbfile)
    cur = db.cursor()

    # If we just created an empty DB populate it from the zip file
    if not dbExists or dbOld:
        with zipfile.ZipFile(inputfile) as myzip:
            parse('courses', cur, myzip)
            parse('course_memberships', cur, myzip)
            parse('course_grades', cur, myzip)
            parse('users_courses__certificate_payments', cur, myzip)

        cur.execute('CREATE INDEX course_idx1 ON courses(course_id);')
        cur.execute('CREATE INDEX course_idx2 ON course_memberships(course_id);')
        cur.execute('CREATE INDEX course_idx3 ON course_grades(course_id);')
        cur.execute('CREATE INDEX date_idx1 ON course_memberships(course_membership_ts);')
        db.commit()

    with open('{0}/report.{1}.{2}.csv'.format(resultspath, startDate, endDate), 'w') as reportfile:
        writer = csv.writer(reportfile)
        cur.execute("""SELECT courses.course_name,
        count(distinct course_memberships.gatech_user_id) AS members,
        count(CASE course_memberships.course_membership_role WHEN 'LEARNER' THEN 1 ELSE null END) AS active,
        payments.paid AS paid,
        completed.completed AS completed
        FROM courses
                JOIN course_memberships ON courses.course_id = course_memberships.course_id
                JOIN (
                    SELECT course_memberships.course_id, count(course_memberships.course_id) AS paid FROM users_courses__certificate_payments JOIN course_memberships ON users_courses__certificate_payments.course_id = course_memberships.course_id AND users_courses__certificate_payments.gatech_user_id = course_memberships.gatech_user_id WHERE course_membership_ts BETWEEN ? AND ? GROUP BY course_memberships.course_id
                ) AS payments ON courses.course_id = payments.course_id
                JOIN (
                    SELECT course_grades.course_id, count(course_memberships.course_id) AS completed FROM course_grades JOIN course_memberships ON course_grades.course_id = course_memberships.course_id AND course_grades.gatech_user_id = course_memberships.gatech_user_id WHERE (course_grades.course_passing_state_id = '1' OR course_grades.course_passing_state_id = '2') AND course_memberships.course_membership_ts BETWEEN ? AND ? GROUP BY course_memberships.course_id
                ) AS completed ON courses.course_id = completed.course_id
                WHERE course_memberships.course_membership_ts BETWEEN ? AND ? GROUP BY courses.course_id;""", (startDate, endDate, startDate, endDate, startDate, endDate))
        writer.writerow([d[0] for d in cur.description])
        writer.writerows(cur.fetchall())

def parse(name, cur, zipfile):
    """Extract a csv from the zip and read it into a table"""
    csvfile = io.TextIOWrapper(zipfile.open(name + '.csv'), encoding='utf-8')
    dr = csv.DictReader(csvfile)
    cur.execute('CREATE TABLE {0} ({1});'.format(name, ', '.join(dr.fieldnames)))
    for row in dr:
        columns = ', '.join([':' + key for key in filter(None, row.keys())])
        cur.execute('INSERT INTO {0} ({1}) VALUES ({2});'.format(name, ', '.join(filter(None,row.keys())), columns), row)

def getFileModDate(filename):
    """Get the datetime when a file was modified"""
    t = os.path.getmtime(filename)
    return datetime.datetime.fromtimestamp(t)

if __name__ == '__main__':
    main(sys.argv[1:])
<|endoftext|>


<commit_before>import json
import os

import redis
from django.core import signing
from django.core.exceptions import ObjectDoesNotExist
from django.core.serializers.json import DjangoJSONEncoder
from django.core.signing import TimestampSigner
from django.shortcuts import redirect, render
from django.utils.http import quote
from django.views.decorators.csrf import (csrf_exempt, csrf_protect,
                                          ensure_csrf_cookie)

from dashboard.models import App, User
from dashboard.tasks import keen_add_event_task as keen_add_event
from roombookings.helpers import PrettyJsonResponse as JsonResponse
from uclapi.settings import REDIS_UCLAPI_HOST

from .app_helpers import generate_random_verification_code
from .decorators import oauth_token_check
from .models import OAuthToken
from .scoping import Scopes

# The endpoint that creates a Shibboleth login and redirects the user to it
def authorise(request):
    client_id = request.GET.get("client_id", None)
    state = request.GET.get("state", None)
    if not (client_id and state):
        return JsonResponse({
            "ok": False,
            "error": "incorrect parameters supplied"
        })

    try:
        app = App.objects.get(client_id=client_id)
    except ObjectDoesNotExist:
        return JsonResponse({
            "ok": False,
            "error": "App does not exist for client id"
        })

    if app.callback_url is None:
        return JsonResponse({
            "ok": False,
            "error": "No callback URL set for this app."
        })

    # Sign the app and state pair before heading to Shibboleth to help protect
    # against CSRF and XSS attacks
    signer = TimestampSigner()
    data = app.client_id + state
    signed_data = signer.sign(data)

    # Build Shibboleth callback URL
    url = os.environ.get("SHIBBOLETH_ROOT") + "/Login?target="
    target = request.build_absolute_uri(
        "/oauth/shibcallback?appdata={}".format(signed_data)
    )
    target = quote(target)
    url += target

    # Send the user to Shibboleth to log in
    return redirect(url)

@csrf_exempt
@ensure_csrf_cookie
def shibcallback(request):
    # Callback from Shib login. Get ALL the meta!
    appdata_signed = request.GET.get("appdata", None)
    if not appdata_signed:
        return JsonResponse({
            "ok": False,
            "error": ("No signed app data returned from Shibboleth."
                      " Please use the authorise endpoint.")
        })

    signer = TimestampSigner()
    try:
        # Expire our signed tokens after five minutes for added security
        appdata = signer.unsign(appdata_signed, max_age=300)
    except signing.BadSignature:
        return JsonResponse({
            "ok": False,
            "error": "Bad signature. Please try login again."
        })
    except signing.SignatureExpired:
        return JsonResponse({
            "ok": False,
            "error": "Signature has expired. Please try login again."
        })

    client_id = appdata[:33]
    state = appdata[33:]

    app = App.objects.get(client_id=client_id)

    try:
        eppn = request.META['HTTP_EPPN']
        groups = request.META['HTTP_UCLINTRANETGROUPS']
        cn = request.META['HTTP_CN']
        department = request.META['HTTP_DEPARTMENT']
        given_name = request.META['HTTP_GIVENNAME']
        display_name = request.META['HTTP_DISPLAYNAME']
        employee_id = request.META['HTTP_EMPLOYEEID']
    except KeyError:
        context = {
            "error": "Didn't receive all required Shibboleth data."
        }
        return render(
            request,
            'shibboleth_error.html',
            context=context,
            status=400
        )

    # If a user has never used the API before then we need to sign them up
    try:
        user = User.objects.get(email=eppn)
    except ObjectDoesNotExist:
        # create a new user
        user = User(
            email=eppn,
            full_name=display_name,
            given_name=given_name,
            department=department,
            cn=cn,
            raw_intranet_groups=groups,
            employee_id=employee_id
        )

        user.save()
        keen_add_event.delay("signup", {
            "id": user.id,
            "email": eppn,
            "name": display_name
        })

    signer = TimestampSigner()
    response_data = {
        "client_id": app.client_id,
        "state": state,
        "user_upi": user.employee_id
    }

    response_data_str = json.dumps(response_data, cls=DjangoJSONEncoder)
    response_data_signed = signer.sign(response_data_str)

    s = Scopes()

    page_data = {
        "app_name": app.name,
        "creator": app.user.full_name,
        "client_id": app.client_id,
        "state": state,
        "scopes": s.scope_dict(app.scope.scope_number),
        "user": {
            "full_name": user.full_name,
            "cn": user.cn,
            "email": user.email,
            "department": user.department,
            "upi": user.employee_id
        },
        "signed_data": response_data_signed
    }

    initial_data = json.dumps(page_data, cls=DjangoJSONEncoder)
    return render(request, 'permissions.html', {
        'initial_data': initial_data
    })

@csrf_protect
def userdeny(request):
    signer = TimestampSigner()

    try:
        signed_data = request.POST.get("signed_app_data")
        raw_data_str = signer.unsign(signed_data, max_age=300)
    except:
        return JsonResponse({
            "ok": False,
            "error": ("The signed data received was invalid."
                      " Please try the login process again. "
                      "If this issue persists, please contact support.")
        })

    try:
        data = json.loads(raw_data_str)
    except:
        return JsonResponse({
            "ok": False,
            "error": ("The JSON data was not in the expected format."
                      " Please contact support.")
        })

    app = App.objects.get(client_id=data["client_id"])
    state = data["state"]

    redir = "{}?result=denied&state={}".format(app.callback_url, state)

    # Now check if a token has been granted in the past. If so, invalidate it.
    # There shouldn't be a situation where more than one user/app token pair
    # exists but, just in case, let's invalidate them all.
    user = User.objects.get(employee_id=data['user_upi'])
    tokens = OAuthToken.objects.filter(app=app, user=user)
    for token in tokens:
        token.active = False
        token.save()

    # Send the user to the app's denied permission page
    return redirect(redir)

@csrf_protect
def userallow(request):
    signer = TimestampSigner()

    try:
        raw_data_str = signer.unsign(
            request.POST.get("signed_app_data"), max_age=300)
    except (signing.BadSignature, KeyError):
        return JsonResponse({
            "ok": False,
            "error": ("The signed data received was invalid."
                      " Please try the login process again."
                      " If this issue persists, please contact support.")
        })

    try:
        data = json.loads(raw_data_str)
    except ValueError:
        return JsonResponse({
            "ok": False,
            "error": ("The JSON data was not in the expected format."
                      " Please contact support.")
        })

    app = App.objects.get(client_id=data["client_id"])
    state = data["state"]

    # Now we have the data we need to generate a random code and
    # store it in redis along with the request properties.
    # Once the client is redirected to they can make a request
    # with that code to obtain an OAuth token. This can then
    # be used to obtain User Data.

    code = generate_random_verification_code()

    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)

    verification_data = {
        "client_id": app.client_id,
        "state": state,
        "upi": data["user_upi"]
    }

    verification_data_str = json.dumps(
        verification_data, cls=DjangoJSONEncoder)

    # Store this verification data in redis so that it can be obtained later
    # when the client wants to swap the code for a token.
    # The code will only be valid for 90 seconds after which redis will just
    # drop it and the process will be invalidated.
    r.set(code, verification_data_str, ex=90)

    # Now redirect the user back to the app, at long last.
    # Just in case they've tried to be super clever and host multiple apps with
    # the same callback URL, we'll provide the client ID along with the state
    return redirect(
            app.callback_url + "?result=allowed&code=" + code + "&client_id=" +
            app.client_id + "&state=" + state
        )

def token(request):
    try:
        code = request.GET.get("code")
        client_id = request.GET.get("client_id")
        client_secret = request.GET.get("client_secret")
    except KeyError:
        return JsonResponse({
            "ok": False,
            "error": ("The client did not provide"
                      " the requisite data to get a token.")
        })

    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)
    try:
        data_json = r.get(code).decode('ascii')

    except:
        return JsonResponse({
            "ok": False,
            "error": ("The code received was invalid, or has expired."
                      " Please try again.")
        })

    # Remove code from Redis once used to protect against replay attacks.
    # This is in a try...except to prevent against the edge case when the
    # code has expired between getting and deleting.
    try:
        r.delete(code)
    except:
        pass

    data = json.loads(data_json)

    client_id = data["client_id"]
    state = data["state"]
    upi = data["upi"]

    app = App.objects.get(client_id=client_id)
    if app.client_secret != client_secret:
        response = JsonResponse({
            "ok": False,
            "error": "Client secret incorrect"
        })
        response.status_code = 400
        return response

    user = User.objects.get(employee_id=upi)

    # Since the data has passed verification at this point, and we have
    # checked the validity of the client secret, we can
    # now generate an OAuth access token for the user.
    # But first, we should check if a token has been generated already.
    # If a token does already exist then we should not add yet another one to
    # the database. We can just pass those keys to the app
    # again (in case it has lost them).

    try:
        token = OAuthToken.objects.get(app=app, user=user)

        # If the code gets here then the user has used this app before,
        # so let's check that the scope does
        # not need changing
        if not token.scope.scopeIsEqual(app.scope):
            # Remove the current scope from the token
            token.scope.delete()

            # Clone the scope of the app
            app_scope = app.scope
            app_scope.id = None
            app_scope.save()

            # Assign the new scope to the token
            token.scope = app_scope

            # Save the token with the new scope
            token.save()

        # If the user has denied this app access before and invalidated a token
        # then let's re-enabled that token because access is permitted again.
        if token.active is False:
            token.active = True
            token.save()

    except OAuthToken.DoesNotExist:
        # The user has never logged in before so let's clone the scope and
        # create a brand new OAuth token

        # Clone the scope defined in the app model
        app_scope = app.scope
        app_scope.id = None
        app_scope.save()

        # Now set up a new token with that scope
        token = OAuthToken(
            app=app,
            user=user,
            scope=app_scope
        )
        token.save()

    # Now that we have a token we can pass one back to the app
    # We sincerely hope they'll save this token!
    # The app can use the token to pull in any personal data (name, UPI, etc.)
    # later on, so we won't bother to give it to them just yet.

    s = Scopes()

    oauth_data = {
        "ok": True,
        "state": state,
        "client_id": app.client_id,
        "token": token.token,
        "scope": json.dumps(s.scope_dict(token.scope.scope_number))
    }

    return JsonResponse(oauth_data)

@oauth_token_check([])
def userdata(request, *args, **kwargs):
    token = kwargs['token']

    return JsonResponse({
        "ok": True,
        "full_name": token.user.full_name,
        "email": token.user.email,
        "given_name": token.user.given_name,
        "cn": token.user.cn,
        "department": token.user.department,
        "upi": token.user.employee_id,
        "scope_number": token.scope.scope_number
    })

def scope_map(request):
    s = Scopes()
    scope_map = {
        "scope_map": s.get_scope_map()
    }
    return JsonResponse(scope_map)

@oauth_token_check([])
def token_test(request, *args, **kwargs):
    s = Scopes()

    token = kwargs['token']

    return JsonResponse({
        "ok": True,
        "active": token.active,
        "user_upi": token.user.employee_id,
        "scopes": s.scope_dict_all(
            current=token.scope.scope_number,
            pretty_print=False
        ),
        "scope_number": token.scope.scope_number
    })
<commit_msg>Improves error responses and handling in the OAuth views<commit_after>import json
import os

import redis
from django.core import signing
from django.core.exceptions import ObjectDoesNotExist
from django.core.serializers.json import DjangoJSONEncoder
from django.core.signing import TimestampSigner
from django.shortcuts import redirect, render
from django.utils.http import quote
from django.views.decorators.csrf import (csrf_exempt, csrf_protect,
                                          ensure_csrf_cookie)

from dashboard.models import App, User
from dashboard.tasks import keen_add_event_task as keen_add_event
from roombookings.helpers import PrettyJsonResponse
from uclapi.settings import REDIS_UCLAPI_HOST

from .app_helpers import generate_random_verification_code
from .decorators import oauth_token_check
from .models import OAuthToken
from .scoping import Scopes

# The endpoint that creates a Shibboleth login and redirects the user to it
def authorise(request):
    client_id = request.GET.get("client_id", None)
    state = request.GET.get("state", None)
    if not (client_id and state):
        response = PrettyJsonResponse({
            "ok": False,
            "error": "incorrect parameters supplied"
        })
        response.status_code = 400
        return response

    try:
        app = App.objects.get(client_id=client_id)
    except ObjectDoesNotExist:
        response = PrettyJsonResponse({
            "ok": False,
            "error": "App does not exist for client id"
        })
        response.status_code = 400
        return response

    if app.callback_url is None:
        response = PrettyJsonResponse({
            "ok": False,
            "error": "No callback URL set for this app."
        })
        response.status_code = 400
        return response

    # Sign the app and state pair before heading to Shibboleth to help protect
    # against CSRF and XSS attacks
    signer = TimestampSigner()
    data = app.client_id + state
    signed_data = signer.sign(data)

    # Build Shibboleth callback URL
    url = os.environ.get("SHIBBOLETH_ROOT") + "/Login?target="
    target = request.build_absolute_uri(
        "/oauth/shibcallback?appdata={}".format(signed_data)
    )
    target = quote(target)
    url += target

    # Send the user to Shibboleth to log in
    return redirect(url)

@csrf_exempt
@ensure_csrf_cookie
def shibcallback(request):
    # Callback from Shib login. Get ALL the meta!
    appdata_signed = request.GET.get("appdata", None)
    if not appdata_signed:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("No signed app data returned from Shibboleth."
                      " Please use the authorise endpoint.")
        })
        response.status_code = 400
        return response

    signer = TimestampSigner()
    try:
        # Expire our signed tokens after five minutes for added security
        appdata = signer.unsign(appdata_signed, max_age=300)
    except signing.BadSignature:
        response = PrettyJsonResponse({
            "ok": False,
            "error": "Bad signature. Please try login again."
        })
        response.status_code = 400
        return response
    except signing.SignatureExpired:
        response = PrettyJsonResponse({
            "ok": False,
            "error": "Signature has expired. Please try login again."
        })
        response.status_code = 400
        return response

    client_id = appdata[:33]
    state = appdata[33:]

    app = App.objects.get(client_id=client_id)

    try:
        eppn = request.META['HTTP_EPPN']
        groups = request.META['HTTP_UCLINTRANETGROUPS']
        cn = request.META['HTTP_CN']
        department = request.META['HTTP_DEPARTMENT']
        given_name = request.META['HTTP_GIVENNAME']
        display_name = request.META['HTTP_DISPLAYNAME']
        employee_id = request.META['HTTP_EMPLOYEEID']
    except KeyError:
        context = {
            "error": "Didn't receive all required Shibboleth data."
        }
        return render(
            request,
            'shibboleth_error.html',
            context=context,
            status=400
        )

    # If a user has never used the API before then we need to sign them up
    try:
        user = User.objects.get(email=eppn)
    except ObjectDoesNotExist:
        # create a new user
        user = User(
            email=eppn,
            full_name=display_name,
            given_name=given_name,
            department=department,
            cn=cn,
            raw_intranet_groups=groups,
            employee_id=employee_id
        )

        user.save()
        keen_add_event.delay("signup", {
            "id": user.id,
            "email": eppn,
            "name": display_name
        })

    signer = TimestampSigner()
    response_data = {
        "client_id": app.client_id,
        "state": state,
        "user_upi": user.employee_id
    }

    response_data_str = json.dumps(response_data, cls=DjangoJSONEncoder)
    response_data_signed = signer.sign(response_data_str)

    s = Scopes()

    page_data = {
        "app_name": app.name,
        "creator": app.user.full_name,
        "client_id": app.client_id,
        "state": state,
        "scopes": s.scope_dict(app.scope.scope_number),
        "user": {
            "full_name": user.full_name,
            "cn": user.cn,
            "email": user.email,
            "department": user.department,
            "upi": user.employee_id
        },
        "signed_data": response_data_signed
    }

    initial_data = json.dumps(page_data, cls=DjangoJSONEncoder)
    return render(request, 'permissions.html', {
        'initial_data': initial_data
    })

@csrf_protect
def userdeny(request):
    signer = TimestampSigner()

    try:
        signed_data = request.POST.get("signed_app_data")
        raw_data_str = signer.unsign(signed_data, max_age=300)
    except:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The signed data received was invalid."
                      " Please try the login process again. "
                      "If this issue persists, please contact support.")
        })
        response.status_code = 400
        return response

    try:
        data = json.loads(raw_data_str)
    except:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The JSON data was not in the expected format."
                      " Please contact support.")
        })
        response.status_code = 400
        return response

    app = App.objects.get(client_id=data["client_id"])
    state = data["state"]

    redir = "{}?result=denied&state={}".format(app.callback_url, state)

    # Now check if a token has been granted in the past. If so, invalidate it.
    # There shouldn't be a situation where more than one user/app token pair
    # exists but, just in case, let's invalidate them all.
    try:
        users = User.objects.filter(employee_id=data["user_upi"])
        user = users[0]
    except (User.DoesNotExist, KeyError):
        response = PrettyJsonResponse({
            "ok": False,
            "error":
                "User does not exist. This should never occur. "
                "Please contact support."
        })
        response.status_code = 400
        return response

    tokens = OAuthToken.objects.filter(app=app, user=user)
    for token in tokens:
        token.active = False
        token.save()

    # Send the user to the app's denied permission page
    return redirect(redir)

@csrf_protect
def userallow(request):
    signer = TimestampSigner()

    try:
        raw_data_str = signer.unsign(
            request.POST.get("signed_app_data"), max_age=300)
    except (signing.BadSignature, KeyError):
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The signed data received was invalid."
                      " Please try the login process again."
                      " If this issue persists, please contact support.")
        })
        response.status_code = 400
        return response

    try:
        data = json.loads(raw_data_str)
    except ValueError:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The JSON data was not in the expected format."
                      " Please contact support.")
        })
        response.status_code = 400
        return response

    app = App.objects.get(client_id=data["client_id"])
    state = data["state"]

    # Now we have the data we need to generate a random code and
    # store it in redis along with the request properties.
    # Once the client is redirected to they can make a request
    # with that code to obtain an OAuth token. This can then
    # be used to obtain User Data.

    code = generate_random_verification_code()

    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)

    verification_data = {
        "client_id": app.client_id,
        "state": state,
        "upi": data["user_upi"]
    }

    verification_data_str = json.dumps(
        verification_data, cls=DjangoJSONEncoder)

    # Store this verification data in redis so that it can be obtained later
    # when the client wants to swap the code for a token.
    # The code will only be valid for 90 seconds after which redis will just
    # drop it and the process will be invalidated.
    r.set(code, verification_data_str, ex=90)

    # Now redirect the user back to the app, at long last.
    # Just in case they've tried to be super clever and host multiple apps with
    # the same callback URL, we'll provide the client ID along with the state
    return redirect(
            app.callback_url + "?result=allowed&code=" + code + "&client_id=" +
            app.client_id + "&state=" + state
        )

def token(request):
    try:
        code = request.GET.get("code")
        client_id = request.GET.get("client_id")
        client_secret = request.GET.get("client_secret")
    except KeyError:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The client did not provide"
                      " the requisite data to get a token.")
        })
        response.status_code = 400
        return response

    r = redis.StrictRedis(host=REDIS_UCLAPI_HOST)
    try:
        data_json = r.get(code).decode('ascii')

    except:
        response = PrettyJsonResponse({
            "ok": False,
            "error": ("The code received was invalid, or has expired."
                      " Please try again.")
        })
        response.status_code = 400
        return response

    # Remove code from Redis once used to protect against replay attacks.
    # This is in a try...except to prevent against the edge case when the
    # code has expired between getting and deleting.
    try:
        r.delete(code)
    except:
        pass

    data = json.loads(data_json)

    client_id = data["client_id"]
    state = data["state"]
    upi = data["upi"]

    app = App.objects.get(client_id=client_id)
    if app.client_secret != client_secret:
        response = PrettyJsonResponse({
            "ok": False,
            "error": "Client secret incorrect"
        })
        response.status_code = 400
        return response

    user = User.objects.get(employee_id=upi)

    # Since the data has passed verification at this point, and we have
    # checked the validity of the client secret, we can
    # now generate an OAuth access token for the user.
    # But first, we should check if a token has been generated already.
    # If a token does already exist then we should not add yet another one to
    # the database. We can just pass those keys to the app
    # again (in case it has lost them).

    try:
        token = OAuthToken.objects.get(app=app, user=user)

        # If the code gets here then the user has used this app before,
        # so let's check that the scope does
        # not need changing
        if not token.scope.scopeIsEqual(app.scope):
            # Remove the current scope from the token
            token.scope.delete()

            # Clone the scope of the app
            app_scope = app.scope
            app_scope.id = None
            app_scope.save()

            # Assign the new scope to the token
            token.scope = app_scope

            # Save the token with the new scope
            token.save()

        # If the user has denied this app access before and invalidated a token
        # then let's re-enabled that token because access is permitted again.
        if token.active is False:
            token.active = True
            token.save()

    except OAuthToken.DoesNotExist:
        # The user has never logged in before so let's clone the scope and
        # create a brand new OAuth token

        # Clone the scope defined in the app model
        app_scope = app.scope
        app_scope.id = None
        app_scope.save()

        # Now set up a new token with that scope
        token = OAuthToken(
            app=app,
            user=user,
            scope=app_scope
        )
        token.save()

    # Now that we have a token we can pass one back to the app
    # We sincerely hope they'll save this token!
    # The app can use the token to pull in any personal data (name, UPI, etc.)
    # later on, so we won't bother to give it to them just yet.

    s = Scopes()

    oauth_data = {
        "ok": True,
        "state": state,
        "client_id": app.client_id,
        "token": token.token,
        "scope": json.dumps(s.scope_dict(token.scope.scope_number))
    }

    return PrettyJsonResponse(oauth_data)

@oauth_token_check([])
def userdata(request, *args, **kwargs):
    token = kwargs['token']

    return PrettyJsonResponse({
        "ok": True,
        "full_name": token.user.full_name,
        "email": token.user.email,
        "given_name": token.user.given_name,
        "cn": token.user.cn,
        "department": token.user.department,
        "upi": token.user.employee_id,
        "scope_number": token.scope.scope_number
    })

def scope_map(request):
    s = Scopes()
    scope_map = {
        "scope_map": s.get_scope_map()
    }
    return PrettyJsonResponse(scope_map)

@oauth_token_check([])
def token_test(request, *args, **kwargs):
    s = Scopes()

    token = kwargs['token']

    return PrettyJsonResponse({
        "ok": True,
        "active": token.active,
        "user_upi": token.user.employee_id,
        "scopes": s.scope_dict_all(
            current=token.scope.scope_number,
            pretty_print=False
        ),
        "scope_number": token.scope.scope_number
    })
<|endoftext|>


<commit_before>__all__ = ('MaxHeap',)

class AbstractHeap(object):
    """Heap is a tree-based data structure that satisfies the heap property:

    Given A is a parent node of B, then Key(A) is ordered in respect to Key(B)
    with the same ordering applying across the heap.

    Heaps can be further classified into "max heap" or "min heap". In a max
    heap, parent nodes are always greater or equal than those of children, and
    the highest key is the root node.
    """

    @staticmethod
    def parent(idx):
        return max(idx / 2 - 1, 0)

    @staticmethod
    def child_left(idx):
        return 2 * idx + 1

    @staticmethod
    def child_right(idx):
        return 2 * idx + 2

class MaxHeap(AbstractHeap):
    """ MaxHeap is a heap where the root node is the largest key. Parent nodes
    are always greater or equal than children nodes, therefore the smallest
    keys appear on the leaves.

    Heaps are typically implemented making use of arrays. Root is assigned to
    items[0].

    Definition (Height of a node): number of edges on the longest simple
    downward path from the node to a leaf

    Definition (Height of a heap): height of the root node

    """

    def __init__(self, items=[]):
        self.items = items[:]
        self.length = len(self.items)

        if self.length > 0:
            for i in range(self.length/2, -1, -1):
                self._max_heapify(i)

    @property
    def maximum(self):
        return self.items[0]

    def insert(self, key):
        self.length += 1
        self.items.append(float('-inf'))
        self.increase_key(self.length - 1, key)

    def increase_key(self, index, key):
        """ Sets items[index] = key and rebalances the tree efficiently.

        Traverse upwards through parents finding a potential new suitable
        position for this new value. This looks like the new large value
        is bubbling up to its appropriate position.
        """
        assert key >= self.items[index]

        self.items[index] = key

        while index > 0 and self.items[index] > self.items[self.parent(index)]:
            # This consists of a violation: item's value greater than parent
            self._swap(index, self.parent(index))
            index = self.parent(index)

    def _max_heapify(self, idx=0):
        """ Possible violations of the heap property

            - items[idx] is smaller than items[idx_left]
            - items[idx] is smaller than items[idx_right]

        Procedure:

            - Find which side violation is worse. Swap. Descend heapify.
        """
        idx_left = self.child_left(idx)
        idx_right = self.child_right(idx)
        idx_largest = idx

        if idx_left <= self.length - 1 and \
            self.items[idx] < self.items[idx_left]:
            idx_largest = idx_left

        if idx_right <= self.length - 1 and \
            self.items[idx_largest] < self.items[idx_right]:
            idx_largest = idx_right

        if idx_largest != idx:
            self._swap(idx, idx_largest)
            self._max_heapify(idx_largest)

    def _swap(self, a, b):
        self.items[a], self.items[b] = self.items[b], self.items[a]

    def __getitem__(self, index):
        return self.items[index]

    def print_repr(self, idx=0, nesting=0):
        indent = '--' * nesting
        indent_child = '--' * (nesting + 1)

        print '{}{}'.format(indent, self.items[idx])

        try:
            left = self.child_left(idx)
            if left != 0 and left <= self.length - 1:
                self.print_repr(left, nesting + 1)
        except:
            pass

        try:
            right = self.child_right(idx)
            if right <= self.length - 1:
                self.print_repr(right, nesting + 1)
        except:
            pass
<commit_msg>data_structures/heap: use six.moves.range<commit_after>from six.moves import range

__all__ = ('MaxHeap',)

class AbstractHeap(object):
    """Heap is a tree-based data structure that satisfies the heap property:

    Given A is a parent node of B, then Key(A) is ordered in respect to Key(B)
    with the same ordering applying across the heap.

    Heaps can be further classified into "max heap" or "min heap". In a max
    heap, parent nodes are always greater or equal than those of children, and
    the highest key is the root node.
    """

    @staticmethod
    def parent(idx):
        return max(idx / 2 - 1, 0)

    @staticmethod
    def child_left(idx):
        return 2 * idx + 1

    @staticmethod
    def child_right(idx):
        return 2 * idx + 2

class MaxHeap(AbstractHeap):
    """ MaxHeap is a heap where the root node is the largest key. Parent nodes
    are always greater or equal than children nodes, therefore the smallest
    keys appear on the leaves.

    Heaps are typically implemented making use of arrays. Root is assigned to
    items[0].

    Definition (Height of a node): number of edges on the longest simple
    downward path from the node to a leaf

    Definition (Height of a heap): height of the root node

    """

    def __init__(self, items=[]):
        self.items = items[:]
        self.length = len(self.items)

        if self.length > 0:
            for i in range(self.length/2, -1, -1):
                self._max_heapify(i)

    @property
    def maximum(self):
        return self.items[0]

    def insert(self, key):
        self.length += 1
        self.items.append(float('-inf'))
        self.increase_key(self.length - 1, key)

    def increase_key(self, index, key):
        """ Sets items[index] = key and rebalances the tree efficiently.

        Traverse upwards through parents finding a potential new suitable
        position for this new value. This looks like the new large value
        is bubbling up to its appropriate position.
        """
        assert key >= self.items[index]

        self.items[index] = key

        while index > 0 and self.items[index] > self.items[self.parent(index)]:
            # This consists of a violation: item's value greater than parent
            self._swap(index, self.parent(index))
            index = self.parent(index)

    def _max_heapify(self, idx=0):
        """ Possible violations of the heap property

            - items[idx] is smaller than items[idx_left]
            - items[idx] is smaller than items[idx_right]

        Procedure:

            - Find which side violation is worse. Swap. Descend heapify.
        """
        idx_left = self.child_left(idx)
        idx_right = self.child_right(idx)
        idx_largest = idx

        if idx_left <= self.length - 1 and \
            self.items[idx] < self.items[idx_left]:
            idx_largest = idx_left

        if idx_right <= self.length - 1 and \
            self.items[idx_largest] < self.items[idx_right]:
            idx_largest = idx_right

        if idx_largest != idx:
            self._swap(idx, idx_largest)
            self._max_heapify(idx_largest)

    def _swap(self, a, b):
        self.items[a], self.items[b] = self.items[b], self.items[a]

    def __getitem__(self, index):
        return self.items[index]

    def print_repr(self, idx=0, nesting=0):
        indent = '--' * nesting
        indent_child = '--' * (nesting + 1)

        print '{}{}'.format(indent, self.items[idx])

        try:
            left = self.child_left(idx)
            if left != 0 and left <= self.length - 1:
                self.print_repr(left, nesting + 1)
        except:
            pass

        try:
            right = self.child_right(idx)
            if right <= self.length - 1:
                self.print_repr(right, nesting + 1)
        except:
            pass
<|endoftext|>


<commit_before>import os
import stat
import hashlib

from confiture.schema.containers import Value
from confiture.schema.types import String

from marty.remotemethods import DefaultRemoteMethodSchema, RemoteMethod, RemoteOperationError
from marty.datastructures import Tree, Blob

class LocalRemoteMethodSchema(DefaultRemoteMethodSchema):

    root = Value(String(), default='/')

class Local(RemoteMethod):

    """ Local remote.
    """

    config_schema = LocalRemoteMethodSchema()

    @property
    def root(self):
        return self.config.get('root').encode('utf-8')

    def get_tree(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        directory = os.path.join(self.root, path)
        tree = Tree()
        try:
            directory_items = os.listdir(directory)
        except OSError as err:
            raise RemoteOperationError(err.strerror)

        for filename in directory_items:
            assert isinstance(filename, bytes)
            item = {}
            fullname = os.path.join(directory, filename)
            fstat = os.lstat(fullname)
            if stat.S_ISREG(fstat.st_mode):
                item['type'] = 'blob'
                item['filetype'] = 'regular'
            elif stat.S_ISDIR(fstat.st_mode):
                item['type'] = 'tree'
                item['filetype'] = 'directory'
            elif stat.S_ISLNK(fstat.st_mode):
                item['filetype'] = 'link'
                item['link'] = os.readlink(os.path.join(directory, filename))
            elif stat.S_ISFIFO(fstat.st_mode):
                item['filetype'] = 'fifo'
            else:
                continue  # FIXME: Warn
            fmode = fstat.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO | stat.S_ISVTX)

            item['uid'] = fstat.st_uid
            item['gid'] = fstat.st_gid
            item['mode'] = fmode
            item['atime'] = int(fstat.st_atime)
            item['mtime'] = int(fstat.st_mtime)
            item['ctime'] = int(fstat.st_ctime)
            item['size'] = fstat.st_size

            tree.add(filename, item)
        return tree

    def get_blob(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        try:
            blob = Blob(blob=open(os.path.join(self.root, path), 'rb'))
        except OSError as err:
            raise RemoteOperationError(err.strerror)
        return blob

    def checksum(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        filename = os.path.join(self.root, path)
        filehash = hashlib.sha1()
        try:
            with open(filename, 'rb') as fhash:
                buf = True
                while buf:
                    buf = fhash.read(8192)
                    filehash.update(buf)
        except OSError as err:
            raise RemoteOperationError(err.strerror)
        return filehash.hexdigest()

    def newer(self, attr_new, attr_old):
        return attr_new.get('mtime', 0) != attr_old.get('mtime', 0)
<commit_msg>Implemented restore in local remote method (#5)<commit_after>import os
import stat
import hashlib
import shutil

from confiture.schema.containers import Value
from confiture.schema.types import String

from marty.remotemethods import DefaultRemoteMethodSchema, RemoteMethod, RemoteOperationError
from marty.datastructures import Tree, Blob

class LocalRemoteMethodSchema(DefaultRemoteMethodSchema):

    root = Value(String(), default='/')

class Local(RemoteMethod):

    """ Local remote.
    """

    config_schema = LocalRemoteMethodSchema()

    @property
    def root(self):
        return self.config.get('root').encode('utf-8')

    def get_tree(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        directory = os.path.join(self.root, path)
        tree = Tree()
        try:
            directory_items = os.listdir(directory)
        except OSError as err:
            raise RemoteOperationError(err.strerror)

        for filename in directory_items:
            assert isinstance(filename, bytes)
            item = {}
            fullname = os.path.join(directory, filename)
            fstat = os.lstat(fullname)
            if stat.S_ISREG(fstat.st_mode):
                item['type'] = 'blob'
                item['filetype'] = 'regular'
            elif stat.S_ISDIR(fstat.st_mode):
                item['type'] = 'tree'
                item['filetype'] = 'directory'
            elif stat.S_ISLNK(fstat.st_mode):
                item['filetype'] = 'link'
                item['link'] = os.readlink(os.path.join(directory, filename))
            elif stat.S_ISFIFO(fstat.st_mode):
                item['filetype'] = 'fifo'
            else:
                continue  # FIXME: Warn
            fmode = fstat.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO | stat.S_ISVTX)

            item['uid'] = fstat.st_uid
            item['gid'] = fstat.st_gid
            item['mode'] = fmode
            item['atime'] = int(fstat.st_atime)
            item['mtime'] = int(fstat.st_mtime)
            item['ctime'] = int(fstat.st_ctime)
            item['size'] = fstat.st_size

            tree.add(filename, item)
        return tree

    def put_tree(self, tree, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        directory = os.path.join(self.root, path)
        for name, item in tree.items():
            fullname = os.path.join(directory, name)
            try:
                fstat = os.lstat(fullname)
            except FileNotFoundError:
                fstat = None

            # Create the file itself according to its type:
            if item.get('filetype') == 'regular':
                if fstat is not None and not stat.S_ISREG(fstat.st_mode):
                    raise RemoteOperationError('%s already exists and not a regular file' % fullname)
                else:
                    try:
                        with open(fullname, 'a'):
                            pass
                    except OSError as err:
                        raise RemoteOperationError(err.strerror)
            elif item.get('filetype') == 'directory':
                try:
                    os.mkdir(fullname)
                except FileExistsError:
                    if stat.S_ISDIR(fstat.st_mode):
                        pass  # Ignore already existing directory
                    else:
                        raise RemoteOperationError('%s already exists and not a directory' % fullname)
                except OSError as err:
                    raise RemoteOperationError(err.strerror)
            elif item.get('filetype') == 'link':
                if 'link' in item:
                    try:
                        if fstat is not None:
                            if stat.S_ISLNK(fstat.st_mode):
                                os.unlink(fullname)
                            else:
                                raise RemoteOperationError('%s already exists and not a link' % fullname)
                        os.symlink(item['link'], fullname)
                    except OSError as err:
                        raise RemoteOperationError(err.strerror)
            elif item.get('filetype') == 'fifo':
                try:
                    os.mkfifo(fullname)
                except FileExistsError:
                    if stat.S_ISFIFO(fstat.st_mode):
                        pass  # Ignore already existing FIFO file
                    else:
                        raise RemoteOperationError('%s already exists and not a FIFO file' % fullname)
                except OSError as err:
                    raise RemoteOperationError(err.strerror)
            else:
                pass  # Ignore unknown file types

            # Set files metadata:
            os.chown(fullname, item.get('uid', -1), item.get('gid', -1), follow_symlinks=False)
            if 'mode' in item:
                try:
                    os.chmod(fullname, item['mode'], follow_symlinks=False)
                except SystemError:
                    pass  # Workaround follow_symlinks not implemented in Python 3.5 (bug?)

    def get_blob(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        try:
            blob = Blob(blob=open(os.path.join(self.root, path), 'rb'))
        except OSError as err:
            raise RemoteOperationError(err.strerror)
        return blob

    def put_blob(self, blob, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        fullname = os.path.join(self.root, path)
        try:
            shutil.copyfileobj(blob.to_file(), open(fullname, 'wb'))
        except OSError as err:
            raise RemoteOperationError(err.strerror)

    def checksum(self, path):
        path = path.lstrip(os.sep.encode('utf-8'))
        filename = os.path.join(self.root, path)
        filehash = hashlib.sha1()
        try:
            with open(filename, 'rb') as fhash:
                buf = True
                while buf:
                    buf = fhash.read(8192)
                    filehash.update(buf)
        except OSError as err:
            raise RemoteOperationError(err.strerror)
        return filehash.hexdigest()

    def newer(self, attr_new, attr_old):
        return attr_new.get('mtime', 0) != attr_old.get('mtime', 0)
<|endoftext|>


<commit_before>"""uclapi URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^dashboard/', include('dashboard.urls')),
    url(r'^v0/roombookings/', include('roombookings.urls')),
]
<commit_msg>Remove versioning from URL scheme<commit_after>"""uclapi URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.10/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^dashboard/', include('dashboard.urls')),
    url(r'^roombookings/', include('roombookings.urls')),
]
<|endoftext|>