예제 #1
0
파일: registry.py 프로젝트: zhill/quay
def get_image_ancestry(namespace, repository, image_id, headers):
    logger.debug("Checking repo permissions")
    permission = ReadRepositoryPermission(namespace, repository)
    repository_ref = registry_model.lookup_repository(namespace,
                                                      repository,
                                                      kind_filter="image")
    if not permission.can() and not (repository_ref is not None
                                     and repository_ref.is_public):
        abort(403)

    logger.debug("Looking up repo image")
    legacy_image = registry_model.get_legacy_image(repository_ref,
                                                   image_id,
                                                   include_parents=True)
    if legacy_image is None:
        abort(404,
              "Image %(image_id)s not found",
              issue="unknown-image",
              image_id=image_id)

    # NOTE: We can not use jsonify here because we are returning a list not an object.
    ancestor_ids = [legacy_image.docker_image_id
                    ] + [a.docker_image_id for a in legacy_image.parents]
    response = make_response(json.dumps(ancestor_ids), 200)
    response.headers.extend(headers)
    return response
예제 #2
0
파일: registry.py 프로젝트: zhill/quay
def get_image_json(namespace, repository, image_id, headers):
    logger.debug("Checking repo permissions")
    permission = ReadRepositoryPermission(namespace, repository)
    repository_ref = registry_model.lookup_repository(namespace,
                                                      repository,
                                                      kind_filter="image")
    if not permission.can() and not (repository_ref is not None
                                     and repository_ref.is_public):
        abort(403)

    logger.debug("Looking up repo image")
    legacy_image = registry_model.get_legacy_image(repository_ref,
                                                   image_id,
                                                   include_blob=True)
    if legacy_image is None:
        flask_abort(404)

    size = legacy_image.blob.compressed_size
    if size is not None:
        # Note: X-Docker-Size is optional and we *can* end up with a NULL image_size,
        # so handle this case rather than failing.
        headers["X-Docker-Size"] = str(size)

    response = make_response(legacy_image.v1_metadata_string, 200)
    response.headers.extend(headers)
    return response
예제 #3
0
    def wrapper(*args, **kwargs):
        if 'namespace' not in session or 'repository' not in session:
            logger.error(
                'Unable to load namespace or repository from session: %s',
                session)
            abort(400, message='Missing namespace in request')

        return func(session['namespace'], session['repository'], *args,
                    **kwargs)
예제 #4
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,
    })
예제 #5
0
    def wrapper(*args, **kwargs):
        if "namespace" not in session or "repository" not in session:
            logger.error(
                "Unable to load namespace or repository from session: %s",
                session)
            abort(400, message="Missing namespace in request")

        return func(session["namespace"], session["repository"], *args,
                    **kwargs)
예제 #6
0
    def wrapper(*args, **kwargs):
        result = validate_session_cookie()
        if result.has_nonrobot_user:
            result.apply_to_context()
            authentication_count.labels(result.kind, True).inc()
            return func(*args, **kwargs)
        elif not result.missing:
            authentication_count.labels(result.kind, False).inc()

        abort(401, message="Method requires login and no valid login could be loaded.")
예제 #7
0
    def wrapper(namespace_name, repo_name, *args, **kwargs):
        namespace = model.user.get_namespace_user(namespace_name)
        is_namespace_enabled = namespace is not None and namespace.enabled
        if not is_namespace_enabled:
            abort(
                400,
                message=
                "Namespace is disabled. Please contact your system administrator."
            )

        return f(namespace_name, repo_name, *args, **kwargs)
예제 #8
0
파일: decorators.py 프로젝트: zhill/quay
 def wrapped(*args, **kwargs):
     namespace_name, repo_name = get_reponame_method(*args, **kwargs)
     image_repo = model.repository.get_repository(
         namespace_name, repo_name, kind_filter="image"
     )
     if image_repo is not None:
         logger.debug("Tried to invoked a CNR method on an image repository")
         abort(
             405,
             message="Cannot push an application to an image repository with the same name",
         )
     return func(*args, **kwargs)
