Skip to content
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

Oauth2 to OIDC #2448

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions apps/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,5 @@
from django.conf.urls import url

from apps.shop import views as shop_views
from apps.sso import views as sso_views

urlpatterns = [
url(r"^v1/rfid/$", shop_views.SetRFIDView.as_view(), name="set_rfid"),
url(r"^v1/auth/$", sso_views.TokenView.as_view(), name="oauth2_provider_token"),
]
urlpatterns = [url(r"^v1/rfid/$", shop_views.SetRFIDView.as_view(), name="set_rfid")]
5 changes: 1 addition & 4 deletions apps/authentication/migrations/0002_auto_20150304_2211.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's that smart to change migrations.

Removing the dependency on this migration doesn't remove the tables/columns from the database. A separate migration is required to do this. After that migration is done, a new change can be done where the requirement is removed, and migrations can be squashed to remove the dependency.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just saw that you've done it correctly with the sso app migration, good. But I still think this migration shouldn't be changed (and therefore all the sso migrations have to be kept), until it is squashed so that sso is fully removed. It's a bit of a long run, but I don't think it matters that much to keep the migrations in for a while. Maybe @duvholt could chime in on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sklirg I see! So it would be better to not involve this and perhaps add something like it in it's own PR for example?

class Migration(migrations.Migration):

dependencies = [
("sso", "0002_auto_20171127_1304"),
("authentication", "0001_initial"),
]
dependencies = [("authentication", "0001_initial")]

