This is my first post on Stackoverflow and I'm new to Django, I hope you'll understand.
I want to use Django to provide a portal with authentication, which will have to consume an Keystone/Openstack API, to create/delete Projects, grant/remove rights.
Openstack provides a RestFul API, on which I have to authenticate (I provide credentials, and receive back a token).
I have 2 possibilities to access this API:
Using python client: python-keystoneclient
Using directly the restfulAPI
Nevermind the option 1 or 2, I'm able to login and interact with the API, I do this in the view.
My problem is, each time I change the page/view, I have to authenticate again. I don't know how to use/share the "session or client object" in other views.
>>> from keystoneauth1.identity import v3
>>> from keystoneauth1 import session
>>> from keystoneclient.v3 import client
>>> auth = v3.Password(auth_url='https://my.keystone.com:5000/v3',
... user_id='myuserid',
... password='mypassword',
... project_id='myprojectid')
>>> sess = session.Session(auth=auth)
>>> keystone = client.Client(session=sess, include_metadata=True)
I tried to pass the object as a session variable with request.session and request.session.get, but the object is not serializable. I serialized it, but I can't use it on the other view.
Maybe I shouldn't access the API in the view?
I'm sure I'm not the first in this usecase, regardless of the remote API. But I googled a lot without finding a proper way. Maybe I don't search with the right words
Thanks for your help.
I did it like this and it works well:
in apps.py:
from django.apps import AppConfig
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient.v3 import client
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'mySharedVar'
auth = v3.Password(auth_url='https://my.keystone.com:5000/v3', user_id='myuserid', password='mypassword',project_id='myprojectid')
ses1 = session.Session(auth=auth)
in my views, I can now access the "share variable" with:
from keystoneauth1.identity import v3
from keystoneauth1 import session
from keystoneclient.v3 import client
#login_required
def list_project(request):
sharedVar=apps.get_app_config('mySharedVar')
keystone = client.Client(session=sharedVar.ses1, include_metadata=True)
pl = keystone.projects.list()
context = {
"title": "Project List",
"projects": pl.data
}
return render(request, "myapp/list_project.html",context=context)
I hope this can help someone.
Related
I'm building a SPA using Flask as an api and Vue js as front end. I'm using Authlib for user authentication and planning to use Facebook OAuth. I've already tried the example with Facebook and it's working. But I want to build this in a RESTful way. What I'm trying to do is change this part from app.py from the example:
#app.route('/login')
def login():
redirect_uri = url_for('auth', _external=True)
return oauth.google.authorize_redirect(redirect_uri)
to a json.
Is there a method in the library to get the url of Facebook dialog so that I can pass that as json and do the redirection in Vue?
Do you mean that you want to return a url value in the JSON response?
resp = oauth.google.authorize_redirect(redirect_uri)
url = resp.location
return jsonify(url=url)
Like the above code?
Per the documentation:
>>> import requests_oauthlib
>>> from requests_oauthlib import OAuth2Session
>>> from requests_oauthlib.compliance_fixes import facebook_compliance_fix
>>> facebook = OAuth2Session(client_id='test', redirect_uri='https://test.me')
>>> facebook = facebook_compliance_fix(facebook)
>>> authorization_url, state = facebook.authorization_url('https://www.facebook.com/dialog/oauth')
>>> authorization_url
'https://www.facebook.com/dialog/oauth?response_type=code&client_id=test&redirect_uri=https%3A%2F%2Ftest.me&state=bfipsir5GsKc1CbdPZCgBT0jIn2eq6'
I want to allow the django users to use a key in the api urls for authentication.
I do not have OAUTH set up yet so I guess the key could be a sesion key or a digest key.
I'm having 2 problems.
I've tried sending this request:
http://192.166.166.11:8000/api?task=search&term=115&csrf_token=s69WAIZqlCTur1XZQr72QhCc7fzqaRtM
First of all, I've tried using the csrf_token but it does not work.
It takes me to the login page.
Secondly, I do not know how to retrieve csrf_token of other users (the admin is trying to get their csrf_tokens).
My attempt:
x = User.objects.get(username='someone')
x.get_session_auth_hash()
gives me the user's authentication hash but it is a different value.
Can someone please guide me get these two problems sorted out?
You are creating a token-based authentication. You already mentioned OAUTH as one option, and I strongly recommend using one of the existing implementations like django-oauth-toolkit. However, you can also create your own quick solution to create a token-based authentication.
Disclaimer: This is for demo purposes only. Do not copy it in any existing project. It will make your application vulnerable.
First, we create an additional model handling the authentication tokens:
/auth_tokens/models.py
from django.db import models
from django.conf import settings
import string, random
def random_string(length = 64, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(length))
class AuthToken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
token = models.CharField(max_length=64, default=random_string)
/auth_tokens/middleware.py
from auth_tokens.models import AuthToken
class AuthTokenMiddleware:
def process_request(self, request):
token = request.GET.get('auth', None)
if not token:
return
token = AuthToken.objects.get(token=token)
request.user = token.user
return request
Including the middleware into your settings.MIDDLEWARE_CLASSES should enable you to add ?token=<token> to your URL to login your users.
I ended up using token authentication:
http://www.django-rest-framework.org/api-guide/authentication/
so I'd like to share the workflow.
First, you need to do the set up. In settings.py, modify INSTALLED_APPS and add REST_FRAMEWORK as in documentation.
Then you need to run python manage.py syncdb because it needs to add some tables.
Then, you need to add some urls to urls.py to route the api.
You can create and retrieve tokens using this code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=User.objects.get(username='john'))
print token.key
Lastly, you'll have to modify your view which depends on whether you're using a function based or class based view.
Here is a function based view I used:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
#authentication_classes((TokenAuthentication,))
#permission_classes((IsAuthenticated,))
#login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
Lastly, to test it out:
import requests
token = '243124c52f7583e320d043c4395bd99f63344035'
headers = {'Authorization' : 'Token {}'.format(token)}
page = requests.post('http://127.0.0.1:8000/mybooks/', headers=headers)
print page.content
Note that in my case I do not need define plain serialization since I have an advanced custom serialization and that is not the topic here.
Django doesn't provide API Keys out of the box.
Use API providers such as Tastypie to have this feature
I'm trying to write a functional test that uses Selenium to test a Django view. When the user comes to a page ("page2"), the view that renders that page expects to find a session variable "uid" (user ID). I've read a half dozen articles on how this is supposed to be done but none of them have worked for me. The code below shows how the Django documentation says it should be done but it doesn't work for me either. When I run the test, the view never completes executing and I get a "server error occurred" message. Could someone please tell me what I'm doing wrong? Thank you.
views.py:
from django.shortcuts import render_to_response
def page2(request):
uid = request.session['uid']
return render_to_response('session_tests/page2.html', {'uid': uid})
test.py:
from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client
class SessionTest(LiveServerTestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
self.client = Client()
self.session = self.client.session
self.session['uid'] = 1
def tearDown(self):
self.browser.implicitly_wait(3)
self.browser.quit()
def test_session(self):
self.browser.get(self.live_server_url + '/session_tests/page2/')
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Page 2', body.text)
Here's how to solve this problem. James Aylett hinted at the solution when he mentioned the session ID above. jscn showed how to set up a session but he didn't mention the importance of the session key to a solution and he also didn't discuss how to link the session state to Selenium's browser object.
First, you have to understand that when you create a session key/value pair (e.g. 'uid'=1), Django's middleware will create a session key/data/expiration date record in your backend of choice (database, file, etc.). The response object will then send that session key in a cookie back to the client's browser. When the browser sends a subsequent request, it will send a cookie back that contains that key which is then used by the middleware to lookup the user's session items.
Thus, the solution required 1.) finding a way to obtain the session key that is generated when you create a session item and then; 2.) finding a way to pass that key back in a cookie via Selenium's Firefox webdriver browser object. Here's the code that does that:
selenium_test.py:
-----------------
from django.conf import settings
from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client
import pdb
def create_session_store():
""" Creates a session storage object. """
from django.utils.importlib import import_module
engine = import_module(settings.SESSION_ENGINE)
# Implement a database session store object that will contain the session key.
store = engine.SessionStore()
store.save()
return store
class SeleniumTestCase(LiveServerTestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
self.client = Client()
def tearDown(self):
self.browser.implicitly_wait(3)
self.browser.quit()
def test_welcome_page(self):
#pdb.set_trace()
# Create a session storage object.
session_store = create_session_store()
# In pdb, you can do 'session_store.session_key' to view the session key just created.
# Create a session object from the session store object.
session_items = session_store
# Add a session key/value pair.
session_items['uid'] = 1
session_items.save()
# Go to the correct domain.
self.browser.get(self.live_server_url)
# Add the session key to the cookie that will be sent back to the server.
self.browser.add_cookie({'name': settings.SESSION_COOKIE_NAME, 'value': session_store.session_key})
# In pdb, do 'self.browser.get_cookies() to verify that it's there.'
# The client sends a request to the view that's expecting the session item.
self.browser.get(self.live_server_url + '/signup/')
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Welcome', body.text)
There are a couple of tickets in Django's bug tracker around this kind of problem, the main one seems to be: https://code.djangoproject.com/ticket/10899 which hasn't had any movement on it for a few months. Basically, you need to do some extra set up to get the session to work properly. Here's what worked for me (may not work as is with your particular set up, as I wasn't using Selenium):
def setUp(self):
from django.conf import settings
engine = import_module(settings.SESSION_ENGINE)
store = engine.SessionStore()
store.save()
self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
Now you should be able to access self.client.session and it should remember any changes you make to it.
Here is my solution for django==2.2.
from importlib import import_module
from django.conf import settings
from django.contrib.auth import get_user_model
# create the session database instance
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore()
# create the user and instantly login
User = get_user_model()
temp_user = User.objects.create(username='admin')
temp_user.set_password('password')
self.client.login(username='admin', password='password')
# get session object and insert data
session = self.client.session
session[key] = value
session.save()
# update selenium instance with sessionID
selenium.add_cookie({'name': 'sessionid', 'value': session._SessionBase__session_key,
'secure': False, 'path': '/'})
I would like to read some session variables from a test (Django TestCase)
How to do that in a clean way ?
def test_add_docs(self):
"""
Test add docs
"""
# I would access to the session here:
self.request.session['documents_to_share_ids'] = [1]
response = self.client.get(reverse(self.document_add_view_id, args=[1]), follow=True)
self.assertEquals(response.status_code, 200)
As of Django 1.7+ this is much easier. Make sure you set the session as a variable instead of referencing it directly.
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
andreaspelme's workaround is only needed in older versions of django. See docs
Unfortunately, this is not a easy as you would hope for at the moment. As you might have noticed, just using self.client.session directly will not work if you have not called other views that has set up the sessions with appropriate session cookies for you. The session store/cookie must then be set up manually, or via other views.
There is an open ticket to make it easier to mock sessions with the test client: https://code.djangoproject.com/ticket/10899
In addition to the workaround in the ticket, there is a trick that can be used if you are using django.contrib.auth. The test clients login() method sets up a session store/cookie that can be used later in the test.
If you have any other views that sets sessions, requesting them will do the trick too (you probably have another view that sets sessions, otherwise your view that reads the sessions will be pretty unusable).
from django.test import TestCase
from django.contrib.auth.models import User
class YourTest(TestCase):
def test_add_docs(self):
# If you already have another user, you might want to use it instead
User.objects.create_superuser('admin', 'foo#foo.com', 'admin')
# self.client.login sets up self.client.session to be usable
self.client.login(username='admin', password='admin')
session = self.client.session
session['documents_to_share_ids'] = [1]
session.save()
response = self.client.get('/') # request.session['documents_to_share_ids'] will be available
If you need to initialize a session for the request in tests to manipulate it directly:
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
request = HttpRequest()
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
If testing my django project with pytest, I can not see any modifications to the session that are made in the view. (That is because the Sessions middleware doesn't get called.)
I found the following approach to be useful:
from unittest.mock import patch
from django.test import Client
from django.contrib.sessions.backends import db
def test_client_with_session():
client = Client()
session = {} # the session object that will persist throughout the test
with patch.object(db, "SessionStore", return_value=session):
client.post('/url-that-sets-session-key/')
assert session['some_key_set_by_the_view']
client.get('/url-that-reads-session-key/')
This approach has the benefit of not requiring database access.
You should be able to access a Client's session variales through its session property, so I guess self.client.session['documents_to_share_ids'] = [1] should be what you are looking for!
I'm currently developing an application in Django and trying to implement Facebook authentication and requests to the Graph API. I've seen a few different libraries out there, but what is the best way to do the following:
Have a user login via Facebook.
Django creates a new user for them and adds their uid and oauth token.
I can then make calls to the Graph API using Facebook's Python SDK.
I did see this example. Is it that simple on normal Django?
My company has built a library that makes integrating Facebook into your Django application dead simple (we've probably built 10-20 apps with the library, including some with huge amounts of traffic, so it's been battle-tested).
pip install ecl-facebook==1.2.7
In your settings, add values for your FACEBOOK_KEY, FACEBOOK_SECRET, FACEBOOK_SCOPE, FACEBOOK_REDIRECT_URL, and PRIMARY_USER_MODEL. You'll also need to add ecl_facebook.backends.FacebookAuthBackend to your AUTHENTICATION_BACKENDS. For example, in settings.py:
# These aren't actual keys, you'll have to replace them with your own :)
FACEBOOK_KEY = "256064624431781"
FACEBOOK_SECRET = "4925935cb93e3446eff851ddaf5fad07"
FACEBOOK_REDIRECT_URL = "http://example.com/oauth/complete"
FACEBOOK_SCOPE = "email"
# The user model where the Facebook credentials will be stored
PRIMARY_USER_MODEL = "app.User"
AUTHENTICATION_BACKENDS = (
# ...
'ecl_facebook.backends.FacebookAuthBackend',
)
Add some views in your views.py to handle pre- and post-authentication logic.
from django.contrib.auth import authenticate, login
from django.http import HttpResponseRedirect
from ecl_facebook.django_decorators import facebook_begin, facebook_callback
from ecl_facebook import Facebook
from .models import User
# ...
#facebook_begin
def oauth_facebook_begin(request):
# Anything you want to do before sending the user off to Facebook
# for authorization can be done here.
pass
#facebook_callback
def oauth_facebook_complete(request, access_token, error):
if error is None:
facebook = Facebook(token)
fbuser = facebook.me()
user, _ = User.objects.get_or_create(facebook_id=fbuser.id, defaults={
'access_token': access_token})
user = authenticate(id=user.id)
login(request, user)
return HttpResponseRedirect("/")
else:
# Error is of type ecl_facebook.facebook.FacebookError. We pass
# the error back to the callback so that you can handle it
# however you want.
pass
Now just hook up these URLs in your urls.py file and you're done.
# ...
urlpatterns = patterns('app.views',
# ...
url(r'^oauth/facebook/begin$', 'oauth_facebook_begin'),
url(r'^oauth/facebook/complete$', 'oauth_facebook_complete'),
)
Hope this helps!
P.S. You can read the rest of the docs here.
We do a lot of Facebook Application development where I work, and so we've developed an open-source library that makes everything about it really easy.
from django.http import HttpResponse
from fandjango.decorators import facebook_authorization_required
#facebook_authorization_required
def foo(request, *args, **kwargs):
return HttpResponse("Your name is %s" % request.facebook_user.first_name)
I recommend https://github.com/egnity/fb.py. Got my Django-based Facebook app up and running in no time. It includes a middleware that allows you to run code like this in your view:
for the user id:
user_id = request.facebook.graph().get_object("me")['id']
for the oauth token:
user_token = request.facebook.auth_token
You can then add the above to your User model as you please. To make Graph API calls, you can still use fb.py's middleware -- no need for using the primitive python-sdk. The user_id code above is a perfect example of a Graph API call. There's much more you can do with fb.py. The download includes a sample django project to get you going.