diff --git a/README.rst b/README.rst index c60267b..ba6649f 100644 --- a/README.rst +++ b/README.rst @@ -129,6 +129,10 @@ app:: octokit = Octokit(auth='app', app_id=42, private_key=private_key) +app:: + + octokit = Octokit(auth='installation', app_id=42, private_key=private_key) + For applications provide the application id either from the ping webhook or the application's page on GitHub. The :code:`private_key` is a string of your private key provided for the application. The :code:`app` scheme will use the application id and private key to get a token for the first installation id of the application. diff --git a/setup.py b/setup.py index 9331cd4..44c7dfa 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def read(*names, **kwargs): setup( name='octokitpy', - version='0.5.0', + version='0.6.0', license='MIT license', description='Python client for GitHub API', long_description='%s\n%s' % ( diff --git a/src/octokit/__init__.py b/src/octokit/__init__.py index ccfac40..a1cd237 100644 --- a/src/octokit/__init__.py +++ b/src/octokit/__init__.py @@ -55,6 +55,7 @@ class Base(object): authentication_schemes = { 'basic': self._setup_basic_authentication, 'token': self._setup_token_authentication, + 'installation': self._setup_installation_authentication, 'app': self._setup_app_authentication, } if kwargs.get('auth'): @@ -72,13 +73,20 @@ class Base(object): self.token = kwargs['token'] self.auth = kwargs['auth'] - def _setup_app_authentication(self, kwargs): + def _setup_installation_authentication(self, kwargs): assert kwargs['app_id'] assert kwargs['private_key'] self.token, self.expires_at = self._app_auth_get_token(kwargs['app_id'], kwargs['private_key']) self.auth = kwargs['auth'] self.headers['accept'] = 'application/vnd.github.machine-man-preview+json' + def _setup_app_authentication(self, kwargs): + assert kwargs['app_id'] + assert kwargs['private_key'] + self.jwt = self._app_auth_get_jwt(kwargs['app_id'], kwargs['private_key']) + self.auth = kwargs['auth'] + self.headers['accept'] = 'application/vnd.github.machine-man-preview+json' + def _app_auth_get_token(self, app_id, key): headers = { 'Authorization': 'Bearer {}'.format(self._app_auth_get_jwt(app_id, key)), @@ -86,8 +94,8 @@ class Base(object): } installation_url = '{}/app/installations'.format(self.base_url) installations = requests.get(installation_url, headers=headers).json() - installation_id = installations[0]['id'] - installation_token_url = '{}/installations/{}/access_tokens'.format(self.base_url, installation_id) + self.installation_id = [x.get('id') for x in installations if x.get('app_id') == app_id].pop() + installation_token_url = '{}/installations/{}/access_tokens'.format(self.base_url, self.installation_id) response = requests.post(installation_token_url, headers=headers).json() return response['token'], response['expires_at'] @@ -102,7 +110,11 @@ class Base(object): def _auth(self, requests_kwargs): if getattr(self, 'auth', None) == 'basic': return {'auth': (self.username, self.password)} - if getattr(self, 'auth', None) in ['token', 'app']: + if getattr(self, 'auth', None) in ['app']: + headers = requests_kwargs['headers'] + headers.update({'Authorization': 'Bearer {}'.format(self.jwt)}) + return {'headers': headers} + if getattr(self, 'auth', None) in ['token', 'installation']: headers = requests_kwargs['headers'] headers.update({'Authorization': 'token {}'.format(self.token)}) return {'headers': headers} diff --git a/tests/test_auth.py b/tests/test_auth.py index 97093fc..ea59f78 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -58,38 +58,39 @@ class TestAuth(object): headers=headers, ) - def test_can_set_app_authentication(self, mocker): + def test_can_set_installation_authentication(self, mocker): Request = namedtuple('Request', ['json']) get = mocker.patch('requests.get') - get.return_value = Request(json=lambda: [{'id': 37}]) + get.return_value = Request(json=lambda: [{'id': 13, 'app_id': 1}, {'id': 37, 'app_id': 42}]) post = mocker.patch('requests.post') post.return_value = Request(json=lambda: {'token': 'v1.1f699f1069f60', 'expires_at': '2016-07-11T22:14:10Z'}) with open(os.path.join(os.path.dirname(__file__), 'test.pem'), 'r') as f: private_key = f.read() - sut = Octokit(auth='app', app_id=42, private_key=private_key) - assert sut.auth == 'app' + sut = Octokit(auth='installation', app_id=42, private_key=private_key) + assert sut.auth == 'installation' assert sut.token == 'v1.1f699f1069f60' assert sut.expires_at == '2016-07-11T22:14:10Z' - def test_cannot_set_app_authentication_with_out_required_data(self): + def test_cannot_set_installation_authentication_with_out_required_data(self): with pytest.raises(KeyError): - Octokit(auth='app') + Octokit(auth='installation') with pytest.raises(AssertionError): - Octokit(auth='app', app_id='') + Octokit(auth='installation', app_id='') with pytest.raises(KeyError): - Octokit(auth='app', app_id=42) + Octokit(auth='installation', app_id=42) with pytest.raises(AssertionError): - Octokit(auth='app', app_id=42, private_key='') + Octokit(auth='installation', app_id=42, private_key='') - def test_app_token_is_used_if_set(self, mocker): + def test_installation_token_is_used_if_set(self, mocker): Request = namedtuple('Request', ['json']) get = mocker.patch('requests.get') - get.return_value = Request(json=lambda: [{'id': 37}]) + get.return_value = Request(json=lambda: [{'id': 13, 'app_id': 1}, {'id': 37, 'app_id': 42}]) post = mocker.patch('requests.post') post.return_value = Request(json=lambda: {'token': 'v1.1f699f1069f60', 'expires_at': '2016-07-11T22:14:10Z'}) with open(os.path.join(os.path.dirname(__file__), 'test.pem'), 'r') as f: private_key = f.read() - sut = Octokit(auth='app', app_id=42, private_key=private_key) + sut = Octokit(auth='installation', app_id=42, private_key=private_key) + assert sut.installation_id == 37 get = mocker.patch('requests.get') sut.authorization.get(id=100) headers = { @@ -102,3 +103,23 @@ class TestAuth(object): params={}, headers=headers, ) + + def test_cannot_set_app_authentication_with_out_required_data(self): + with pytest.raises(KeyError): + Octokit(auth='app') + with pytest.raises(AssertionError): + Octokit(auth='app', app_id='') + with pytest.raises(KeyError): + Octokit(auth='app', app_id=42) + with pytest.raises(AssertionError): + Octokit(auth='app', app_id=42, private_key='') + + def test_can_set_app_authentication(self, mocker): + Request = namedtuple('Request', ['json']) + get = mocker.patch('requests.get') + get.return_value = Request(json=lambda: [{'id': 37}]) + with open(os.path.join(os.path.dirname(__file__), 'test.pem'), 'r') as f: + private_key = f.read() + sut = Octokit(auth='app', app_id=42, private_key=private_key) + assert sut.auth == 'app' + assert sut.jwt is not None