コード例 #1
0
ファイル: delegation.py プロジェクト: rmistry/luci-py
def check_can_create_token(user_id, subtoken):
  """Checks that caller is allowed to mint a given root token.

  Args:
    caller_id: identity string of a current caller.
    subtoken: instance of delegation_pb2.Subtoken describing root token.

  Raises:
    auth.AuthorizationError if such token is not allowed for the caller.
  """
  # Empty subtoken.services means the token applies to all services.
  rule = get_delegation_rule(user_id, subtoken.services or ['*'])

  if subtoken.validity_duration > rule.max_validity_duration:
    raise auth.AuthorizationError(
        'Maximum allowed validity_duration is %d sec, %d requested.' %
        (rule.max_validity_duration, subtoken.validity_duration))

  # Just delegating one's own identity (not impersonating someone else)? Allow.
  if subtoken.issuer_id == user_id:
    return

  # Verify it's OK to impersonate a given user.
  impersonated = auth.Identity.from_bytes(subtoken.issuer_id)
  for principal_set in rule.allowed_to_impersonate:
    if is_identity_in_principal_set(impersonated, principal_set):
      return

  raise auth.AuthorizationError(
      '"%s" is not allowed to impersonate "%s" on %s' %
      (user_id, subtoken.issuer_id, subtoken.services or ['*']))
コード例 #2
0
def _check_dimension_acls(request):
    """Raises AuthorizationError if some requested dimensions are forbidden.

  Uses 'dimension_acls' field from the settings. See proto/config.proto.
  """
    dim_acls = config.settings().dimension_acls
    if not dim_acls or not dim_acls.entry:
        return  # not configured, this is fine

    ident = request.authenticated
    dims = request.properties.dimensions
    assert 'id' in dims or 'pool' in dims, dims  # see _validate_dimensions
    assert ident is not None  # see task_request.init_new_request

    # Forbid targeting individual bots for non-admins, but allow using 'id' if
    # 'pool' is used as well (so whoever can posts tasks to 'pool', can target an
    # individual bot in that pool).
    if 'id' in dims and 'pool' not in dims:
        if not acl.is_admin():
            raise auth.AuthorizationError(
                'Only Swarming administrators can post tasks with "id" dimension '
                'without specifying a "pool" dimension.')

    for k, v in sorted(dims.iteritems()):
        if not _can_use_dimension(dim_acls, ident, k, v):
            raise auth.AuthorizationError(
                'User %s is not allowed to schedule tasks with dimension "%s:%s"'
                % (ident.to_bytes(), k, v))
コード例 #3
0
def _check_permission(perm, realms, identity=None):
    """Checks if the caller has the realm permission.

  Args:
    perm: An instance of auth.Permission.
    realms: List of realms.
    identity: An instance of auth.Identity to check permission.
              default is auth.get_current_identity().

  Returns:
    None

  Raises:
    auth.AuthorizationError: if the caller is not allowed or realm is missing.
  """

    # Remove None from list
    realms = [r for r in realms if r]

    if not identity:
        identity = auth.get_current_identity()

    if not realms:
        raise auth.AuthorizationError('Realm is missing')

    if not auth.has_permission(perm, realms, identity=identity):
        logging.warning(
            '[realms] %s "%s" does not have permission "%s" in any realms %s',
            identity.kind, identity.name, perm.name, realms)
        raise auth.AuthorizationError(
            '%s "%s" does not have permission "%s"' %
            (identity.kind, identity.name, perm.name))
    logging.info('[realms] %s "%s" has permission "%s" in any realms %s',
                 identity.kind, identity.name, perm.name, realms)
コード例 #4
0
ファイル: handlers_bot.py プロジェクト: rmoorman/luci-py
    def check_bot_code_access(self, bot_id, generate_token):
        """Raises AuthorizationError if caller is not authorized to access bot code.

    Four variants here:
      1. A valid bootstrap token is passed as '?tok=...' parameter.
      2. An user, allowed to do a bootstrap, is using their credentials.
      3. An IP whitelisted machine is making this call.
      4. A bot (with given bot_id) is using it's own machine credentials.

    In later three cases we optionally generate and return a new bootstrap
    token, that can be used to authorize /bot_code calls.
    """
        existing_token = self.request.get('tok')
        if existing_token:
            payload = bot_code.validate_bootstrap_token(existing_token)
            if payload is None:
                raise auth.AuthorizationError('Invalid bootstrap token')
            logging.debug('Using bootstrap token %r', payload)
            return existing_token

        machine_type = None
        if bot_id:
            bot_info = bot_management.get_info_key(bot_id).get()
            if bot_info:
                machine_type = bot_info.machine_type

        # TODO(vadimsh): Remove is_ip_whitelisted_machine check once all bots are
        # using auth for bootstrap and updating.
        if (not acl.can_create_bot() and not acl.is_ip_whitelisted_machine()
                and not (bot_id and bot_auth.is_authenticated_bot(
                    bot_id, machine_type))):
            raise auth.AuthorizationError('Not allowed to access the bot code')

        return bot_code.generate_bootstrap_token() if generate_token else None
