Flak wrappers for authentication - flask

I am trying to put a validate wrapper around my flask endpoints to check if the user has permissions to the data. I want to define the permissions required in the decorator and then the wrapper verifies the user has those roles.
def create_app(config):
app=Flask(__name__)
app.config.from_object(config)
db.init_app(app)
JWTManager(app)
app.config["JWT_ROLE_CLAIM"] = "roles"
jwt.init_app(app)
api.add_namespace(auth_ns)
api.add_namespace(tasks_ns)
return app
from flask_jwt_extended import get_jwt_claims
def requires_roles(*roles):
def wrapper(f):
#wraps(f)
def wrapped(*args, **kwargs):
# Get the user's roles from the JWT token
user_roles = get_jwt_claims().get("roles")
# Check if the user has any of the required roles
if any(role in user_roles for role in roles):
# Call the endpoint function and return its result
return f(*args, **kwargs)
else:
# Return a 403 Forbidden response
return "Forbidden", 403
return wrapped
return wrapper
#auth_ns.route('/authtest')
#requires_roles("admin", "manager")
class AuthtestResource(Resource):
# #jwt_required()
def get(self):
return make_response(jsonify({"msg":"Authenticated success"}),200)
I get this error: cannot import name 'get_jwt_claims' from 'flask_jwt_extended'
I am not very familiar with decorators, what am I doing wrong here?
I have tried creating a decorator and using an AI tool to help me write the code

Related

How do I authenticate/protect non api_views with DRF + JWT eg. rendering a private page

I understand how to protect this for example, get the token, store it as cookie, then get the access token via the cookie and send it via Ajax to /api/hello and I get my JSON
#api_view()
#permission_classes([IsAuthenticated])
def hello(request):
return Response({'message': 'hello this is protected'})
But how would I protect this page where I want only people who are authenticated to see the page, eg. if they click this URL /secretmessage
def secret_message(request):
return render(request, 'secret_message.html')
Am I supposed to use a combination of JWT auth for the API stuff and Session Based auth and not just rely on JWT for all auth?
from django.contrib.auth.decorators import login_required
#login_required
def secret_message(request):
return render(request, 'secret_message.html')
Use #login_required decorator, to allows only users who are logged in to access the view.
If you want in this case you could create your own decorator which could answer if the user logged in with jwt or not.
This could be an example:
decorators.py
from functools import wraps
from django.http import JsonResponse
import jwt
def api_login_required(function):
#wraps(function)
def wrap(request, *args, **kwargs):
token = request.request.COOKIES.get('jwt')
if token == None:
return JsonResponse({'message':"Error: Unauthenticated..."})
try:
payload = jwt.decode(token, 'secret', algorithms=['HS256'])
except Exception: #jwt.ExpiredSignatureError:
return JsonResponse({'message':"Error: Unauthenticated..."})
if payload:
return function(request, *args, **kwargs)
else:
return JsonResponse({'message':"Error: Unauthenticated..."})
return wrap
Then you would only have to make the call with #api_login_required
from django.contrib.auth.decorators import login_required
#api_login_required
def secret_message(request):
return render(request, 'secret_message.html')

django-axes is not getting the request argument

