1
\$\begingroup\$

I am designing a website on which users can configure (CRUD) various objects which are mutually dependent upon other types. I am preventing a cascade delete, so let's say a Team is created in the database and some Players are created associated with that team (by ForeignKey) then the team can't be deleted.

For a user experience my plan is the following:

  1. Have a validator on the javascript client side as pre screening so the user doesnt expect to be able to delete objects that they shouldnt be allowed to.

  2. Have code on the server side that returns a result even if JS fails and database integrity is compromised.

The Javascript pre-screening looks like this, consider a delete button that is only visible and actionable if certain conditions are met:

<button class="btn" v-on:click="delete_()"
 v-bind:class="{'btn-inactive': selected_team.id == '' || selected_team.members != 0}"
 >Delete</button>

// in script..
delete_: function() {
  var cb = function(data) {
    this.$emit('refresh','');
  };
  this.ajax_($API_SCRIPT_ROOT + '/team', 'delete', this.selected_team, cb);
  this.display = 'main';
},

AJAX will refresh the data on success, or on fail update an error message returned by the API.

The server side looks like this:

def delete_user_resource(kind, user_id, resource_id):
    """
    Deletes an existing resource

    Args:
        kind (str): the class of resource to be created, must be permissible for the specific ``User`` subclass.
            (internal)
        user_id (int): the primary key of the ``User`` creating the resource. (internal)
        resource_id (int): the primary key of the resource class. (internal)
        **kwargs: all required additional args for creation are specified in the docstring for underlying resource
            class. (external)

    Returns:
        tuple: success boolean, string representation of state.
    """
    resource = read_user_resource(kind, user_id, resource_id)
    db.session.delete(resource)
    try:
        db.session.commit()
        return True, resource.__repr__()
    except IntegrityError as e:
        db.session.rollback()
        return False, e.statement

@api_0.route('/team', methods=['DELETE'])
@login_required
def team_delete():
    user_args = {
        "id": fields.Int(required=True),
    }
    kwargs = flaskparser.parser.parse(user_args, request)

    result = delete_user_resource(kind='team', user_id=current_user.id, resource_id=kwargs['id'])
    if result[0]:
        return jsonify({'result': 'SUCCESS', 'description': result[1]}), 201
    else:
        return jsonify({'result': 'FAILED', 'description': result[1]}), 400

Here is the function that performs a security check for read access to a resource:

def read_user_resource(kind, user_id, resource_id):
    """
    Read a resource specific to the given ``User``.

    Args:
        kind (str): the class of resource to be created, must be permissible for the specific ``User`` subclass.
            (internal)
        user_id (int): the primary key of the ``User`` creating the resource. (internal)
        resource_id (int): the primary key of the resource class specifed by kind. (internal)

    Returns:
        object: specific resource identified by unique id.
    """
    user = User.query.filter(User.id == user_id).one()

    Resource = RESOURCES2[user.type][kind]
    resource = Resource.query.filter(Resource.id == resource_id).one_or_none()
    if resource.organiser_team_id != user.organiser_team_id:
        # security error since resource does not belong to authorising user
        return None

    return resource

I think this seems a fairly reasonably and decoupled (DRY) way of coding this that is potentially testable and maintainable. Do you agree or are there gaping holes which will cause problems later?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.