コード例 #5
0
ファイル: task_scheduler.py プロジェクト: rmoorman/luci-py
def check_schedule_request_acl(request):
  """Verifies the current caller can schedule a given task request.

  Arguments:
  - request: TaskRequest entity with information about the new task.

  Raises:
    auth.AuthorizationError if the caller is not allowed to schedule this task.
  """
  # Only terminate tasks don't have a pool. ACLs for them are handled through
  # 'acl.can_edit_bot', see 'terminate' RPC handler. Such tasks do not end up
  # hitting this function, and so we can assume there's a pool set (this is
  # checked in TaskProperties's pre put hook).
  #
  # It is guaranteed that at most one item can be specified in 'pool' and that
  # every TaskSlice property dimensions use the same pool and id dimensions.
  pool = request.task_slice(0).properties.dimensions[u'pool'][0]
  pool_cfg = pools_config.get_pool_config(pool)

  # request.service_account can be 'bot' or 'none'. We don't care about these,
  # they are always allowed. We care when the service account is a real email.
  has_service_account = service_accounts.is_service_account(
      request.service_account)

  if not pool_cfg:
    logging.info('Pool "%s" is not in pools.cfg', pool)
    # Deployments that use pools.cfg may forbid unknown pools completely.
    # This is a safeguard against anyone executing tasks in a new not fully
    # configured pool.
    if pools_config.forbid_unknown_pools():
      raise auth.AuthorizationError(
          'Can\'t submit tasks to pool "%s" not defined in pools.cfg' % pool)
    # To be backward compatible with Swarming deployments that do not care about
    # pools isolation pools without pools.cfg entry are relatively wide open
    # (anyone with 'can_create_task' permission can use them). As a downside,
    # they are not allowed to use service accounts, since Swarming doesn't know
    # what accounts are allowed in such unknown pools.
    if has_service_account:
      raise auth.AuthorizationError(
          'Can\'t use account "%s" in the pool "%s" not defined in pools.cfg' %
          (request.service_account, pool))
    return

  logging.info(
      'Looking at the pool "%s" in pools.cfg, rev "%s"', pool, pool_cfg.rev)

  # Verify the caller can use the pool at all.
  if not _is_allowed_to_schedule(pool_cfg):
    raise auth.AuthorizationError(
        'User "%s" is not allowed to schedule tasks in the pool "%s", '
        'see pools.cfg' % (auth.get_current_identity().to_bytes(), pool))

  # Verify the requested task service account is allowed in this pool.
  if (has_service_account and
      not _is_allowed_service_account(request.service_account, pool_cfg)):
    raise auth.AuthorizationError(
        'Service account "%s" is not allowed in the pool "%s", see pools.cfg' %
        (request.service_account, pool))
