Merge "Use app_id with github" into feature/zuulv3

This commit is contained in:
Jenkins 2017-05-30 22:33:22 +00:00 committed by Gerrit Code Review
commit 0bfcfb5b2e
2 changed files with 121 additions and 24 deletions

View File

@ -24,3 +24,5 @@ sqlalchemy
alembic
cryptography>=1.6
cachecontrol
pyjwt
iso8601

View File

@ -13,6 +13,7 @@
# under the License.
import collections
import datetime
import logging
import hmac
import hashlib
@ -20,6 +21,9 @@ import time
import cachecontrol
from cachecontrol.cache import DictCache
import iso8601
import jwt
import requests
import webob
import webob.dec
import voluptuous as v
@ -31,6 +35,25 @@ from zuul.model import Ref
from zuul.exceptions import MergeFailure
from zuul.driver.github.githubmodel import PullRequest, GithubTriggerEvent
ACCESS_TOKEN_URL = 'https://api.github.com/installations/%s/access_tokens'
PREVIEW_JSON_ACCEPT = 'application/vnd.github.machine-man-preview+json'
class UTC(datetime.tzinfo):
"""UTC"""
def utcoffset(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return datetime.timedelta(0)
utc = UTC()
class GithubWebhookListener():
@ -279,20 +302,25 @@ class GithubConnection(BaseConnection):
driver_name = 'github'
log = logging.getLogger("connection.github")
payload_path = 'payload'
git_user = 'git'
def __init__(self, driver, connection_name, connection_config):
super(GithubConnection, self).__init__(
driver, connection_name, connection_config)
self.github = None
self._change_cache = {}
self.projects = {}
self._git_ssh = bool(self.connection_config.get('sshkey', None))
self.git_ssh_key = self.connection_config.get('sshkey')
self.git_host = self.connection_config.get('git_host', 'github.com')
self.canonical_hostname = self.connection_config.get(
'canonical_hostname', self.git_host)
self.source = driver.getSource(self)
self._github = None
self.app_id = None
self.app_key = None
self.installation_id = None
self.installation_token = None
self.installation_expiry = None
# NOTE(jamielennox): Better here would be to cache to memcache or file
# or something external - but zuul already sucks at restarting so in
# memory probably doesn't make this much worse.
@ -310,23 +338,86 @@ class GithubConnection(BaseConnection):
self.unregisterHttpHandler(self.payload_path)
def _authenticateGithubAPI(self):
token = self.connection_config.get('api_token', None)
if token is not None:
if self.git_host != 'github.com':
url = 'https://%s/' % self.git_host
self.github = github3.enterprise_login(token=token, url=url)
else:
self.github = github3.login(token=token)
self.log.info("Github API Authentication successful.")
config = self.connection_config
# anything going through requests to http/s goes through cache
self.github.session.mount('http://', self.cache_adapter)
self.github.session.mount('https://', self.cache_adapter)
if self.git_host != 'github.com':
url = 'https://%s/' % self.git_host
github = github3.GitHubEnterprise(url)
else:
self.github = None
self.log.info(
"No Github credentials found in zuul configuration, cannot "
"authenticate.")
github = github3.GitHub()
# anything going through requests to http/s goes through cache
github.session.mount('http://', self.cache_adapter)
github.session.mount('https://', self.cache_adapter)
api_token = config.get('api_token')
if api_token:
github.login(token=api_token)
else:
app_id = config.get('app_id')
installation_id = config.get('installation_id')
app_key_file = config.get('app_key')
if app_key_file:
with open(app_key_file, 'r') as f:
app_key = f.read()
if not (app_id and app_key and installation_id):
self.log.warning("You must provide an app_id, "
"app_key and installation_id to use "
"installation based authentication")
return
self.app_id = int(app_id)
self.installation_id = int(installation_id)
self.app_key = app_key
self._github = github
def _get_installation_key(self, user_id=None):
if not (self.installation_id and self.app_id):
return None
now = datetime.datetime.now(utc)
if ((not self.installation_expiry) or
(not self.installation_token) or
(now < self.installation_expiry)):
expiry = now + datetime.timedelta(minutes=5)
data = {'iat': now, 'exp': expiry, 'iss': self.app_id}
app_token = jwt.encode(data,
self.app_key,
algorithm='RS256')
url = ACCESS_TOKEN_URL % self.installation_id
headers = {'Accept': PREVIEW_JSON_ACCEPT,
'Authorization': 'Bearer %s' % app_token}
json_data = {'user_id': user_id} if user_id else None
response = requests.post(url, headers=headers, json=json_data)
response.raise_for_status()
data = response.json()
self.installation_expiry = iso8601.parse_date(data['expires_at'])
self.installation_expiry -= datetime.timedelta(minutes=5)
self.installation_token = data['token']
return self.installation_token
@property
def github(self):
# if we're using api_key authentication then we don't need to fetch
# new installation tokens so return the existing one.
installation_key = self._get_installation_key()
if installation_key:
self._github.login(token=installation_key)
return self._github
def maintainCache(self, relevant):
for key, change in self._change_cache.items():
@ -363,12 +454,16 @@ class GithubConnection(BaseConnection):
return change
def getGitUrl(self, project):
if self._git_ssh:
url = 'ssh://%s@%s/%s.git' % \
(self.git_user, self.git_host, project)
else:
url = 'https://%s/%s' % (self.git_host, project)
return url
if self.git_ssh_key:
return 'ssh://git@%s/%s.git' % (self.git_host, project)
installation_key = self._get_installation_key()
if installation_key:
return 'https://x-access-token:%s@%s/%s' % (installation_key,
self.git_host,
project)
return 'https://%s/%s' % (self.git_host, project)
def getGitwebUrl(self, project, sha=None):
url = 'https://%s/%s' % (self.git_host, project)