Ejemplo n.º 1
0
def get_user(admin, user_id):
    """
    Returns the user with the requested id.

    :param admin: Is the administrator user, determined by @adminOptional.
    :param user_id:            Is the user id.

    :return:                   The requested user as JSON object.
    """
    # Query user
    user = User.query.filter(User.id == user_id).first()
    if not user:
        raise exc.EntryNotFound()

    if admin is None:
        # Check if the user has been verified.
        if not user.is_verified:
            raise exc.UserIsNotVerified()
        # Check if the user is inactive
        if not user.active:
            raise exc.UserIsInactive()

    fields = ['id', 'firstname', 'lastname', 'fullname', 'credit', 'rank_id', 'imagename', 'active',
              'is_admin', 'creation_date', 'verification_date', 'is_verified']
    user = convert_minimal(user, fields)[0]
    return jsonify(user), 200
Ejemplo n.º 2
0
def login():
    """
    Registered users can log in on this route.

    :return:                    A temporary valid token, which users can use
                                to identify themselves when making requests to
                                the API.

    :raises DataIsMissing:      If the id or password (or both) is not included
                                in the request.
    :raises UnknownField:       If an unknown parameter exists in the request
                                data.
    :raises InvalidType:        If one or more parameters have an invalid type.
    :raises InvalidCredentials: If no user can be found with the given data.
    :raises UserIsNotVerified:  If the user has not yet been verified.
    """
    data = json_body()
    # Check all items in the json body.
    required = {'id': int, 'password': str}
    check_fields_and_types(data, required)

    # Try to get the user with the id
    user = User.query.filter_by(id=data['id']).first()

    # If no user with this data exists cancel the authentication.
    if not user:
        raise exc.InvalidCredentials()

    # Check if the user has already been verified.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    # Check if the user is inactive
    if not user.active:
        raise exc.UserIsInactive()

    # Check if the user has set a password.
    if not user.password:
        raise exc.InvalidCredentials()

    # Check if the password matches the user's password.
    if not bcrypt.check_password_hash(user.password, str(data['password'])):
        raise exc.InvalidCredentials()

    # Create a dictionary object of the user.
    fields = ['id', 'firstname', 'lastname', 'credit', 'is_admin']
    d_user = convert_minimal(user, fields)[0]

    # Create a token.
    exp = datetime.datetime.utcnow() + datetime.timedelta(minutes=60)
    token = jwt.encode({'user': d_user, 'exp': exp}, app.config['SECRET_KEY'])

    # Return the result.
    return jsonify({'result': True, 'token': token.decode('UTF-8')}), 200
Ejemplo n.º 3
0
    def decorator(*args, **kwargs):
        user = User.query.filter_by(id=kwargs['user_id']).first()
        if not user:
            raise exc.EntryNotFound()

        # Check if the user has been verified.
        if not user.is_verified:
            raise exc.UserIsNotVerified()

        # Check if the user is inactive
        if not user.active:
            raise exc.UserIsInactive()

        # If all criteria are met, the requested function can be executed.
        return f(user, *args, **kwargs)
Ejemplo n.º 4
0
def update_user(admin, user_id):
    """
    Update the user with the given id.

    :param admin:   Is the administrator user, determined by @adminRequired.
    :param user_id: Is the user id.

    :return:        A message that the update was successful and a list of all updated fields.
    """
    # Get the update data
    data = json_body()

    # Query the user. If he/she is not verified yet, there *must* be a
    # rank_id given in the update data.
    user = User.query.filter(User.id == user_id).first()
    if not user:
        raise exc.EntryNotFound()

    if not user.is_verified and 'rank_id' not in data:
        raise exc.UserIsNotVerified()

    # The password pre-check must be done here...
    if 'password' in data:
        # The repeat password must be there, too!
        if 'password_repeat' not in data:
            raise exc.DataIsMissing()

        # Both must be strings
        if not all([isinstance(x, str) for x in [data['password'], data['password_repeat']]]):
            raise exc.WrongType()

        # Passwords must match
        if data['password'] != data['password_repeat']:
            raise exc.PasswordsDoNotMatch()

        # Minimum password length
        if len(data['password']) < app.config['MINIMUM_PASSWORD_LENGTH']:
            raise exc.PasswordTooShort()

        # Convert the password into a salted hash
        # DONT YOU DARE TO REMOVE THIS LINE
        data['password'] = bcrypt.generate_password_hash(data['password'])
        # DONT YOU DARE TO REMOVE THIS LINE

        # All fine, delete repeat_password from the dict and do the rest of the update
        del data['password_repeat']

    return generic_update(User, user_id, data, admin)