コード例 #6
0
def validate_bot_id_and_fetch_config(bot_id):
    """Verifies ID reported by a bot matches the credentials being used.

  Expected to be called in a context of some bot API request handler. Uses
  bots.cfg config to look up what credentials are expected to be used by the bot
  with given ID.

  Raises auth.AuthorizationError if bot_id is unknown or bot is using invalid
  credentials.

  On success returns the configuration for this bot (BotGroupConfig tuple), as
  defined in bots.cfg
  """
    cfg = bot_groups_config.get_bot_group_config(bot_id)
    if not cfg:
        logging.error(
            'bot_auth: unknown bot_id, not in the config\nbot_id: "%s"',
            bot_id)
        raise auth.AuthorizationError('Unknown bot ID, not in config')

    peer_ident = auth.get_peer_identity()
    if cfg.require_luci_machine_token:
        if not _is_valid_ident_for_bot(peer_ident, bot_id):
            logging.error(
                'bot_auth: bot ID doesn\'t match the machine token used\n'
                'bot_id: "%s", peer_ident: "%s"', bot_id,
                peer_ident.to_bytes())
            raise auth.AuthorizationError(
                'Bot ID doesn\'t match the token used')
    elif cfg.require_service_account:
        expected_id = auth.Identity(auth.IDENTITY_USER,
                                    cfg.require_service_account)
        if peer_ident != expected_id:
            logging.error(
                'bot_auth: bot is not using expected service account\n'
                'bot_id: "%s", expected_id: "%s", peer_ident: "%s"', bot_id,
                expected_id.to_bytes(), peer_ident.to_bytes())
            raise auth.AuthorizationError(
                'bot is not using expected service account')
    elif not cfg.ip_whitelist:
        # This branch should not be hit for validated configs.
        logging.error(
            'bot_auth: invalid bot group config, no auth method defined\n'
            'bot_id: "%s"', bot_id)
        raise auth.AuthorizationError('Invalid bot group config')

    # Check that IP whitelist applies (in addition to credentials).
    if cfg.ip_whitelist:
        ip = auth.get_peer_ip()
        if not auth.is_in_ip_whitelist(cfg.ip_whitelist, ip):
            logging.error(
                'bot_auth: bot IP is not whitelisted\n'
                'bot_id: "%s", peer_ip: "%s", ip_whitelist: "%s"', bot_id,
                ipaddr.ip_to_string(ip), cfg.ip_whitelist)
            raise auth.AuthorizationError('Not IP whitelisted')

    return cfg
コード例 #7
0
ファイル: importer.py プロジェクト: stefb965/luci-py
def ingest_tarball(name, content):
    """Handles upload of tarballs specified in 'tarball_upload' config entries.

  Expected to be called in an auth context of the upload PUT request.

  Args:
    name: name of the corresponding 'tarball_upload' entry (defines ACLs).
    content: raw byte buffer with *.tar.gz file data.

  Returns:
    (list of modified groups, new AuthDB revision number or 0 if no changes).

  Raises:
    auth.AuthorizationError if caller is not authorized to do this upload.
    BundleImportError if the tarball can't be imported (e.g. wrong format).
  """
    # Return generic HTTP 403 error unless we can verify the caller to avoid
    # leaking information about our config.
    config = load_config()
    if not config:
        logging.error('Group imports are not configured')
        raise auth.AuthorizationError()

    # Same here. We should not leak config entry names to untrusted callers.
    entry = None
    for entry in config.tarball_upload:
        if entry.name == name:
            break
    else:
        logging.error('No such tarball_upload entry in the config: "%s"', name)
        raise auth.AuthorizationError()

    # The caller must be specified in 'authorized_uploader' field.
    caller = auth.get_current_identity()
    for email in entry.authorized_uploader:
        if caller == model.Identity(model.IDENTITY_USER, email):
            break
    else:
        logging.error('Caller %s is not authorized to upload tarball "%s"',
                      caller.to_bytes(), entry.name)
        raise auth.AuthorizationError()

    # Authorization check passed. Now parse the tarball, converting it into
    # {system -> {group -> identities}} map (aka "bundles set") and import it into
    # the datastore.
    logging.info('Ingesting tarball "%s" uploaded by %s', name,
                 caller.to_bytes())
    bundles = load_tarball(content, entry.systems, entry.groups, entry.domain)
    return import_bundles(bundles, caller,
                          'Uploaded as "%s" tarball' % entry.name)
コード例 #8
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def search_instances(self, request):
    """Returns package instances with given tag (in no particular order)."""
    tag = validate_instance_tag(request.tag)
    if request.package_name:
      package_name = validate_package_name(request.package_name)
    else:
      package_name = None

    caller = auth.get_current_identity()
    callback = None
    if package_name:
      # If search is limited to one package, check its ACL only once.
      if not acl.can_fetch_instance(package_name, caller):
        raise auth.AuthorizationError()
    else:
      # Filter out packages not allowed by ACL.
      acl_cache = {}
      def check_readable(package_name, _instance_id):
        if package_name not in acl_cache:
          acl_cache[package_name] = acl.can_fetch_instance(package_name, caller)
        return acl_cache[package_name]
      callback = check_readable

    found = self.service.search_by_tag(tag, package_name, callback)
    return SearchResponse(instances=[instance_to_proto(i) for i in found])
