-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Finish Shibboleth Integration #48
Conversation
Is there a TLDR on how this is tested? |
I see the unit tests for exception handling but wondering if its rigorous enough for route authorization and whatnot |
The unit tests are primarily to ensure that a user can log in though Shibboleth. Testing to make sure no one can break it should just involve a code review (including nginx configs) and sending requests to I've done a few quick tests, and I'm pretty sure it's secure, but a few additional eyes wouldn't hurt. |
accounts/views.py
Outdated
@@ -36,8 +33,7 @@ def get(self, request): | |||
user = auth.authenticate(remote_user=pennkey, shibboleth_attributes=shibboleth_attributes) | |||
if user: | |||
auth.login(request, user) | |||
params = request.get_full_path().split('next=')[1] | |||
return redirect('https://platform.pennlabs.org' + params) | |||
return redirect(request.GET.get('next')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does GET['next']
contain the redirect URL for success? Is pennlabs.org
hardcoded anywhere? Just curious about design choices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's it. It's a relative URL to redirect once logged in. There's no reference to pennlabs.org
. In the normal login process next should be accounts/authorize?x=...
, Since users will be logging in through OAuth2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. If we don't hardcode pennlabs.org
is there any way somebody could spoof the redirect URL back to ours with their own hosted instance of Shibboleth?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, the redirect doesn't contain any user information. At that point the user is already logged into platform and a cookie is set. The redirect is just to let the user continue the OAuth2 flow. There shouldn't be any way to leak data through this.
Why is it on every login? |
When should Shibboleth send emails? Is this functionality we need for forgotten passwords or something? Do we handle that? |
I don't really have a great reason for it, but my thinking was when people become alumni their affiliations will change. Additionally if a student teaches a class like CIS 19x, then their affiliations may change. Eventually we may want to use this affiliations to determine what people can do. An example being letting professors choose comments on PCR |
Sorry, this is poor wording. I meant Penn does not send us a user's email through Shibboleth. In certain configurations (not Penn's) Shibboleth provides additional metadata on users who are logging in. It would be nice for us if we got students emails through Shibboleth, but it seems like we won't be able to do that. Penn would have to make that change. If we can't get a directory API key, then it's worth asking them to change, if possible. |
Got it. Can't find the affiliation changing code in this PR but it should be fine if we're using the standard Django abstractions. I can't really think of a better way anyway to update affiliation so this flow LGTM |
Is |
This block in backends.py handles the affiliation updating. It just adds the correct affiliation in a many-to-many field in the user. |
We don't have a directory API key that lets us search for the data we want (the data is hidden behind a PennKey login). So I can't yet implement the method. I've requested a key with elevated privileges, so we'll have to wait and see if we can get one. |
Gotcha. I would personally abstract that into a helper function somewhere, just because from a dev perspective I guess it's not authentication code specifically? But non-breaking of course so maybe a later refactor could handle this |
Also for the sake of security updating the Django version number in |
That should be done, but I was waiting for Edit: updated in 37f8d6b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good to me! Most pressing open question for me is how this works as part of the reusable app. But assuming that that's fine, this looks good! Excited for integrating this.
setattr(user, key, self.searchPennDirectory(username, key)) | ||
|
||
if key != 'affiliation': | ||
setattr(user, key, value if value else self.searchPennDirectory(username, key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we worried about latency issues for an external request to the Penn directory? What if the directory API is down, but shibboleth is still up? We should probably still accept logins in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read the thread with @kirubarajan, but still think this is something to think about at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question. Polling the directory only happens on a user's first login, so I don't think latency will be a real issue. We probably should add some error checking if the directory is down, but since we don't have access to it yet, I think we should leave it as is.
accounts/backends.py
Outdated
user.save() | ||
user = self.configure_user(request, user) | ||
|
||
# Update affiliations with every log in | ||
if shibboleth_attributes is not None and 'affiliation' in shibboleth_attributes: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably could move the not-None
check to encompass the if created
branch as well. Not sure exactly how shibboleth_attributes is populated, but would it be a successful login if it was None
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment below
pennkey = request.META.get('HTTP_EPPN', '').lower().split('@')[0] | ||
first_name = request.META.get('HTTP_GIVENNAME', '').lower().capitalize() | ||
last_name = request.META.get('HTTP_SN', '').lower().capitalize() | ||
email = request.META.get('HTTP_MAIL', '').lower() | ||
shibboleth_attributes = {'first_name': first_name, 'last_name': last_name, 'email': email} | ||
affiliation = request.META.get('HTTP_UNSCOPED_AFFILIATION', '').split(';') | ||
shibboleth_attributes = {'first_name': first_name, 'last_name': last_name, 'email': email, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my comment above, looks like shibboleth_attributes being None would mean that something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true. I added the check since I had previously written a test where shibboleth_attributes
was empty, but you're right, it would be impossible for that to happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in b75f0af
""" | ||
Log in a user. | ||
WARNING: You must ensure this page is protected by Shibboleth and Clean Headers | ||
See https://github.com/nginx-shib/nginx-http-shibboleth |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a cursory read of the linked page, I'm guessing this means that not everyone can hit the LoginView
endpoint. I see this is in the accounts
package... will we have to add a custom nginx rule on every dokku app that uses this package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We just need to make sure that when platform is (re)deployed in production, we configure nginx to require Shibboleth authentication on the accounts/login/
route. This only needs to be done on platform though.
The reusable app portion will be django-labs-accounts which I can finish once this PR is merged. The reusable app will just act as an OAuth2 client to interface with platform. |
Does this repo |
Completely forgot about |
So this is where I need to document things better. The way things are set up, |
user = auth.authenticate(remote_user=pennkey, shibboleth_attributes=shibboleth_attributes) | ||
if user: | ||
auth.login(request, user) | ||
params = request.get_full_path().split('next=')[1] | ||
return redirect('https://platform.pennlabs.org' + params) | ||
return redirect(request.GET.get('next', '/')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would protect this using a whitelist, since it could possibly open us up to phishing attacks by making the URL appear to come from us.
It might be a bit difficult, since there are a lot of different products and domains we could use this with. Maybe a simple list of domain names for now and expanding that into a model later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe the redirect exposes us to any potential attack vectors. the user.authenticate
method creates a cookie valid only on platform.pennlabs.org
to verify the user is logged in, that information doesn't get sent through the redirect.
Note CircleCI tests are failing due to the updated version of shortener. This is fixed in #49. |
This PR finalizes the Shibboleth login integration.
When a user logs in through Shibboleth, if it is their first time logging in a new user will be created with the supplied username, first name, last name, email. Note it appears Penn does not send an email through Shibboleth
On every login, user's affiliations are updated.
This PR should be heavily reviewed to ensure no authentication bypasses are possible.