Handle empty scopes and always send the WWW-Authenticate header, as per spec

Fixes #1045
This commit is contained in:
Joseph Schorr 2015-12-09 15:07:37 -05:00
parent c8f43ed08e
commit ca7d36bf14
10 changed files with 47 additions and 41 deletions

View file

@ -9,16 +9,13 @@ import features
from app import metric_queue
from endpoints.decorators import anon_protect, anon_allowed
from endpoints.v2.errors import V2RegistryException
from auth.jwt_auth import process_jwt_auth
from auth.auth_context import get_grant_context
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
AdministerRepositoryPermission)
from data import model
from util.http import abort
from util.saas.metricqueue import time_blueprint
from util import get_app_url
from app import app
from auth.registry_jwt_auth import process_registry_jwt_auth, get_auth_headers
logger = logging.getLogger(__name__)
v2_bp = Blueprint('v2', __name__)
@ -75,21 +72,15 @@ def route_show_if(value):
@v2_bp.route('/')
@route_show_if(features.ADVERTISE_V2)
@process_jwt_auth
@process_registry_jwt_auth
@anon_allowed
def v2_support_enabled():
response = make_response('true', 200)
if get_grant_context() is None:
response = make_response('true', 401)
realm_auth_path = url_for('v2.generate_registry_jwt')
authenticate = 'Bearer realm="{0}{1}",service="{2}"'.format(get_app_url(app.config),
realm_auth_path,
app.config['SERVER_HOSTNAME'])
response.headers['WWW-Authenticate'] = authenticate
response.headers['Docker-Distribution-API-Version'] = 'registry/2.0'
response.headers.extend(get_auth_headers())
return response

View file

@ -4,12 +4,12 @@ import re
from flask import make_response, url_for, request, redirect, Response, abort as flask_abort
from app import storage, app
from auth.registry_jwt_auth import process_registry_jwt_auth
from data import model, database
from digest import digest_tools
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write, get_input_stream
from endpoints.v2.errors import (BlobUnknown, BlobUploadInvalid, BlobUploadUnknown, Unsupported,
NameUnknown)
from auth.jwt_auth import process_jwt_auth
from endpoints.decorators import anon_protect
from util.cache import cache_control
from util.registry.filelike import wrap_with_handler, StreamSlice
@ -53,7 +53,7 @@ def _base_blob_fetch(namespace, repo_name, digest):
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['HEAD'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_read
@anon_protect
@cache_control(max_age=31436000)
@ -68,7 +68,7 @@ def check_blob_exists(namespace, repo_name, digest):
@v2_bp.route(BLOB_DIGEST_ROUTE, methods=['GET'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_read
@anon_protect
@cache_control(max_age=31536000)
@ -101,7 +101,7 @@ def _render_range(num_uploaded_bytes, with_bytes_prefix=True):
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/', methods=['POST'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def start_blob_upload(namespace, repo_name):
@ -134,7 +134,7 @@ def start_blob_upload(namespace, repo_name):
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/<upload_uuid>', methods=['GET'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def fetch_existing_upload(namespace, repo_name, upload_uuid):
@ -290,7 +290,7 @@ def _finish_upload(namespace, repo_name, upload_obj, expected_digest):
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/<upload_uuid>', methods=['PATCH'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def upload_chunk(namespace, repo_name, upload_uuid):
@ -308,7 +308,7 @@ def upload_chunk(namespace, repo_name, upload_uuid):
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/<upload_uuid>', methods=['PUT'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def monolithic_upload_or_last_chunk(namespace, repo_name, upload_uuid):
@ -326,7 +326,7 @@ def monolithic_upload_or_last_chunk(namespace, repo_name, upload_uuid):
@v2_bp.route('/<namespace>/<repo_name>/blobs/uploads/<upload_uuid>', methods=['DELETE'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def cancel_upload(namespace, repo_name, upload_uuid):
@ -345,7 +345,7 @@ def cancel_upload(namespace, repo_name, upload_uuid):
@v2_bp.route('/<namespace>/<repo_name>/blobs/<digest>', methods=['DELETE'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def delete_digest(namespace, repo_name, upload_uuid):

View file

@ -9,7 +9,7 @@ from jwkest.jws import SIGNER_ALGS, keyrep
from datetime import datetime
from app import docker_v2_signing_key
from auth.jwt_auth import process_jwt_auth
from auth.registry_jwt_auth import process_registry_jwt_auth
from endpoints.decorators import anon_protect
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnverified,
@ -212,7 +212,7 @@ class SignedManifestBuilder(object):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['GET'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_read
@anon_protect
def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
@ -241,7 +241,7 @@ def fetch_manifest_by_tagname(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['GET'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_read
@anon_protect
def fetch_manifest_by_digest(namespace, repo_name, manifest_ref):
@ -261,7 +261,7 @@ def fetch_manifest_by_digest(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_TAGNAME_ROUTE, methods=['PUT'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
@ -277,7 +277,7 @@ def write_manifest_by_tagname(namespace, repo_name, manifest_ref):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['PUT'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def write_manifest_by_digest(namespace, repo_name, manifest_ref):
@ -375,7 +375,7 @@ def _write_manifest(namespace, repo_name, manifest):
@v2_bp.route(MANIFEST_DIGEST_ROUTE, methods=['DELETE'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_write
@anon_protect
def delete_manifest_by_digest(namespace, repo_name, manifest_ref):

View file

@ -1,14 +1,14 @@
from flask import jsonify, url_for
from auth.registry_jwt_auth import process_registry_jwt_auth
from endpoints.v2 import v2_bp, require_repo_read
from endpoints.v2.errors import NameUnknown
from endpoints.v2.v2util import add_pagination
from auth.jwt_auth import process_jwt_auth
from endpoints.decorators import anon_protect
from data import model
@v2_bp.route('/<namespace>/<repo_name>/tags/list', methods=['GET'])
@process_jwt_auth
@process_registry_jwt_auth
@require_repo_read
@anon_protect
def list_all_tags(namespace, repo_name):

View file

@ -9,7 +9,7 @@ from cachetools import lru_cache
from app import app
from data import model
from auth.auth import process_auth
from auth.jwt_auth import build_context_and_subject
from auth.registry_jwt_auth import build_context_and_subject
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
from auth.permissions import (ModifyRepositoryPermission, ReadRepositoryPermission,
CreateRepositoryPermission)
@ -49,7 +49,7 @@ def generate_registry_jwt():
audience_param = request.args.get('service')
logger.debug('Request audience: %s', audience_param)
scope_param = request.args.get('scope')
scope_param = request.args.get('scope') or ''
logger.debug('Scope request: %s', scope_param)
user = get_authenticated_user()
@ -62,7 +62,8 @@ def generate_registry_jwt():
logger.debug('Authenticated OAuth token: %s', oauthtoken)
access = []
if scope_param is not None:
if len(scope_param) > 0:
match = SCOPE_REGEX.match(scope_param)
if match is None:
logger.debug('Match: %s', match)