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
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)