Skip to content

Commit 9340ae9

Browse files
committed
Merge branch '3078-otp-generator' into develop
Issue #3078 PR #3187
2 parents e3f04ea + 445e3dc commit 9340ae9

File tree

7 files changed

+100
-1
lines changed

7 files changed

+100
-1
lines changed

monkey/monkey_island/cc/services/authentication_service/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
from .flask_resources import register_resources
33

44
from .setup import setup_authentication
5+
6+
from .i_otp_generator import IOTPGenerator
7+
from .authentication_service_otp_generator import AuthenticationServiceOTPGenerator

monkey/monkey_island/cc/services/authentication_service/authentication_facade.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
import string
2+
import time
13
from typing import Tuple
24

35
from flask_security import UserDatastore
46

7+
from common.utils.code_utils import secure_generate_random_string
58
from monkey_island.cc.event_queue import IIslandEventQueue, IslandEventTopic
69
from monkey_island.cc.models import IslandMode
710
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
811
from monkey_island.cc.services.authentication_service.token_generator import TokenGenerator
912

1013
from . import AccountRole
14+
from .i_otp_repository import IOTPRepository
1115
from .token_parser import ParsedToken, TokenParser
12-
from .types import Token
16+
from .types import OTP, Token
1317
from .user import User
1418

19+
OTP_EXPIRATION_TIME = 2 * 60 # 2 minutes
20+
1521

1622
class AuthenticationFacade:
1723
"""
@@ -25,12 +31,14 @@ def __init__(
2531
user_datastore: UserDatastore,
2632
token_generator: TokenGenerator,
2733
token_parser: TokenParser,
34+
otp_repository: IOTPRepository,
2835
):
2936
self._repository_encryptor = repository_encryptor
3037
self._island_event_queue = island_event_queue
3138
self._datastore = user_datastore
3239
self._token_generator = token_generator
3340
self._token_parser = token_parser
41+
self._otp_repository = otp_repository
3442

3543
def needs_registration(self) -> bool:
3644
"""
@@ -71,6 +79,18 @@ def _get_refresh_token_owner(self, refresh_token: ParsedToken) -> User:
7179
raise Exception("Invalid refresh token")
7280
return user
7381