コード例 #9
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def register_instance(self, request):
    """Registers a new package instance in the repository."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)

    caller = auth.get_current_identity()
    if not acl.can_register_instance(package_name, caller):
      raise auth.AuthorizationError()

    instance = self.service.get_instance(package_name, instance_id)
    if instance is not None:
      return RegisterInstanceResponse(
          status=Status.ALREADY_REGISTERED,
          instance=instance_to_proto(instance))

    # Need to upload to CAS first? Open an upload session. Caller must use
    # CASServiceApi to finish the upload and then call registerInstance again.
    if not self.service.is_instance_file_uploaded(package_name, instance_id):
      upload_url, upload_session_id = self.service.create_upload_session(
          package_name, instance_id, caller)
      return RegisterInstanceResponse(
          status=Status.UPLOAD_FIRST,
          upload_session_id=upload_session_id,
          upload_url=upload_url)

    # Package data is in the store. Make an entity.
    instance, registered = self.service.register_instance(
        package_name=package_name,
        instance_id=instance_id,
        caller=caller,
        now=utils.utcnow())
    return RegisterInstanceResponse(
        status=Status.REGISTERED if registered else Status.ALREADY_REGISTERED,
        instance=instance_to_proto(instance))
コード例 #10
0
ファイル: acl.py プロジェクト: mcgreevy/chromium-infra
def current_identity_cannot(action_format, *args):  # pragma: no cover
    """Returns AuthorizationError."""
    action = action_format % args
    msg = 'User %s cannot %s' % (auth.get_current_identity().to_bytes(),
                                 action)
    logging.warning(msg)
    return auth.AuthorizationError(msg)
コード例 #11
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def fetch_client_binary(self, request):
    """Returns signed URL that can be used to fetch CIPD client binary."""
    package_name = validate_package_name(request.package_name)
    if not client.is_cipd_client_package(package_name):
      raise ValidationError('Not a CIPD client package')
    instance_id = validate_instance_id(request.instance_id)

    caller = auth.get_current_identity()
    if not acl.can_fetch_instance(package_name, caller):
      raise auth.AuthorizationError()

    # Grab the location of the extracted binary.
    instance = self.get_instance(package_name, instance_id)
    client_info, error_message = self.service.get_client_binary_info(instance)
    if error_message:
      raise Error(error_message)
    if client_info is None:
      return FetchClientBinaryResponse(
        status=Status.NOT_EXTRACTED_YET,
        instance=instance_to_proto(instance))

    return FetchClientBinaryResponse(
        instance=instance_to_proto(instance),
        client_binary=FetchClientBinaryResponse.ClientBinary(
            sha1=client_info.sha1,
            size=client_info.size,
            fetch_url=client_info.fetch_url,
            file_name=client.get_cipd_client_filename(package_name)))
コード例 #12
0
def init_new_request(request, allow_high_priority):
  """Initializes a new TaskRequest but doesn't store it.

  Fills up some values and does minimal checks.

  If parent_task_id is set, properties for the parent are used:
  - priority: defaults to parent.priority - 1
  - user: overridden by parent.user

  """
  assert request.__class__ is TaskRequest, request
  if request.parent_task_id:
    run_result_key = task_pack.unpack_run_result_key(request.parent_task_id)
    result_summary_key = task_pack.run_result_key_to_result_summary_key(
        run_result_key)
    request_key = task_pack.result_summary_key_to_request_key(
        result_summary_key)
    parent = request_key.get()
    if not parent:
      raise ValueError('parent_task_id is not a valid task')
    request.priority = max(min(request.priority, parent.priority - 1), 0)
    # Drop the previous user.
    request.user = parent.user

  # If the priority is below 100, make sure the user has right to do so.
  if request.priority < 100 and not allow_high_priority:
    # Special case for terminate request.
    if not request.properties.is_terminate:
      # Silently drop the priority of normal users.
      request.priority = 100

  request.authenticated = auth.get_current_identity()
  if (not request.properties.is_terminate and
      request.properties.grace_period_secs is None):
    request.properties.grace_period_secs = 30
  if request.properties.idempotent is None:
    request.properties.idempotent = False

  request.service_account = 'none'
  if request.service_account_token and request.service_account_token != 'none':
    if request.service_account_token == 'bot':
      request.service_account = 'bot'
    else:
      # TODO(vadimsh): Check the token signature, verify it can be used by the
      # current user, extract service account email.
      raise auth.AuthorizationError('service_account_token is not implemented')

  request.tags.append('priority:%s' % request.priority)
  request.tags.append('user:%s' % request.user)
  request.tags.append('service_account:%s' % request.service_account)
  for key, value in request.properties.dimensions.iteritems():
    request.tags.append('%s:%s' % (key, value))
  request.tags = sorted(set(request.tags))

  if request.properties.idempotent:
    request.properties_hash = request.HASHING_ALGO(
      utils.encode_to_json(request.properties)).digest()
  else:
    request.properties_hash = None
コード例 #13
0
def check_task_cancel_acl(task_request):
    """Checks if the caller is allowed to cancel the task.

  Checks if the caller has global permission using acl.can_edit_task().

  If the caller doesn't have any global permissions,
    Checks if the caller has either of 'swarming.pools.cancelTask' or
    'swarming.tasks.cancel' permission.

  Args:
    task_request: An instance of TaskRequest.

  Returns:
    None

  Raises:
    auth.AuthorizationError: if the caller is not allowed.
  """

    # check global permission.
    if acl.can_edit_task(task_request):
        return

    # check 'swarming.pools.cancelTask' permission of the pool in task dimensions.
    if task_request.pool:
        pool_cfg = pools_config.get_pool_config(task_request.pool)
        if not pool_cfg:
            raise endpoints.InternalServerErrorException(
                'Pool cfg not found. pool: %s' % task_request.pool)
        if pool_cfg.realm and auth.has_permission(
                get_permission(realms_pb2.REALM_PERMISSION_POOLS_CANCEL_TASK),
            [pool_cfg.realm]):
            return

    # check 'swarming.pools.cancelTask' permission of the pool in bot dimensions.
    if task_request.bot_id:
        pools = bot_management.get_pools_from_dimensions_flat(
            _retrieve_bot_dimensions(task_request.bot_id))
        pool_realms = [
            p.realm for p in map(pools_config.get_pool_config, pools)
            if p.realm
        ]
        if pool_realms and auth.has_permission(
                get_permission(realms_pb2.REALM_PERMISSION_POOLS_CANCEL_TASK),
                pool_realms):
            return

    # check 'swarming.tasks.cancel' permission.
    task_realm = task_request.realm
    if task_realm and auth.has_permission(
            get_permission(realms_pb2.REALM_PERMISSION_TASKS_CANCEL),
        [task_realm]):
        return

    raise auth.AuthorizationError('Task "%s" is not accessible' %
                                  task_request.task_id)
コード例 #14
0
def _get_bot_group_config(bot_id):
    """Finds a bot group config for given bot_id

  A dockerized bot may contain the magic word '--' in the bot_id.
  At the first attempt, the full bot_id is used to get bot group config.
  If it didn't find one, the hostname is used.

  Args:
    bot_id: ID of the bot.

  Returns:
    auth_bot_id: ID which will be used for authentication.
    bot_group_config: matched bot group config.

  Raises:
    auth.AuthorizationError: if the bot_id doesn't match any groups in
                             the bots.cfg.
  """
    # In many cases, hostname == bot_id. But dockeriezed bots contain
    # magic word '--' to represent host name. e.g. bot_id=foo--bar > hostname=foo
    # Those bots should be authenticated using the hostname.
    hostname = _extract_primary_hostname(bot_id)

    # At first, try to get bot group config with given bot_id
    # return the config if it's not default config
    cfg = bot_groups_config.get_bot_group_config(bot_id)
    if cfg and not cfg.is_default:
        logging.debug(
            'bot_auth: found a bot group cfg for bot_id: "%s"\n'
            'hostname: "%s" will be used for authentication', bot_id, hostname)
        return hostname, cfg

    # if hostname == bot_id, the same config would be returned at next attempt.
    if cfg and hostname == bot_id:
        logging.debug(
            'bot_auth: found a bot group cfg for bot_id: "%s"\n'
            'bot_id will be used for authentication', bot_id)
        return hostname, cfg

    # For a dockerized bot which has magic word '--' in the bot_id,
    # try to get bot group config with the host name
    cfg = bot_groups_config.get_bot_group_config(hostname)
    if cfg:
        logging.debug(
            'bot_auth: found a bot group cfg for bot_id: "%s"'
            ' using hostname: "%s"\nhostname will be used for authentication',
            bot_id, hostname)
        return hostname, cfg

    # This path isn't reachable when bots.cfg has a default group.
    logging.error(
        'bot_auth: unknown bot_id, not in the config\n'
        'bot_id: "%s" hostname: "%s"', bot_id, hostname)
    raise auth.AuthorizationError('Unknown bot ID, not in config')
コード例 #15
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def delete_package(self, request):
    """Deletes a package along with all its instances."""
    package_name = validate_package_name(request.package_name)

    caller = auth.get_current_identity()
    if not acl.can_delete_package(package_name, caller):
      raise auth.AuthorizationError()

    deleted = self.service.delete_package(package_name)
    if not deleted:
      raise PackageNotFoundError()
    return DeletePackageResponse()
コード例 #16
0
  def fetch_package(self, request):
    """Returns information about a package."""
    package_name = validate_package_name(request.package_name)

    caller = auth.get_current_identity()
    if not acl.can_fetch_package(package_name, caller):
      raise auth.AuthorizationError()

    pkg = self.service.get_package(package_name)
    if pkg is None:
      raise PackageNotFoundError()
    return FetchPackageResponse(package=package_to_proto(pkg))
コード例 #17
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def fetch_acl(self, request):
    """Returns access control list for a given package path."""
    package_path = validate_package_path(request.package_path)

    caller = auth.get_current_identity()
    if not acl.can_fetch_acl(package_path, caller):
      raise auth.AuthorizationError()

    return FetchACLResponse(
        acls=package_acls_to_proto({
          role: acl.get_package_acls(package_path, role)
          for role in acl.ROLES
        }))
コード例 #18
0
ファイル: delegation.py プロジェクト: molodiuc/luci-py
    def post(self):
        # Forbid usage of delegation tokens for this particular call. Using
        # delegation when creating delegation tokens is too deep. Redelegation will
        # be done as separate explicit API call that accept existing delegation
        # token via request body, not via headers.
        if auth.get_current_identity() != auth.get_peer_identity():
            raise auth.AuthorizationError(
                'This API call must not be used with active delegation token')

        # Convert request body to proto (with validation). Verify IP format.
        try:
            body = self.parse_body()
            subtoken = subtoken_from_jsonish(body)
            intent = body.get('intent') or ''
            if not isinstance(intent, basestring):
                raise TypeError('"intent" must be string')
        except (TypeError, ValueError) as exc:
            self.abort_with_error(400, text=str(exc))

        # Fill in defaults.
        assert not subtoken.requestor_identity
        user_id = auth.get_current_identity().to_bytes()
        subtoken.requestor_identity = user_id
        if not subtoken.delegated_identity:
            subtoken.delegated_identity = user_id
        subtoken.creation_time = int(utils.time_time())
        if not subtoken.validity_duration:
            subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC
        if '*' in subtoken.services:
            subtoken.services[:] = get_default_allowed_services(user_id)

        # Check ACL (raises auth.AuthorizationError on errors).
        rule = check_can_create_token(user_id, subtoken)

        # Register the token in the datastore, generate its ID.
        subtoken.subtoken_id = register_subtoken(subtoken, rule, intent,
                                                 auth.get_peer_ip())

        # Create and sign the token.
        try:
            token = delegation.serialize_token(delegation.seal_token(subtoken))
        except delegation.BadTokenError as exc:
            # This happens if resulting token is too large.
            self.abort_with_error(400, text=str(exc))

        self.send_response(response={
            'delegation_token': token,
            'subtoken_id': str(subtoken.subtoken_id),
            'validity_duration': subtoken.validity_duration,
        },
                           http_code=201)
コード例 #19
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def fetch_instance(self, request):
    """Returns signed URL that can be used to fetch a package instance."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)

    caller = auth.get_current_identity()
    if not acl.can_fetch_instance(package_name, caller):
      raise auth.AuthorizationError()

    instance = self.get_instance(package_name, instance_id)
    return FetchInstanceResponse(
        instance=instance_to_proto(instance),
        fetch_url=self.service.generate_fetch_url(instance),
        processors=processors_protos(instance))
