From d254a61d66da3a22020a7ec80bb9e5960635db56 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sun, 22 Oct 2017 15:53:12 +0200 Subject: [PATCH] Make "app" optionnal in security This introduce a breaking change, since app is no longer required and in first position Also increase version to 0.2.0 #20 --- esipy/__init__.py | 2 +- esipy/security.py | 70 ++++++++++++++++++++++++------------------ test/mock.py | 8 ++--- test/test_cache.py | 5 +-- test/test_client.py | 12 ++++---- test/test_security.py | 71 +++++++++++++++++++++++++++++++++++-------- 6 files changed, 112 insertions(+), 56 deletions(-) diff --git a/esipy/__init__.py b/esipy/__init__.py index f5a2dc2..1d73fcb 100644 --- a/esipy/__init__.py +++ b/esipy/__init__.py @@ -9,4 +9,4 @@ # Not installed or in install (not yet installed) so ignore pass -__version__ = '0.1.8' +__version__ = '0.2.0' diff --git a/esipy/security.py b/esipy/security.py index 94129a2..b069bca 100644 --- a/esipy/security.py +++ b/esipy/security.py @@ -23,48 +23,63 @@ class EsiSecurity(object): def __init__( self, - app, redirect_uri, client_id, secret_key, + app=None, + ssoUrl="https://login.eveonline.com", + esiUrl="https://esi.tech.ccp.is", security_name="evesso"): """ Init the ESI Security Object - :param app: the pyswagger app object :param redirect_uri: the uri to redirect the user after login into SSO :param client_id: the OAuth2 client ID :param secret_key: the OAuth2 secret key + :param ssoUrl: the default sso URL used when no "app" is provided + :param esiUrl: the default esi URL used for verify endpoint + :param app: (optionnal) the pyswagger app object :param security_name: (optionnal) the name of the object holding the - informations in the securityDefinitions. + informations in the securityDefinitions, used to check authed endpoint """ - # check if the security_name actually exists in the securityDefinition - security = app.root.securityDefinitions.get(security_name, None) - if security is None: - raise NameError( - "%s is not defined in the securityDefinitions" % security_name - ) - - self.app = app self.security_name = security_name self.redirect_uri = redirect_uri self.client_id = client_id self.secret_key = secret_key - # some URL we still need to "manually" define... sadly - # we parse the authUrl so we don't care if it's TQ or SISI. - parsed_uri = urlparse(security.authorizationUrl) - self.oauth_verify = '%s://%s/oauth/verify' % ( - parsed_uri.scheme, - parsed_uri.netloc - ) + # we provide app object, so we don't use ssoUrl + if app is not None: + # check if the security_name exists in the securityDefinition + security = app.root.securityDefinitions.get(security_name, None) + if security is None: + raise NameError( + "%s is not defined in the securityDefinitions" % + security_name + ) - # should be: security_definition.tokenUrl - # but not yet implemented by CCP in ESI so we need the full URL... - # https://github.com/ccpgames/esi-issues/issues/92 - self.oauth_token = '%s://%s/oauth/token' % ( - parsed_uri.scheme, - parsed_uri.netloc - ) + self.oauth_authorize = security.authorizationUrl + + # some URL we still need to "manually" define... sadly + # we parse the authUrl so we don't care if it's TQ or SISI. + # https://github.com/ccpgames/esi-issues/issues/92 + parsed_uri = urlparse(security.authorizationUrl) + self.oauth_token = '%s://%s/oauth/token' % ( + parsed_uri.scheme, + parsed_uri.netloc + ) + + # no app object is provided, so we use direct URLs + else: + if ssoUrl is None or ssoUrl == "": + raise AttributeError("ssoUrl cannot be None or empty " + "without app parameter") + + self.oauth_authorize = '%s/oauth/authorize' % ssoUrl + self.oauth_token = '%s/oauth/token' % ssoUrl + + # use ESI url for verify, since it's better for caching + if esiUrl is None or esiUrl == "": + raise AttributeError("esiUrl cannot be None or empty") + self.oauth_verify = '%s/verify/' % esiUrl # session request stuff self._session = Session() @@ -121,15 +136,12 @@ def get_auth_uri(self, scopes=None, state=None, implicit=False): :param state: The state to pass through the auth process :return: the authorizationUrl with the correct parameters. """ - security_definition = self.app.root.securityDefinitions.get( - self.security_name - ) s = [] if not scopes else scopes response_type = 'code' if not implicit else 'token' return '%s?response_type=%s&redirect_uri=%s&client_id=%s%s%s' % ( - security_definition.authorizationUrl, + self.oauth_authorize, response_type, quote(self.redirect_uri, safe=''), self.client_id, diff --git a/test/mock.py b/test/mock.py index a4ed868..93d4ad0 100644 --- a/test/mock.py +++ b/test/mock.py @@ -48,8 +48,8 @@ def oauth_token(url, request): @httmock.urlmatch( scheme="https", - netloc=r"login\.eveonline\.com$", - path=r"^/oauth/verify$" + netloc=r"esi\.tech\.ccp\.is$", + path=r"^/verify/$" ) def oauth_verify(url, request): return httmock.response( @@ -64,8 +64,8 @@ def oauth_verify(url, request): @httmock.urlmatch( scheme="https", - netloc=r"login\.eveonline\.com$", - path=r"^/oauth/verify$" + netloc=r"esi\.tech\.ccp\.is$", + path=r"^/verify/$" ) def oauth_verify_fail(url, request): return httmock.response( diff --git a/test/test_cache.py b/test/test_cache.py index 4b2afbf..7ad8d7d 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -1,14 +1,11 @@ # -*- encoding: utf-8 -*- -# -- Tests based on pycrest cache tests (as these are the same classes) -# -- https://github.com/pycrest/PyCrest/blob/master/tests/test_pycrest.py from __future__ import absolute_import import memcache -import mock +import redis import shutil import time import unittest -import redis from collections import namedtuple diff --git a/test/test_client.py b/test/test_client.py index a209b2e..115a44f 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -5,8 +5,8 @@ from .mock import public_incursion from .mock import public_incursion_no_expires from .mock import public_incursion_no_expires_second -from .mock import public_incursion_warning from .mock import public_incursion_server_error +from .mock import public_incursion_warning from esipy import App from esipy import EsiClient from esipy import EsiSecurity @@ -19,8 +19,8 @@ import httmock import mock -import time import six +import time import unittest import warnings @@ -50,10 +50,10 @@ def setUp(self, urlopen_mock): ) self.security = EsiSecurity( - self.app, - TestEsiPy.CALLBACK_URI, - TestEsiPy.CLIENT_ID, - TestEsiPy.SECRET_KEY + app=self.app, + redirect_uri=TestEsiPy.CALLBACK_URI, + client_id=TestEsiPy.CLIENT_ID, + secret_key=TestEsiPy.SECRET_KEY, ) self.cache = DictCache() diff --git a/test/test_security.py b/test/test_security.py index 12dfbf4..be408ab 100644 --- a/test/test_security.py +++ b/test/test_security.py @@ -25,8 +25,9 @@ class TestEsiSecurity(unittest.TestCase): CALLBACK_URI = "https://foo.bar/baz/callback" LOGIN_EVE = "https://login.eveonline.com" - OAUTH_VERIFY = "%s/oauth/verify" % LOGIN_EVE + OAUTH_VERIFY = "https://esi.tech.ccp.is/verify/" OAUTH_TOKEN = "%s/oauth/token" % LOGIN_EVE + OAUTH_AUTHORIZE = "%s/oauth/authorize" % LOGIN_EVE CLIENT_ID = 'foo' SECRET_KEY = 'bar' BASIC_TOKEN = six.u('Zm9vOmJhcg==') @@ -42,20 +43,30 @@ def setUp(self, urlopen_mock): ) self.security = EsiSecurity( - self.app, - TestEsiSecurity.CALLBACK_URI, - TestEsiSecurity.CLIENT_ID, - TestEsiSecurity.SECRET_KEY + app=self.app, + redirect_uri=TestEsiSecurity.CALLBACK_URI, + client_id=TestEsiSecurity.CLIENT_ID, + secret_key=TestEsiSecurity.SECRET_KEY, ) - def test_esisecurity_init(self): + def test_esisecurity_init_with_app(self): + """ test security init with app and URL""" with self.assertRaises(NameError): EsiSecurity( - self.app, - TestEsiSecurity.CALLBACK_URI, - TestEsiSecurity.CLIENT_ID, - TestEsiSecurity.SECRET_KEY, - "security_name_that_does_not_exist" + app=self.app, + redirect_uri=TestEsiSecurity.CALLBACK_URI, + client_id=TestEsiSecurity.CLIENT_ID, + secret_key=TestEsiSecurity.SECRET_KEY, + security_name="security_name_that_does_not_exist" + ) + + with self.assertRaises(AttributeError): + EsiSecurity( + app=self.app, + redirect_uri=TestEsiSecurity.CALLBACK_URI, + client_id=TestEsiSecurity.CLIENT_ID, + secret_key=TestEsiSecurity.SECRET_KEY, + esiUrl="" ) self.assertEqual( @@ -82,6 +93,41 @@ def test_esisecurity_init(self): self.security.oauth_token, TestEsiSecurity.OAUTH_TOKEN ) + self.assertEqual( + self.security.oauth_authorize, + TestEsiSecurity.OAUTH_AUTHORIZE + ) + + def test_esisecurity_other_init(self): + """ test security init without app and with urls """ + with self.assertRaises(AttributeError): + EsiSecurity( + redirect_uri=TestEsiSecurity.CALLBACK_URI, + client_id=TestEsiSecurity.CLIENT_ID, + secret_key=TestEsiSecurity.SECRET_KEY, + ssoUrl="" + ) + + security = EsiSecurity( + redirect_uri=TestEsiSecurity.CALLBACK_URI, + client_id=TestEsiSecurity.CLIENT_ID, + secret_key=TestEsiSecurity.SECRET_KEY, + ssoUrl='foo.com', + esiUrl='bar.baz' + ) + + self.assertEqual( + security.oauth_verify, + "bar.baz/verify/" + ) + self.assertEqual( + security.oauth_token, + "foo.com/oauth/token" + ) + self.assertEqual( + security.oauth_authorize, + "foo.com/oauth/authorize" + ) def test_esisecurity_update_token(self): self.security.update_token({ @@ -91,7 +137,7 @@ def test_esisecurity_update_token(self): }) self.assertEqual(self.security.access_token, 'access_token') self.assertEqual(self.security.refresh_token, 'refresh_token') - self.assertEqual(self.security.token_expiry, int(time.time()+60)) + self.assertEqual(self.security.token_expiry, int(time.time() + 60)) def test_esisecurity_get_auth_uri(self): self.assertEqual( @@ -229,6 +275,7 @@ def test_esisecurity_verify(self): def test_esisecurity_call(self): class RequestTest(object): + def __init__(self): self._security = [] self._p = {'header': {}}