operations = [
migrations.AlterField(
Expand Down
4 changes: 3 additions & 1 deletion apps/dashboard/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

from apps.dashboard import views

urlpatterns = [url(r"^$", views.index, name="dashboard_index")]
app_name = "dashboard"

urlpatterns = [url(r"^$", views.index, name="index")]
Empty file removed apps/oauth2_provider/__init__.py
Empty file.
57 changes: 0 additions & 57 deletions apps/oauth2_provider/test.py

This file was deleted.

56 changes: 56 additions & 0 deletions apps/online_oidc_provider/authentication.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.core.exceptions import ImproperlyConfigured
from oidc_provider.lib.utils.oauth2 import extract_access_token
from oidc_provider.models import Token
from rest_framework import authentication, exceptions
from rest_framework.exceptions import PermissionDenied
from rest_framework.permissions import BasePermission


class OidcOauth2Auth(authentication.BaseAuthentication):
Expand All @@ -20,3 +23,56 @@ def authenticate(self, request):
raise exceptions.AuthenticationFailed("The oauth2 token has expired")

return oauth2_token.user, None


class TokenHasScope(BasePermission):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we are writing our own version of TokenHasScope, instead of just using DRF's built in one? If so that should be clearly stated in a comment here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK DRF does not have a built-in TokenHasScope. We previously used oauth2_provider's TokenHasScope, made by Django OAuth Toolkit to provide a support layer for DRF. As we are no longer using oauth2_provider, we cannot use their TokenHasScope either.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also a bit vary of this. Could you do some more research with regards to this? Could we depend on oauth2_provider for that function alone, or is it tightly coupled to its own OAuth2 provider? Do we want to depend on it, or do we want to run our own fork of the code? At least get some tests up if we do :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I've been digging through the code for the OAuth2 provider, and the answer is a big I don't know. I guess I can continue testing some more, but it seems that if we are to use the TokenHasScope from oauth2_provider, we will need duplicates of settings, to ensure that the oidc_provider and oauth2_provider has the same settings, as TokenHasScope relies on settings for oauth2_provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class is by the way taken from this stale issue at oidc_provider's repo juanifioren/django-oidc-provider#342

Other issues related to this worth a read:
juanifioren/django-oidc-provider#242
juanifioren/django-oidc-provider#78

"""
The request is authenticated and the token used has the right scope
"""

def has_permission(self, request, view):

try:
token = Token.objects.get(
access_token=extract_access_token(request)
) # The Token object retrieved from access_token

if token.has_expired():
self.message = {
"detail": PermissionDenied.default_detail,
"error": "Token has expired",
}
return False

except Token.DoesNotExist:
self.message = {
"detail": PermissionDenied.default_detail,
"error": "No token provided or token does not exist",
}

return False

if hasattr(token, "scope"):
required_scopes = self.get_scopes(request, view)

if set(required_scopes).issubset(set(token.scope)):
return True

# Provide information about required scope
self.message = {
"detail": PermissionDenied.default_detail,
"error": "Permission denied, required_scopes: "
+ str(list(required_scopes)),
}

return False

return False

def get_scopes(self, request, view):
try:
return getattr(view, "required_scopes")
except AttributeError:
raise ImproperlyConfigured(
"TokenHasScope requires the view to define the required_scopes attribute"
)
9 changes: 7 additions & 2 deletions apps/online_oidc_provider/claims.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.utils.translation import ugettext as _
from oidc_provider.lib.claims import ScopeClaims

from apps.sso.userinfo import Onlineweb4Userinfo
from apps.online_oidc_provider.userinfo import Onlineweb4Userinfo


def userinfo(claims, user):
Expand Down Expand Up @@ -33,7 +33,7 @@ def userinfo(claims, user):
class Onlineweb4ScopeClaims(ScopeClaims):

info_onlineweb4 = (
_("Onlineweb4"),
"Onlineweb4",
_(
"Informasjon om brukerprofilen din på online.ntnu.no, "
"medlemskapet ditt i Online og din studieretning."
Expand All @@ -42,3 +42,8 @@ class Onlineweb4ScopeClaims(ScopeClaims):

def scope_onlineweb4(self):
return Onlineweb4Userinfo(self.user).oidc()

info_nibble = ("Nibble", _("Informasjon om dine kjøp og saldo på Nibble"))

def scope_nibble(self):
return {"test": "this data was returned"}
File renamed without changes.
5 changes: 2 additions & 3 deletions apps/shop/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from apps.authentication.models import Email, OnlineUser
from apps.inventory.models import Item
from apps.oauth2_provider.test import OAuth2TestCase
from apps.shop.models import MagicToken


Expand Down Expand Up @@ -34,8 +33,8 @@ def test_item_detail(self):
self.assertEqual(response.status_code, status.HTTP_200_OK)


class ShopSetRFIDTestCase(OAuth2TestCase):
scopes = ["shop.readwrite", "read", "write"]
class ShopSetRFIDTestCase:
scopes = ["nibble"]

def setUp(self):
super().setUp()
Expand Down
18 changes: 9 additions & 9 deletions apps/shop/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.urls import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import FormView
from oauth2_provider.contrib.rest_framework import OAuth2Authentication, TokenHasScope
from rest_framework import mixins, status, viewsets
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
Expand All @@ -17,6 +16,7 @@
from apps.authentication.models import Email
from apps.authentication.models import OnlineUser as User
from apps.inventory.models import Item
from apps.online_oidc_provider.authentication import OidcOauth2Auth, TokenHasScope
from apps.payment.models import PaymentTransaction
from apps.shop.forms import SetRFIDForm
from apps.shop.models import MagicToken, OrderLine
Expand All @@ -33,9 +33,9 @@
class OrderLineViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin):
queryset = OrderLine.objects.all()
serializer_class = OrderLineSerializer
authentication_classes = [OAuth2Authentication]
authentication_classes = [OidcOauth2Auth]
permission_classes = [TokenHasScope]
required_scopes = ["shop.readwrite"]
required_scopes = ["nibble"]

def create(self, request):
serializer = OrderLineSerializer(data=request.data)
Expand All @@ -51,9 +51,9 @@ def perform_create(self, serializer):
class TransactionViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin):
queryset = PaymentTransaction.objects.all()
serializer_class = TransactionSerializer
authentication_classes = [OAuth2Authentication]
authentication_classes = [OidcOauth2Auth]
permission_classes = [TokenHasScope]
required_scopes = ["shop.readwrite"]
required_scopes = ["nibble"]

def create(self, request):
serializer = TransactionSerializer(data=request.data)
Expand All @@ -80,9 +80,9 @@ class UserViewSet(
):
queryset = User.objects.all()
serializer_class = UserSerializer
authentication_classes = [OAuth2Authentication]
authentication_classes = [OidcOauth2Auth]
permission_classes = [TokenHasScope]
required_scopes = ["shop.readwrite"]
required_scopes = ["nibble"]
filterset_fields = ("rfid",)


Expand All @@ -96,9 +96,9 @@ class InventoryViewSet(


class SetRFIDView(APIView):
authentication_classes = [OAuth2Authentication]
authentication_classes = [OidcOauth2Auth]
permission_classes = [TokenHasScope]
required_scopes = ["shop.readwrite"]
required_scopes = ["nibble"]

def post(self, request):
username = request.data.get("username", "").lower()
Expand Down
1 change: 0 additions & 1 deletion apps/sso/__init__.py

This file was deleted.

6 changes: 0 additions & 6 deletions apps/sso/appconfig.py

This file was deleted.

1 change: 0 additions & 1 deletion apps/sso/dashboard/__init__.py

This file was deleted.

49 changes: 0 additions & 49 deletions apps/sso/dashboard/forms.py

This file was deleted.

15 changes: 0 additions & 15 deletions apps/sso/dashboard/urls.py

This file was deleted.

Loading