コード例 #20
0
    def test_schedule_build_requests(self, add_many_async):
        add_many_async.return_value = future([
            (test_util.build(id=42), None),
            (test_util.build(id=43), None),
            (None, errors.InvalidInputError('bad')),
            (None, Exception('unexpected')),
            (None, auth.AuthorizationError('bad')),
        ])

        user.can_async.side_effect = (
            lambda bucket_id, _: future('forbidden' not in bucket_id))

        linux_builder = dict(project='chromium', bucket='try', builder='linux')
        win_builder = dict(project='chromium', bucket='try', builder='windows')
        req = rpc_pb2.BatchRequest(
            requests=[
                dict(schedule_build=dict(builder=linux_builder)),
                dict(schedule_build=dict(builder=linux_builder,
                                         fields=dict(paths=['tags']))),
                dict(schedule_build=dict(builder=linux_builder,
                                         fields=dict(paths=['wrong-field']))),
                dict(schedule_build=dict(builder=win_builder)),
                dict(schedule_build=dict(builder=win_builder)),
                dict(schedule_build=dict(builder=win_builder)),
                dict(schedule_build=dict(builder=dict(project='chromium',
                                                      bucket='forbidden',
                                                      builder='nope'), )),
                dict(
                    schedule_build=dict(),  # invalid request
                ),
            ], )

        res = self.call(self.api.Batch, req)

        codes = [r.error.code for r in res.responses]
        self.assertEqual(codes, [
            prpc.StatusCode.OK.value,
            prpc.StatusCode.OK.value,
            prpc.StatusCode.INVALID_ARGUMENT.value,
            prpc.StatusCode.INVALID_ARGUMENT.value,
            prpc.StatusCode.INTERNAL.value,
            prpc.StatusCode.PERMISSION_DENIED.value,
            prpc.StatusCode.PERMISSION_DENIED.value,
            prpc.StatusCode.INVALID_ARGUMENT.value,
        ])
        self.assertEqual(res.responses[0].schedule_build.id, 42)
        self.assertFalse(len(res.responses[0].schedule_build.tags))
        self.assertTrue(len(res.responses[1].schedule_build.tags))
