Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Island: Move construction of AuthenicationFacade #3190

Merged
merged 3 commits into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 2 additions & 37 deletions monkey/monkey_island/cc/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@

import flask_restful
from flask import Flask, Response, send_from_directory
from flask_security import Security
from werkzeug.exceptions import NotFound

from common import DIContainer
from monkey_island.cc.event_queue import IIslandEventQueue
from monkey_island.cc.flask_utils import FlaskDIWrapper
from monkey_island.cc.resources import (
AgentBinaries,
Expand Down Expand Up @@ -36,17 +34,7 @@
from monkey_island.cc.resources.security_report import SecurityReport
from monkey_island.cc.resources.version import Version
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.server_utils.encryption import ILockableEncryptor
from monkey_island.cc.services import register_agent_configuration_resources, setup_authentication
from monkey_island.cc.services.authentication_service.authentication_facade import (
AuthenticationFacade,
)
from monkey_island.cc.services.authentication_service.configure_flask_security import (
configure_flask_security,
)
from monkey_island.cc.services.authentication_service.mongo_otp_repository import MongoOTPRepository
from monkey_island.cc.services.authentication_service.token_generator import TokenGenerator
from monkey_island.cc.services.authentication_service.token_parser import TokenParser
from monkey_island.cc.services.representations import output_json

HOME_FILE = "index.html"
Expand Down Expand Up @@ -139,7 +127,7 @@ def init_app(
data_dir: Path,
):
"""
Simple docstirng for init_app
Simple docstring for init_app

:param mongo_url: A url
:param container: Dependency injection container
Expand All @@ -152,31 +140,8 @@ def init_app(
init_app_config(app)
init_app_url_rules(app)

setup_authentication(api, app, container, data_dir)
flask_resource_manager = FlaskDIWrapper(api, container)
datastore = configure_flask_security(app, data_dir)
authentication_facade = _build_authentication_facade(container, datastore)
setup_authentication(api, authentication_facade)
init_api_resources(flask_resource_manager)

return app


def _build_authentication_facade(container: DIContainer, security: Security):
repository_encryptor = container.resolve(ILockableEncryptor)
island_event_queue = container.resolve(IIslandEventQueue)

token_generator = TokenGenerator(security)
refresh_token_expiration = (
security.app.config["SECURITY_TOKEN_MAX_AGE"]
+ security.app.config["SECURITY_REFRESH_TOKEN_TIMEDELTA"]
)
token_parser = TokenParser(security, refresh_token_expiration)

return AuthenticationFacade(
repository_encryptor,
island_event_queue,
security.datastore,
token_generator,
token_parser,
container.resolve(MongoOTPRepository),
)
41 changes: 39 additions & 2 deletions monkey/monkey_island/cc/services/authentication_service/setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,45 @@
from . import register_resources
from pathlib import Path

from flask import Flask
from flask_security import Security

from common import DIContainer
from monkey_island.cc.event_queue import IIslandEventQueue
from monkey_island.cc.server_utils.encryption import ILockableEncryptor

from .authentication_facade import AuthenticationFacade
from .configure_flask_security import configure_flask_security
from .flask_resources import register_resources
from .mongo_otp_repository import MongoOTPRepository
from .token_generator import TokenGenerator
from .token_parser import TokenParser


def setup_authentication(api, authentication_facade: AuthenticationFacade):
def setup_authentication(api, app: Flask, container: DIContainer, data_dir: Path):
security = configure_flask_security(app, data_dir)
authentication_facade = _build_authentication_facade(container, security)
register_resources(api, authentication_facade)

# revoke all old tokens so that the user has to log in again on startup
authentication_facade.revoke_all_tokens_for_all_users()


def _build_authentication_facade(container: DIContainer, security: Security):
repository_encryptor = container.resolve(ILockableEncryptor)
island_event_queue = container.resolve(IIslandEventQueue)

token_generator = TokenGenerator(security)
refresh_token_expiration = (
security.app.config["SECURITY_TOKEN_MAX_AGE"]
+ security.app.config["SECURITY_REFRESH_TOKEN_TIMEDELTA"]
)
token_parser = TokenParser(security, refresh_token_expiration)

return AuthenticationFacade(
repository_encryptor,
island_event_queue,
security.datastore,
token_generator,
token_parser,
container.resolve(MongoOTPRepository),
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import time
from pathlib import Path
from unittest.mock import MagicMock, call

import mongomock
import pymongo
import pytest
from flask_security import UserDatastore
from tests.common import StubDIContainer
Expand Down Expand Up @@ -181,11 +184,13 @@ def test_revoke_all_tokens_for_all_users(
mock_user_datastore: UserDatastore,
authentication_facade: AuthenticationFacade,
):
[user.save() for user in USERS]
for user in USERS:
user.save(force_insert=True)
authentication_facade.revoke_all_tokens_for_all_users()

assert mock_user_datastore.set_uniquifier.call_count == len(USERS)
[mock_user_datastore.set_uniquifier.assert_any_call(user) for user in USERS]
for user in USERS:
mock_user_datastore.set_uniquifier.assert_any_call(user)


def test_generate_otp__saves_otp(
Expand All @@ -206,14 +211,35 @@ def test_generate_otp__uses_expected_expiration_time(
assert expiration_time == expected_expiration_time


# mongomock.MongoClient is not a pymongo.MongoClient. This class allows us to register a
# mongomock.MongoClient as a pymongo.MongoClient with the StubDIContainer.
class MockMongoClient(mongomock.MongoClient, pymongo.MongoClient):
pass


def test_setup_authentication__revokes_tokens(
monkeypatch,
mock_flask_app,
mock_user_datastore: UserDatastore,
mock_island_event_queue: IIslandEventQueue,
mock_repository_encryptor: ILockableEncryptor,
mock_authentication_facade: AuthenticationFacade,
):
for user in USERS:
user.save(force_insert=True)

mock_security = MagicMock()
mock_security.datastore = mock_user_datastore
monkeypatch.setattr(
"monkey_island.cc.services.authentication_service.setup.configure_flask_security",
lambda *args: mock_security,
)

container = StubDIContainer()
container.register_instance(ILockableEncryptor, mock_repository_encryptor)
container.register_instance(IIslandEventQueue, mock_island_event_queue)
setup_authentication(MagicMock(), mock_authentication_facade)
container.register_instance(pymongo.MongoClient, MockMongoClient())
setup_authentication(MagicMock(), MagicMock(), container, Path("data_dir"))

assert mock_authentication_facade.revoke_all_tokens_for_all_users.called
assert mock_user_datastore.set_uniquifier.call_count == len(USERS)
for user in USERS:
mock_user_datastore.set_uniquifier.assert_any_call(user)