Ejemplo n.º 5
0
def create_refund(admin):
    """
    Insert a new refund.

    :param admin:                Is the administrator user, determined by @adminRequired.

    :return:                     A message that the creation was successful.

    :raises DataIsMissing:       If not all required data is available.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the user with this ID does not exist.
    :raises UserIsNotVerified:   If the user has not yet been verified.
    :raises InvalidAmount:       If amount is equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {'user_id': int, 'total_price': int, 'comment': str}
    check_fields_and_types(data, required)

    user = User.query.filter_by(id=data['user_id']).first()
    if not user:
        raise exc.EntryNotFound()

    # Check if the user has been verified.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    # Check if the user is inactive
    if not user.active:
        raise exc.UserIsInactive()

    # Check amount
    if data['total_price'] <= 0:
        raise exc.InvalidAmount()

    # Create and insert refund
    try:
        refund = Refund(**data)
        refund.admin_id = admin.id
        db.session.add(refund)
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Created refund.'}), 200
Ejemplo n.º 6
0
def insert_deposit(data, admin):
    """
    This help function creates a new deposit with the given data.

    :raises DataIsMissing:       If not all required data is available.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the user with this ID does not exist.
    :raises UserIsNotVerified:   If the user has not yet been verified.
    :raises InvalidAmount:       If amount is equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """

    required = {'user_id': int, 'amount': int, 'comment': str}
    check_fields_and_types(data, required)

    # Check user
    user = User.query.filter_by(id=data['user_id']).first()
    if not user:
        raise exc.EntryNotFound()

    # Check if the user has been verified.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    # Check if the user is inactive
    if not user.active:
        raise exc.UserIsInactive()

    # Check amount
    if data['amount'] == 0:
        raise exc.InvalidAmount()

    # Create and insert deposit
    try:
        deposit = Deposit(**data)
        deposit.admin_id = admin.id
        db.session.add(deposit)
    except IntegrityError:
        raise exc.CouldNotCreateEntry()
Ejemplo n.º 7
0
def create_replenishmentcollection(admin):
    """
    Insert a new replenishmentcollection.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.

    :return:                     A message that the creation was successful.

    :raises DataIsMissing:       If not all required data is available.
    :raises ForbiddenField :     If a forbidden field is in the data.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the product with with the id of any
                                 replenishment does not exist.
    :raises InvalidAmount:       If amount of any replenishment is less than
                                 or equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {
        'replenishments': list,
        'comment': str,
        'timestamp': str,
        'seller_id': int
    }
    required_repl = {'product_id': int, 'amount': int, 'total_price': int}

    # Check all required fields
    check_fields_and_types(data, required)

    # Check seller
    seller = User.query.filter_by(id=data['seller_id']).first()
    if not seller:
        raise exc.EntryNotFound()

    # Check if the seller has been verified.
    if not seller.is_verified:
        raise exc.UserIsNotVerified()

    # Parse timestamp
    data = parse_timestamp(data, required=True)

    replenishments = data['replenishments']
    # Check for the replenishments in the collection
    if not replenishments:
        raise exc.DataIsMissing()

    for repl in replenishments:
        # Check all required fields
        check_fields_and_types(repl, required_repl)

        product_id = repl.get('product_id')
        amount = repl.get('amount')

        # Check amount
        if amount <= 0:
            raise exc.InvalidAmount()
        # Check product
        product = Product.query.filter_by(id=product_id).first()
        if not product:
            raise exc.EntryNotFound()

        # If the product has been marked as inactive, it will now be marked as
        # active again.
        if not product.active:
            product.active = True

    # Create and insert replenishmentcollection
    try:
        collection = ReplenishmentCollection(admin_id=admin.id,
                                             seller_id=seller.id,
                                             timestamp=data['timestamp'],
                                             comment=data['comment'],
                                             revoked=False)
        db.session.add(collection)
        db.session.flush()

        for repl in replenishments:
            rep = Replenishment(replcoll_id=collection.id, **repl)
            db.session.add(rep)
        db.session.commit()

    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Created replenishmentcollection.'}), 201
Ejemplo n.º 8
0
def update_user(admin, id):
    """
    Update the user with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the user id.
    :return:                     A message that the update was
                                 successful and a list of all updated fields.

    :raises EntryNotFound:        If the user with this ID does not exist.
    :raises UserIsNotVerified:   If the user has not yet been verified.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid type.
    :raises PasswordsDoNotMatch: If the password and its repetition do not
                                 match.
    :raises DataIsMissing:       If the password is to be updated but no
                                 repetition of the password exists in the
                                 request.
    """
    data = json_body()

    # Query user
    user = User.query.filter(User.id == id).first()
    if not user:
        raise exc.EntryNotFound()

    # Raise an exception if the user has not been verified yet.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    allowed = {
        'firstname': str,
        'lastname': str,
        'password': str,
        'password_repeat': str,
        'is_admin': bool,
        'rank_id': int
    }

    # Check the data for forbidden fields.
    check_forbidden(data, allowed, user)
    # Check all allowed fields and for their types.
    check_fields_and_types(data, None, allowed)

    updated_fields = []

    # Update admin role
    if 'is_admin' in data:
        user.set_admin(is_admin=data['is_admin'], admin_id=admin.id)
        if not user.is_admin:
            users = User.query.all()
            admins = list(filter(lambda x: x.is_admin, users))
            if not admins:
                raise exc.NoRemainingAdmin()

        updated_fields.append('is_admin')
        del data['is_admin']

    # Update rank
    if 'rank_id' in data:
        user.set_rank_id(rank_id=data['rank_id'], admin_id=admin.id)
        updated_fields.append('rank_id')
        del data['rank_id']

    # Check password
    if 'password' in data:
        if 'password_repeat' in data:
            password = data['password'].strip()
            password_repeat = data['password_repeat'].strip()

            if password != password_repeat:
                raise exc.PasswordsDoNotMatch()

            if len(password) < app.config['MINIMUM_PASSWORD_LENGTH']:
                raise exc.PasswordTooShort()
            user.password = bcrypt.generate_password_hash(password)
            updated_fields.append('password')
            del data['password_repeat']
        else:
            raise exc.DataIsMissing()

        del data['password']

    # All other fields
    updateable = ['firstname', 'lastname']
    check_forbidden(data, updateable, user)
    updated_fields = update_fields(data, user, updated=updated_fields)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated user.',
        'updated_fields': updated_fields
    }), 201