예제 #9
0
파일: registry.py 프로젝트: epasham/quay-1
def get_image_layer(namespace, repository, image_id, headers):
    permission = ReadRepositoryPermission(namespace, repository)
    repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image")

    logger.debug("Checking repo permissions")
    if permission.can() or (repository_ref is not None and repository_ref.is_public):
        if repository_ref is None:
            abort(404)

        legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True)
        if legacy_image is None:
            abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id)

        path = legacy_image.blob.storage_path
        image_pulled_bytes.labels("v1").inc(legacy_image.blob.compressed_size)

        try:
            logger.debug("Looking up the direct download URL for path: %s", path)
            direct_download_url = store.get_direct_download_url(
                legacy_image.blob.placements, path, get_request_ip()
            )
            if direct_download_url:
                logger.debug("Returning direct download URL")
                resp = redirect(direct_download_url)
                return resp

            # Close the database handle here for this process before we send the long download.
            database.close_db_filter(None)
            logger.debug("Streaming layer data")
            return Response(store.stream_read(legacy_image.blob.placements, path), headers=headers)
        except (IOError, AttributeError):
            logger.exception("Image layer data not found")
            abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id)

    abort(403)
예제 #10
0
파일: registry.py 프로젝트: epasham/quay-1
    def wrapper(namespace, repository, *args, **kwargs):
        image_id = kwargs["image_id"]
        repository_ref = registry_model.lookup_repository(namespace, repository)
        if repository_ref is not None:
            legacy_image = registry_model.get_legacy_image(repository_ref, image_id)
            if legacy_image is not None and legacy_image.uploading:
                abort(
                    400,
                    "Image %(image_id)s is being uploaded, retry later",
                    issue="upload-in-progress",
                    image_id=image_id,
                )

        return f(namespace, repository, *args, **kwargs)
예제 #11
0
    def wrapper(*args, **kwargs):
        result = validate_session_cookie()
        if result.has_nonrobot_user:
            result.apply_to_context()
            metric_queue.authentication_count.Inc(
                labelvalues=[result.kind, True])
            return func(*args, **kwargs)
        elif not result.missing:
            metric_queue.authentication_count.Inc(
                labelvalues=[result.kind, False])

        abort(
            401,
            message='Method requires login and no valid login could be loaded.'
        )
예제 #12
0
def update_user(username):
    permission = UserAdminPermission(username)
    if permission.can():
        update_request = request.get_json()

        if "password" in update_request:
            logger.debug("Updating user password")
            model.user.change_password(get_authenticated_user(),
                                       update_request["password"])

        return jsonify({
            "username": get_authenticated_user().username,
            "email": get_authenticated_user().email,
        })

    abort(403)
예제 #13
0
파일: __init__.py 프로젝트: xzwupeng/quay
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
예제 #14
0
파일: index.py 프로젝트: xzwupeng/quay
def update_user(username):
    permission = UserAdminPermission(username)
    if permission.can():
        update_request = request.get_json()

        if 'password' in update_request:
            logger.debug('Updating user password')
            model.user.change_password(get_authenticated_user(),
                                       update_request['password'])

        return jsonify({
            'username': get_authenticated_user().username,
            'email': get_authenticated_user().email,
        })

    abort(403)
예제 #15
0
파일: csrf.py 프로젝트: xzwupeng/quay
def verify_csrf(session_token_name=_QUAY_CSRF_TOKEN_NAME,
                request_token_name=_QUAY_CSRF_TOKEN_NAME,
                check_header=True):
    """ Verifies that the CSRF token with the given name is found in the session and
      that the matching token is found in the request args or values.
  """
    token = str(session.get(session_token_name, ''))
    found_token = str(request.values.get(request_token_name, ''))
    if check_header and not found_token:
        found_token = str(request.headers.get(_QUAY_CSRF_HEADER_NAME, ''))

    if not token or not found_token or not hmac.compare_digest(
            token, found_token):
        msg = 'CSRF Failure. Session token (%s) was %s and request token (%s) was %s'
        logger.error(msg, session_token_name, token, request_token_name,
                     found_token)
        abort(403, message='CSRF token was invalid or missing.')
예제 #16
0
파일: __init__.py 프로젝트: xzwupeng/quay
        def decorated(*args, **kwargs):
            if namespace_name_kwarg in kwargs:
                namespace_name = kwargs[namespace_name_kwarg]
            else:
                namespace_name = args[0]

            if features.RESTRICTED_V1_PUSH:
                whitelist = app.config.get('V1_PUSH_WHITELIST') or []
                logger.debug('V1 push is restricted to whitelist: %s',
                             whitelist)
                if namespace_name not in whitelist:
                    abort(
                        405,
                        message=
                        ('V1 push support has been deprecated. To enable for this '
                         + 'namespace, please contact support.'))

            return wrapped(*args, **kwargs)
