Example #1
0
  def wrapper(*args, **kwargs):
    # Skip if anonymous access is allowed.
    if features.ANONYMOUS_ACCESS or '__anon_allowed' in dir(func):
      return func(*args, **kwargs)

    # Check for validated context. If none exists, fail with a 401.
    if get_authenticated_context() and not get_authenticated_context().is_anonymous:
      return func(*args, **kwargs)

    abort(401, message='Anonymous access is not allowed')
Example #2
0
def abort(status_code, message=None, issue=None, headers=None, **kwargs):
    message = str(message) % kwargs if message else DEFAULT_MESSAGE.get(
        status_code, "")

    params = dict(request.view_args or {})
    params.update(kwargs)

    params["url"] = request.url
    params["status_code"] = status_code
    params["message"] = message

    # Add the user information.
    auth_context = get_authenticated_context()
    if auth_context is not None:
        message = "%s (authorized: %s)" % (message, auth_context.description)

    # Log the abort.
    logger.error("Error %s: %s; Arguments: %s" %
                 (status_code, message, params))

    # Create the final response data and message.
    data = {}
    data["error"] = message

    if headers is None:
        headers = {}

    _abort(status_code, data, message, headers)
Example #3
0
def get_user():
    context = get_authenticated_context()
    if not context or context.is_anonymous:
        abort(404)

    return jsonify({
        "username": context.credential_username,
        "email": None,
    })
Example #4
0
def catalog_search(start_id, limit, pagination_callback):
    def _load_catalog():
        include_public = bool(features.PUBLIC_CATALOG)
        if not include_public and not get_authenticated_user():
            return []

        username = get_authenticated_user().username if get_authenticated_user(
        ) else None
        if username and not get_authenticated_user().enabled:
            return []

        query = model.repository.get_visible_repositories(
            username,
            kind_filter="image",
            include_public=include_public,
            start_id=start_id,
            limit=limit + 1,
        )
        # NOTE: The repository ID is in `rid` (not `id`) here, as per the requirements of
        # the `get_visible_repositories` call.
        return [
            Repository(repo.rid, repo.namespace_user.username,
                       repo.name)._asdict() for repo in query
        ]

    context_key = get_authenticated_context(
    ).unique_key if get_authenticated_context() else None
    catalog_cache_key = cache_key.for_catalog_page(context_key, start_id,
                                                   limit,
                                                   model_cache.cache_config)
    visible_repositories = [
        Repository(**repo_dict)
        for repo_dict in model_cache.retrieve(catalog_cache_key, _load_catalog)
    ]

    response = jsonify({
        "repositories": [
            "%s/%s" % (repo.namespace_name, repo.name)
            for repo in visible_repositories
        ][0:limit],
    })

    pagination_callback(visible_repositories, response)
    return response
Example #5
0
def test_apply_context(get_entity, entity_kind, app):
    assert get_authenticated_context() is None

    entity = get_entity()
    args = {}
    args[entity_kind] = entity

    result = ValidateResult(AuthKind.basic, **args)
    result.apply_to_context()

    expected_user = entity if entity_kind == 'user' or entity_kind == 'robot' else None
    if entity_kind == 'oauthtoken':
        expected_user = entity.authorized_user

    if entity_kind == 'appspecifictoken':
        expected_user = entity.user

    expected_token = entity if entity_kind == 'token' else None
    expected_oauth = entity if entity_kind == 'oauthtoken' else None
    expected_appspecifictoken = entity if entity_kind == 'appspecifictoken' else None
    expected_grant = entity if entity_kind == 'signed_data' else None

    assert get_authenticated_context().authed_user == expected_user
    assert get_authenticated_context().token == expected_token
    assert get_authenticated_context().oauthtoken == expected_oauth
    assert get_authenticated_context(
    ).appspecifictoken == expected_appspecifictoken
    assert get_authenticated_context().signed_data == expected_grant
Example #6
0
def v2_support_enabled():
  docker_ver = docker_version(request.user_agent.string)

  # Check if our version is one of the blacklisted versions, if we can't
  # identify the version (None) we will fail open and assume that it is
  # newer and therefore should not be blacklisted.
  if docker_ver is not None and Spec(app.config['BLACKLIST_V2_SPEC']).match(docker_ver):
    abort(404)

  response = make_response('true', 200)

  if get_authenticated_context() is None:
    response = make_response('true', 401)

  response.headers.extend(get_auth_headers())
  return response
Example #7
0
def abort(status_code, message=None, issue=None, headers=None, **kwargs):
    message = (str(message) %
               kwargs if message else DEFAULT_MESSAGE.get(status_code, ''))

    params = dict(request.view_args or {})
    params.update(kwargs)

    params['url'] = request.url
    params['status_code'] = status_code
    params['message'] = message

    # Add the user information.
    auth_context = get_authenticated_context()
    if auth_context is not None:
        message = '%s (authorized: %s)' % (message, auth_context.description)

    # Log the abort.
    logger.error('Error %s: %s; Arguments: %s' %
                 (status_code, message, params))

    # Calculate the issue URL (if the issue ID was supplied).
    issue_url = None
    if issue:
        issue_url = 'http://docs.quay.io/issues/%s.html' % (issue)

    # Create the final response data and message.
    data = {}
    data['error'] = message

    if issue_url:
        data['info_url'] = issue_url

    if headers is None:
        headers = {}

    _abort(status_code, data, message, headers)
