From 7a6bc2f7749c10c1d3344ea0196e81ae95b7f0d7 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 21 Jul 2019 11:45:29 +0300 Subject: Add: TokenGetter --- ick2/__init__.py | 1 + ick2/apibase.py | 4 +++ ick2/controllerapi.py | 10 ++++--- ick2/projectapi.py | 2 +- ick2/projectapi_tests.py | 11 +++++++- ick2/tokengetter.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ ick2/workapi.py | 3 +++ ick2/workapi_tests.py | 16 ++++++++++- ick2/workerapi.py | 3 ++- 9 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 ick2/tokengetter.py (limited to 'ick2') diff --git a/ick2/__init__.py b/ick2/__init__.py index 235ee96..586ddb5 100644 --- a/ick2/__init__.py +++ b/ick2/__init__.py @@ -89,6 +89,7 @@ from .client import ( AuthClient, Reporter, ) +from .tokengetter import TokenGetter from .actionenvs import ( Runner, ActionEnvironment, diff --git a/ick2/apibase.py b/ick2/apibase.py index 46c17cf..0537b6b 100644 --- a/ick2/apibase.py +++ b/ick2/apibase.py @@ -26,6 +26,10 @@ class APIbase: isinstance(state, ick2.MemoryStore) or isinstance(state, ick2.MuckStore)) self._trans = ick2.TransactionalState(state) + self._token_getter = None + + def set_token_getter(self, getter): # pragma: no cover + self._token_getter = getter def get_routes(self, path): resource_path = '{}/'.format(path) diff --git a/ick2/controllerapi.py b/ick2/controllerapi.py index b28e1f7..a673e0d 100644 --- a/ick2/controllerapi.py +++ b/ick2/controllerapi.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2018 Lars Wirzenius +# Copyright (C) 2017-2019 Lars Wirzenius # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -18,8 +18,9 @@ import ick2 class ControllerAPI: - def __init__(self, state): + def __init__(self, state, token_getter): self._state = state + self._token_getter = token_getter self._apis = {} def set_apt_server(self, domain): # pragma: no cover @@ -30,6 +31,7 @@ class ControllerAPI: def set_auth_url(self, url): # pragma: no cover self._set_url('set_auth_url', url) + self._token_getter.set_auth_url(url) def set_notify_url(self, url): # pragma: no cover self._set_url('set_notify_url', url) @@ -60,7 +62,9 @@ class ControllerAPI: for path in apis: if path not in self._apis: - self._apis[path] = apis[path](self._state) + api = apis[path](self._state) + api.set_token_getter(self._token_getter) + self._apis[path] = api routes = [] for path, api in self._apis.items(): diff --git a/ick2/projectapi.py b/ick2/projectapi.py index a854716..00fce8a 100644 --- a/ick2/projectapi.py +++ b/ick2/projectapi.py @@ -50,7 +50,7 @@ class ProjectAPI(ick2.ResourceApiBase): ] def trigger_project(self, project, **kwargs): # pragma: no cover - token = 'FIXME' + token = self._token_getter.get_token() with self._trans.modify(token, 'projects', project) as p: self._start_build(token, p) return {'status': ick2.BUILD_TRIGGERED} diff --git a/ick2/projectapi_tests.py b/ick2/projectapi_tests.py index f8b3c2a..bdb58d1 100644 --- a/ick2/projectapi_tests.py +++ b/ick2/projectapi_tests.py @@ -20,6 +20,12 @@ import unittest import ick2 +class DummyTokenGetter: + + def get_token(self): + return 'DUMMY.TOKEN' + + class ProjectAPITests(unittest.TestCase): def setUp(self): @@ -29,7 +35,10 @@ class ProjectAPITests(unittest.TestCase): return ick2.ProjectAPI(self.state) def create_pipeline_api(self): - return ick2.PipelineAPI(self.state) + getter = DummyTokenGetter() + api = ick2.PipelineAPI(self.state) + api.set_token_getter(getter) + return api def test_has_not_projects_initially(self): api = self.create_api() diff --git a/ick2/tokengetter.py b/ick2/tokengetter.py new file mode 100644 index 0000000..c152148 --- /dev/null +++ b/ick2/tokengetter.py @@ -0,0 +1,70 @@ +# Copyright (C) 2019 Lars Wirzenius +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import time + + +import jwt + + +import ick2 + + +class TokenGetter: + + scopes = [ + 'super', + 'create', + 'update', + 'show', + 'delete', + 'uapi_workers_post', + 'uapi_workers_id_get', + 'uapi_workers_id_put', + 'uapi_workers_id_delete', + 'uapi_builds_post', + 'uapi_builds_id_get', + 'uapi_builds_id_put', + 'uapi_builds_id_delete', + 'uapi_logs_post', + 'uapi_logs_id_get', + 'uapi_logs_id_put', + 'uapi_logs_id_delete', + ] + + def __init__(self, client_id, client_secret): + self._ac = ick2.AuthClient() + self._ac.set_client_creds(client_id, client_secret) + self._token = None + self._token_exp = None + + def set_auth_url(self, auth_url): + self._ac.set_auth_url(auth_url) + + def get_token(self): + if not self._got_valid_token(): + self._get_new_token() + return self._token + + def _got_valid_token(self): + fuzz = 10 + return (self._token is not None and + self._token_exp is not None and + time.time() + fuzz < self._token_exp) + + def _get_new_token(self): + self._token = self._ac.get_token(' '.join(self.scopes)) + parsed = jwt.decode(self._token, verify=False) + self._token_exp = parsed['exp'] diff --git a/ick2/workapi.py b/ick2/workapi.py index 3eeb0fa..7ff6f93 100644 --- a/ick2/workapi.py +++ b/ick2/workapi.py @@ -41,6 +41,7 @@ class WorkAPI(ick2.APIbase): ] def get_work(self, token=None, **kwargs): + token = self._token_getter.get_token() worker_id = self._get_client_id(**kwargs) ick2.log.log( 'trace', msg_text='Worker wants work', worker_id=worker_id) @@ -140,6 +141,8 @@ class WorkAPI(ick2.APIbase): return None def update_work(self, update, token=None, **kwargs): + token = self._token_getter.get_token() + try: worker_id = update['worker'] build_id = update['build_id'] diff --git a/ick2/workapi_tests.py b/ick2/workapi_tests.py index 72bcaf1..0ac3a86 100644 --- a/ick2/workapi_tests.py +++ b/ick2/workapi_tests.py @@ -36,7 +36,9 @@ class WorkAPITests(unittest.TestCase): ], } + getter = DummyTokenGetter() pipeapi = ick2.PipelineAPI(self.state) + pipeapi.set_token_getter(getter) pipeapi.create(pipeline) project = { @@ -47,6 +49,7 @@ class WorkAPITests(unittest.TestCase): 'pipelines': ['build'], } api = ick2.ProjectAPI(self.state) + api.set_token_getter(getter) api.create(project) return api @@ -57,12 +60,17 @@ class WorkAPITests(unittest.TestCase): self.claims = { 'aud': 'asterix', } + getter = DummyTokenGetter() api = ick2.WorkerAPI(self.state) + api.set_token_getter(getter) api.create(worker, claims=self.claims) return api def create_work_api(self): - return ick2.WorkAPI(self.state) + getter = DummyTokenGetter() + api = ick2.WorkAPI(self.state) + api.set_token_getter(getter) + return api def test_worker_gets_no_work_when_no_builds_have_been_triggered(self): self.create_project_api() @@ -287,3 +295,9 @@ class WorkAPITests(unittest.TestCase): # Ask for work again. self.assertEqual(work.get_work(claims=self.claims), {}) + + +class DummyTokenGetter: + + def get_token(self): + return 'DUMMY.TOKEN' diff --git a/ick2/workerapi.py b/ick2/workerapi.py index d4b508c..9a2c5f2 100644 --- a/ick2/workerapi.py +++ b/ick2/workerapi.py @@ -1,4 +1,4 @@ -# Copyright (C) 2017-2018 Lars Wirzenius +# Copyright (C) 2017-2019 Lars Wirzenius # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -34,4 +34,5 @@ class WorkerAPI(ick2.ResourceApiBase): # pragma: no cover def create(self, body, **kwargs): client_id = self._get_client_id(**kwargs) body['worker'] = client_id + kwargs['token'] = self._token_getter.get_token() return super().create(body, **kwargs) -- cgit v1.2.1