82+
def generate_otp(self) -> OTP:
83+
"""
84+
Generates a new OTP
85+
86+
The generated OTP is saved to the `IOTPRepository`
87+
"""
88+
otp = secure_generate_random_string(32, string.ascii_letters + string.digits + "._-")
89+
expiration_time = time.monotonic() + OTP_EXPIRATION_TIME
90+
self._otp_repository.insert_otp(otp, expiration_time)
91+
92+
return otp
93+
7494
def generate_refresh_token(self, user: User) -> Token:
7595
"""
7696
Generates a refresh token for a specific user
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from .authentication_facade import AuthenticationFacade
2+
from .i_otp_generator import IOTPGenerator
3+
from .types import OTP
4+
5+
6+
class AuthenticationServiceOTPGenerator(IOTPGenerator):
7+
"""
8+
Generates OTPs
9+
"""
10+
11+
def __init__(self, authentication_facade: AuthenticationFacade):
12+
self._authentication_facade = authentication_facade
13+
14+
def generate_otp(self) -> OTP:
15+
"""
16+
Generates a new OTP
17+
"""
18+
return self._authentication_facade.generate_otp()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from abc import ABC, abstractmethod
2+
3+
from .types import OTP
4+
5+
6+
class IOTPGenerator(ABC):
7+
"""Generator for OTPs"""
8+
9+
@abstractmethod
10+
def generate_otp(self) -> OTP:
11+
"""
12+
Generates a new OTP
13+
"""

monkey/tests/unit_tests/monkey_island/cc/services/authentication_service/test_authentication_service.py

+28
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import time
12
from unittest.mock import MagicMock, call
23

34
import pytest
@@ -8,8 +9,10 @@
89
from monkey_island.cc.models import IslandMode
910
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
1011
from monkey_island.cc.services.authentication_service.authentication_facade import (
12+
OTP_EXPIRATION_TIME,
1113
AuthenticationFacade,
1214
)
15+
from monkey_island.cc.services.authentication_service.i_otp_repository import IOTPRepository
1316
from monkey_island.cc.services.authentication_service.setup import setup_authentication
1417
from monkey_island.cc.services.authentication_service.token_generator import TokenGenerator
1518
from monkey_island.cc.services.authentication_service.token_parser import (
@@ -58,6 +61,11 @@ def mock_token_parser() -> TokenParser:
5861
return MagicMock(spec=TokenParser)
5962

6063

64+
@pytest.fixture
65+
def mock_otp_repository() -> IOTPRepository:
66+
return MagicMock(spec=IOTPRepository)
67+
68+
6169
@pytest.fixture
6270
def authentication_facade(
6371
mock_flask_app,
@@ -66,13 +74,15 @@ def authentication_facade(
6674
mock_user_datastore: UserDatastore,
6775
mock_token_generator: TokenGenerator,
6876
mock_token_parser: TokenParser,
77+
mock_otp_repository: IOTPRepository,
6978
) -> AuthenticationFacade:
7079
return AuthenticationFacade(
7180
mock_repository_encryptor,
7281
mock_island_event_queue,
7382
mock_user_datastore,
7483
mock_token_generator,
7584
mock_token_parser,
85+
mock_otp_repository,
7686
)
7787

7888

@@ -178,6 +188,24 @@ def test_revoke_all_tokens_for_all_users(
178188
[mock_user_datastore.set_uniquifier.assert_any_call(user) for user in USERS]
179189

180190

191+
def test_generate_otp__saves_otp(
192+
authentication_facade: AuthenticationFacade, mock_otp_repository: IOTPRepository
193+
):
194+
otp = authentication_facade.generate_otp()
195+
196+
assert mock_otp_repository.insert_otp.called_once_with(otp)
197+
198+
199+
def test_generate_otp__uses_expected_expiration_time(
200+
freezer, authentication_facade: AuthenticationFacade, mock_otp_repository: IOTPRepository
201+
):
202+
authentication_facade.generate_otp()
203+
204+
expiration_time = mock_otp_repository.insert_otp.call_args[0][1]
205+
expected_expiration_time = time.monotonic() + OTP_EXPIRATION_TIME
206+
assert expiration_time == expected_expiration_time
207+
208+
181209
def test_setup_authentication__revokes_tokens(
182210
mock_island_event_queue: IIslandEventQueue,
183211
mock_repository_encryptor: ILockableEncryptor,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from unittest.mock import MagicMock
2+
3+
from monkey_island.cc.services.authentication_service import AuthenticationServiceOTPGenerator
4+
from monkey_island.cc.services.authentication_service.authentication_facade import (
5+
AuthenticationFacade,
6+
)
7+
8+
9+
def test_authentication_service_otp_generator__generates_otp():
10+
mock_authentication_facade = MagicMock(spec=AuthenticationFacade)
11+
12+
otp_generator = AuthenticationServiceOTPGenerator(mock_authentication_facade)
13+
otp_generator.generate_otp()
14+
15+
assert mock_authentication_facade.generate_otp.called_once

vulture_allowlist.py

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
MongoAgentEventRepository,
2525
MongoOTPRepository,
2626
)
27+
from monkey_island.cc.services.authentication_service import AuthenticationServiceOTPGenerator
2728
from monkey_island.cc.services.authentication_service.token import TokenValidator
2829
from monkey_island.cc.services.authentication_service.user import User
2930
from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import MonkeyExploitation
@@ -160,3 +161,4 @@
160161
IOTPRepository.get_expiration
161162
IOTPRepository.reset
162163
MongoOTPRepository
164+
AuthenticationServiceOTPGenerator.generate_otp

0 commit comments

Comments
 (0)