Skip to content

Commit

Permalink
Lock down OAuth2 views
Browse files Browse the repository at this point in the history
Doing this properly with RBAC depends on ansible#424.

For now, we limit Application views to superusers. We limit Token
views using a custom DRF permission class based on what was specified
in AWX's access.py

Signed-off-by: Rick Elrod <[email protected]>
  • Loading branch information
relrod committed Jun 4, 2024
1 parent b8edc8c commit 4890a1b
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-06-04 12:32

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dab_oauth2_provider', '0003_remove_oauth2application_logo_data'),
]

operations = [
migrations.AlterField(
model_name='oauth2accesstoken',
name='scope',
field=models.CharField(blank=True, default='write', help_text="Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write'].", max_length=32),
),
]
6 changes: 0 additions & 6 deletions ansible_base/oauth2_provider/models/access_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ class Meta(oauth2_models.AbstractAccessToken.Meta):
ordering = ('id',)
swappable = "OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL"

SCOPE_CHOICES = [
('read', _('Read')),
('write', _('Write')),
]

user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -60,7 +55,6 @@ class Meta(oauth2_models.AbstractAccessToken.Meta):
blank=True,
default='write',
max_length=32,
choices=SCOPE_CHOICES,
help_text=_("Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write']."),
)
token = prevent_search(
Expand Down
2 changes: 1 addition & 1 deletion ansible_base/oauth2_provider/serializers/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
class BaseOAuth2TokenSerializer(CommonModelSerializer):
refresh_token = SerializerMethodField()
token = SerializerMethodField()
ALLOWED_SCOPES = [x[0] for x in OAuth2AccessToken.SCOPE_CHOICES]
ALLOWED_SCOPES = ['read', 'write']

class Meta:
model = OAuth2AccessToken
Expand Down
7 changes: 5 additions & 2 deletions ansible_base/oauth2_provider/views/application.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from rest_framework import permissions
from rest_framework.viewsets import ModelViewSet

from ansible_base.lib.utils.views.django_app_api import AnsibleBaseDjangoAppApiView
from ansible_base.lib.utils.views.permissions import IsSuperuser
from ansible_base.oauth2_provider.models import OAuth2Application
from ansible_base.oauth2_provider.serializers import OAuth2ApplicationSerializer


class OAuth2ApplicationViewSet(AnsibleBaseDjangoAppApiView, ModelViewSet):
queryset = OAuth2Application.objects.all()
serializer_class = OAuth2ApplicationSerializer
permission_classes = [permissions.IsAuthenticated]
permission_classes = [IsSuperuser]

def filter_queryset(self, queryset):
return super().filter_queryset(OAuth2Application.access_qs(self.request.user, queryset=queryset))
38 changes: 37 additions & 1 deletion ansible_base/oauth2_provider/views/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,43 @@ def create_token_response(self, request):
return request.build_absolute_uri(), {}, str(e), e.status_code


class OAuth2TokenPermission(permissions.BasePermission):
# An app token is a token that has an application attached to it
# A personal access token (PAT) is a token with no application attached to it
# With that in mind:
# - An app token can be read, changed, or deleted if:
# - I am the superuser
# - I am the admin of the organization of the application of the token
# - I am the user of the token
# - An app token can be created if:
# - I am the superuser
# - A PAT can be read, changed, or deleted if:
# - I am the superuser
# - I am the user of the token
# - A PAT can be created if:
# - I am a user

def has_permission(self, request, view):
# Handle PAT and app token creation separately
if request.method == 'POST':
if request.data.get('application'):
return request.user.is_superuser
return request.user.is_authenticated
return request.user.is_authenticated

def has_object_permission(self, request, view, obj):
if request.method in ['GET', 'PATCH', 'DELETE']:
if request.user.is_superuser:
return True
if obj.application:
if obj.application.organization.admins.filter(id=request.user.id).exists():
return True
return obj.user == request.user
return request.user == obj.user
return False


class OAuth2TokenViewSet(ModelViewSet, AnsibleBaseDjangoAppApiView):
queryset = OAuth2AccessToken.objects.all()
serializer_class = OAuth2TokenSerializer
permission_classes = [permissions.IsAuthenticated]
permission_classes = [OAuth2TokenPermission]
Loading

0 comments on commit 4890a1b

Please sign in to comment.