custom authentication decorator - python

I'm trying to implement a decorator that authenticates the user's token before granting access to a function. My current implementation is kind of wonky in that I need to do two queries since I can't get locals in the decorator. Is there a better way to do this?
def require_auth(func):
print 'require_auth'
#wraps(func)
def inner():
if 'token' in request.json:
token = request.json['token']
session = Session()
for instance in session.query(SQLTypes.User).filter(SQLTypes.User.token==token):
auth_user = instance.username
try:
auth_user
print 'authenticated!'
except NameError:
abort(401)
else:
abort(401)
return func()
return inner
#app.route('/st/projects', methods=['POST'])
#require_auth
def post_project():
session = Session()
for instance in session.query(SQLTypes.User).filter(SQLTypes.User.token==request.json['token']):
auth_user = instance.username
# do something with auth_user
session.close()

You can store your authenticated user in flask.g:
from flask import g
# ...
def require_auth(func):
# ...
for instance in session.query(SQLTypes.User).filter(SQLTypes.User.token==token):
g.auth_user = instance.username
try:
g.auth_user
print 'authenticated!'
except AttributeError:
abort(401)
# ...
Then in your view function you can access the user as g.auth_user.

Related

How to mock an mutation argument in Django?

I have a mutation that has a decorator that checks the permissions (scopes) from the user token before s/he goes inside it. The decorator gets the input parameter from the mutate method and extracts the token and verify if the user has one of the allowed scopes.
The unit tests don't succeed if I don't remove or mock the requires_scope part from the code. The problem is that I don't know exactly what I have to mock to succeed in the unit tests. Should I mock the input itself? The token? The return of the requires_scopes decorator?
mutations.py
class MyMutation(graphene.Mutation):
success = graphene.Boolean()
class Arguments:
input = graphene.Argument(IdInput, required=True)
#classmethod
#requires_scopes(['app:first_test_permission', 'app:second_test_permission'])
def mutate(cls, root, info, input):
pass
decorators.py
def get_access_token_from_header(request):
"""
Obtains the Access Token from the Authorization Header
"""
header = request.context.META.get('HTTP_AUTHORIZATION', None)
header = header.split()
token = header[1]
return token
def requires_scopes(scopes: list):
"""
Determines if the required scope is present in the Access Token
Args:
scopes (list): The scopes required to access the resource
"""
def require_scopes(f):
#wraps(f)
def decorated(*args, **kwargs):
token = get_access_token_from_header(args[2])
decoded = jwt.decode(token, verify=False)
if decoded.get("scope"):
token_scopes = set(decoded["scope"].split())
required_scopes = set(scopes)
if required_scopes.intersection(token_scopes):
return f(*args, **kwargs)
raise Exception({'message': 'You don\'t have access to this resource'})
return decorated
return require_scopes
I mocked the get_access_token_from_header function:
MY_TOKEN = 'mytoken'
#patch('app.decorators.get_access_token_from_header', return_value=MY_TOKEN)
def test_my_mutation_successfully(self, get_access_token_from_header_mocker):
pass

create custom decorator for views that check if user is blacklisted

