def test_token(self):
    calls = []
    def mocked(**kwargs):
      calls.append(kwargs)
      return 'fake-token', utils.time_time() + 3600
    self.mock(auth, 'get_access_token', mocked)

    tok = service_accounts.AccessToken('fake-token', utils.time_time() + 3600)
    self.assertEqual(
        ('*****@*****.**', tok),
        service_accounts.get_system_account_token('*****@*****.**', ['scope']))

    self.assertEqual([{
        'act_as': '*****@*****.**',
        'min_lifetime_sec': service_accounts.MIN_TOKEN_LIFETIME_SEC,
        'scopes': ['scope'],
    }], calls)
Beispiel #2
0
    def post(self):
        request = self.parse_body()
        logging.debug('Request body: %s', request)
        msg = log_unexpected_subset_keys(self.ACCEPTED_KEYS,
                                         self.REQUIRED_KEYS, request,
                                         self.request, 'bot', 'keys')
        if msg:
            self.abort_with_error(400, error=msg)

        account_id = request['account_id']
        bot_id = request['id']
        scopes = request['scopes']
        task_id = request.get('task_id')

        # Scopes should be a list of strings, always.
        if (not scopes or not isinstance(scopes, list)
                or not all(isinstance(s, basestring) for s in scopes)):
            self.abort_with_error(400,
                                  error='"scopes" must be a list of strings')

        # Only two flavors of accounts are supported.
        if account_id not in ('system', 'task'):
            self.abort_with_error(
                400,
                error='Unknown "account_id", expecting "task" or "system"')

        # If using 'task' account, task_id is required. We'll double check the bot
        # still executes this task (based on data in datastore), and if so, will
        # use a service account associated with this particular task.
        if account_id == 'task' and not task_id:
            self.abort_with_error(
                400,
                error='"task_id" is required when using "account_id" == "task"'
            )

        # Need machine type associated with the bot for bots.cfg query below.
        # BotInfo also contains ID of a task the bot currently executes (to compare
        # with 'task_id' request parameter).
        machine_type = None
        current_task_id = None
        bot_info = bot_management.get_info_key(bot_id).get()
        if bot_info:
            machine_type = bot_info.machine_type
            current_task_id = bot_info.task_id

        # Make sure bot self-reported ID matches the authentication token. Raises
        # auth.AuthorizationError if not. Also fetches corresponding BotGroupConfig
        # that contains system service account email for this bot.
        bot_group_cfg = bot_auth.validate_bot_id_and_fetch_config(
            bot_id, machine_type)

        # At this point, the request is valid structurally, and the bot used proper
        # authentication when making it.
        logging.info('Requesting a "%s" token with scopes %s', account_id,
                     scopes)

        # This is mostly a precaution against confused bot processes. We can always
        # just use 'current_task_id' to look up per-task service account. Datastore
        # is the source of truth here, not whatever bot reports.
        if account_id == 'task' and task_id != current_task_id:
            logging.error(
                'Bot %s requested "task" access token for task %s, but runs %s',
                bot_id, task_id, current_task_id)
            self.abort_with_error(
                400, error='Wrong task_id: the bot is not executing this task')

        account = None  # an email or 'bot' or 'none'
        token = None  # service_accounts.AccessToken
        try:
            if account_id == 'task':
                account, token = service_accounts.get_task_account_token(
                    task_id, bot_id, scopes)
            elif account_id == 'system':
                account, token = service_accounts.get_system_account_token(
                    bot_group_cfg.system_service_account, scopes)
            else:
                raise AssertionError('Impossible, there is a check above')
        except auth.AccessTokenError as exc:
            # Note: no need to log this, it is already logged at the source. Also
            # we cautiously do not return any error details to the bot, just in case
            # they may contain something we don't want to disclose.
            if exc.transient:
                self.abort_with_error(
                    500, error='Transient error when generating the token')
            self.abort_with_error(
                403,
                error='Fatal error when generating the token, see server logs')

        # Note: the token info is already logged by service_accounts.get_*_token.
        if token:
            self.send_response({
                'service_account': account,
                'access_token': token.access_token,
                'expiry': token.expiry,
            })
        else:
            assert account in ('bot', 'none'), account
            self.send_response({'service_account': account})
 def test_bot(self):
   self.assertEqual(
       ('bot', None),
       service_accounts.get_system_account_token('bot', ['scope']))
 def test_none(self):
   self.assertEqual(
       ('none', None),
       service_accounts.get_system_account_token(None, ['scope']))