コード例 #21
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def detach_tags(self, request):
    """Removes given tags from a package instance."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)
    tags = validate_instance_tag_list(request.tag)

    caller = auth.get_current_identity()
    for tag in tags:
      if not acl.can_detach_tag(package_name, tag, caller):
        raise auth.AuthorizationError('Not authorized to detach "%s"' % tag)
    self.verify_instance_exists(package_name, instance_id)

    self.service.detach_tags(
        package_name=package_name,
        instance_id=instance_id,
        tags=tags)
    return DetachTagsResponse()
コード例 #22
0
    def post(self):
        # Forbid usage of delegation tokens for this particular call. Using
        # delegation when creating delegation tokens is too deep. Redelegation will
        # be done as separate explicit API call that accept existing delegation
        # token via request body, not via headers.
        if auth.get_current_identity() != auth.get_peer_identity():
            raise auth.AuthorizationError(
                'This API call must not be used with active delegation token')

        # Convert request body to proto (with validation).
        try:
            subtoken = subtoken_from_jsonish(self.parse_body())
        except (TypeError, ValueError) as exc:
            self.abort_with_error(400, text=str(exc))

        # Fill in defaults.
        assert not subtoken.impersonator_id
        user_id = auth.get_current_identity().to_bytes()
        if not subtoken.issuer_id:
            subtoken.issuer_id = user_id
        if subtoken.issuer_id != user_id:
            subtoken.impersonator_id = user_id
        subtoken.creation_time = int(utils.time_time())
        if not subtoken.validity_duration:
            subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC
        if not subtoken.services or '*' in subtoken.services:
            subtoken.services[:] = get_default_allowed_services(user_id)

        # Check ACL (raises auth.AuthorizationError on errors).
        check_can_create_token(user_id, subtoken)

        # Create and sign the token.
        try:
            token = delegation.serialize_token(
                delegation.seal_token(
                    delegation_pb2.SubtokenList(subtokens=[subtoken])))
        except delegation.BadTokenError as exc:
            # This happens if resulting token is too large.
            self.abort_with_error(400, text=str(exc))

        self.send_response(response={
            'delegation_token': token,
            'validity_duration': subtoken.validity_duration,
        },
                           http_code=201)
コード例 #23
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def set_ref(self, request):
    """Creates a ref or moves an existing one."""
    package_name = validate_package_name(request.package_name)
    ref = validate_package_ref(request.ref)
    instance_id = validate_instance_id(request.instance_id)

    caller = auth.get_current_identity()
    if not acl.can_move_ref(package_name, ref, caller):
      raise auth.AuthorizationError('Not authorized to move "%s"' % ref)
    self.verify_instance_is_ready(package_name, instance_id)

    ref_entity = self.service.set_package_ref(
        package_name=package_name,
        ref=ref,
        instance_id=instance_id,
        caller=caller,
        now=utils.utcnow())
    return SetRefResponse(ref=package_ref_to_proto(ref_entity))
コード例 #24
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def set_package_hidden(self, package_name, hidden):
    """Common implementation for hide_package and unhide_package."""
    package_name = validate_package_name(package_name)

    caller = auth.get_current_identity()
    if not acl.can_modify_hidden(package_name, caller):
      raise auth.AuthorizationError()

    def mutation(pkg):
      if pkg.hidden == hidden:
        return False
      pkg.hidden = hidden
      return True

    pkg = self.service.modify_package(package_name, mutation)
    if pkg is None:
      raise PackageNotFoundError()

    return PackageResponse(package=package_to_proto(pkg))
コード例 #25
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def attach_tags(self, request):
    """Attaches a set of tags to a package instance."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)
    tags = validate_instance_tag_list(request.tags)

    caller = auth.get_current_identity()
    for tag in tags:
      if not acl.can_attach_tag(package_name, tag, caller):
        raise auth.AuthorizationError('Not authorized to attach "%s"' % tag)
    self.verify_instance_is_ready(package_name, instance_id)

    attached = self.service.attach_tags(
        package_name=package_name,
        instance_id=instance_id,
        tags=tags,
        caller=caller,
        now=utils.utcnow())
    return AttachTagsResponse(tags=[tag_to_proto(attached[t]) for t in tags])
