Question

I want to implement a "more complex" filter based on the access rights of the user in Python eve restframework.

Problem

We have token authentification, the user account is fetched in the TokenAuth Class. The user has some contracts, each contract has bills. I want to implement an endpoint /bills which shows bills of his contract. We use mongodb.

For better understanding, someting like the SQL Statement "SELECT * FROM bills WHERE bills.contract IN user.contracts"

user { contracts : ["a","b","c"] }
bills { contract: "a" }

Background information

class TokenAuth(TokenAuthBase):
    def check_auth(self, token, allowed_roles, resource, method):
       users = app.data.driver.db['users']
       TokenAuth.account = users.find_one(lookup)
       ...

(update)

User-Restricted Resource Access¶ (URRA)

For the situation of user 1 : n bill relation, URRA will do the job. See URRA in python eve docs.

In a more complicated case it is necessary to write a custom filter query. I need this option :).

Update

I found a solution, see updated answer.

Was it helpful?

Solution 2

Thanks to Nicola Iaorcci I found a solution.

First, I use a custom authentication class for every endpoint.

class MyCreditNoteAuth(TokenAuthBase):
   def check_auth(self, token, allowed_roles, resource, method):
        account = app.data.driver.db['users'].find_one({'api_access_token': token})

This method fetches the user's account from mongodb and now I have access to his contract ids.

Second, still in the upper method I update the filter of the datasource on each request:

mynotes['datasource']['filter']['contract'] = { '$in': account['contracts'] }

Now the customer sees only his own "notes" on the given endpoint.

OTHER TIPS

What about Predefined Database Filters then?

bills = {
    'datasource': {
        'source': 'bills',
        'filter': {'field1': 'value1', 'field2': 'value2'}
    }
}

source is a mongodb collection; filter is a mongodb query. It will be transparently applied to any request hitting the endpoint (before client queries).

I think it might be better to do this on a per request basis. Modifying the 'DOMAIN' might lead to cases where multiple people are logged in and sending requests at the same time and the domain changes in between requests depending on how you implement that sort of thing. Mayby something like this would work:

from eve import Eve
from eve.auth import BasicAuth

def pre_get_api_stuff(resource, request, lookup):
    username = request.authorization['username']
    accounts = app.data.driver.db['accounts']
    account = accounts.find_one({'username': username})
    if resource == 'notes':
        lookup.update({'username': username})

app = Eve(auth=BasicAuth)
app.on_pre_GET += pre_get_api_stuff

Have you looked in to User-Restricted Resource Access?

At quick glance I'd say that by storing the user token (or id, or whatever identifies the user in your setup) with your bill documents and then enabling URRA at the billsendpoint, would do the trick.

A more complete example is as follows, suppose we have the following in settings.py

note_schema = {
    'user_id': {
        'type': 'objectid',
        'required': True,
        'readonly': True
    }
}
user_schema = {
    'username': {
        'type': 'string',
        'required': True,
        'unique': True,
    },
    'password': {
        'type': 'string',
        'required': True,
    },
    'token': {
        'type': 'string',
        'required': True,
    }
}

DOMAIN = {
    'user': {
        'schema': user_schema,
    },
    'note': {
        'schema': note_schema,
    },
}

And the auth comes with adding a filter based on the username, the logic of filter is simple here, but you could add anything you want to the filter

class MyAuth(BasicAuth):
    def check_auth(self, token, allowed_roles, resource, method):
        username = get_username_from_token(token)
        if resource == 'note':
            note = app.config['DOMAIN']['note']
            user = app.data.driver.db['user'].find_one({'username': username})
            note['datasource']['filter'] = {'user_id': user['_id']}

        return True
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top