blob: 512d4c62677842686d3949ce0ee8351565ec30fb [file] [log] [blame] [edit]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module provides means to authenticate buildbot master to GAE apps.
A master may have an assigned service account specified in the
"service_account_file" attribute of a master definition in
master_site_config.py. It is a name of a JSON key file created and deployed by
administrators. One service account can be used to authenticate to different GAE
apps.
Cloud Endpoints and other Google APIs
-------------------------------------
This sample creates a Cloud Endpoints service client for Twisted code,
authorized with master's service account:
from master import auth
from master import deferred_resource
MY_SERVICE_HOSTNAME = 'my_service.appspot.com'
MY_SERVICE_DISCOVERY_URL = (
'%s/_ah/api/discovery/v1/apis/{api}/{apiVersion}/rest' %
MY_SERVICE_HOSTNAME
)
@defer.inlineCallbacks
def my_call(active_master)
creds = auth.create_credentials_for_master(active_master)
my_service = yield deferred_resource.DeferredResource.build(
'my_service',
'v1',
credentials=creds,
discoveryServiceUrl=MY_SERVICE_DISCOVERY_URL)
res = yield my_service.api.greet('John', body={'message': 'hi'})
Other Google APIs can be called in the same way, but you don't have to specify
discoveryServiceUrl.
"""
import json
import logging
from oauth2client.client import SignedJwtAssertionCredentials
DEFAULT_SCOPES = ['email']
class Error(Exception):
"""Authorization-related error."""
def validate_json_key(key):
"""Validates a parsed JSON key. Raises Error if it is invalid.
Example of a well-formatted key (this is not a real key):
{
"private_key_id": "4168d274cdc7a1eaef1c59f5b34bdf255",
"private_key": ("-----BEGIN PRIVATE KEY-----\nMIIhkiG9w0BAQEFAASCAmEwsd" +
"sdfsfFd\ngfxFChctlOdTNm2Wrr919Nx9q+sPV5ibyaQt5Dgn89fKV" +
"jftrO3AMDS3sMjaE4Ib\nZwJgy90wwBbMT7/YOzCgf5PZfivUe8KkB" +
-----END PRIVATE KEY-----\n",
"client_email": "234243-rjstu8hi95iglc8at3@developer.gserviceaccount.com",
"client_id": "234243-rjstu8hi95iglc8at3.apps.googleusercontent.com",
"type": "service_account"
}
"""
if not isinstance(key, dict):
raise Error('key is not a dict')
if key.get('type') != 'service_account':
raise Error('Unexpected key type: %s' % key.get('type'))
if not key.get('client_email'):
raise Error('Client email not specified')
if not key.get('private_key'):
raise Error('Private key not specified')
def create_service_account_credentials(json_key_filename, scope=None):
"""Creates SignedJwtAssertionCredentials with values in json_key_filename.
Args:
json_key_filename (str): path to the service account key file.
See validate_json_key() docstring for the file format.
scope (str|list of str): scope(s) of the credentials being
requested. Defaults to https://www.googleapis.com/auth/userinfo.email.
"""
scope = scope or DEFAULT_SCOPES
if isinstance(scope, basestring):
scope = [scope]
assert all(isinstance(s, basestring) for s in scope)
try:
with open(json_key_filename, 'r') as f:
key = json.load(f)
validate_json_key(key)
return SignedJwtAssertionCredentials(
key['client_email'], key['private_key'], scope)
except Exception as ex:
msg = ('Invalid service account json key in %s: %s' %
(json_key_filename, ex))
logging.error(msg)
raise Error(msg)
def create_credentials_for_master(master, scope=None):
"""Creates service account credentials for the master.
Args:
master (config.Master): master configuration (what is normally
called ActiveMaster in master.cfg) with service_account_path set.
scope (str|list of str): scope(s) of the credentials being
requested. Defaults to https://www.googleapis.com/auth/userinfo.email.
Returns:
oauth2client.client.SignedJwtAssertionCredentials.
"""
if master.service_account_path:
return create_service_account_credentials(
master.service_account_path, scope)
if master.is_production_host:
# If we're a live master and there is no configured service account,
# that is an error.
raise Error(
'Production instances must have a service account configured. '
'Set service_account_path or service_account_file in the master site '
'config.')
return None