예제 #17
0
def _user_teams(user, resource):
    changed = False
    p_exact_teams = resource["exact_teams"]
    p_add_teams = resource["add_teams"]
    p_remove_teams = resource["remove_teams"]

    team_names = p_exact_teams or p_add_teams or p_remove_teams
    if team_names is None:
        return False

    teams = []
    for name in team_names:
        try:
            teams.append(Team.get(Team.name == name))
        except model.InvalidTeamException:
            abort(400, message="Team '%s' does not exist" % name)
    teams = set(teams)

    current_teams = set(Team.select().join(TeamMember).join(User)).where(
        User.username == user.username)

    teams_to_add = teams - current_teams
    teams_to_remove = current_teams - teams
    if p_add_teams:
        teams_to_remove = []
    elif p_remove_teams:
        teams_to_add = []

    for team in teams_to_add:
        changed = True
        model.team.add_user_to_team(user, team)

    query = TeamMember.select().join(User).switch(TeamMember).join(Team).join(
        TeamRole)
    for team in teams_to_remove:
        changed = True
        found = list(
            query.where(User.username == user.username,
                        Team.name == team.name))
        found[0].delete_instance()

    return changed
예제 #18
0
def update_images(namespace_name, repo_name):
    permission = ModifyRepositoryPermission(namespace_name, repo_name)
    if permission.can():
        logger.debug("Looking up repository")
        repository_ref = registry_model.lookup_repository(namespace_name,
                                                          repo_name,
                                                          kind_filter="image")
        if repository_ref is None:
            # Make sure the repo actually exists.
            image_pushes.labels("v1", 404, "").inc()
            abort(404, message="Unknown repository", issue="unknown-repo")

        builder = lookup_manifest_builder(repository_ref,
                                          session.get("manifest_builder"),
                                          storage, docker_v2_signing_key)
        if builder is None:
            image_pushes.labels("v1", 400, "").inc()
            abort(400)

        # Generate a job for each notification that has been added to this repo
        logger.debug("Adding notifications for repository")
        event_data = {
            "updated_tags": [tag.name for tag in builder.committed_tags],
        }

        builder.done()

        track_and_log("push_repo", repository_ref)
        spawn_notification(repository_ref, "repo_push", event_data)
        image_pushes.labels("v1", 204, "").inc()
        return make_response("Updated", 204)

    image_pushes.labels("v1", 403, "").inc()
    abort(403)
예제 #19
0
파일: registry.py 프로젝트: epasham/quay-1
def head_image_layer(namespace, repository, image_id, headers):
    permission = ReadRepositoryPermission(namespace, repository)
    repository_ref = registry_model.lookup_repository(namespace, repository, kind_filter="image")

    logger.debug("Checking repo permissions")
    if permission.can() or (repository_ref is not None and repository_ref.is_public):
        if repository_ref is None:
            abort(404)

        logger.debug("Looking up placement locations")
        legacy_image = registry_model.get_legacy_image(repository_ref, image_id, include_blob=True)
        if legacy_image is None:
            logger.debug("Could not find any blob placement locations")
            abort(404, "Image %(image_id)s not found", issue="unknown-image", image_id=image_id)

        # Add the Accept-Ranges header if the storage engine supports resumable
        # downloads.
        extra_headers = {}
        if store.get_supports_resumable_downloads(legacy_image.blob.placements):
            logger.debug("Storage supports resumable downloads")
            extra_headers["Accept-Ranges"] = "bytes"

        resp = make_response("")
        resp.headers.extend(headers)
        resp.headers.extend(extra_headers)
        return resp

    abort(403)
