def set_collection(self, user_id, collection_name, **values): """Creates a collection""" # XXX values is not used for now because there are no values besides # the name if self.collection_exists(user_id, collection_name): return values['userid'] = user_id values['name'] = collection_name if self.standard_collections: ids = _STANDARD_COLLECTIONS.keys() min_id = max(ids) + 1 else: min_id = 0 # getting the max collection_id # XXX why don't we have an autoinc here ? # see https://bugzilla.mozilla.org/show_bug.cgi?id=579096 next_id = -1 while next_id < min_id: query = self._get_query('COLLECTION_NEXTID', user_id) max_ = safe_execute(self._engine, query, user_id=user_id).first() if max_[0] is None: next_id = min_id else: next_id = max_[0] + 1 # insertion values['collectionid'] = next_id query = insert(collections).values(**values) safe_execute(self._engine, query, **values) return next_id
def delete_storage(self, user_id): """Removes all user data""" for query in ('DELETE_USER_COLLECTIONS', 'DELETE_USER_WBOS'): query = self._get_query(query, user_id) safe_execute(self._engine, query, user_id=user_id) # XXX see if we want to check the rowcount return True
def set_user(self, user_id, **values): """set information for a user. values contains the fields to set. If the user doesn't exists, it will be created. """ values['id'] = user_id if not self.user_exists(user_id): query = insert(users).values(**values) else: query = update(users).where(users.c.id == user_id) query = query.values(**values) safe_execute(self._engine, query)
def get_user_info(self, user, attrs): """Returns user info Args: user: the user object attrs: the pieces of data requested Returns: user object populated with attrs """ user_id = self.get_user_id(user) if user_id is None: return user attrs = [attr for attr in attrs if not user.get(attr)] if attrs == []: return user fields = [] for attr in attrs: fields.append(getattr(users.c, attr)) _USER_INFO = select(fields, users.c.userid == bindparam('user_id')) res = safe_execute(self._engine, _USER_INFO, user_id=user_id).fetchone() if res is None: return user for attr in attrs: try: user[attr] = getattr(res, attr) except AttributeError: user[attr] = None return user
def clear_reset_code(self, user_id): if self._engine is None: raise NotImplementedError() query = delete(reset_codes).where(reset_codes.c.username == user_id) res = safe_execute(self._engine, query) return res.rowcount > 0
def update_password(self, user_id, password, old_password=None, key=None): """Change the user password Args: user_id: user id password: new password Returns: True if the change was successful, False otherwise """ if old_password is None: if key: #using a key, therefore we should check it if self._get_reset_code(user_id) == key: self.clear_reset_code(user_id) else: logger.error("bad key used for update password") return False else: return False password_hash = ssha256(password) query = update(users).where(users.c.id == user_id) res = safe_execute(self._engine, query.values(password_hash=password_hash)) return res.rowcount == 1
def get_collection(self, user_id, collection_name, fields=None, create=True): """Return information about a collection.""" if fields is None: fields = [collections] field_names = collections.columns.keys() else: field_names = fields fields = [getattr(collections.c, field) for field in fields] query = select(fields, and_(collections.c.userid == user_id, collections.c.name == collection_name)) res = safe_execute(self._engine, query).first() # the collection is created if res is None and create: collid = self.set_collection(user_id, collection_name) res = {'userid': user_id, 'collectionid': collid, 'name': collection_name} if fields is not None: for key in res.keys(): if key not in field_names: del res[key] else: # make this a single step res = dict([(key, value) for key, value in res.items() if value is not None]) return res
def create_user(self, username, password, email, **extra_fields): """Creates a user. Returns True on success.""" if not self.allow_new_users: raise BackendError("Creation of new users is disabled") password_hash = sscrypt(password) values = { 'username': username, 'password': password_hash, 'mail': email, } for field in ('userid', 'accountStatus', 'mailVerified', 'syncNode'): if field in extra_fields: values[field] = extra_fields[field] query = insert(users).values(**values) try: res = safe_execute(self._engine, query) except IntegrityError: #Name already exists return False if res.rowcount != 1: return False #need a copy with some of the info for the return value userobj = User() userobj['username'] = username userobj['userid'] = res.lastrowid userobj['mail'] = email return userobj
def get_user_id(self, user_name): """Returns the id for a user name""" user = safe_execute(self._engine, _USER_ID, user_name=user_name).fetchone() if user is None: return None return user.id
def create_user(self, user_name, password, email): """Creates a user. Returns True on success.""" password_hash = ssha256(password) query = insert(users).values(username=user_name, email=email, password_hash=password_hash, status=1) res = safe_execute(self._engine, query) return res.rowcount == 1
def collection_exists(self, user_id, collection_name): """Returns True if the collection exists""" query = self._get_query('COLLECTION_EXISTS', user_id) res = safe_execute(self._engine, query, user_id=user_id, collection_name=collection_name) res = res.fetchone() return res is not None
def clear_reset_code(self, user_id): query = update(users).where(users.c.id == user_id) code = expiration = None res = safe_execute( self._engine, query.values(id=user_id, reset=code, reset_expiration=expiration)) return res.rowcount == 1
def _set_reset_code(self, user_id): code, expiration = generate_reset_code() query = update(users).values(reset=code, reset_expiration=expiration) res = safe_execute(self._engine, query.where(users.c.id == user_id)) if res.rowcount != 1: logger.debug('Unable to add a new reset code') return None # XXX see if appropriate return code
def delete_item(self, user_id, collection_name, item_id, storage_time=None): """Deletes an item""" collection_id = self._get_collection_id(user_id, collection_name) query = self._get_query('DELETE_SOME_USER_WBO', user_id) res = safe_execute(self._engine, query, user_id=user_id, collection_id=collection_id, item_id=item_id) return res.rowcount == 1
def get_collections(self, user_id, fields=None): """returns the collections information """ if fields is None: fields = [collections] else: fields = [getattr(collections.c, field) for field in fields] query = select(fields, collections.c.userid == user_id) return safe_execute(self._engine, query).fetchall()
def _set_reset_code(self, user_id): rc = ResetCode() code = rc._generate_reset_code() expiration = datetime.datetime.now() + datetime.timedelta(hours=6) query = update(users).values(reset=code, reset_expiration=expiration) res = safe_execute(self._engine, query.where(users.c.id == user_id)) if res.rowcount != 1: self.logger.debug('Unable to add a new reset code') return None # XXX see if appropriate return code
def get_collection_timestamps(self, user_id): """return the collection names for a given user""" query = 'COLLECTION_STAMPS' query = self._get_query(query, user_id) res = safe_execute(self._engine, query, user_id=user_id) try: return dict([(self._collid2name(user_id, coll_id), bigint2time(stamp)) for coll_id, stamp in res]) finally: self._purge_cache(user_id)
def get_collection_counts(self, user_id): """Return the collection counts for a given user""" query = self._get_query('COLLECTION_COUNTS', user_id) res = safe_execute(self._engine, query, user_id=user_id, ttl=_int_now()) try: return dict([(self._collid2name(user_id, collid), count) for collid, count in res]) finally: self._purge_cache(user_id)
def item_exists(self, user_id, collection_name, item_id): """Returns a timestamp if an item exists.""" collection_id = self._get_collection_id(user_id, collection_name) query = self._get_query('ITEM_EXISTS', user_id) res = safe_execute(self._engine, query, user_id=user_id, item_id=item_id, collection_id=collection_id, ttl=_int_now()) res = res.fetchone() if res is None: return None return bigint2time(res[0])
def get_collection_max_timestamp(self, user_id, collection_name): """Returns the max timestamp of a collection.""" query = self._get_query('COLLECTION_MAX_STAMPS', user_id) collection_id = self._get_collection_id(user_id, collection_name) res = safe_execute(self._engine, query, user_id=user_id, collection_id=collection_id) res = res.fetchone() stamp = res[0] if stamp is None: return None return bigint2time(stamp)
def delete_collection(self, user_id, collection_name): """deletes a collection""" if not self.collection_exists(user_id, collection_name): return # removing items first self.delete_items(user_id, collection_name) # then the collection query = self._get_query('DELETE_USER_COLLECTION', user_id) return safe_execute(self._engine, query, user_id=user_id, collection_name=collection_name)
def get_user(self, user_id, fields=None): """Returns user information. If fields is provided, it is a list of fields to return """ if fields is None: fields = [users] else: fields = [getattr(users.c, field) for field in fields] query = select(fields, users.c.id == user_id) return safe_execute(self._engine, query).first()
def get_total_size(self, user_id, recalculate=False): """Returns the total size in KB of a user storage. The size is the sum of stored payloads. """ query = self._get_query('USER_STORAGE_SIZE', user_id) res = safe_execute(self._engine, query, user_id=user_id, ttl=_int_now()) res = res.fetchone() if res is None or res[0] is None: return 0.0 return int(res[0]) / _KB
def delete_items(self, user_id, collection_name, item_ids=None, filters=None, limit=None, offset=None, sort=None, storage_time=None): """Deletes items. All items are removed unless item_ids is provided""" collection_id = self._get_collection_id(user_id, collection_name) wbo = self._get_wbo_table(user_id) query = _delete(wbo) where = [wbo.c.username == bindparam('user_id'), wbo.c.collection == bindparam('collection_id')] if item_ids is not None: where.append(wbo.c.id.in_(item_ids)) if filters is not None: for field, value in filters.items(): field = getattr(wbo.c, field) operator, value = value if field.name == 'modified': value = _roundedbigint(value) if isinstance(value, (list, tuple)): where.append(field.in_(value)) else: if operator == '=': where.append(field == value) elif operator == '<': where.append(field < value) elif operator == '>': where.append(field > value) where = and_(*where) query = query.where(where) if self.engine_name != 'sqlite': if sort is not None: if sort == 'oldest': query = query.order_by(wbo.c.modified.asc()) elif sort == 'newest': query = query.order_by(wbo.c.modified.desc()) else: query = query.order_by(wbo.c.sortindex.desc()) if limit is not None and int(limit) > 0: query = query.limit(int(limit)) if offset is not None and int(offset) > 0: query = query.offset(int(offset)) # XXX see if we want to send back more details # e.g. by checking the rowcount res = safe_execute(self._engine, query, user_id=user_id, collection_id=collection_id) return res.rowcount > 0
def _set_item(self, user_id, collection_name, item_id, **values): """Adds or update an item""" wbo = self._get_wbo_table(user_id) if 'modified' in values: values['modified'] = _roundedbigint(values['modified']) if 'ttl' not in values: values['ttl'] = MAX_TTL else: # ttl is provided in seconds, so we add it # to the current timestamp values['ttl'] += _int_now() modified = self.item_exists(user_id, collection_name, item_id) if 'payload' in values: values['payload_size'] = len(values['payload']) collection_id = self._get_collection_id(user_id, collection_name) if modified is None: # does not exists values['collection'] = collection_id values['id'] = item_id values['username'] = user_id query = insert(wbo).values(**values) else: if 'id' in values: del values['id'] key = and_(wbo.c.id == item_id, wbo.c.username == user_id, wbo.c.collection == collection_id) query = update(wbo).where(key).values(**values) safe_execute(self._engine, query) if 'modified' in values: return bigint2time(values['modified']) return modified
def authenticate_user(self, user_name, password): """Authenticates a user given a user_name and password. Returns the user id in case of success. Returns None otherwise.""" user = safe_execute(self._engine, _USER_AUTH, user_name=user_name).fetchone() if user is None: return None if user.status != 1: # user is disabled return None if validate_password(password, user.password_hash): return user.id
def get_collection_sizes(self, user_id): """Returns the total size in KB for each collection of a user storage. The size is the sum of stored payloads. """ query = self._get_query('COLLECTIONS_STORAGE_SIZE', user_id) res = safe_execute(self._engine, query, user_id=user_id, ttl=_int_now()) try: return dict([(self._collid2name(user_id, col[0]), int(col[1]) / _KB) for col in res]) finally: self._purge_cache(user_id)
def update_email(self, user_id, email, password=None): """Change the user e-mail Args: user_id: user id email: new email Returns: True if the change was successful, False otherwise """ query = update(users).where(users.c.id == user_id) res = safe_execute(self._engine, query.values(email=email)) return res.rowcount == 1
def delete_user(self, user_id, password=None): """Deletes a user Args: user_id: user id password: user password, if needed Returns: True if the deletion was successful, False otherwise """ if password is not None: # we want to control if the password is good user = safe_execute(self._engine, _USER_PASSWORD, user_id=user_id).fetchone() if user is None: return False if not validate_password(password, user.password_hash): return False query = delete(users).where(users.c.id == user_id) res = safe_execute(self._engine, query) return res.rowcount == 1
def admin_update_field(self, user, key, value): """Change the value of a user's field using an admin bind True if the change was successful, False otherwise The sql model doesn't have the concept of different permissions, """ user_id = self.get_user_id(user) if user_id is None: return False query = update(users, users.c.userid == user_id, {key: value}) res = safe_execute(self._engine, query) user[key] = value return res.rowcount == 1
def get_user_info(self, user_id): """Returns user info Args: user_id: user id Returns: tuple: username, email """ res = safe_execute(self._engine, _USER_INFO, user_id=user_id).fetchone() if res is None: return None, None return res.username, res.email
def get_user_id(self, user): """Returns the id for a user name""" user_id = user.get('userid') if user_id is not None: return user_id username = user['username'] if username is None: return None res = safe_execute(self._engine, _USER_ID, username=username).fetchone() if res is None: return None user['userid'] = res.userid return res.userid
def _set_reset_code(self, user_id): code, expiration = generate_reset_code() query = delete(reset_codes).where(reset_codes.c.username == user_id) self._engine.execute(query) query = insert(reset_codes).values(reset=code, expiration=expiration, username=user_id) res = safe_execute(self._engine, query) if res.rowcount != 1: logger.debug('Unable to add a new reset code in the' ' reset_code table') return None # XXX see if appropriate return code
def update_password(self, user_id, password, old_password): """Change the user password Args: user_id: user id password: new password old_password: the old password Returns: True if the change was successful, False otherwise """ # XXX check the old password # password_hash = ssha256(password) query = update(users).where(users.c.id == user_id) res = safe_execute(self._engine, query.values(password_hash=password_hash)) return res.rowcount == 1
def _set_reset_code(self, user_id): code = self._generate_reset_code() query = delete(reset_codes).where( and_(reset_codes.c.username == user_id, reset_codes.c.product == self.product)) self._engine.execute(query) expiration_time = datetime.datetime.now() + \ datetime.timedelta(seconds=self.expiration) query = insert(reset_codes).values(reset=code, expiration=expiration_time, product=self.product, username=user_id) res = safe_execute(self._engine, query) if res.rowcount != 1: raise BackendError('adding a reset code to the reset table failed') return code
def authenticate_user(self, user, credentials, attrs=None): """Authenticates a user given a user object and credentials. Returns the user id in case of success. Returns None otherwise. """ username = credentials.get("username") if username is None: return None if user.get("username") not in (None, username): return None password = credentials.get("password") if password is None: return None fields = [users.c.userid, users.c.password, users.c.accountStatus] if attrs is not None: for attr in attrs: fields.append(getattr(users.c, attr)) else: attrs = [] _USER_AUTH = select(fields, users.c.username == bindparam('username')) res = safe_execute(self._engine, _USER_AUTH, username=username).fetchone() if res is None: return None if self.check_account_state and res.accountStatus != 1: return None if not validate_password(password, res.password): return None user['username'] = username user['userid'] = res.userid for attr in attrs: user[attr] = getattr(res, attr) return res.userid
def delete_user(self, user, credentials=None): """ Deletes a user. Needs to be done with admin privileges, since users don't have permission to do it themselves. Args: user: the user object Returns: True if the deletion was successful, False otherwise """ if credentials is not None: if not self.authenticate_user(user, credentials): return False user_id = self.get_user_id(user) if user_id is None: return False query = delete(users).where(users.c.userid == user_id) res = safe_execute(self._engine, query) return res.rowcount == 1
def _safe_execute(self, *args, **kwds): return safe_execute(self.auth._engine, *args, **kwds)
def _get_username(self, uid): """Returns the id for a user name""" user = safe_execute(self._engine, _USER_NAME, uid=uid).fetchone() if user is None: return None return user.username
def _delete_reset_code(self, user_id): query = delete(reset_codes).where(reset_codes.c.username == user_id) res = safe_execute(self._engine, query) return res.rowcount > 0