I recently added django-axes to my Django project. It is suppose to work out the box with django-restframework. However, I am using django-rest-framework-simplejwt to handle authentication. But it should still work out the box since the only thing that is required for django-axes is passing Django's authentication method the request object which it does in it's source code (line 39 and 43).
When I try to authenticate, I get this error from django-axes:
axes.exceptions.AxesBackendRequestParameterRequired: AxesBackend requires a request as an argument to authenticate
You need to add requests to the authentication function. See sample code below.
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def _authenticate_user_email(self, email, password, request):
# This is key: Pass request to the authenticate function
self.user = authenticate(email=email, password=password, request=request)
return self.user
def validate(self, attrs):
password = attrs.get('password')
email = attrs.get('email')
request = self.context.get('request') # <<=== Grab request
self.user = self._authenticate_user_email(email, password, request)
# All error handling should be done by this code line
refresh = self.get_token(self.user)
data = {}
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
return data
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from authy.serializers import MyTokenObtainPairSerializer
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
urls.py
from authy.views import MyTokenObtainPairView
url(r'^/auth/api/token/$', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
It is also worth mentioning that the simple jwt lib uses the authenticate function, however it does not pass the request parameter. Therefore you need call authenticate, get_token and return data object yourself.
In addition, if you have extended the AbstractBaseUser model of django. And set the USERNAME_FIELD. Then use the param username instead of email. E.g: authenticate(username=email, password=password, request=request)
Use this:
from axes.backends import AxesBackend
class MyBackend(AxesBackend)
def authenticate(self, request=None, *args, **kwargs):
if request:
return super().authenticate(request, *args, **kwargs)
This would skip the AxesBackend in AUTHENTICATION_BACKENDS if the request is unset and would weaken your security setup.
source: https://github.com/jazzband/django-axes/issues/478

Test API Key to return mock data with Django Rest Framework and TokenAuthentication

I'm building a REST-ful API using Django Rest Framework. I have handled integration with REST APIs before for work and have been given test API keys specifically for use with testing for integration, keys that allow you to send data and returns mock data, but doesn't put the data in the system. I am trying to implement this same feature in my API, but I haven't been able to find a way to do so yet.
you can use some decorator to do that :)
from django.contrib.auth import authenticate
def mock_funcname(func):
def wrapper(request, *args, **kwargs):
user = authenticate(token=request.REQUEST['token'])
if user.id == 'TEST USER ID':
return Response({'detail': "Some stuff mocked for this user :)"})
return func(request, *args, **kwargs)
return wrapper
So now you can put it like other decorator :
#api_view(['POST'])
#permission_classes((IsAuthenticated,))
#mock_funcname
def funcname(request, pid=None):
return response......
Its a basic function you need to make it more complicate
First of all, you can install mommy to create mock data in your tests.
So in your tests.py you can create a test to your API, checking the status_code and the response:
def test(self):
user = mommy.make(User) # Create a fake user
token = Token.objects.get(user__username=user.username) # Generate a token
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token.key) # Token for your request
response = self.client.post("url", your_data) # POST in your URL
self.assertEqual(response.status_code, status.HTTP_201_CREATED) # Status Code of Creation
self.assertEqual(User.objects.last().first_name, user.first_name) # Check the fields created
and then run the tests running python manage.py test or follow the documetation in DRF

Django test client http basic auth for post request

everyone. I am trying to write tests for RESTful API implemented using django-tastypie with http basic auth. So, I have the following code:
def http_auth(username, password):
credentials = base64.encodestring('%s:%s' % (username, password)).strip()
auth_string = 'Basic %s' % credentials
return auth_string
class FileApiTest(TestCase):
fixtures = ['test/fixtures/test_users.json']
def setUp(self):
self.extra = {
'HTTP_AUTHORIZATION': http_auth('testuser', 'qwerty')
}
def test_folder_resource(self):
response = self.client.get('/api/1.0/folder/', **self.extra)
self.assertEqual(response.status_code, 200)
def test_folder_resource_post(self):
response = self.client.post('/api/1.0/folder/', **self.extra)
self.assertNotEqual(response.status_code, 401)
GET request is done well, returning status code 200. But POST request always returns 401. I am sure I am doing something wrong. Any advice?
Check out this question. I've used that code for tests using both GET and POST and it worked. The only difference I can see is that you have used base64.encodestring instead of base64.b64encode.
Otherwise, if that doesn't work, how are you performing the HTTP Authentication? I wrote and use this function decorator:
import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate, login
def http_auth(view, request, realm="", must_be='', *args, **kwargs):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
if must_be in ('', uname):
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
login(request, user)
request.user = user
return view(request, *args, **kwargs)
# They mustn't be logged in
response = HttpResponse('Failed')
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
def http_auth_required(realm="", must_be=''):
""" Decorator that requires HTTP Basic authentication, eg API views. """
def view_decorator(func):
def wrapper(request, *args, **kwargs):
return http_auth(func, request, realm, must_be, *args, **kwargs)
return wrapper
return view_decorator
I've found a reason of my problem. DjangoAuthorization checks permissions with django premissions framework, since I don't use it in my project — all post/put/delete requests from non superuser are unauthorized. My bad.
Anyway, thanks a lot to you, guys, for responses.
On Python 3
#staticmethod
def http_auth(username, password):
"""
Encode Basic Auth username:password.
:param username:
:param password:
:return String:
"""
data = f"{username}:{password}"
credentials = base64.b64encode(data.encode("utf-8")).strip()
auth_string = f'Basic {credentials.decode("utf-8")}'
return auth_string
def post_json(self, url_name: AnyStr, url_kwargs: Dict, data: Dict):
"""
Offers a shortcut alternative to doing this manually each time
"""
header = {'HTTP_AUTHORIZATION': self.http_auth('username', 'password')}
return self.post(
reverse(url_name, kwargs=url_kwargs),
json.dumps(data),
content_type="application/json",
**header
)

