Skip to content

Commit

Permalink
Use custom TokenPermission class
Browse files Browse the repository at this point in the history
  • Loading branch information
mdellweg committed Jun 26, 2020
1 parent d23235c commit 415b582
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 27 deletions.
47 changes: 28 additions & 19 deletions pulp_container/app/token_verification.py
Expand Up @@ -6,6 +6,7 @@

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import BasePermission, SAFE_METHODS


CONTENT_HOST = re.sub(r"(http://|https://)", "", settings.CONTENT_ORIGIN, count=1)
Expand All @@ -24,17 +25,14 @@ def _decode_token(encoded_token):
"audience": CONTENT_HOST,
}
with open(settings.PUBLIC_KEY_PATH, "rb") as public_key:
try:
decoded_token = jwt.decode(encoded_token, public_key.read(), **JWT_DECODER_CONFIG)
except jwt.exceptions.InvalidTokenError:
decoded_token = {"access": []}
decoded_token = jwt.decode(encoded_token, public_key.read(), **JWT_DECODER_CONFIG)
return decoded_token


def _access_scope(request):
repository_path = request.resolver_match.kwargs.get("path", "")

if request.method in ["GET", "HEAD", "OPTIONS"]:
if request.method in SAFE_METHODS:
access_action = "pull"
else:
access_action = "push"
Expand Down Expand Up @@ -79,21 +77,15 @@ def authenticate(self, request):
try:
authorization_header = request.headers["Authorization"]
except KeyError:
raise AuthenticationFailed(
detail="Access to the requested resource is not authorized. "
"A Bearer token is missing in a request header.",
)
# No authorization
return None
if not authorization_header.lower().startswith(self.keyword.lower() + " "):
raise AuthenticationFailed(
detail="Access to the requested resource is not authorized. "
"A Bearer token is missing in a request header.",
)
# Not our type of authorization
return None
token = authorization_header[len(self.keyword) + 1 :]
decoded_token = _decode_token(token)
repository_path, access_action = _access_scope(request)

# Without repository_path, there is no action
if not _contains_accessible_actions(decoded_token, repository_path, access_action):
try:
decoded_token = _decode_token(token)
except jwt.exceptions.InvalidTokenError:
raise AuthenticationFailed(
detail="Access to the requested resource is not authorized. "
"A provided Bearer token is invalid.",
Expand All @@ -107,7 +99,7 @@ def authenticate(self, request):
raise AuthenticationFailed("No such user")
else:
user = AnonymousUser()
return (user, None)
return (user, decoded_token)

def authenticate_header(self, request):
"""
Expand All @@ -126,3 +118,20 @@ def authenticate_header(self, request):
scope = f"repository:{repository_path}:{access_action}"
authenticate_string += f',scope="{scope}"'
return authenticate_string


class TokenPermission(BasePermission):
"""
Permission class to determine permissions based on the scope of a token.
"""

message = "Access to the requested resource is not authorized."

def has_permission(self, request, view):
"""
Decide upon permission based on token
"""
try:
return _contains_accessible_actions(request.auth, *_access_scope(request))
except Exception:
return False
14 changes: 7 additions & 7 deletions pulp_container/app/viewsets.py
Expand Up @@ -48,7 +48,7 @@

from pulp_container.app import models, serializers, tasks
from pulp_container.app.authorization import AuthorizationService
from pulp_container.app.token_verification import TokenAuthentication
from pulp_container.app.token_verification import TokenAuthentication, TokenPermission


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -600,7 +600,7 @@ class VersionView(ContainerRegistryApiMixin, APIView):
"""

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]

def get(self, request):
"""Handles GET requests for the /v2/ endpoint."""
Expand All @@ -616,7 +616,7 @@ class CatalogView(ContainerRegistryApiMixin, APIView):
"""

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]

def get(self, request):
"""Handles GET requests for the /v2/_catalog endpoint."""
Expand All @@ -633,7 +633,7 @@ class TagsListView(ContainerRegistryApiMixin, APIView):
"""

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]

def get(self, request, path):
"""
Expand Down Expand Up @@ -666,7 +666,7 @@ class BlobUploads(ContainerRegistryApiMixin, ViewSet):
queryset = models.Upload.objects.all()

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]

content_range_pattern = re.compile(r"^(?P<start>\d+)-(?P<end>\d+)$")

Expand Down Expand Up @@ -785,7 +785,7 @@ class Blobs(ContainerRegistryApiMixin, ViewSet):
"""

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]

def head(self, request, path, pk=None):
"""
Expand Down Expand Up @@ -830,7 +830,7 @@ class Manifests(ContainerRegistryApiMixin, ViewSet):
"""

authentication_classes = [TokenAuthentication]
permission_classes = []
permission_classes = [TokenPermission]
renderer_classes = [ManifestRenderer]
# The lookup regex does not allow /, ^, &, *, %, !, ~, @, #, +, =, ?
lookup_value_regex = "[^/^&*%!~@#+=?]+"
Expand Down
17 changes: 16 additions & 1 deletion pulp_container/tests/functional/api/test_push_content.py
Expand Up @@ -2,7 +2,7 @@
"""Tests that verify that images can be pushed to Pulp."""
import unittest

from pulp_smash import cli, config
from pulp_smash import cli, config, exceptions

from pulp_container.tests.functional.utils import (
gen_container_client,
Expand Down Expand Up @@ -34,13 +34,28 @@ def test_push_using_registry_client(self):
# TODO better handling of the "http://"
registry_name = cfg.get_base_url()[7:]
local_url = "/".join([registry_name, "foo/bar:1.0"])
# Be sure to not being logged in
registry.logout(registry_name)
# Pull an image with large blobs
registry.pull("centos:7")
# Tag it to registry under test
registry.tag("centos:7", local_url)
# Try to push without permission
with self.assertRaises(exceptions.CalledProcessError):
registry.push(local_url)
# Log in
registry.login("-u", "admin", "-p", "password", registry_name)
# Push successfully
registry.push(local_url)
# Pull while logged in
registry.pull(local_url)
# Log out
registry.logout(registry_name)
# Untag local copy
registry.rmi(local_url)
# Pull while logged out
registry.pull(local_url)
# cleanup
repository = repositories_api.list(name="foo/bar").results[0]
distribution = distributions_api.list(name="foo/bar").results[0]
self.addCleanup(repositories_api.delete, repository.pulp_href)
Expand Down

0 comments on commit 415b582

Please sign in to comment.