Skip to content

Commit f14f733

Browse files
electiselectis
andauthored
PolicyBasedCheckOtpController (#31)
OTP support is implemented via PolicyBasedCheckOtpController and check_otp method in PolicyBasedController. The flag of connected OTP support is reading from token_info.otp (filled in genesis core) --------- Co-authored-by: electis <electis@tokens.team>
1 parent 914f08c commit f14f733

5 files changed

Lines changed: 93 additions & 18 deletions

File tree

gcl_iam/controllers.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import uuid
1818

19+
from restalchemy.api import constants
1920
from restalchemy.api import controllers
2021
from restalchemy.common import contexts
2122
from restalchemy.dm import filters
@@ -29,6 +30,7 @@ class PolicyBasedControllerMixin(object):
2930

3031
__policy_service_name__ = ""
3132
__policy_name__ = None
33+
_otp_mandatory = set()
3234

3335
def __init__(self, *args, **kwargs):
3436
super().__init__(*args, **kwargs)
@@ -76,6 +78,14 @@ def _enforce_and_override_project_id_in_kwargs(self, method, kwargs):
7678
self._ctx_project_id
7779
)
7880

81+
def _check_otp(self, method):
82+
if (
83+
self._introspection.get("otp_enabled")
84+
or method in self._otp_mandatory
85+
):
86+
if not self._introspection.get("otp_verified"):
87+
raise exceptions.OTPInvalidCodeError()
88+
7989

8090
class PolicyBasedController(
8191
PolicyBasedControllerMixin, controllers.BaseResourceController
@@ -174,3 +184,27 @@ def update(self, uuid, **kwargs):
174184
dm.update_dm(values=kwargs)
175185
dm.update()
176186
return dm
187+
188+
189+
class PolicyBasedCheckOtpController(PolicyBasedController):
190+
_otp_mandatory = {constants.CREATE, constants.UPDATE, constants.DELETE}
191+
192+
def create(self, **kwargs):
193+
self._check_otp(constants.CREATE)
194+
return PolicyBasedController(self).create(**kwargs)
195+
196+
def get(self, **kwargs):
197+
self._check_otp(constants.GET)
198+
return PolicyBasedController(self).get(**kwargs)
199+
200+
def filter(self, filters, order_by=None):
201+
self._check_otp(constants.FILTER)
202+
return PolicyBasedController(self).filter(filters, order_by=order_by)
203+
204+
def delete(self, uuid):
205+
self._check_otp(constants.DELETE)
206+
PolicyBasedController(self).delete(uuid)
207+
208+
def update(self, uuid, **kwargs):
209+
self._check_otp(constants.UPDATE)
210+
return PolicyBasedController(self).update(uuid, **kwargs)

gcl_iam/drivers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
import abc
1818

19-
import bazooka
19+
import bazooka.exceptions
2020

2121
from gcl_iam import exceptions
2222

@@ -76,5 +76,5 @@ def get_introspection_info(self, token_info, otp_code=None):
7676
introspection_url,
7777
headers=headers,
7878
).json()
79-
except bazooka.exceptions.RequestError:
79+
except bazooka.exceptions.BadRequestError:
8080
raise exceptions.InvalidAuthTokenError()

gcl_iam/engines.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def __init__(
9595
self._introspection_info["permissions"]
9696
)
9797

98+
self._introspection_info["otp_enabled"] = self._token_info.otp_enabled
99+
98100
@property
99101
def token_info(self):
100102
return self._token_info

gcl_iam/tests/unit/test_controllers.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from unittest import mock
2222

23+
from restalchemy.api import constants
2324
from restalchemy.common import contexts
2425

2526
from gcl_iam import controllers
@@ -41,8 +42,7 @@ def __init__(self, *args, **kwargs):
4142
super().__init__(*args, **kwargs)
4243

4344