Django, request.user is always Anonymous User

I am using a custom authentication backend for Django (which runs off couchdb). I have a custom user model.
As part of the login, I am doing a request.user = user and saving the user id in session.
However, on subsequent requests, I am not able to retrieve the request.user. It is always an AnonymousUser. I can, however, retrieve the user id from the session and can confirm that the session cookie is being set correctly.
What am I missing?
I do not want to use a relational db as I want to maintain all my user data in couchdb.
Edit: I have written a class which does not inherit from Django's auth User. It, however, has the username and email attributes. For this reason, my backend does not return a class which derives from auth User.
The request.user is set by the django.contrib.auth.middleware.AuthenticationMiddleware.
Check django/contrib/auth/middleware.py:
class LazyUser(object):
def __get__(self, request, obj_type=None):
if not hasattr(request, '_cached_user'):
from django.contrib.auth import get_user
request._cached_user = get_user(request)
return request._cached_user
class AuthenticationMiddleware(object):
def process_request(self, request):
request.__class__.user = LazyUser()
return None
Then look at the get_user function in django/contrib/auth/__init__.py:
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
user = AnonymousUser()
return user
Your backend will need to implement the get_user function.
I too have custom authentication backend and always got AnonymousUser after successful authentication and login. I had the get_user method in my backend. What I was missing was that get_user must get the user by pk only, not by email or whatever your credentials in authenticate are:
class AccountAuthBackend(object):
#staticmethod
def authenticate(email=None, password=None):
try:
user = User.objects.get(email=email)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
#staticmethod
def get_user(id_):
try:
return User.objects.get(pk=id_) # <-- tried to get by email here
except User.DoesNotExist:
return None
Its easy to miss this line in the docs:
The get_user method takes a user_id – which could be a username,
database ID or whatever, but has to be the primary key of your User
object – and returns a User object.
It so happened that email is not primary key in my schema. Hope this saves somebody some time.
You say you've written a custom authentication backend, but in fact what you seem to have written is a complete custom authentication app, which doesn't interface with Django's contrib.auth.
If you want to use a non-relational database for your authentication data, all you need to do is create a class that provides two methods: get_user(user_id) and authenticate(**credentials). See the documentation. Once you have authenticated a user, you simply call Django's normal login methods. There should be no reason to manually set request.user or put anything into the session.
Update after edit That has nothing to do with it. There's no requirement that the user class derives from auth.models.User. You still just need to define a get_user method that will return an instance of your user class.
Please elaborate. If you are using a custom user model (which is different from a custom user PROFILE model), then you are basically on your own and the django.contrib.auth framework can not help you with authentication. If you are writing your own authentication system and are not using django.contrib.auth, then you need to turn that off because it seem to be interfering with your system.
In case you are using an API (Django-rest-framework) and accessing a view using a get, post, etc. methods.
You can get a user by sending the Bearer/JWT token corresponding to that user.
Wrong
# prints Anonymous User
def printUser(request):
print(request.user)
Correct
# using decorators
# prints username of the user
#api_view(['GET']) # or ['POST'] .... according to the requirement
def printUser()
print(request.user)
I had similar problem when I used custom authentication backend. I used field different than the primary key in the method get_user.
It directly solved after using primary key which must be number (not str)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id) # <-- must be primary key and number
except User.DoesNotExist:
return None
After sending Token using Authorization header, the token will be gotten in dispatch function as bellow:
'''
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
So you are using django_role_permission's HasRoleMixin, the dispatch method of this mixin will hide dispatch of the view.
I think that the solution is to redefine the mixin of roles-permissions
user = authenticate(username=username, password=password)
if user is not None:
return render(request, 'home.html',{'user_id':user.id})
Added these in my view
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
and started getting original user