I want to create a decorator for my views that checks the DB if the user has over or equal to 5 warnings, and if so then I redirect him to a page telling him he has been flagged etc.
I wrote the code for it but I want to pass the request object to the decorator so I can call request.user for db queries but that doesn't seem like it's possible. How would I go on about this?
Decorator:
def check_blacklist(request):
try:
db = blacklist.objects.get(user=request.user)
warnings = db.warning
if warnings >= 5:
return redirect("security:flagged")
except:
pass
models.py
class blacklist(models.Model):
user = models.ForeignKey(User, default=None, on_delete=models.CASCADE)
warning = models.IntegerField()
views.py
#check_blacklist(request)
def task_info(request):
return render(request, 'dashboard/task.html',)
I would do something like that :
Use #app.before_request decorator to trap everything before it reachs your router
Define your blacklist fonction to be called before every request
Use request object, as it's global
It should give your something like this (not tested):
#app.before_request
def check_blacklist():
if must_check_blacklist(request.endpoint):
try:
db = blacklist.objects.get(user=request.user)
warnings = db.warning
if warnings >= 5:
return redirect("security:flagged")
except:
pass
I would suggest to call the decorator directly in your urls.py file (it is possible, and I'm doing it for something similar to your usecase). This is what I would do for instance:
# decorators.py
def check_blacklist():
def decorator_fun(func):
def wrapper_func(request, *args, **kwargs):
my_user = request.user
called_func = func(request, *args, **kwargs)
# your stuff goes here
return called_func
return wrapper_func
return decorator_fun
# urls.py
from app.decorators import check_blacklist
urlpatterns = [
path('your_url', check_blacklist()(your_view.as_view()), name='your_view'),
]
Hope that helps!

How can you persist a single authenticated requests session in python?

I am using the python requests library to intereact with an api.
I am first authenticating, getting a session-id and then creating a request session to persist that connection.
So I created a class to do this, but every time I use the class I am reinitialising and doing the authentication again. Which I want to avoid.
Essentially I created an API between the API I am calling using DRF.
How can I ensure that only 1 authenticated session is used across the entire app and that it persists through multiple request?
The class:
class RestClient:
session = None
def create_rest_client(self):
auth = requests.auth.HTTPBasicAuth(
USERNAME,
PASSWORD
)
response = requests.post(
f'https://{ settings.HOST }/rest/session',
auth=auth
)
session = requests.Session()
session_id = response.json().get('value')
session.headers.update({'api-session-id': session_id})
return session
def get_rest_client(self):
if self.session:
return self.session
else:
return self.create_rest_client()
Using the class I instantiate and get the client (naturally redoing the auth). I think this should either be global or a singleton.
Using class:
class ProductDetail(APIView):
def get(self, request, format=None, **kwargs):
response = []
rest_client = RestClient()
session = rest_client.get_rest_client()
response = session.get(
....use authenticated session
)
return Response(response.json())
I'd wire up a property like this:
class RestClient:
_session = None
#property
def session(self):
if self._session:
return self._session
auth = requests.auth.HTTPBasicAuth(USERNAME, PASSWORD)
response = requests.post(
f"https://{ settings.HOST }/rest/session", auth=auth
)
session = requests.Session()
session_id = response.json().get("value")
session.headers.update({"api-session-id": session_id})
self._session = session
return session
Now you can simply do client.session and it will be set up on the first access and reused thereafter.
EDIT: To persist this between RestClient instances, change references to self._session to RestClient._session.

Django TestCase object has no attribute 'session'

I'm learning about testing in Django. I need to first create user and login before I can test anything. I have tried the following...
class ProjectTest(TestCase):
def setUp(self):
self.email = 'test#test.com'
self.password = 'test'
self.new_user = AppUser.objects.create_superuser(email=self.email, password=self.password)
new_user = authenticate(username=self.email,
password=self.password)
login(request, new_user)
self.assertEqual(login, True)
def tearDown(self):
self.new_user.delete()
which gives me the error: AttributeError: 'str' object has no attribute 'session'
I have also tried:
def setUp(self):
self.email = 'test#test.com'
self.password = 'test'
self.new_user = AppUser.objects.create_superuser(email=self.email, password=self.password)
login = self.new_user.login(username=self.email, password=self.password)
But it states I don't have anything called login.
What is the correct way of doing this?
You don't call login on a user, you call it on an instance of the test client.
self.client.login(username=self.email, password=self.password)
class ProjectTest(TestCase):
def test_login_feature(self):
user = User.objects.create_user(username='joe', password='pass')
self.client.login(username='joe', password='pass')
# client now authenticated and can access
# restricted views.
So this is how you would do it. Create a user and use self.client.login you can read more about how to use it in the documentation

Retriving User info using Webapp2 Auth

I'm learning how to implement user login in GAE following the instruction in http://gosurob.com/post/20024043690/gaewebapp2accounts.
Below is a portion of the code that creates a user aware handler that my other handlers are supposed to inherit. This allows me to simply return user info by self.user. Now my question is how do I retrieve the user info in a ndb class which obviously inherits ndb.Model rather than BaseHandler. Please advise. Thanks!
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
#super(BaseHandler,self).dispatch()
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def auth(self):
return auth.get_auth(request=self.request)
#webapp2.cached_property
def user(self):
user = self.auth.get_user_by_session()
return user
#webapp2.cached_property
def user_model(self):
user_model, timestamp = self.auth.store.user_model.get_by_auth_token(
self.user['user_id'],
self.user['token']) if self.user else (None, None)
return user_model
class Global_Var(ndb.Model): #<<< This is the ndb class from which I want to use the user info.
current_user = ndb.StringProperty()
#staticmethod
def get_id():
return user['email'] #<<< This, of course, errors out since user is not defined in this class

Categories