I'm trying to do single sign-on (SSO) with an intranet web application written in Pylons and I'd like to use repoze.what for authorization. I have Apache configured with mod_sspi and it correctly authenticates the user and sets the REMOTE_USER environment variable. However, I can't figure out how to convince repoze.who that the user is, indeed, authenticated.
I tried creating an Identifier that looks like this:
class NtlmIdentifier(object):
def identify(self, environ):
if environ['AUTH_TYPE'] == 'NTLM':
return { 'repoze.who.userid': environ['REMOTE_USER'] }
return None
def remember(self, environ, identity):
pass
def forget(self, environ, identity):
pass
And registering the middleware later on like this:
return setup_auth(app, groups, permissions, identifiers=identifiers, authenticators=[], challengers=[])
But it seems that my identifier's identify method is never called by the framework.
How do you integrate SPNEGO/SSPI with repoze.who and repoze.what?
When the REMOTE_USER variable is set beforehand (e.g., by the web server), repoze.who won't do anything, not even call the registered plugins.
As for repoze.what v1, because it is set up from a repoze.who plugin, this means the repoze.what credentials won't be available and therefore the user would always be anonymous; this won't be a problem in repoze.what 2 (under development).
To make everything work as you expect, you can keep the identifier you wrote and pass the remote_user_key argument to setup_auth:
return setup_auth(app, groups, permissions, remote_user_key=None, identifiers=identifiers, authenticators=[], challengers=[])
HTH.
Related
Is there a way to include a value from the flask session (like username in this case) into the access log of gunicorn?
I know that I can include custom values with a filter like this:
class UserFilter(logging.Filter):
def filter(self, record):
record.username = "TestUser"
return True
But as soon as I try to get the value for record.username from the session I get:
RuntimeError: Working outside of request context.
This typically means that you attempted to use functionality that
needed an active HTTP request. Consult the documentation on testing
for information about how to avoid this problem.
I know question sounds strange, I will explain it here.
I have two Django servers which share the same DB. One is a light front/back server and the order one takes the heavy computing part. They share the same database.
I am currently securing the web, and I have a couple of views in the light server requiring user login:
#login_required()
#permission_required('auth.can_upload', login_url='/accounts/login/')
This works nicely in the light server since the user is authenticated (request.user returns a valid user in the views).
The problem comes when I have to send the uploaded data to the other server since it is protected as I showed earlier, I do not know how to pass it the user that is already logged (user is valid since servers share the DB).
# send an upload request using requests
s = requests.Session()
r1 = s.get(upload_process_url)
csrf_token = r1.cookies['csrftoken']
a = s.post(upload_process_url, files=request.FILES,
data={'csrfmiddlewaretoken': csrf_token},
headers=dict(Referer=upload_process_url))
I cannot ask every time the user and password or save them. The thing is I want to pass the user that is already logged in a request.
The user was logged using the default django accounts/login page and authentication.
Any clues and what could I try? I think this problem cannot be as difficult as it looks to me. I just want to send an authenticated request. If I remove the decorators everything works nicely with this code
Thanks a lot
Have a look at REMOTE_USER authentication:
This document describes how to make use of external authentication sources (where the Web server sets the REMOTE_USER environment variable) in your Django applications. This type of authentication solution is typically seen on intranet sites, with single sign-on solutions such as IIS and Integrated Windows Authentication or Apache and mod_authnz_ldap, CAS, Cosign, WebAuth, mod_auth_sspi, etc.
Basically your "light" server does the authentication as it already does. When you are doing a request to your "heavy" server, you should set a Auth-User header containing the username of your user. Django will then automatically authenticates the corresponding user.
By default, Django will read an environment variable set by an authentication server. But we can make it work with a HTTP header instead:
# middlewares.py
from django.contrib.auth.middleware import RemoteUserMiddleware
class CustomHeaderMiddleware(RemoteUserMiddleware):
header = 'HTTP_AUTH_USER'
# settings.py
MIDDLEWARE = [
'...',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'my_project.middlewares.CustomHeaderMiddleware',
'...',
]
Then, you can do something like this then in your request (assuming you have your Django user at hand):
s = requests.Session()
r1 = s.get(upload_process_url)
a = s.post(
upload_process_url,
files=request.FILES,
headers={
'Auth-User': user.username,
},
)
Since you're not doing a request from a browser, you can avoid the CSRF protection by marking the called "heavy" view with #csrf_exempt decorator (as you found yourself).
Be careful though that your "heavy" server should not be accessible directly on the internet and always behind a proxy/VPN accessible only by your "light" server.
I am using social-app-django (part of python-social-auth) to implement Facebook login for the users of my Django site. I have a requirement that users must be found in the local user database before they can log in with Facebook. I have replaced the auth_allowed-part of the authentication pipeline, to perform this check:
def auth_allowed(backend, details, response, *args, **kwargs):
if not backend.auth_allowed(response, details) or not is_registered_user(response, details):
raise AuthForbidden(backend)
where is_registered_user() is a custom function that checks whether the user exists locally.
My problem is that the users are not redirected to the login error URL, if this check fails and the AuthForbidden exception is thrown. Instead, the site returns a 500.
The error-page URL is configured as follows in settings.py:
LOGIN_ERROR_URL = "/"
I am using Django CMS, so I have also tried implementing middleware that overwrites SocialAuthExceptionMiddleware (which is supposed to handle the AuthForbidden exception in a standard setup) to return a CMS-page instead of an URL from settings.py but this middleware is not invoked in the process, as far as I can see.
Any ideas on how to fix this? Am I missing some configuration?
I think I solved the problem: I accidentally used
social.apps.django_app.middleware.SocialAuthExceptionMiddleware
instead of
social_django.middleware.SocialAuthExceptionMiddleware
when referring to the exception-handling middleware. The bug was most likely introduced during an upgrade from django-social-auth to python-social-auth/social-app-django some time ago.
I've met a scenario which I have to override a common middleware in CKAN. And in CKAN default plugins/interface.py:
class IMiddleware(Interface):
u'''Hook into CKAN middleware stack
Note that methods on this interface will be called two times,
one for the Pylons stack and one for the Flask stack (eventually
there will be only the Flask stack).
'''
def make_middleware(self, app, config):
u'''Return an app configured with this middleware
When called on the Flask stack, this method will get the actual Flask
app so plugins wanting to install Flask extensions can do it like
this::
import ckan.plugins as p
from flask_mail import Mail
class MyPlugin(p.SingletonPlugin):
p.implements(p.I18nMiddleware)
def make_middleware(app, config):
mail = Mail(app)
return app
'''
return app
It shows that I have to define "MyMiddleWare" class under a plugin that I want to implement in an extension. However, as it shows in the example, the actual middleware Mail is imported from a different class. I want to override TrackingMiddleware especially the __call__(self, environ, start_response) method, which environ and start_response are passed in when make_pylons_stack are invoked during the configuration phase. If I want to override TrackingMiddleware Should I create another config/middleware/myTrackingMiddleware.py under ckanext-myext/ and then in plugin.py I implement the following?:
from myTrackingMiddleware import MyTrackingMiddleware
class MyPlugin(plugins.SingletonPlugin):
plugins.implements(plugins.IMiddleware, inherit=True)
def make_middleware(self, app, config):
tracking = MytrackingMiddleware(app, config)
return app
Update:
I tried to make the myTrackingMiddleware in an hierarchy and imported it in plugin.py, but I didn't received any request to '/_tracking' in myTrackingMiddleware.
I have implemented a set of process, and it works for myself. Basically, I kept what I have done as what I have mentioned in my own question. Then, if your middleware has some conflict with CKAN default Middleware, you probably have to completely disable the default one. I discussed with some major contributors of CKAN here: https://github.com/ckan/ckan/issues/4451. After I disabled CKAN ckan.tracking_enabled in dev.ini, I have the flexibility to get values from environ and handle tracking with my customized logic.
I am trying to figure out the best way to implement token based authentication in my django app. An external, non-django application is setting a cookie, with a token, and I have a webservice that can retrieve user information based off of that token. If the user has the cookie set, they should not need to authenticate on my site and should be automatically logged in based on the info passed back by the web service. As I see it, there are a few different options to perform the actual check and I'm not sure which is best:
Write a custom decorator like the one in this snippet and use it instead of
login_required.
Call a custom authenticate method inside base_site through an ajax call. On every page, a check would be made and if the cookie exists and is valid, then the user would be automatically logged in.
Add some javascript to the LOGIN_REDIRECT_URL page that will check/validate the cookie in an ajax call, and automatically redirect back to the referrer if the cookie authenticated.
Is there an option I am missing? Ideally, there would be a way to build this into login_required, without having to write a custom decorator.
Before searching for code, be sure you read the documentation. http://docs.djangoproject.com/en/1.2/topics/auth/#other-authentication-sources
Also read the supplied Django source.
You want to create three things.
Middleware to capture the token. This is where most of the work happens. It checks for the token, authenticates it (by confirming it with the identity manager) and then logs in the user.
Authentication backend to find Users. This is a stub. All it does is create users as needed. Your identity manager has the details. You're just caching the current version of the user on Django's local DB.
Here's the middleware (edited).
from django.contrib.auth import authenticate, login
class CookieMiddleware( object ):
"""Authentication Middleware for OpenAM using a cookie with a token.
Backend will get user.
"""
def process_request(self, request):
if not hasattr(request, 'user'):
raise ImproperlyConfigured()
if "thecookiename" not in request.COOKIES:
return
token= request.COOKIES["thecookiename"]
# REST request to OpenAM server for user attributes.
token, attribute, role = identity_manager.get_attributes( token )
user = authenticate(remote_user=attribute['uid'][0])
request.user = user
login(request, user)
The identity_manager.get_attributes is a separate class we wrote to validate the token and get details on the user from the IM source. This, of course, has to be mocked for testing purposes.
Here's a backend (edited)
class Backend( RemoteUserBackend ):
def authenticate(**credentials):
"""We could authenticate the token by checking with OpenAM
Server. We don't do that here, instead we trust the middleware to do it.
"""
try:
user= User.objects.get(username=credentials['remote_user'])
except User.DoesNotExist:
user= User.objects.create(username=credentials['remote_user'] )
# Here is a good place to map roles to Django Group instances or other features.
return user
This does not materially change the decorators for authentication or authorization.
To make sure of this, we actually refresh the User and Group information from our
identity manager.
Note that the middleware runs for every single request. Sometimes, it's okay to pass the token to the backed authenticate method. If the token exists in the local user DB, the request can proceed without contacting the identity manager.
We, however, have complex rules and timeouts in the identity manager, so we have to examine every token to be sure it's valid. Once the middleware is sure the token is valid, we can then allow the backend to do any additional processing.
This isn't our live code (it's a little too complex to make a good example.)