In the Flask initalization file, I have
app = Flask("myapp")
jwt = JWT(app, AuthController.staff_login, identity)
api.add_resource(StaffLogin, '/login')
StaffLogin checks for the username / password, and will in turn call AuthController.staff_login
class AuthController():
def __init__(self):
# TODO: Create a config.yaml
self.engine = create_engine(DefaultConfig.SQLALCHEMY_DATABASE_URI)
Base.metadata.create_all(self.engine)
DBSession = sessionmaker(bind=self.engine)
self.session = DBSession()
def staff_login(self, staff_name, staff_password):
result = self.session.query(Staff) # Irrelevant details of query removed
if Staff_Authentication.check_password(staff_password, query['staff_password']):
# Build login_response as JSON
return login_response
Now I need a custom login response in this format
[
{
"projects": [
{
"project_id": 1,
"project_name": "omnipresence",
"project_root": "/path/to/project/files"
}
],
"staff_id": 13,
"staff_name": "adalovelace"
}
]
Question
How do I get Flask-JWT to use my function as authenticate?
Flask-JWT is old and has been abandoned. Check out flask-jwt-extended or flask-jwt-simple as alternatives, they are better designed and still maintained (I am the author of those extensions, so I am of course biased).
They are setup so you provide your own endpoint instead of having the extension manage an endpoint for you.
Related
I'm using a fixture in pytest that returns a client that has been logged in:
#pytest.fixture
def create_client(create_user) -> APIClient:
data = {
"email": create_user.email,
"password": "TestPassword",
}
client = APIClient()
client.post(path="/user/login/", data=data)
return client
How I get the user that has been logged in in the test?
I understand that I might be able to use a get a request from the client and get it that way:
def test_get_user(create_client):
response = create_client.get(path="/some/random/path/")
user = response.user
return user
but is there a better way to do this?
I'm sure that the create_user is another fixture and thus you can have that in your test_get_user(...) as
def test_get_user(create_client, create_user):
print(create_user)
# do some tests here
for an existing mobile app i want to create api through controller to communicate with odoo, i know about login fields/parameters
{"jsonrpc": "2.0","params":{"db":"odoodb","login":"myemail#smtpserver.com","password":"admin123"}}
for which api already have created, but now i want to know about odoo's default change password form's fields/parameters for PUT method, please help. using odoo 14.
regards
You can use this url to change the password of the user.
But the user must be logged to an Odoo session in the request headers
http://localhost:8069/my/security
You can send the params through GET or POST:
old = Old password
new1 = New password
new2 = Re-enter new Password
See the code in the web module for more info here
#route('/my/security', type='http', auth='user', website=True, methods=['GET', 'POST'])
def security(self, **post):
values = self._prepare_portal_layout_values()
values['get_error'] = get_error
if request.httprequest.method == 'POST':
values.update(self._update_password(
post['old'].strip(),
post['new1'].strip(),
post['new2'].strip()
))
return request.render('portal.portal_my_security', values, headers={
'X-Frame-Options': 'DENY'
})
The payload for that purpose is:
{
"params": {
"fields": [
{"name": "old_pwd", "value": "123456"},
{"name": "new_password", "value": "654321"},
{"name": "confirm_pwd", "value": "654321"}
]
}
}
Tested in v15 but in v13 and v14 the code looks the same.
I hope this answer could help.
I want to disable OPTIONS method on my API built with Django Rest Framework (DRF) globally (on all the API endpoints)
currently an OPTIONS call returns,
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json"
],
"name": "Login Oauth2",
"description": ""
}
which is something that I don't want someone to peek into. I want to return a blank character as github does on its API or something else.
I tried
#api_view(['POST'])
def my_method(request):
if request.method == 'OPTIONS':
return Response()
on an function based view, which returns an but inspecting the headers show,
Allow →POST, OPTIONS, OPTIONS
which has a repeated OPTIONS.
How do I achieve it? Thanks.
In your settings, add something like:
REST_FRAMEWORK = {
'DEFAULT_METADATA_CLASS': None,
}
if DEBUG:
# the default value
REST_FRAMEWORK['DEFAULT_METADATA_CLASS']: 'rest_framework.metadata.SimpleMetadata'
You can implement your own metadata class as well. Setting this to None will make it return HTTP 405 on OPTIONS requests.
Just implement a custom permission class.
your_app/permissions.py (permissions file for your app)
from rest_framework import permissions
class DisableOptionsPermission(permissions.BasePermission):
"""
Global permission to disallow all requests for method OPTIONS.
"""
def has_permission(self, request, view):
if request.method == 'OPTIONS':
return False
return True
Also set this as the default permission globally, using the DEFAULT_PERMISSION_CLASSES setting.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'your_app_name.permissions.DisableOptionsPermission',
)
}
This will disallow all incoming requests for OPTIONS method.
I am trying to enable authentication in my single page angular app using Satellizer as client on frontend and python social auth on backend using django rest framework. The main problem is that I want to use JWT instead of session authentication.
I have tried passing the response I get after the user confirms the popup window to social/complete/backend (python social auth view) but with no luck.
In satellizer for google configuration I put:
$authProvider.google({
clientId:'609163425136-1i7b7jlr4j4hlqtnb1gk3al2kagavcjm.apps.googleusercontent.com',
url: 'social/complete/google-oauth2/',
optionalUrlParams: ['display', 'state'],
state: function() {
return Math.random();
}
});
The problems I encounter are in python social auth:
Missing needed parameter state
for google-oauth2
and
Missing needed parameter code
for facebook.
That is very strange to me because those parameters are present in the request payload and I can get then in my own custom view.
The closest I have come to a solution is writing my own view in which I can accept the parameter state normally.
This is my view which handles the response and creates a new jwt token using the django-rest-framework-jwt:
class SocialAuthView(APIView):
throttle_classes = ()
permission_classes = ()
authentication_classes = ()
social_serializer = SocialAuthSerializer
user_serializer = None
def post(self, request):
access_token_url = 'https://accounts.google.com/o/oauth2/token'
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
from django.conf import settings
payload = dict(client_id=request.data['clientId'],
redirect_uri=request.data['redirectUri'],
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET,
code=request.data['code'],
grant_type='authorization_code')
# Step 1. Exchange authorization code for access token.
import requests
import json
r = requests.post(access_token_url, data=payload)
token = json.loads(r.text)
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
# Step 2. Retrieve information about the current user.
r = requests.get(people_api_url, headers=headers)
profile = json.loads(r.text)
try:
user = User.objects.filter(email=profile['email'])[0]
except IndexError:
pass
import jwt
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
if user:
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'token': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
u = User.objects.create(username=profile['name'], first_name=profile['given_name'],
last_name=profile['family_name'], email=profile['email'])
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'Bearer': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
I mean, it works, but I don't get all the benefits that I would get if I could implement it with python social auth. Maybe there is a way to authenticate with python social auth calling some functions like do_auth from this view?
It amazes me how much I struggle with this problem.
All help is welcome.
tl,dr: How to implement angular single page application using Satellizer, django-rest-framework, python social auth and JWT?
Not quite an answer, but I don't have sufficient reputation to comment.
I've been trying to do something similar. Satellizer works well with DRF's djoser module for email/pass token auth.
As for the social aspect, I'm finding OAuth2 quite complicated. You might want to look at the following project (no relation to me), it seems to be trying to implement just that (angularjs-satellizer/psa/jwt):
https://github.com/hsr-ba-fs15-dat/opendatahub
specifically:
https://github.com/hsr-ba-fs15-dat/opendatahub/blob/master/src/main/python/authentication/views.py
I have a Django-rest-framework viewset/router to define an API endpoint. The viewset is defined as such:
class DocumentViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
model = Document
And the router is defined as
router = DefaultRouter()
router.register(r'documents', viewsets.DocumentViewSet)
with url pattern url(r'^api/', include(router.urls))
I can hit this endpoint in the browser/through curl just fine by getting the right access token and using it for authorization. However, it's not clear how to write tests against this endpoint.
Here is what I've tried:
class DocumentAPITests(APITestCase):
def test_get_all_documents(self):
user = User.objects.create_user('test', 'test#test.com', 'test')
client = APIClient()
client.credentials(username="test", password="test")
response = client.get("/api/documents/")
self.assertEqual(response.status_code, 200)
This fails with an HTTP 401 response from the client.get() call. What is the right way to test an API endpoint in DRF using django-oauth-toolkit for oauth2 authentication?
When you are writing tests, you should aim to extract anything you are not testing from the test itself, typically putting any setup code in the setUp method of the test. In the case of API tests with OAuth, this usually includes the test user, OAuth application, and the active access token.
For django-oauth-toolkit, and other Django applications, I would always recommend looking at the tests to see how they do it. This allows you to avoid making unneeded API calls, especially for multi-part processes like OAuth, and only create the few model objects that are required.
def setUp(self):
self.test_user = UserModel.objects.create_user("test_user", "test#user.com", "123456")
self.application = Application(
name="Test Application",
redirect_uris="http://localhost",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.application.save()
def test_revoke_access_token(self):
from datetime import datetime
from django.utils import timezone
tok = AccessToken.objects.create(
user=self.test_user, token='1234567890',
application=self.application, scope='read write',
expires=timezone.now() + datetime.timedelta(days=1)
)
From there you just need to authenticate using the token that has been generated. You can do this by injecting the Authorization header, or you can use the force_authenticate method provided by Django REST Framework.
I have used the same library for OAuth2,
This worked for me
from oauth2_provider.settings import oauth2_settings
from oauth2_provider.models import get_access_token_model,
get_application_model
from django.contrib.auth import get_user_model
from django.utils import timezone
from rest_framework.test import APITestCase
Application = get_application_model()
AccessToken = get_access_token_model()
UserModel = get_user_model()
class Test_mytest(APITestCase):
def setUp(self):
oauth2_settings._SCOPES = ["read", "write", "scope1", "scope2", "resource1"]
self.test_user = UserModel.objects.create_user("test_user", "test#example.com", "123456")
self.application = Application.objects.create(
name="Test Application",
redirect_uris="http://localhost http://example.com http://example.org",
user=self.test_user,
client_type=Application.CLIENT_CONFIDENTIAL,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
)
self.access_token = AccessToken.objects.create(
user=self.test_user,
scope="read write",
expires=timezone.now() + timezone.timedelta(seconds=300),
token="secret-access-token-key",
application=self.application
)
# read or write as per your choice
self.access_token.scope = "read"
self.access_token.save()
# correct token and correct scope
self.auth = "Bearer {0}".format(self.access_token.token)
def test_success_response(self):
url = reverse('my_url',)
# Obtaining the POST response for the input data
response = self.client.get(url, HTTP_AUTHORIZATION=self.auth)
# checking wether the response is success
self.assertEqual(response.status_code, status.HTTP_200_OK)
Now everything will work as expected.
Hope this helps. Thanks
from oauth2_provider.models import (
get_access_token_model,
get_application_model,
get_id_token_model,
get_refresh_token_model,
)
class TestOauth(APITestCase):
def setUp(self):
""" create and register user """
self.test_user = User.create..
def test_oauth_application_and_tokens_add(self):
print(self.test_user, self.test_user.id)
"""Applications"""
Application = get_application_model()
app = Application()
app.name = "test"
app.client_type = "confidential"
app.authorization_grant_type = "password"
app.user_id = self.test_user.id
app.save()
# client_id:
print("Application Client ID: ", app.client_id)
# client_secret:
print("Application Client SECRET: ", app.client_secret)
"""Access Token"""
AccessToken = get_access_token_model()
token = AccessToken()
token.user_id = self.test_user.id
token.scope = "read write"
token.expires = timezone.now() + timezone.timedelta(seconds=300)
token.token = "secret-access-token-key"
token.application = app
token.save()
# token
print("Access Token: ", token)
self.auth = "Bearer {0}".format(token.token)
""" ID Token """
IDToken = get_id_token_model()
idt = IDToken()
idt.user_id = self.test_user.id
idt.application = app
idt.expires = timezone.now() + timezone.timedelta(days=10)
idt.scope = "read write"
idt.save()
# id token - returns jti token - successfull
print("ID Token: ", idt)
""" Refresh Token """
RefreshToken = get_refresh_token_model()
refr = RefreshToken()
refr.user_id = self.test_user.id
refr.application = app
refr.token = "statictoken" # The token is issued statically.
refr.access_token = (
token # The access token must not have been used before.
)
refr.revoked = timezone.now() + timezone.timedelta(days=10)
refr.save()
# refresh token
print("Refresh Token: ", refr)