Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from pennlabs/feature/ipc
IPC on behalf of user
- Loading branch information
Showing
21 changed files
with
538 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from datetime import timedelta | ||
|
||
import requests | ||
from django.utils import timezone | ||
|
||
from accounts.settings import accounts_settings | ||
|
||
|
||
def authenticated_request(user, method, url, | ||
params=None, data=None, headers=None, cookies=None, files=None, | ||
auth=None, timeout=None, allow_redirects=True, proxies=None, | ||
hooks=None, stream=None, verify=None, cert=None, json=None): | ||
""" | ||
Helper method to make an authenticated request using the user's access token | ||
NOTE be ABSOLUTELY sure you only make a request to Penn Labs products, otherwise | ||
you will expose user's access tokens to the URL you provide and bad things will | ||
happen | ||
""" | ||
|
||
# Access token is expired. Try to refresh access token | ||
if user.accesstoken.expires_at < timezone.now(): | ||
if not _refresh_access_token(user): | ||
# Couldn't update the user's access token. Return a response with a 403 status code | ||
# as if the user didn't have access to the requested resource | ||
response = requests.models.Response | ||
response.status_code = 403 | ||
return response | ||
|
||
# Update Headers | ||
headers = {} if headers is None else headers | ||
headers['Authorization'] = f'Bearer {user.accesstoken.token}' | ||
|
||
# Make the request | ||
# We're only using a session to provide an easy wrapper to define the http method | ||
# GET, POST, etc in the method call. | ||
s = requests.Session() | ||
return s.request( | ||
method=method, | ||
url=url, | ||
params=params, | ||
data=data, | ||
headers=headers, | ||
cookies=cookies, | ||
files=files, | ||
auth=None, | ||
timeout=None, | ||
allow_redirects=allow_redirects, | ||
proxies=proxies, | ||
hooks=hooks, | ||
stream=stream, | ||
verify=verify, | ||
cert=cert, | ||
json=json, | ||
) | ||
|
||
|
||
def _refresh_access_token(user): | ||
""" | ||
Helper method to update a user's access token. Should be used when a user's | ||
access token has expired, but still has a valid refresh token. | ||
Returns: | ||
bool: true if the access token is updated, false otherwise. | ||
""" | ||
body = { | ||
'grant_type': 'refresh_token', | ||
'client_id': accounts_settings.CLIENT_ID, | ||
'client_secret': accounts_settings.CLIENT_SECRET, | ||
'refresh_token': user.refreshtoken.token, | ||
} | ||
try: | ||
data = requests.post( | ||
url=accounts_settings.PLATFORM_URL + '/accounts/token/', | ||
data=body | ||
) | ||
if data.status_code == 200: # Access token refreshed successfully | ||
data = data.json() | ||
# Update Access token | ||
user.accesstoken.token = data['access_token'] | ||
user.accesstoken.expires_at = timezone.now() + timedelta(seconds=data['expires_in']) | ||
user.accesstoken.save() | ||
|
||
# Update Refresh Token | ||
user.refreshtoken.token = data['refresh_token'] | ||
user.refreshtoken.save() | ||
|
||
return True | ||
except requests.exceptions.RequestException: # Can't connect to platform | ||
return False | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import requests | ||
from django.contrib.auth import get_user_model | ||
from django.http import HttpResponseForbidden | ||
|
||
from accounts.settings import accounts_settings | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
class OAuth2TokenMiddleware: | ||
""" | ||
When a view is requested using a Bearer Authorization header, | ||
check and set request.user to the owner of said token | ||
""" | ||
|
||
def __init__(self, get_response): | ||
self.get_response = get_response | ||
|
||
def __call__(self, request): | ||
authorization = request.META.get('HTTP_AUTHORIZATION') | ||
if authorization and ' ' in authorization: | ||
auth_type, token = authorization.split() | ||
if auth_type == 'Bearer': # Only validate if Authorization header type is Bearer | ||
body = {'token': token} | ||
headers = {'Authorization': 'Bearer {}'.format(token)} | ||
try: | ||
data = requests.post( | ||
url=accounts_settings.PLATFORM_URL + '/accounts/introspect/', | ||
headers=headers, | ||
data=body | ||
) | ||
if data.status_code == 200: # Access token is valid | ||
data = data.json() | ||
user = User.objects.filter(id=int(data['user']['pennid'])) | ||
if len(user) == 1: # User has an account on this product | ||
request.user = user.first() | ||
else: # Access token is invalid | ||
return HttpResponseForbidden() | ||
except requests.exceptions.RequestException: # Can't connect to platform | ||
# Throw a 403 because we can't verify the incoming access token so we | ||
# treat it as invalid. Ideally platform will never go down, so this | ||
# should never happen. | ||
return HttpResponseForbidden() | ||
|
||
response = self.get_response(request) | ||
return response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Generated by Django 2.2.7 on 2019-11-26 19:08 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='RefreshToken', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('token', models.CharField(blank=True, max_length=255, null=True)), | ||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='AccessToken', | ||
fields=[ | ||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
('token', models.CharField(blank=True, max_length=255, null=True)), | ||
('expires_at', models.DateTimeField()), | ||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from django.conf import settings | ||
from django.db import models | ||
|
||
|
||
class AccessToken(models.Model): | ||
token = models.CharField(max_length=255, blank=True, null=True) | ||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) | ||
expires_at = models.DateTimeField() | ||
|
||
def __str__(self): | ||
return str(self.token) | ||
|
||
|
||
class RefreshToken(models.Model): | ||
token = models.CharField(max_length=255, blank=True, null=True) | ||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) | ||
|
||
def __str__(self): | ||
return str(self.token) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.