예제 #20
0
파일: index.py 프로젝트: xzwupeng/quay
def update_images(namespace_name, repo_name):
    permission = ModifyRepositoryPermission(namespace_name, repo_name)

    if permission.can():
        logger.debug('Looking up repository')
        repository_ref = registry_model.lookup_repository(namespace_name,
                                                          repo_name,
                                                          kind_filter='image')
        if repository_ref is None:
            # Make sure the repo actually exists.
            abort(404, message='Unknown repository', issue='unknown-repo')

        builder = lookup_manifest_builder(repository_ref,
                                          session.get('manifest_builder'),
                                          storage, docker_v2_signing_key)
        if builder is None:
            abort(400)

        # Generate a job for each notification that has been added to this repo
        logger.debug('Adding notifications for repository')
        event_data = {
            'updated_tags': [tag.name for tag in builder.committed_tags],
        }

        builder.done()

        track_and_log('push_repo', repository_ref)
        spawn_notification(repository_ref, 'repo_push', event_data)
        metric_queue.repository_push.Inc(
            labelvalues=[namespace_name, repo_name, 'v1', True])
        return make_response('Updated', 204)

    abort(403)
예제 #21
0
파일: index.py 프로젝트: tylerauerbeck/quay
def get_repository_images(namespace_name, repo_name):
    repository_ref = registry_model.lookup_repository(
        namespace_name, repo_name, kind_filter="image"
    )

    permission = ReadRepositoryPermission(namespace_name, repo_name)
    if permission.can() or (repository_ref and repository_ref.is_public):
        # We can't rely on permissions to tell us if a repo exists anymore
        if repository_ref is None:
            abort(404, message="Unknown repository", issue="unknown-repo")

        logger.debug("Building repository image response")
        resp = make_response(json.dumps([]), 200)
        resp.mimetype = "application/json"

        track_and_log(
            "pull_repo", repository_ref, analytics_name="pull_repo_100x", analytics_sample=0.01
        )
        metric_queue.repository_pull.Inc(labelvalues=[namespace_name, repo_name, "v1", True])
        return resp

    abort(403)
예제 #22
0
def get_image_layer(namespace, repository, image_id, headers):
    permission = ReadRepositoryPermission(namespace, repository)
    repository_ref = registry_model.lookup_repository(namespace,
                                                      repository,
                                                      kind_filter='image')

    logger.debug('Checking repo permissions')
    if permission.can() or (repository_ref is not None
                            and repository_ref.is_public):
        if repository_ref is None:
            abort(404)

        legacy_image = registry_model.get_legacy_image(repository_ref,
                                                       image_id,
                                                       include_blob=True)
        if legacy_image is None:
            abort(404,
                  'Image %(image_id)s not found',
                  issue='unknown-image',
                  image_id=image_id)

        path = legacy_image.blob.storage_path
        metric_queue.pull_byte_count.Inc(legacy_image.blob.compressed_size,
                                         labelvalues=['v1'])

        try:
            logger.debug('Looking up the direct download URL for path: %s',
                         path)
            direct_download_url = store.get_direct_download_url(
                legacy_image.blob.placements, path, get_request_ip())
            if direct_download_url:
                logger.debug('Returning direct download URL')
                resp = redirect(direct_download_url)
                return resp

            # Close the database handle here for this process before we send the long download.
            database.close_db_filter(None)
            logger.debug('Streaming layer data')
            return Response(store.stream_read(legacy_image.blob.placements,
                                              path),
                            headers=headers)
        except (IOError, AttributeError):
            logger.exception('Image layer data not found')
            abort(404,
                  'Image %(image_id)s not found',
                  issue='unknown-image',
                  image_id=image_id)

    abort(403)
예제 #23
0
def attach_bitbucket_build_trigger(trigger_uuid):
    trigger = model.build.get_build_trigger(trigger_uuid)
    if not trigger or trigger.service.name != BitbucketBuildTrigger.service_name():
        abort(404)

    if trigger.connected_user != current_user.db_user():
        abort(404)

    verifier = request.args.get("oauth_verifier")
    handler = BuildTriggerHandler.get_handler(trigger)
    result = handler.exchange_verifier(verifier)
    if not result:
        trigger.delete_instance()
        return "Token has expired"

    namespace = trigger.repository.namespace_user.username
    repository = trigger.repository.name

    repo_path = "%s/%s" % (namespace, repository)
    full_url = url_for("web.buildtrigger", path=repo_path, trigger=trigger.uuid)

    logger.debug("Redirecting to full url: %s", full_url)
    return redirect(full_url)