44-
@pytest.fixture
45-
def user_context():
45+
def ctx_storage_context(**kwargs):
4646
ctx_storage = mock.Mock()
4747
contexts.ContextWithStorage._store_context_session(ctx_storage)
4848
ctx_storage.iam_context.introspection_info.return_value = {
@@ -56,31 +56,45 @@ def user_context():
5656
"genesis_core.vm.admin",
5757
],
5858
}
59-
60-
yield ctx_storage.iam_context.introspection_info
61-
62-
contexts.ContextWithStorage._clear_context()
59+
ctx_storage.iam_context.introspection_info.return_value.update(kwargs)
60+
return ctx_storage.iam_context.introspection_info
6361

6462

6563
@pytest.fixture
6664
def unscoped_context():
67-
ctx_storage = mock.Mock()
68-
contexts.ContextWithStorage._store_context_session(ctx_storage)
69-
ctx_storage.iam_context.introspection_info.return_value = {
70-
"user_info": {},
71-
"project_id": None,
72-
"otp_verified": True,
73-
"permission_hash": "xxxx",
74-
"permissions": [
65+
yield ctx_storage_context(
66+
project_id=None,
67+
introspection_infopermissions=[
7568
"service.resource.action",
7669
"genesis_core.vm.create",
7770
"genesis_core.vm.admin",
7871
"*.*.*",
7972
],
80-
}
73+
)
74+
contexts.ContextWithStorage._clear_context()
75+
76+
77+
@pytest.fixture
78+
def user_context():
79+
yield ctx_storage_context(project_id=FAKE_PROJECT_ID)
80+
contexts.ContextWithStorage._clear_context()
8181

82-
yield ctx_storage.iam_context.introspection_info
8382

83+
@pytest.fixture
84+
def otp_enabled_context():
85+
yield ctx_storage_context(otp_enabled=True)
86+
contexts.ContextWithStorage._clear_context()
87+
88+
89+
@pytest.fixture
90+
def otp_not_verified_context():
91+
yield ctx_storage_context(otp_enabled=True, otp_verified=False)
92+
contexts.ContextWithStorage._clear_context()
93+
94+
95+
@pytest.fixture
96+
def otp_not_enabled_context():
97+
yield ctx_storage_context(otp_verified=False)
8498
contexts.ContextWithStorage._clear_context()
8599

86100

@@ -164,3 +178,24 @@ def test_override_project_id_from_ctx(self, user_context):
164178
pc._enforce_and_override_project_id_in_kwargs("create", kwargs)
165179

166180
assert kwargs == {"project_id": FAKE_PROJECT_ID}
181+
182+
183+
class TestPolicyBasedCheckOtpController:
184+
185+
def test_check_otp_verified_true(self, otp_enabled_context):
186+
pc = controllers.PolicyBasedCheckOtpController(request=mock.Mock())
187+
pc._check_otp(constants.GET)
188+
189+
def test_check_otp_verified_false(self, otp_not_verified_context):
190+
pc = controllers.PolicyBasedCheckOtpController(request=mock.Mock())
191+
with pytest.raises(exceptions.OTPInvalidCodeError):
192+
pc._check_otp(constants.GET)
193+
194+
def test_check_otp_mandatory_on(self, otp_not_enabled_context):
195+
pc = controllers.PolicyBasedCheckOtpController(request=mock.Mock())
196+
with pytest.raises(exceptions.OTPInvalidCodeError):
197+
pc._check_otp(constants.CREATE)
198+
199+
def test_check_otp_mandatory_off(self, otp_not_enabled_context):
200+
pc = controllers.PolicyBasedCheckOtpController(request=mock.Mock())
201+
pc._check_otp(constants.FILTER)

gcl_iam/tokens.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def autenticated_at(self):
102102
def token_type(self):
103103
return self._token_info["typ"]
104104

105+
@property
106+
def otp_enabled(self):
107+
return self._token_info.get("otp")
108+
105109

106110
class IdToken(BaseToken):
107111

0 commit comments

Comments
 (0)