# Get hold of the private key contents
fname = 'my-first-github-app.2017-08-19.private-key.pem'
cert_str = open(fname, 'r').read()
cert_bytes = cert_str.encode()
# For ease of confirming you have the right kind of pem file,
# I print out some of mine (though obviously not all of it)
for i, line in enumerate(cert_str.strip().split('\n')):
if line.startswith('-') or i == 3:
print(line.replace('X', 'm'))
else:
print('<secret>')
-----BEGIN RSA PRIVATE KEY----- <secret> <secret> BmykmtcVx8gxwRHKfQMKeThnOiLP1gkA5CbVLgwhyc88hr+Z8ujmhYoPJlqf47wN <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> <secret> -----END RSA PRIVATE KEY-----
from cryptography.hazmat.backends import default_backend
import jwt
private_key = default_backend().load_pem_private_key(cert_bytes, None)
# Encode *anything* using the requires RS256 algorithm
test_jwt = jwt.encode({'some': 'payload'}, private_key, algorithm='RS256')
import requests
headers = {"Authorization": "I have nothing :(",
"Accept": "application/vnd.github.machine-man-preview+json"}
resp = requests.get('https://api.github.com/app', headers=headers)
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode())
Code: 401 Content: {"message":"Invalid credentials","documentation_url":"https://developer.github.com/v3"}
Now I try putting in my test jwt content, and see that I get a different response:
headers = {"Authorization": "Bearer {}".format(test_jwt.decode()),
"Accept": "application/vnd.github.machine-man-preview+json"}
resp = requests.get('https://api.github.com/app', headers=headers)
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode())
Code: 401 Content: {"message":"Missing 'issued at' claim ('iat') in assertion","documentation_url":"https://developer.github.com/v3"}
OK, let's now do this properly. We need the current time in seconds sinch the UNIX epoch, plus the "iss" which is described as:
For the issuer claim (iss), you can obtain the GitHub App identifier via the initial webhook ping after creating the integration, or at any time from the integration settings page in the web UI.
import time
def app_headers():
time_since_epoch_in_seconds = int(time.time())
payload = {
# issued at time
'iat': time_since_epoch_in_seconds,
# JWT expiration time (10 minute maximum)
'exp': time_since_epoch_in_seconds + (10 * 60),
# GitHub App's identifier
'iss': '4397'
}
actual_jwt = jwt.encode(payload, private_key, algorithm='RS256')
headers = {"Authorization": "Bearer {}".format(actual_jwt.decode()),
"Accept": "application/vnd.github.machine-man-preview+json"}
return headers
resp = requests.get('https://api.github.com/app', headers=app_headers())
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode())
Code: 200 Content: {"id":4397,"owner":{"login":"pelson","id":810663,"avatar_url":"https://avatars3.githubusercontent.com/u/810663?v=4","gravatar_id":"","url":"https://api.github.com/users/pelson","html_url":"https://github.com/pelson","followers_url":"https://api.github.com/users/pelson/followers","following_url":"https://api.github.com/users/pelson/following{/other_user}","gists_url":"https://api.github.com/users/pelson/gists{/gist_id}","starred_url":"https://api.github.com/users/pelson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pelson/subscriptions","organizations_url":"https://api.github.com/users/pelson/orgs","repos_url":"https://api.github.com/users/pelson/repos","events_url":"https://api.github.com/users/pelson/events{/privacy}","received_events_url":"https://api.github.com/users/pelson/received_events","type":"User","site_admin":false},"name":"scitools-cla-checker","description":"","external_url":"https://scitools-cla-check.herokuapp.com/","html_url":"https://github.com/apps/scitools-cla-checker","created_at":"2017-08-11T05:56:48Z","updated_at":"2017-08-11T05:56:48Z"}
Success!
Now let's get a token so that we can do something with the app.
Go to the advanced tab for your application, and take a look at the "Recent deliveries" (which will have failed with a 404 because we don't have a web service running yet). In there, take a look for the delivery that would have been created when you "installed the app" as yourself. My installation looks like the following:
{ "action": "created", "installation": { "id": 4<secret>7, "account": { "login": "pelson", ... }, "repository_selection": "selected", ... }, "repositories": [ ... ], "sender": { ... } }
installation_id = 4<secret>7
resp = requests.post('https://api.github.com/installations/{}/access_tokens'.format(installation_id),
headers=app_headers())
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode())
Code: 201 Content: {"token":"v1.a4dccac2307ddea5a623782667431a67ade2000a","expires_at":"2017-08-11T08:26:52Z"}
headers = {"Authorization": "token {}".format("v1.a4dccac2307ddea5a623782667431a67ade2000a"),
"Accept": "application/vnd.github.machine-man-preview+json"}
print(headers)
resp = requests.get('https://api.github.com/installation/repositories', headers=headers)
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode()[:100] + '...')
{'Authorization': 'token v1.a4dccac2307ddea5a623782667431a67ade2000a', 'Accept': 'application/vnd.github.machine-man-preview+json'} Code: 200 Content: {"total_count":1,"repository_selection":"selected","repositories":[{"id":99950115,"name":"scitools-c...
resp = requests.post('https://api.github.com/repos/pelson/scitools-cla-checker/issues/1/labels',
json=["bug"], headers=headers)
print('Code: ', resp.status_code)
print('Content: ', resp.content.decode()[:100] + '...')
Code: 200 Content: [{"id":666183343,"url":"https://api.github.com/repos/pelson/scitools-cla-checker/labels/bug","name":...
from IPython.display import Image
Image('/Users/pelson/Desktop/Screen Shot 2017-08-11 at 08.58.20.png')