예제 #24
0
def attach_github_build_trigger(namespace_name, repo_name):
    permission = AdministerRepositoryPermission(namespace_name, repo_name)
    if permission.can():
        code = request.args.get('code')
        token = github_trigger.exchange_code_for_token(app.config, client,
                                                       code)
        repo = model.repository.get_repository(namespace_name, repo_name)
        if not repo:
            msg = 'Invalid repository: %s/%s' % (namespace_name, repo_name)
            abort(404, message=msg)
        elif repo.kind.name != 'image':
            abort(501)

        trigger = model.build.create_build_trigger(repo, 'github', token,
                                                   current_user.db_user())
        repo_path = '%s/%s' % (namespace_name, repo_name)
        full_url = url_for('web.buildtrigger',
                           path=repo_path,
                           trigger=trigger.uuid)

        logger.debug('Redirecting to full url: %s', full_url)
        return redirect(full_url)

    abort(403)
예제 #25
0
def put_image_json(namespace, repository, image_id):
    logger.debug("Checking repo permissions")
    permission = ModifyRepositoryPermission(namespace, repository)
    if not permission.can():
        abort(403)

    repository_ref = registry_model.lookup_repository(namespace,
                                                      repository,
                                                      kind_filter="image")
    if repository_ref is None:
        abort(403)

    builder = lookup_manifest_builder(repository_ref,
                                      session.get("manifest_builder"), store,
                                      docker_v2_signing_key)
    if builder is None:
        abort(400)

    logger.debug("Parsing image JSON")
    try:
        uploaded_metadata = request.data
        uploaded_metadata_string = uploaded_metadata.decode("utf-8")
        data = json.loads(uploaded_metadata_string)
    except ValueError:
        pass

    if not data or not isinstance(data, dict):
        abort(
            400,
            "Invalid JSON for image: %(image_id)s\nJSON: %(json)s",
            issue="invalid-request",
            image_id=image_id,
            json=request.data,
        )

    if "id" not in data:
        abort(
            400,
            "Missing key `id` in JSON for image: %(image_id)s",
            issue="invalid-request",
            image_id=image_id,
        )

    if image_id != data["id"]:
        abort(
            400,
            "JSON data contains invalid id for image: %(image_id)s",
            issue="invalid-request",
            image_id=image_id,
        )

    logger.debug("Looking up repo image")
    location_pref = store.preferred_locations[0]
    username = get_authenticated_user() and get_authenticated_user().username
    layer = builder.start_layer(
        image_id,
        uploaded_metadata_string,
        location_pref,
        username,
        app.config["PUSH_TEMP_TAG_EXPIRATION_SEC"],
    )
    if layer is None:
        abort(
            400,
            "Image %(image_id)s has invalid metadata",
            issue="invalid-request",
            image_id=image_id,
        )

    return make_response("true", 200)
예제 #26
0
def put_repository_auth(namespace_name, repo_name):
    abort(501, "Not Implemented", issue="not-implemented")
예제 #27
0
def delete_repository_images(namespace_name, repo_name):
    abort(501, "Not Implemented", issue="not-implemented")
예제 #28
0
def put_image_checksum(namespace, repository, image_id):
    logger.debug("Checking repo permissions")
    permission = ModifyRepositoryPermission(namespace, repository)
    if not permission.can():
        abort(403)

    repository_ref = registry_model.lookup_repository(namespace,
                                                      repository,
                                                      kind_filter="image")
    if repository_ref is None:
        abort(403)

    # Docker Version < 0.10 (tarsum+sha):
    old_checksum = request.headers.get("X-Docker-Checksum")

    # Docker Version >= 0.10 (sha):
    new_checksum = request.headers.get("X-Docker-Checksum-Payload")

    checksum = new_checksum or old_checksum
    if not checksum:
        abort(
            400,
            "Missing checksum for image %(image_id)s",
            issue="missing-checksum",
            image_id=image_id,
        )

    logger.debug("Checking for image in manifest builder")
    builder = lookup_manifest_builder(repository_ref,
                                      session.get("manifest_builder"), store,
                                      docker_v2_signing_key)
    if builder is None:
        abort(400)

    layer = builder.lookup_layer(image_id)
    if layer is None:
        abort(404)

    if old_checksum:
        builder.save_precomputed_checksum(layer, checksum)
        return make_response("true", 200)

    if not builder.validate_layer_checksum(layer, checksum):
        logger.debug(
            "put_image_checksum: Wrong checksum. Given: %s and expected: %s",
            checksum,
            builder.get_layer_checksums(layer),
        )
        abort(
            400,
            "Checksum mismatch for image: %(image_id)s",
            issue="checksum-mismatch",
            image_id=image_id,
        )

    return make_response("true", 200)