Example #8
0
def generate_registry_jwt(auth_result):
    """
    This endpoint will generate a JWT conforming to the Docker Registry v2 Auth Spec:

    https://docs.docker.com/registry/spec/auth/token/
    """
    audience_param = request.args.get("service")
    logger.debug("Request audience: %s", audience_param)

    scope_params = request.args.getlist("scope") or []
    logger.debug("Scope request: %s", scope_params)

    auth_header = request.headers.get("authorization", "")
    auth_credentials_sent = bool(auth_header)

    # Load the auth context and verify thatg we've directly received credentials.
    has_valid_auth_context = False
    if get_authenticated_context():
        has_valid_auth_context = not get_authenticated_context().is_anonymous

    if auth_credentials_sent and not has_valid_auth_context:
        # The auth credentials sent for the user are invalid.
        raise InvalidLogin(auth_result.error_message)

    if not has_valid_auth_context and len(scope_params) == 0:
        # In this case, we are doing an auth flow, and it's not an anonymous pull.
        logger.debug("No user and no token sent for empty scope list")
        raise Unauthorized()

    # Build the access list for the authenticated context.
    access = []
    scope_results = []
    for scope_param in scope_params:
        scope_result = _authorize_or_downscope_request(scope_param,
                                                       has_valid_auth_context)
        if scope_result is None:
            continue

        scope_results.append(scope_result)
        access.append({
            "type": "repository",
            "name": scope_result.registry_and_repo,
            "actions": scope_result.actions,
        })

    # Issue user events.
    user_event_data = {
        "action": "login",
    }

    # Set the user event data for when authed.
    if len(scope_results) > 0:
        if "push" in scope_results[0].actions:
            user_action = "push_start"
        elif "pull" in scope_results[0].actions:
            user_action = "pull_start"
        else:
            user_action = "login"

        user_event_data = {
            "action": user_action,
            "namespace": scope_results[0].namespace,
            "repository": scope_results[0].repository,
        }

    # Send the user event.
    if get_authenticated_user() is not None:
        event = userevents.get_event(get_authenticated_user().username)
        event.publish_event_data("docker-cli", user_event_data)

    # Build the signed JWT.
    tuf_roots = {
        "%s/%s" % (scope_result.namespace, scope_result.repository):
        scope_result.tuf_root
        for scope_result in scope_results
    }
    context, subject = build_context_and_subject(get_authenticated_context(),
                                                 tuf_roots=tuf_roots)
    token = generate_bearer_token(audience_param, subject, context, access,
                                  TOKEN_VALIDITY_LIFETIME_S, instance_keys)
    return jsonify({"token": token})
Example #9
0
def track_and_log(event_name,
                  repo_obj,
                  analytics_name=None,
                  analytics_sample=1,
                  **kwargs):
    repo_name = repo_obj.name
    namespace_name = repo_obj.namespace_name
    metadata = {
        "repo": repo_name,
        "namespace": namespace_name,
        "user-agent": request.user_agent.string,
    }
    metadata.update(kwargs)

    is_free_namespace = False
    if hasattr(repo_obj, "is_free_namespace"):
        is_free_namespace = repo_obj.is_free_namespace

    # Add auth context metadata.
    analytics_id = "anonymous"
    auth_context = get_authenticated_context()
    if auth_context is not None:
        analytics_id, context_metadata = auth_context.analytics_id_and_public_metadata(
        )
        metadata.update(context_metadata)

    # Publish the user event (if applicable)
    logger.debug("Checking publishing %s to the user events system",
                 event_name)
    if auth_context and auth_context.has_nonrobot_user:
        logger.debug("Publishing %s to the user events system", event_name)
        user_event_data = {
            "action": event_name,
            "repository": repo_name,
            "namespace": namespace_name,
        }

        event = userevents.get_event(auth_context.authed_user.username)
        event.publish_event_data("docker-cli", user_event_data)

    # Save the action to mixpanel.
    if random.random() < analytics_sample:
        if analytics_name is None:
            analytics_name = event_name

        logger.debug("Logging the %s to analytics engine", analytics_name)

        request_parsed = urlparse(request.url_root)
        extra_params = {
            "repository": "%s/%s" % (namespace_name, repo_name),
            "user-agent": request.user_agent.string,
            "hostname": request_parsed.hostname,
        }

        analytics.track(analytics_id, analytics_name, extra_params)

    # Add the resolved information to the metadata.
    logger.debug("Resolving IP address %s", get_request_ip())
    resolved_ip = ip_resolver.resolve_ip(get_request_ip())
    if resolved_ip is not None:
        metadata["resolved_ip"] = resolved_ip._asdict()

    logger.debug("Resolved IP address %s", get_request_ip())

    # Log the action to the database.
    logger.debug("Logging the %s to logs system", event_name)
    try:
        logs_model.log_action(
            event_name,
            namespace_name,
            performer=get_authenticated_user(),
            ip=get_request_ip(),
            metadata=metadata,
            repository=repo_obj,
            is_free_namespace=is_free_namespace,
        )
        logger.debug("Track and log of %s complete", event_name)
    except ReadOnlyModeException:
        pass