def process_value(self, value, property, prefix=""): unique = property.get('unique', True) expected_type = property.expected_type.key at = {"key": self.key, "property": prefix + property.name} if isinstance(value, list): if unique is True: raise common.BadData( message='expected atom, found list', at=at, value=value ) p = web.storage(property.copy()) p.unique = True return [self.process_value(v, p) for v in value] if unique is False: raise common.BadData( message='expected list, found atom', at=at, value=value ) type_found = common.find_type(value) if expected_type in common.primitive_types: # string can be converted to any type and int can be converted to float try: if type_found == '/type/string' and expected_type != '/type/string': value = common.primitive_types[expected_type](value) elif type_found == '/type/int' and expected_type == '/type/float': value = float(value) except ValueError as e: raise common.BadData(message=str(e), at=at, value=value) elif property.expected_type.kind == 'embeddable': if isinstance(value, dict): return self.process_data( value, property.expected_type, prefix=at['property'] + "." ) else: raise common.TypeMismatch(expected_type, type_found, at=at, value=value) else: if type_found == '/type/string': value = common.Reference(value) type_found = common.find_type(value) if type_found == '/type/object': type_found = self.get_type(value) # type is not found only when the thing id not found. if type_found is None: raise common.NotFound(key=text_type(value), at=at) if expected_type != type_found: raise common.BadData( message='expected %s, found %s' % (property.expected_type.key, type_found), at=at, value=value, ) return value
def store_account_info(self, username, email, enc_password, data): """Store account info in the store so that the account can be created after verifying the email.""" store = self.site.store.store email = email.strip() account_key = "account/" + username email_key = "account-email/" + email.lower() if store.get(account_key): raise common.BadData(message="User already exists: %s" % username) if store.get(email_key): raise common.BadData(message='Email is already used: ' + email) now = datetime.utcnow() expires_on = now + timedelta(days=14) # 2 weeks account_doc = { "_key": account_key, "type": "account", "status": "pending", "created_on": now.isoformat(), "username": username, "lusername": username.lower(), # lowercase username "email": email, "enc_password": enc_password, "data": data, } email_doc = { "_key": email_key, "type": "account-email", "username": username } store.put_many([account_doc, email_doc])
def validate_properties(self, data): rx = web.re_compile('^[a-z][a-z0-9_]*$') for key in data: if not rx.match(key): raise common.BadData( message="Bad Property: %s" % repr(key), at=dict(key=self.key) )
def get_user_email(self, username): logger.debug("get_user_email", username) if username.startswith("/"): # this is user key userkey = username username = username.split("/")[-1] else: userkey = get_user_root() + username details = self.site.store.get_user_details(username) logger.debug("get_user_email details %s %s", username, details) if details: return details.email doc = self.site.store.store.get("account/" + username) logger.debug("get_user_email doc %s", doc) if doc and doc.get("type") == "pending-account": return doc['email'] raise common.BadData( message='No user registered with username: ' + username, error="account_not_found", )
def POST_activate(self, site): i = input('username') a = site.get_account_manager() status = a.activate(i.username) if status == "ok": return {"ok": "true"} else: raise common.BadData(error_code=status, message="Account activation failed.")
def check_reset_code(self, username, code): SEC_PER_WEEK = 7 * 24 * 3600 timestamp, code = code.split('$', 1) # code is valid only for a week if int(timestamp) + SEC_PER_WEEK < int(time.time()): raise common.BadData(message='Password Reset code expired') username = get_user_root() + username details = self.site.store.get_user_details(username) if not details: raise common.BadData(message="Invalid username") text = details.password + '$' + timestamp if not self._check_salted_hash(self.secret_key, text, code): raise common.BadData(message="Invalid password reset code")
def POST_login(self, site): i = input('username', 'password') a = site.get_account_manager() status = a.login(i.username, i.password) if status == "ok": a.set_auth_token(get_user_root() + i.username) return {"ok": True} else: raise common.BadData(code=status, message="Login failed")
def input(*required, **defaults): if 'infobase_input' in web.ctx: d = web.ctx.infobase_input else: d = web.input() for k in required: if k not in d: raise common.BadData(message="Missing argument: " + repr(k)) result = web.storage(defaults) result.update(d) return result
def update_user(self, old_password, new_password, email): user = self.get_user() if user is None: raise common.PermissionDenied(message="Not logged in") if not self.checkpassword(user.key, old_password): raise common.BadData(message='Invalid Password') new_password and self.assert_password(new_password) email and self.assert_email(email) enc_password = new_password and self._generate_salted_hash( self.secret_key, new_password) self.update_user1(user, enc_password, email)
def POST_update_user(self, site): i = input('old_password', new_password=None, email=None) a = site.get_account_manager() user = a.get_user() username = user.key.split("/")[-1] status = a.login(username, i.old_password) if status == "ok": kw = {} if i.new_password: kw['password'] = i.new_password if i.email: kw['email'] = i.email a.update(username, **kw) else: raise common.BadData(code=status, message="Invalid password")
def _process(self, key, data, prev_data=None): self.key = key # hack to make key available when raising exceptions. if 'key' not in data: data['key'] = key if web.ctx.get('infobase_bootstrap', False): return data assert data['key'] == key data = common.parse_query(data) self.validate_properties(data) prev_data = prev_data and common.parse_query(prev_data) if not web.ctx.get( 'disable_permission_check', False ) and not self.has_permission(self.author, key): raise common.PermissionDenied( message='Permission denied to modify %s' % repr(key) ) type = data.get('type') if type is None: raise common.BadData(message="missing type", at=dict(key=key)) type = self.process_value(type, self.get_property(None, 'type')) type = self.get_thing(type) # when type is changed, consider as all object is modified and don't compare with prev data. if prev_data and prev_data.get('type') != type.key: prev_data = None data = self.process_data(data, type, prev_data) for k in common.READ_ONLY_PROPERTIES: data.pop(k, None) prev_data and prev_data.pop(k, None) if data == prev_data: return None else: return data
def to_int(value, key): try: return int(value) except: raise common.BadData( message=f"Bad integer value for {repr(key)}: {repr(value)}")
def from_json(s): try: return json.loads(s) except ValueError as e: raise common.BadData(message="Bad JSON: " + str(e))
def to_int(value, key): try: return int(value) except: raise common.BadData(message="Bad integer value for %s: %s" % (repr(key), repr(value)))
def assert_type_required(self): type_required = any(c.key not in common.COMMON_PROPERTIES for c in self.conditions if not isinstance(c, Query)) if type_required and self.get_type() is None: raise common.BadData(message="missing 'type' in query")
def things(self, query): type = query.get_type() if type: type_metedata = self.get_metadata(type) if type_metedata: type_id = type_metedata.id else: # Return empty result when type not found return [] else: type_id = None # type is required if there are conditions/sort on keys other than [key, type, created, last_modified] common_properties = ['key', 'type', 'created', 'last_modified'] _sort = query.sort and query.sort.key if _sort and _sort.startswith('-'): _sort = _sort[1:] type_required = bool([ c for c in query.conditions if c.key not in common_properties ]) or (_sort and _sort not in common_properties) if type_required and type is None: raise common.BadData("Type Required") class DBTable: def __init__(self, name, label=None): self.name = name self.label = label or name def sql(self): if self.label != self.name: return "%s as %s" % (self.name, self.label) else: return self.name def __repr__(self): return self.label class Literal: def __init__(self, value): self.value = value def __repr__(self): return self.value tables = {} def get_table(datatype, key): if key not in tables: assert type is not None, "Missing type" table = self.schema.find_table(type, datatype, key) label = 'd%d' % len(tables) tables[key] = DBTable(table, label) return tables[key] wheres = [] def process(c, ordering_func=None): # ordering_func is used when the query contains emebbabdle objects # # example: {'links': {'title: 'foo', 'url': 'http://example.com/foo'}} if c.datatype == 'ref': metadata = self.get_metadata(c.value) if metadata is None: # required object is not found so the query result will be empty. # Raise StopIteration to indicate empty result. raise StopIteration c.value = metadata.id if c.op == '~': op = Literal('LIKE') c.value = c.value.replace('*', '%').replace('_', r'\_') else: op = Literal(c.op) if c.key in ['key', 'type', 'created', 'last_modified']: #@@ special optimization to avoid join with thing.type when there are non-common properties in the query. #@@ Since type information is already present in property table, #@@ getting property id is equivalent to join with type. if c.key == 'type' and type_required: return if isinstance(c.value, list): q = web.sqlors('thing.%s %s ' % (c.key, op), c.value) else: q = web.reparam('thing.%s %s $c.value' % (c.key, op), locals()) xwheres = [q] # Add thing table explicitly because get_table is not called tables['_thing'] = DBTable("thing") else: table = get_table(c.datatype, c.key) key_id = self.get_property_id(type, c.key) if not key_id: raise StopIteration q1 = web.reparam('%(table)s.key_id=$key_id' % {'table': table}, locals()) if isinstance(c.value, list): q2 = web.sqlors('%s.value %s ' % (table, op), c.value) else: q2 = web.reparam('%s.value %s $c.value' % (table, op), locals()) xwheres = [q1, q2] if ordering_func: xwheres.append(ordering_func(table)) wheres.extend(xwheres) def make_ordering_func(): d = web.storage(table=None) def f(table): d.table = d.table or table if d.table == table: # avoid a comparison when both tables are same. it fails when ordering is None return "1 = 1" else: return '%s.ordering = %s.ordering' % (table, d.table) return f import readquery def process_query(q, ordering_func=None): for c in q.conditions: if isinstance(c, readquery.Query): process_query(c, ordering_func or make_ordering_func()) else: process(c, ordering_func) def process_sort(query): """Process sort field in the query and returns the db column to order by.""" if query.sort: sort_key = query.sort.key if sort_key.startswith('-'): ascending = " desc" sort_key = sort_key[1:] else: ascending = "" if sort_key in ['key', 'type', 'created', 'last_modified']: order = 'thing.' + sort_key # make sure c.key is valid # Add thing table explicitly because get_table is not called tables['_thing'] = DBTable("thing") else: table = get_table(query.sort.datatype, sort_key) key_id = self.get_property_id(type, sort_key) if key_id is None: raise StopIteration q = '%(table)s.key_id=$key_id' % {'table': table} wheres.append(web.reparam(q, locals())) order = table.label + '.value' return order + ascending else: return None try: process_query(query) # special care for case where query {}. if not tables: tables['_thing'] = DBTable('thing') order = process_sort(query) except StopIteration: return [] def add_joins(): labels = [t.label for t in tables.values()] def get_column(table): if table == 'thing': return 'thing.id' else: return table + '.thing_id' if len(labels) > 1: x = labels[0] xwheres = [ get_column(x) + ' = ' + get_column(y) for y in labels[1:] ] wheres.extend(xwheres) add_joins() wheres = wheres or ['1 = 1'] table_names = [t.sql() for t in tables.values()] t = self.db.transaction() if config.query_timeout: self.db.query( "SELECT set_config('statement_timeout', $query_timeout, false)", dict(query_timeout=config.query_timeout)) if 'thing' in table_names: result = self.db.select( what='thing.key', tables=table_names, where=self.sqljoin(wheres, ' AND '), order=order, limit=query.limit, offset=query.offset, ) keys = [r.key for r in result] else: result = self.db.select( what='d0.thing_id', tables=table_names, where=self.sqljoin(wheres, ' AND '), order=order, limit=query.limit, offset=query.offset, ) ids = [r.thing_id for r in result] rows = ids and self.db.query( 'SELECT id, key FROM thing where id in $ids', vars={"ids": ids}) d = dict((r.id, r.key) for r in rows) keys = [d[id] for id in ids] t.commit() return keys
def register1(self, username, email, enc_password, data, ip=None, timestamp=None): ip = ip or web.ctx.ip key = get_user_root() + username if self.site.get(key): raise common.BadData(message="User already exists: " + username) if self.site.store.find_user(email): raise common.BadData(message='Email is already used: ' + email) def f(): web.ctx.disable_permission_check = True d = web.storage({"key": key, "type": {"key": "/type/user"}}) d.update(data) self.site.save(key, d, timestamp=timestamp, author=d, comment="Created new account") q = make_query(d) account_bot = config.get('account_bot') account_bot = account_bot and web.storage({ "key": account_bot, "type": { "key": "/type/user" } }) self.site.save_many( q, ip=ip, timestamp=timestamp, author=account_bot, action='register', comment="Setup new account", ) self.site.store.register(key, email, enc_password) self.update_user_details(username, verified=True, active=True) # Add account doc to store olddoc = self.site.store.store.get("account/" + username) or {} doc = { "_key": "account/" + username, "_rev": olddoc.get("_rev"), "type": "account", "registered_on": olddoc['registered_on'], "activated_on": timestamp.isoformat(), "last_login": timestamp.isoformat(), } self.site.store.store.put("account/" + username, doc) timestamp = timestamp or datetime.utcnow() self.site.store.transact(f) event_data = dict(data, username=username, email=email, password=enc_password) self.site._fire_event( "register", timestamp=timestamp, ip=ip or web.ctx.ip, username=None, data=event_data, ) self.set_auth_token(key) return username