#!/usr/bin/env python
"""Root-access-level API handlers for binary management."""

from grr_response_core import config
from grr_response_core.lib.rdfvalues import crypto as rdf_crypto
from grr_response_core.lib.rdfvalues import structs as rdf_structs
from grr_response_proto.api.root import binary_management_pb2
from grr_response_server import access_control
from grr_response_server import signed_binary_utils
from grr_response_server.gui import api_call_handler_base
from grr_response_server.gui.api_plugins import config as api_config


class GrrBinaryNotFoundError(api_call_handler_base.ResourceNotFoundError):
  """Raised when a flow is not found."""


class GrrBinaryIsNotOverwritableError(access_control.UnauthorizedAccess):
  """Raised when one tries to overwrite an existing GRR binary."""


class GrrBinaryIsNotDeletableError(access_control.UnauthorizedAccess):
  """Raised when one tries to delete an existing GRR binary."""


class ApiUploadGrrBinaryArgs(rdf_structs.RDFProtoStruct):
  protobuf = binary_management_pb2.ApiUploadGrrBinaryArgs
  rdf_deps = [
      rdf_crypto.SignedBlob,
  ]


class ApiDeleteGrrBinaryArgs(rdf_structs.RDFProtoStruct):
  protobuf = binary_management_pb2.ApiDeleteGrrBinaryArgs
  rdf_deps = []


def _GetBinaryRootUrn(binary_type):
  if binary_type == api_config.ApiGrrBinary.Type.PYTHON_HACK:
    return signed_binary_utils.GetAFF4PythonHackRoot()
  elif binary_type == api_config.ApiGrrBinary.Type.EXECUTABLE:
    return signed_binary_utils.GetAFF4ExecutablesRoot()
  else:
    raise ValueError("Invalid binary type: %s" % binary_type)


class ApiUploadGrrBinaryHandler(api_call_handler_base.ApiCallHandler):
  """Uploads GRR binary to a given path."""

  args_type = ApiUploadGrrBinaryArgs

  def Handle(self, args, context=None):
    if not args.path:
      raise ValueError("Invalid binary path: %s" % args.path)

    root_urn = _GetBinaryRootUrn(args.type)
    urn = root_urn.Add(args.path)

    # If GRR binaries are readonly, check if a binary with the given
    # name and type exists and raise if it does.
    if config.CONFIG["Server.grr_binaries_readonly"]:
      try:
        signed_binary_utils.FetchBlobsForSignedBinaryByURN(urn)
        raise GrrBinaryIsNotOverwritableError(
            f"GRR binary ({args.path}, {args.type}) can't be overwritten: "
            "all binaries are read-only.")
      except signed_binary_utils.SignedBinaryNotFoundError:
        pass

    signed_binary_utils.WriteSignedBinaryBlobs(urn, list(args.blobs))


class ApiDeleteGrrBinaryHandler(api_call_handler_base.ApiCallHandler):
  """Deletes GRR binary with a given type and path."""

  args_type = ApiDeleteGrrBinaryArgs

  def Handle(self, args, context=None):
    if not args.path:
      raise ValueError("Invalid binary path: %s" % args.path)

    # We do not allow deleting GRR binaries if the readonly configuration
    # flag is set.
    if config.CONFIG["Server.grr_binaries_readonly"]:
      raise GrrBinaryIsNotDeletableError(
          f"GRR binary ({args.path}, {args.type}) can't be deleted: "
          "all binaries are read-only.")

    root_urn = _GetBinaryRootUrn(args.type)
    try:
      signed_binary_utils.DeleteSignedBinary(root_urn.Add(args.path))
    except signed_binary_utils.SignedBinaryNotFoundError:
      raise GrrBinaryNotFoundError(
          "No binary with type=%s and path=%s was found." %
          (args.type, args.path))