예제 #29
0
def create_repository(namespace_name, repo_name):
    # Verify that the repository name is valid.
    if not REPOSITORY_NAME_REGEX.match(repo_name):
        abort(
            400,
            message=
            "Invalid repository name. Repository names cannot contain slashes."
        )

    logger.debug("Looking up repository %s/%s", namespace_name, repo_name)
    repository_ref = registry_model.lookup_repository(namespace_name,
                                                      repo_name)
    if repository_ref is None and get_authenticated_user() is None:
        logger.debug("Attempt to create repository %s/%s without user auth",
                     namespace_name, repo_name)
        abort(
            401,
            message=
            'Cannot create a repository as a guest. Please login via "docker login" first.',
            issue="no-login",
        )
    elif repository_ref:
        modify_perm = ModifyRepositoryPermission(namespace_name, repo_name)
        if not modify_perm.can():
            abort(
                403,
                message=
                "You do not have permission to modify repository %(namespace)s/%(repository)s",
                issue="no-repo-write-permission",
                namespace=namespace_name,
                repository=repo_name,
            )
        elif repository_ref.kind != "image":
            msg = (
                "This repository is for managing %s resources and not container images."
                % repository_ref.kind)
            abort(405, message=msg, namespace=namespace_name)
    else:
        create_perm = CreateRepositoryPermission(namespace_name)
        if not create_perm.can():
            logger.warning(
                "Attempt to create a new repo %s/%s with insufficient perms",
                namespace_name,
                repo_name,
            )
            msg = 'You do not have permission to create repositories in namespace "%(namespace)s"'
            abort(403,
                  message=msg,
                  issue="no-create-permission",
                  namespace=namespace_name)

        # Attempt to create the new repository.
        logger.debug(
            "Creating repository %s/%s with owner: %s",
            namespace_name,
            repo_name,
            get_authenticated_user().username,
        )

        repository_ref = model.repository.create_repository(
            namespace_name, repo_name, get_authenticated_user())

    if get_authenticated_user():
        user_event_data = {
            "action": "push_start",
            "repository": repo_name,
            "namespace": namespace_name,
        }

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

    # Start a new builder for the repository and save its ID in the session.
    assert repository_ref
    builder = create_manifest_builder(repository_ref, storage,
                                      docker_v2_signing_key)
    logger.debug("Started repo push with manifest builder %s", builder)
    if builder is None:
        abort(404, message="Unknown repository", issue="unknown-repo")

    session["manifest_builder"] = builder.builder_id
    return make_response("Created", 201)
예제 #30
0
def create_user():
    user_data = request.get_json()
    if not user_data or not "username" in user_data:
        abort(400, "Missing username")

    username = user_data["username"]
    password = user_data.get("password", "")

    # UGH! we have to use this response when the login actually worked, in order
    # to get the CLI to try again with a get, and then tell us login succeeded.
    success = make_response('"Username or email already exists"', 400)
    result, kind = validate_credentials(username, password)
    if not result.auth_valid:
        if kind == CredentialKind.token:
            abort(400, "Invalid access token.", issue="invalid-access-token")

        if kind == CredentialKind.robot:
            abort(400,
                  "Invalid robot account or password.",
                  issue="robot-login-failure")

        if kind == CredentialKind.oauth_token:
            abort(400,
                  "Invalid oauth access token.",
                  issue="invalid-oauth-access-token")

        if kind == CredentialKind.user:
            # Mark that the login failed.
            event = userevents.get_event(username)
            event.publish_event_data("docker-cli", {"action": "loginfailure"})
            abort(400, result.error_message, issue="login-failure")

        # Default case: Just fail.
        abort(400, result.error_message, issue="login-failure")

    if result.has_nonrobot_user:
        # Mark that the user was logged in.
        event = userevents.get_event(username)
        event.publish_event_data("docker-cli", {"action": "login"})

    return success