コード例 #26
0
  def fetch_tags(self, request):
    """Lists package instance tags (in no particular order)."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)
    tags = validate_instance_tag_list(request.tag) if request.tag else None

    caller = auth.get_current_identity()
    if not acl.can_fetch_instance(package_name, caller):
      raise auth.AuthorizationError()
    self.verify_instance_exists(package_name, instance_id)

    if not tags:
      # Fetch all.
      attached = self.service.query_tags(package_name, instance_id)
    else:
      # Fetch selected only. "Is tagged by?" check essentially.
      found = self.service.get_tags(package_name, instance_id, tags)
      attached = [found[tag] for tag in tags if found[tag]]

    return FetchTagsResponse(tags=[tag_to_proto(tag) for tag in attached])
コード例 #27
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def modify_acl(self, request):
    """Changes access control list for a given package path."""
    package_path = validate_package_path(request.package_path)

    try:
      changes = [
        role_change_from_proto(msg, package_path)
        for msg in request.changes
      ]
    except ValueError as exc:
      raise ValidationError('Invalid role change request: %s' % exc)

    caller = auth.get_current_identity()
    if not acl.can_modify_acl(package_path, caller):
      raise auth.AuthorizationError()

    # Apply changes. Do not catch ValueError. Validation above should be
    # sufficient. If it is not, HTTP 500 and an uncaught exception in logs is
    # exactly what is needed.
    acl.modify_roles(changes, caller, utils.utcnow())
    return ModifyACLResponse()
コード例 #28
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def increment_counter(self, request):
    """Increments a counter on a package instance."""
    package_name = validate_package_name(request.package_name)
    instance_id = validate_instance_id(request.instance_id)
    counter_name = validate_counter_name(request.counter_name)
    delta = request.delta

    if delta not in (0, 1):
      raise ValidationError('Delta must be either 0 or 1')

    caller = auth.get_current_identity()
    if not acl.can_modify_counter(package_name, caller):
      raise auth.AuthorizationError()
    self.verify_instance_exists(package_name, instance_id)

    self.service.increment_counter(
        package_name=package_name,
        instance_id=instance_id,
        counter_name=counter_name,
        delta=delta)
    return IncrementCounterResponse()
コード例 #29
0
ファイル: api.py プロジェクト: mcgreevy/chromium-infra
  def resolve_version(self, request):
    """Returns instance ID of an existing instance given a ref or a tag."""
    package_name = validate_package_name(request.package_name)
    version = validate_instance_version(request.version)

    caller = auth.get_current_identity()
    if not acl.can_fetch_instance(package_name, caller):
      raise auth.AuthorizationError()

    pkg = self.service.get_package(package_name)
    if pkg is None:
      raise PackageNotFoundError()

    ids = self.service.resolve_version(package_name, version, limit=2)
    if not ids:
      raise InstanceNotFoundError()
    if len(ids) > 1:
      return ResolveVersionResponse(
          status=Status.AMBIGUOUS_VERSION,
          error_message='More than one instance has tag "%s" set' % version)
    return ResolveVersionResponse(instance_id=ids[0])
コード例 #30
0
def _check_pools_filters_acl(perm_enum, pools):
    """Checks if the caller has the permission in the specified pools.

  The caller needs to have the permission in *all* pools.

  Raises:
    auth.AuthorizationError: if the caller is not allowed.
  """
    # Pool dimension is required if the caller doesn't have global permission.
    if not pools:
        raise auth.AuthorizationError('No pool is specified')

    perm = get_permission(perm_enum)

    # the caller needs to have all permissions of the pools.
    for p in pools:
        pool_cfg = pools_config.get_pool_config(p)
        if not pool_cfg:
            raise endpoints.BadRequestException('Pool "%s" not found' % p)

        _check_permission(perm, [pool_cfg.realm])