Ejemplo n.º 9
0
def create_purchase(admin):
    """
    Insert a new purchase.

    :param admin:                Is the administrator user, determined by @adminOptional.

    :return:                     A message that the creation was successful.

    :raises DataIsMissing:       If not all required data is available.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the user with this ID does not exist.
    :raises UserIsNotVerified:   If the user has not yet been verified.
    :raises EntryNotFound:       If the product with this ID does not exist.
    :raises EntryIsInactive:     If the product is inactive.
    :raises InvalidAmount:       If amount is less than or equal to zero.
    :raises InsufficientCredit:  If the credit balance of the user is not
                                 sufficient.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {'user_id': int, 'product_id': int, 'amount': int}

    check_fields_and_types(data, required)

    # Check user
    user = User.query.filter_by(id=data['user_id']).first()
    if not user:
        raise exc.EntryNotFound()

    # Check if the user has been verified.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    # Check if the user is inactive
    if not user.active:
        raise exc.UserIsInactive()

    # Check product
    product = Product.query.filter_by(id=data['product_id']).first()
    if not product:
        raise exc.EntryNotFound()
    if not product.active:
        raise exc.EntryIsInactive()

    # Check amount
    if data['amount'] <= 0:
        raise exc.InvalidAmount()

    # If the purchase is made by an administrator, the credit limit
    # may be exceeded.
    if not admin:
        limit = Rank.query.filter_by(id=user.rank_id).first().debt_limit
        current_credit = user.credit
        future_credit = current_credit - (product.price * data['amount'])
        if future_credit < limit:
            raise exc.InsufficientCredit()

    try:
        purchase = Purchase(**data)
        db.session.add(purchase)
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Purchase created.'}), 200
Ejemplo n.º 10
0
def insert_purchase(admin: Optional[User], data: dict) -> None:
    """
    This helper function creates a single purchase without doing a commit.

    :param admin:                Is the administrator user, determined by @adminOptional.
    :param data:                 Is the purchase data.

    :return:                     None
    """
    required = {'user_id': int, 'product_id': int, 'amount': int}
    optional = {'timestamp': str}

    # If the request is not made by an administrator, the timestamp can't be set
    if admin is None and 'timestamp' in data:
        raise exc.ForbiddenField()

    check_fields_and_types(data, required, optional)

    # Check user
    user = User.query.filter_by(id=data['user_id']).first()
    if not user:
        raise exc.EntryNotFound()

    # Check if the user has been verified.
    if not user.is_verified:
        raise exc.UserIsNotVerified()

    # Check if the user is inactive
    if not user.active:
        raise exc.UserIsInactive()

    # Check the user rank. If it is a system user, only administrators are allowed to insert purchases
    if user.rank.is_system_user and admin is None:
        raise exc.UnauthorizedAccess()

    # Parse the timestamp
    data = parse_timestamp(data, required=False)

    # Check product
    product = Product.query.filter_by(id=data['product_id']).first()
    if not product:
        raise exc.EntryNotFound()
    if not admin and not product.active:
        raise exc.EntryIsInactive()

    # Check weather the product is for sale if the request is not made by an administrator
    if not admin and any(map(lambda tag: not tag.is_for_sale, product.tags)):
        raise exc.EntryIsNotForSale()

    # Check amount
    if data['amount'] <= 0:
        raise exc.InvalidAmount()

    # If the purchase is made by an administrator, the credit limit
    # may be exceeded.
    if not admin:
        limit = Rank.query.filter_by(id=user.rank_id).first().debt_limit
        current_credit = user.credit
        future_credit = current_credit - (product.price * data['amount'])
        if future_credit < limit:
            raise exc.InsufficientCredit()

    try:
        if admin:
            purchase = Purchase(admin_id=admin.id, **data)
        else:
            purchase = Purchase(**data)
        db.session.add(purchase)
    except IntegrityError:
        raise exc.CouldNotCreateEntry()