示例#1
0
    def logged(self):
        auth = web.ctx.env.get('HTTP_AUTHORIZATION')  # @UndefinedVariable
        if auth is None:
            return False
        else:
            auth = re.sub('^Basic ', '', auth)
            username, passwd = base64.decodestring(auth).split(':')
            user = self.session.query(User).filter_by(Username=username).first()
            if user is None:
                self.log(u"Non-existent username: %s" % (username,), log.ERROR, unicode(username))
                return False
            if not password.validate(passwd, user.Password):
                self.log(u"Invalid password for username: %s" % (username,), log.ERROR, unicode(username))
                return False
            self.user = user
            self.validator = Permission(user)

            return True
示例#2
0
class Page:

    __metaclass__ = _ActionMetaClass

    table = None
    priority = 80
    user = None
    check_vars = True
    permission_args = {}
    offset = 0
    limit = 100
    base_link = None
    prefix = None
    query = None
    errno = None
    error = None
    error_level = None
    log_error = True
    log_level = log.INFO
    status = None
    session = None
    uuid = None

    def __init__(self):
        self.db = _global_data.db
        self._log = _global_data.log

    def log(self, comment, level=None, user=None):
        if user is None:
            user = self.user

        if level is None:
            if hasattr(self, "log_level") and self.log_level is not None:
                level = self.log_level
            else:
                level = log.DEBUG
        record_level = log.INFO
        if hasattr(self, 'record_log_level') and self.record_log_level is not None:
            record_level = self.record_log_level
        self._log.log(web.ctx, web.ctx.fullpath.replace("/" + self.prefix + "/", ""), user, level, comment, record_level)

    def get_config(self, key, fallback=True):
        """
        Return config value of 'key'.  If config value doesn't exist, return
        None.
        """
        return config.get_config(self.session, self.uuid, key, fallback)

    def set_config(self, key, value):
        """
        Set config 'key' to 'value'.
        """
        return config.set_config(self.session, self.uuid, key, value)

    @mimerender(
        default='html',
        override_input_key='format',
        html=_global_data.rendercom.render_html,
        xml=_global_data.rendercom.render_xml,
        json=_global_data.rendercom.render_json,
        txt=_global_data.rendercom.render_txt,
        csv=_global_data.rendercom.render_csv
    )
    def _render(self, *args, **kwargs):
        if self.errno is not None:
            self.error = unicode(self.error)
            if web_error.has_key(self.errno):
                self.status = web_error[self.errno]
            else:
                self.status = web_error[400]
                self.error += u'\nIn addition, the web server return unknown error code %i' % (self.errno,)
            if self.log_error:
                self.log(self.error, self.error_level)
            return {'errno': unicode(self.status), 'error': self.error}
        if len(args) == 1:
            if self.check_vars:
                _die_on_string(args[0])
            if args[0] is None:
                return {}
            return args[0]
        else:
            raise ValueError('Too many arguments.  This should be impossible')

    def logged(self):
        auth = web.ctx.env.get('HTTP_AUTHORIZATION')  # @UndefinedVariable
        if auth is None:
            return False
        else:
            auth = re.sub('^Basic ', '', auth)
            username, passwd = base64.decodestring(auth).split(':')
            user = self.session.query(User).filter_by(Username=username).first()
            if user is None:
                self.log(u"Non-existent username: %s" % (username,), log.ERROR, unicode(username))
                return False
            if not password.validate(passwd, user.Password):
                self.log(u"Invalid password for username: %s" % (username,), log.ERROR, unicode(username))
                return False
            self.user = user
            self.validator = Permission(user)

            return True

    def gen_link(self, page):
        """
        Generate absolute url from app relative url
        """
        if page.startswith('/'):
            page = page[1:]
        page = '%s/%s' % (web.ctx.home, page)
        return page

    def check_permissions(self):
        if not self.logged():
            web.header('WWW-Authenticate', 'Basic realm="LESSON login"')
            self.errno = UNAUTHORIZED
            self.error = u"You must log in before accessing this page"
            self.log_error = False
            return
        if hasattr(self, 'permission') and not self.validator.has_permission(self.permission, self.permission_args):
            self.errno = FORBIDDEN
            self.error = u"Access to this page is denied"
            self.error_level = log.ERROR
            return
        return None

    def _strip_prefix(self, args, kwargs):
        if len(args) > 0:
            self.prefix = args[0]
            args = tuple(args[1:])

        elif kwargs.has_key('prefix'):
            self.prefix = kwargs['prefix']
            del kwargs['prefix']

        # Strip leading and trailing slashes from prefix
        if self.prefix is None:
            self.prefix = u""
        self.prefix = self.prefix.strip('/')

        return args, kwargs

    def __split_url_segment(self, segment):
        """
        Split a url segment in form table@link_table#key into tuple of (table,
        link_table, key) where link_table and key may be None
        """

        # This would probably be easier as a regex, but I have no idea how to
        # make this work where the order of link_table and key doesn't matter
        x = segment.find('@')
        y = segment.find('$')
        link_table = None
        key = None
        if x > -1:
            if x < y:
                link_table = segment[x + 1:y]
            else:
                link_table = segment[x + 1:]
        if y > -1:
            if y < x:
                key = segment[y + 1: x]
            else:
                key = segment[y + 1:]

        table = segment
        if x > -1 and (x < y or y == -1):
            table = segment[:x]
        elif y > -1 and (y < x or x == -1):
            table = segment[:y]

        return (table, link_table, key)

    def __build_filter_url(self):
        """
        Build filter url from filters.  If filters has been run through
        self._gen_default_query, filter url is canonical.
        """
        filter_url = ""
        for ffilter in self.filters:
            filter_url += "/" + ffilter['table']
            if ffilter['link_table'] is not None:
                filter_url += "@" + ffilter['link_table']
            if ffilter['key'] is not None:
                filter_url += "$" + ffilter['key']
            filter_url += "/" + ffilter['value']
        return filter_url[1:]

    def _get_filters(self):
        """
        Work out SQL filters from self.prefix with very little error checking
        """
        filter_list = self.prefix.split('/')
        self.filters = []

        if filter_list == [u'']:
            return

        step = 1
        for item in filter_list:
            if step == 1:
                # Extract link table and key from link section
                (table, link_table, key) = self.__split_url_segment(item)

                for x in model.TableTop.__subclasses__():  # @UndefinedVariable
                    if hasattr(x, "Link") and x.Link == table:
                        self.filters.append({'table_class': x, 'table': table, 'link_table': link_table, 'key': key})
                        step = 2
                if step != 2:  # We didn't find any tables that matched
                    self.errno = 400
                    self.error = u"Table %s isn't in database" % (table,)
                    return None
                else:
                    continue
            if step == 2:
                self.filters[-1]['value'] = item
                step = 1

    def _gen_default_query(self):
        """
        Generate default query from self.table and filters.  Default query is
        stored in self.query, and there is no return value.  This also
        normalizes the filter list stored in self.filters and redirects if
        filter list isn't canonical.
        """

        if self.table is None:
            self.query = None
            return

        self.filters.reverse()
        query = self.session.query(self.table)

        used_table_list = [(class_mapper(self.table), self.table)]
        for item in self.filters:
            if item['link_table'] is not None and hasattr(used_table_list[0][1], "Link") and item['link_table'] == used_table_list[0][1].Link:
                item['link_table'] = None

            foreign_key = None
            table = class_mapper(item['table_class'])

            # Get linked table in relationship
            link_table = None
            if item['link_table'] is None:
                link_table = used_table_list[0][0]
                link_table_class = used_table_list[0][1]
            else:
                found = False
                for test_table in used_table_list:
                    if test_table[1].Link == item['link_table']:
                        link_table_class = test_table[1]
                        link_table = test_table[0]
                        found = True
                if not found:
                    self.query = False
                    self.errno = 400
                    self.error = u"Unable to find link table %s" % item['link_table']
                    return

            # Create alias if link_table is linked multiple times
            for old_table in used_table_list:
                if old_table[1] == item['table_class']:
                    item['table_class'] = aliased(item['table_class'])

            used_table_list.insert(0, (table, item['table_class']))

            # Get primary key in relationship
            count = 0
            for x in link_table.iterate_properties:
                if isinstance(x, sqlalchemy.orm.properties.RelationshipProperty):
                    if x.remote_side[0] == table.primary_key[0]:
                        count += 1
                        if item['key'] is not None:
                            if item['key'] == unicode(x.local_side[0].key):
                                foreign_key = getattr(link_table_class, x.local_side[0].key)
                                if item['key'] == unicode(x.remote_side[0].key):
                                    item['key'] = None
                        else:  # No specified key
                            foreign_key = getattr(link_table_class, x.local_side[0].key)
                            if count > 1:  # More than one possible relationship, so don't return any
                                self.errno = 400
                                self.error = "There are multiple possible relationships between column %s in table %s and table %s" % (table.primary_key[0].key, item['table_class'].Link, link_table_class.Link)
                                return
            if foreign_key is None:
                self.errno = 400
                self.error = "There's no relationship between column %s in table %s and table %s" % (table.primary_key[0].key, item['table_class'].Link, link_table_class.Link)
                return

            primary_key = getattr(item['table_class'], table.primary_key[0].key)
            query = query.join(item['table_class'], primary_key == foreign_key).filter(primary_key == item['value'])
        self.filters.reverse()

        # Verify that current filter path is canonical.  If not, redirect to
        # canonical path
        filter_url = self.__build_filter_url()
        if self.prefix != filter_url:
            full_path = web.ctx.path[1:]
            full_path = full_path.replace(self.prefix, '')
            print 'Redirecting to /' + filter_url + full_path
            web.seeother('/' + filter_url + full_path)

        self.query = query

    def _set_status(self, procedure, args, kwargs):
        """
        Set self.status to current web.ctx.status, then after page has been
        rendered, set web.ctx.status to updated self.status.  This is needed
        for old web.py (0.32).
        """
        self.status = web.ctx.status
        retval = self._parse(procedure, args, kwargs)
        web.ctx.status = self.status
        return retval

    def _parse(self, procedure, args, kwargs):
        # Open database session
        self.session = self.db.create_session()

        # Get DEBUG level
        self.record_log_level = self.get_config(u'log_level')
        if self.record_log_level is not None:
            self.record_log_level = int(self.record_log_level)

        (args, kwargs) = self._strip_prefix(args, kwargs)

        # Check permissions
        self.check_permissions()

        # Immediately exit if we've hit a permissions error
        if self.errno is not None:
            return self._render()

        # Verify that prefix is in multiples of two
        if len(self.prefix.split('/')) % 2 != 0 and self.prefix != '':
            self.errno = 400
            self.error = u"Filters must be in the form /table[@linktable][$foreign_key]/primary_key, but yours is %s" % (self.prefix,)
            return self._render()

        # Verify that prefix is a valid filter
        self._get_filters()
        if self.errno is not None:
            return self._render()

        # Generate default query
        self._gen_default_query()
        if self.errno is not None:
            return self._render()

        retval = self._render(procedure(*args, **kwargs))
        self.log("Accessed page")

        # Close database session
        self.session.close()
        return retval

    # Pseudo-procedures for base class page.  These should be overridden by any
    # child classes
    def get(self):
        self.errno = BAD_METHOD
        self.error = u"GET method doesn't exist for page class %s" % (self.__class__.__name__,)
        return

    def push(self):
        self.errno = BAD_METHOD
        self.error = u"PUSH method doesn't exist for page class %s" % (self.__class__.__name__,)
        return

    def put(self):
        self.errno = BAD_METHOD
        self.error = u"PUT method doesn't exist for page class %s" % (self.__class__.__name__,)
        return

    def delete(self):
        self.errno = BAD_METHOD
        self.error = u"DELETE method doesn't exist for page class %s" % (self.__class__.__name__,)
        return

    # These check whether the user is logged in and what permissions they have
    # before granting any access to the page. This shouldn't be overridden
    def GET(self, *args, **kwargs):
        retval = self._set_status(self.get, args, kwargs)
        return retval

    def PUSH(self, *args, **kwargs):
        return self._set_status(self.push, args, kwargs)

    def PUT(self, *args, **kwargs):
        return self._set_status(self.put, args, kwargs)

    def DELETE(self, *args, **kwargs):
        return self._set_status(self.delete, args, kwargs)