forked from p15670423/monkey
Merge pull request #1619 from guardicore/1206-remove-credentials-from-server-config
Remove credentials from server config
This commit is contained in:
commit
5e60066480
|
@ -7,6 +7,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
- credentials.json file for storing Monkey Island user login information. #1206
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- "Communicate as Backdoor User" PBA's HTTP requests to request headers only and
|
- "Communicate as Backdoor User" PBA's HTTP requests to request headers only and
|
||||||
|
|
|
@ -12,7 +12,7 @@ Below are some of the most common questions we receive about the Infection Monke
|
||||||
- [How can I use an old data directory?](#how-can-i-use-an-old-data-directory)
|
- [How can I use an old data directory?](#how-can-i-use-an-old-data-directory)
|
||||||
- [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit)
|
- [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit)
|
||||||
- [Is the Infection Monkey a malware/virus?](#is-the-infection-monkey-a-malwarevirus)
|
- [Is the Infection Monkey a malware/virus?](#is-the-infection-monkey-a-malwarevirus)
|
||||||
- [Reset/enable the Monkey Island password](#resetenable-the-monkey-island-password)
|
- [Reset the Monkey Island password](#reset-the-monkey-island-password)
|
||||||
- [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously)
|
- [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously)
|
||||||
- [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly)
|
- [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly)
|
||||||
- [Logging and how to find logs](#logging-and-how-to-find-logs)
|
- [Logging and how to find logs](#logging-and-how-to-find-logs)
|
||||||
|
@ -71,7 +71,7 @@ downloaded installer](/usage/file-checksums) first. Then, create a new folder
|
||||||
and disable antivirus scan for that folder. Lastly, re-install the Infection
|
and disable antivirus scan for that folder. Lastly, re-install the Infection
|
||||||
Monkey in the newly created folder.
|
Monkey in the newly created folder.
|
||||||
|
|
||||||
## Reset/enable the Monkey Island password
|
## Reset the Monkey Island password
|
||||||
|
|
||||||
|
|
||||||
{{% notice warning %}}
|
{{% notice warning %}}
|
||||||
|
@ -82,43 +82,18 @@ However, you can save the Monkey's existing configuration by logging in with you
|
||||||
### On Windows and Linux (AppImage)
|
### On Windows and Linux (AppImage)
|
||||||
|
|
||||||
When you first access the Monkey Island server, you'll be prompted to create an account.
|
When you first access the Monkey Island server, you'll be prompted to create an account.
|
||||||
To reset the credentials, edit the `server_config.json` file manually
|
Creating an account will write your credentials in `credentials.json` file
|
||||||
|
under [data directory]({{< ref "/reference/data_directory" >}}).
|
||||||
|
To reset the credentials:
|
||||||
|
|
||||||
|
1. **Remove** the `credentials.json` file manually
|
||||||
(located in the [data directory]({{< ref "/reference/data_directory" >}})).
|
(located in the [data directory]({{< ref "/reference/data_directory" >}})).
|
||||||
|
|
||||||
In order to reset the credentials, the following edits need to be made:
|
2. Restart the Monkey Island process:
|
||||||
1. Delete the `user` field. It will look like this:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
...
|
|
||||||
"user": "username",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
1. Delete the `password_hash` field. It will look like this:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
...
|
|
||||||
"password_hash": "$2b$12$d050I/MsR5.F5E15Sm7EkunmmwMkUKaZE0P0tJXG.M9tF.Kmkd342",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
1. Set `server_config` to `password`. It should look like this:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
...
|
|
||||||
"environment": {
|
|
||||||
...
|
|
||||||
"server_config": "password",
|
|
||||||
...
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
1. Restart the Monkey Island process:
|
|
||||||
* On Linux, simply kill the Monkey Island process and execute the AppImage.
|
* On Linux, simply kill the Monkey Island process and execute the AppImage.
|
||||||
* On Windows, restart the program.
|
* On Windows, restart the program.
|
||||||
|
|
||||||
1. Go to the Monkey Island's URL and create a new account.
|
3. Go to the Monkey Island's URL and create a new account.
|
||||||
|
|
||||||
If you are still unable to log into Monkey Island after following the above
|
If you are still unable to log into Monkey Island after following the above
|
||||||
steps, you can perform a complete factory reset by removing the entire [data
|
steps, you can perform a complete factory reset by removing the entire [data
|
||||||
|
|
|
@ -10,12 +10,12 @@ class InvalidRegistrationCredentialsError(Exception):
|
||||||
""" Raise when server config file changed and island needs to restart """
|
""" Raise when server config file changed and island needs to restart """
|
||||||
|
|
||||||
|
|
||||||
class RegistrationNotNeededError(Exception):
|
class AlreadyRegisteredError(Exception):
|
||||||
""" Raise to indicate the reason why registration is not required """
|
""" Raise to indicate the reason why registration is not required """
|
||||||
|
|
||||||
|
|
||||||
class AlreadyRegisteredError(RegistrationNotNeededError):
|
class UnknownUserError(Exception):
|
||||||
""" Raise to indicate the reason why registration is not required """
|
""" Raise to indicate that authentication failed """
|
||||||
|
|
||||||
|
|
||||||
class IncorrectCredentialsError(Exception):
|
class IncorrectCredentialsError(Exception):
|
||||||
|
|
|
@ -7,7 +7,6 @@ from common.utils.exceptions import (
|
||||||
InvalidRegistrationCredentialsError,
|
InvalidRegistrationCredentialsError,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.environment.environment_config import EnvironmentConfig
|
from monkey_island.cc.environment.environment_config import EnvironmentConfig
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -23,34 +22,6 @@ class Environment(object, metaclass=ABCMeta):
|
||||||
self._config = config
|
self._config = config
|
||||||
self._testing = False # Assume env is not for unit testing.
|
self._testing = False # Assume env is not for unit testing.
|
||||||
|
|
||||||
def get_user(self):
|
|
||||||
return self._config.user_creds
|
|
||||||
|
|
||||||
def needs_registration(self) -> bool:
|
|
||||||
try:
|
|
||||||
needs_registration = self._try_needs_registration()
|
|
||||||
return needs_registration
|
|
||||||
except (AlreadyRegisteredError) as e:
|
|
||||||
logger.info(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def try_add_user(self, credentials: UserCreds):
|
|
||||||
if not credentials:
|
|
||||||
raise InvalidRegistrationCredentialsError("Missing part of credentials.")
|
|
||||||
if self._try_needs_registration():
|
|
||||||
self._config.add_user(credentials)
|
|
||||||
logger.info(f"New user {credentials.username} registered!")
|
|
||||||
|
|
||||||
def _try_needs_registration(self) -> bool:
|
|
||||||
if self._is_registered():
|
|
||||||
raise AlreadyRegisteredError(
|
|
||||||
"User has already been registered. " "Reset credentials or login."
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _is_registered(self) -> bool:
|
|
||||||
return self._config and self._config.user_creds
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def testing(self):
|
def testing(self):
|
||||||
return self._testing
|
return self._testing
|
||||||
|
|
|
@ -4,14 +4,11 @@ import json
|
||||||
import os
|
import os
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentConfig:
|
class EnvironmentConfig:
|
||||||
def __init__(self, file_path):
|
def __init__(self, file_path):
|
||||||
self._server_config_path = os.path.expanduser(file_path)
|
self._server_config_path = os.path.expanduser(file_path)
|
||||||
self.server_config = None
|
self.server_config = None
|
||||||
self.user_creds = None
|
|
||||||
self.aws = None
|
self.aws = None
|
||||||
|
|
||||||
self._load_from_file(self._server_config_path)
|
self._load_from_file(self._server_config_path)
|
||||||
|
@ -24,7 +21,7 @@ class EnvironmentConfig:
|
||||||
|
|
||||||
self._load_from_json(config_content)
|
self._load_from_json(config_content)
|
||||||
|
|
||||||
def _load_from_json(self, config_json: str) -> EnvironmentConfig:
|
def _load_from_json(self, config_json: str):
|
||||||
data = json.loads(config_json)
|
data = json.loads(config_json)
|
||||||
self._load_from_dict(data["environment"])
|
self._load_from_dict(data["environment"])
|
||||||
|
|
||||||
|
@ -32,7 +29,6 @@ class EnvironmentConfig:
|
||||||
aws = dict_data["aws"] if "aws" in dict_data else None
|
aws = dict_data["aws"] if "aws" in dict_data else None
|
||||||
|
|
||||||
self.server_config = dict_data["server_config"]
|
self.server_config = dict_data["server_config"]
|
||||||
self.user_creds = _get_user_credentials_from_config(dict_data)
|
|
||||||
self.aws = aws
|
self.aws = aws
|
||||||
|
|
||||||
def save_to_file(self):
|
def save_to_file(self):
|
||||||
|
@ -50,16 +46,4 @@ class EnvironmentConfig:
|
||||||
}
|
}
|
||||||
if self.aws:
|
if self.aws:
|
||||||
config_dict.update({"aws": self.aws})
|
config_dict.update({"aws": self.aws})
|
||||||
config_dict.update(self.user_creds.to_dict())
|
|
||||||
return config_dict
|
return config_dict
|
||||||
|
|
||||||
def add_user(self, credentials: UserCreds):
|
|
||||||
self.user_creds = credentials
|
|
||||||
self.save_to_file()
|
|
||||||
|
|
||||||
|
|
||||||
def _get_user_credentials_from_config(dict_data: Dict):
|
|
||||||
username = dict_data.get("user", "")
|
|
||||||
password_hash = dict_data.get("password_hash", "")
|
|
||||||
|
|
||||||
return UserCreds(username, password_hash)
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from jwt import PyJWTError
|
||||||
|
|
||||||
from common.utils.exceptions import IncorrectCredentialsError
|
from common.utils.exceptions import IncorrectCredentialsError
|
||||||
from monkey_island.cc.resources.auth.credential_utils import get_username_password_from_request
|
from monkey_island.cc.resources.auth.credential_utils import get_username_password_from_request
|
||||||
from monkey_island.cc.services.authentication import AuthenticationService
|
from monkey_island.cc.services import AuthenticationService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ import logging
|
||||||
import flask_restful
|
import flask_restful
|
||||||
from flask import make_response, request
|
from flask import make_response, request
|
||||||
|
|
||||||
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
from common.utils.exceptions import AlreadyRegisteredError, InvalidRegistrationCredentialsError
|
||||||
from monkey_island.cc.resources.auth.credential_utils import get_username_password_from_request
|
from monkey_island.cc.resources.auth.credential_utils import get_username_password_from_request
|
||||||
from monkey_island.cc.services.authentication import AuthenticationService
|
from monkey_island.cc.services import AuthenticationService
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -20,5 +20,5 @@ class Registration(flask_restful.Resource):
|
||||||
try:
|
try:
|
||||||
AuthenticationService.register_new_user(username, password)
|
AuthenticationService.register_new_user(username, password)
|
||||||
return make_response({"error": ""}, 200)
|
return make_response({"error": ""}, 200)
|
||||||
except (InvalidRegistrationCredentialsError, RegistrationNotNeededError) as e:
|
except (InvalidRegistrationCredentialsError, AlreadyRegisteredError) as e:
|
||||||
return make_response({"error": str(e)}, 400)
|
return make_response({"error": str(e)}, 400)
|
||||||
|
|
|
@ -55,7 +55,8 @@ def _create_secure_directory_windows(path: str):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def open_new_securely_permissioned_file(path: str, mode: str = "w") -> Generator:
|
def open_new_securely_permissioned_file(path: str, mode: str = "w") -> Generator:
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
fd = _get_file_descriptor_for_new_secure_file_windows(path)
|
# TODO: Switch from string to Path object to avoid this hack.
|
||||||
|
fd = _get_file_descriptor_for_new_secure_file_windows(str(path))
|
||||||
else:
|
else:
|
||||||
fd = _get_file_descriptor_for_new_secure_file_linux(path)
|
fd = _get_file_descriptor_for_new_secure_file_linux(path)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .authentication.authentication_service import AuthenticationService
|
||||||
|
from .authentication.json_file_user_datastore import JsonFileUserDatastore
|
|
@ -1,39 +1,54 @@
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
|
||||||
import monkey_island.cc.environment.environment_singleton as env_singleton
|
from common.utils.exceptions import (
|
||||||
from common.utils.exceptions import IncorrectCredentialsError
|
IncorrectCredentialsError,
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
InvalidRegistrationCredentialsError,
|
||||||
|
UnknownUserError,
|
||||||
|
)
|
||||||
from monkey_island.cc.server_utils.encryption import (
|
from monkey_island.cc.server_utils.encryption import (
|
||||||
reset_datastore_encryptor,
|
reset_datastore_encryptor,
|
||||||
unlock_datastore_encryptor,
|
unlock_datastore_encryptor,
|
||||||
)
|
)
|
||||||
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
from monkey_island.cc.setup.mongo.database_initializer import reset_database
|
||||||
|
|
||||||
|
from .i_user_datastore import IUserDatastore
|
||||||
|
from .user_creds import UserCreds
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationService:
|
class AuthenticationService:
|
||||||
DATA_DIR = None
|
DATA_DIR = None
|
||||||
|
user_datastore = None
|
||||||
|
|
||||||
# TODO: A number of these services should be instance objects instead of
|
# TODO: A number of these services should be instance objects instead of
|
||||||
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
|
# static/singleton hybrids. At the moment, this requires invasive refactoring that's
|
||||||
# not a priority.
|
# not a priority.
|
||||||
@classmethod
|
@classmethod
|
||||||
def initialize(cls, data_dir: str):
|
def initialize(cls, data_dir: str, user_datastore: IUserDatastore):
|
||||||
cls.DATA_DIR = data_dir
|
cls.DATA_DIR = data_dir
|
||||||
|
cls.user_datastore = user_datastore
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def needs_registration() -> bool:
|
def needs_registration(cls) -> bool:
|
||||||
return env_singleton.env.needs_registration()
|
return not cls.user_datastore.has_registered_users()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_new_user(cls, username: str, password: str):
|
def register_new_user(cls, username: str, password: str):
|
||||||
|
if not username or not password:
|
||||||
|
raise InvalidRegistrationCredentialsError("Username or password can not be empty.")
|
||||||
|
|
||||||
credentials = UserCreds(username, _hash_password(password))
|
credentials = UserCreds(username, _hash_password(password))
|
||||||
env_singleton.env.try_add_user(credentials)
|
cls.user_datastore.add_user(credentials)
|
||||||
cls._reset_datastore_encryptor(username, password)
|
cls._reset_datastore_encryptor(username, password)
|
||||||
reset_database()
|
reset_database()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def authenticate(cls, username: str, password: str):
|
def authenticate(cls, username: str, password: str):
|
||||||
if not _credentials_match_registered_user(username, password):
|
try:
|
||||||
|
registered_user = cls.user_datastore.get_user_credentials(username)
|
||||||
|
except UnknownUserError:
|
||||||
|
raise IncorrectCredentialsError()
|
||||||
|
|
||||||
|
if not _credentials_match_registered_user(username, password, registered_user):
|
||||||
raise IncorrectCredentialsError()
|
raise IncorrectCredentialsError()
|
||||||
|
|
||||||
cls._unlock_datastore_encryptor(username, password)
|
cls._unlock_datastore_encryptor(username, password)
|
||||||
|
@ -56,12 +71,9 @@ def _hash_password(plaintext_password: str) -> str:
|
||||||
return password_hash.decode()
|
return password_hash.decode()
|
||||||
|
|
||||||
|
|
||||||
def _credentials_match_registered_user(username: str, password: str) -> bool:
|
def _credentials_match_registered_user(
|
||||||
registered_user = env_singleton.env.get_user()
|
username: str, password: str, registered_user: UserCreds
|
||||||
|
) -> bool:
|
||||||
if not registered_user:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return (registered_user.username == username) and _password_matches_hash(
|
return (registered_user.username == username) and _password_matches_hash(
|
||||||
password, registered_user.password_hash
|
password, registered_user.password_hash
|
||||||
)
|
)
|
|
@ -0,0 +1,36 @@
|
||||||
|
import abc
|
||||||
|
|
||||||
|
from .user_creds import UserCreds
|
||||||
|
|
||||||
|
|
||||||
|
class IUserDatastore(metaclass=abc.ABCMeta):
|
||||||
|
"""
|
||||||
|
Allows user credentials to be stored and retrieved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def has_registered_users(self) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if there are any registered user.
|
||||||
|
:return: True if any users have been registered, False otherwise
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def add_user(self, credentials: UserCreds):
|
||||||
|
"""
|
||||||
|
Adds a new user to the datastore.
|
||||||
|
:param UserCreds credentials: New user credentials to persistant storage.
|
||||||
|
:raises InvalidRegistrationCredentialsError: if the credentials are malformed
|
||||||
|
:raises AlreadyRegisteredError: if the user has already been registered
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_user_credentials(self, username: str) -> UserCreds:
|
||||||
|
"""
|
||||||
|
Gets the user matching `username` from storage.
|
||||||
|
:param str username: The username for which credentials will be retrieved
|
||||||
|
:return: User credentials for username
|
||||||
|
:rtype: UserCreds
|
||||||
|
:raises UnknownUserError: if the username does not exist
|
||||||
|
"""
|
|
@ -0,0 +1,58 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from common.utils.exceptions import (
|
||||||
|
AlreadyRegisteredError,
|
||||||
|
InvalidRegistrationCredentialsError,
|
||||||
|
UnknownUserError,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.server_utils.file_utils import open_new_securely_permissioned_file
|
||||||
|
|
||||||
|
from .i_user_datastore import IUserDatastore
|
||||||
|
from .user_creds import UserCreds
|
||||||
|
|
||||||
|
CREDENTIALS_FILE = "credentials.json"
|
||||||
|
|
||||||
|
|
||||||
|
class JsonFileUserDatastore(IUserDatastore):
|
||||||
|
def __init__(self, data_dir: Path):
|
||||||
|
self._credentials = None
|
||||||
|
self._credentials_file = data_dir / CREDENTIALS_FILE
|
||||||
|
|
||||||
|
if self._credentials_file.exists():
|
||||||
|
self._credentials = self._load_from_file()
|
||||||
|
|
||||||
|
def _load_from_file(self) -> UserCreds:
|
||||||
|
with open(self._credentials_file, "r") as f:
|
||||||
|
credentials_dict = json.load(f)
|
||||||
|
|
||||||
|
return UserCreds(credentials_dict["user"], credentials_dict["password_hash"])
|
||||||
|
|
||||||
|
def has_registered_users(self) -> bool:
|
||||||
|
return self._credentials is not None
|
||||||
|
|
||||||
|
def add_user(self, credentials: UserCreds):
|
||||||
|
if credentials is None:
|
||||||
|
raise InvalidRegistrationCredentialsError("Credentials can not be 'None'")
|
||||||
|
elif not credentials.username:
|
||||||
|
raise InvalidRegistrationCredentialsError("Username can not be empty")
|
||||||
|
elif not credentials.password_hash:
|
||||||
|
raise InvalidRegistrationCredentialsError("Password hash can not be empty")
|
||||||
|
|
||||||
|
if self._credentials:
|
||||||
|
raise AlreadyRegisteredError(
|
||||||
|
"User has already been registered. Reset credentials or login."
|
||||||
|
)
|
||||||
|
|
||||||
|
self._credentials = credentials
|
||||||
|
self._store_credentials_to_file()
|
||||||
|
|
||||||
|
def _store_credentials_to_file(self):
|
||||||
|
with open_new_securely_permissioned_file(self._credentials_file, "w") as f:
|
||||||
|
json.dump(self._credentials.to_dict(), f, indent=2)
|
||||||
|
|
||||||
|
def get_user_credentials(self, username: str) -> UserCreds:
|
||||||
|
if self._credentials is None or self._credentials.username != username:
|
||||||
|
raise UnknownUserError(f"User {username} does not exist.")
|
||||||
|
|
||||||
|
return self._credentials
|
|
@ -1,9 +1,10 @@
|
||||||
from monkey_island.cc.services.authentication import AuthenticationService
|
|
||||||
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
from monkey_island.cc.services.post_breach_files import PostBreachFilesService
|
||||||
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
from monkey_island.cc.services.run_local_monkey import LocalMonkeyRunService
|
||||||
|
|
||||||
|
from . import AuthenticationService, JsonFileUserDatastore
|
||||||
|
|
||||||
|
|
||||||
def initialize_services(data_dir):
|
def initialize_services(data_dir):
|
||||||
PostBreachFilesService.initialize(data_dir)
|
PostBreachFilesService.initialize(data_dir)
|
||||||
LocalMonkeyRunService.initialize(data_dir)
|
LocalMonkeyRunService.initialize(data_dir)
|
||||||
AuthenticationService.initialize(data_dir)
|
AuthenticationService.initialize(data_dir, JsonFileUserDatastore(data_dir))
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"user": "new_user",
|
||||||
|
"password_hash": "new_hash"
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"environment" : {
|
|
||||||
"server_config": "password",
|
|
||||||
"user": "test"
|
|
||||||
},
|
|
||||||
"mongodb": {
|
|
||||||
"start_mongodb": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"log_level": "NOTICE",
|
|
||||||
"environment" : {
|
|
||||||
"server_config": "password",
|
|
||||||
"user": "test",
|
|
||||||
"password_hash": "abcdef"
|
|
||||||
},
|
|
||||||
"mongodb": {
|
|
||||||
"start_mongodb": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,16 +3,6 @@ import os
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def with_credentials(server_configs_dir):
|
|
||||||
return os.path.join(server_configs_dir, "server_config_with_credentials.json")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def no_credentials(server_configs_dir):
|
def no_credentials(server_configs_dir):
|
||||||
return os.path.join(server_configs_dir, "server_config_no_credentials.json")
|
return os.path.join(server_configs_dir, "server_config_no_credentials.json")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def partial_credentials(server_configs_dir):
|
|
||||||
return os.path.join(server_configs_dir, "server_config_partial_credentials.json")
|
|
||||||
|
|
|
@ -1,91 +1,10 @@
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import pytest
|
from monkey_island.cc.environment import Environment, EnvironmentConfig
|
||||||
|
|
||||||
from common.utils.exceptions import AlreadyRegisteredError, InvalidRegistrationCredentialsError
|
|
||||||
from monkey_island.cc.environment import Environment, EnvironmentConfig, UserCreds
|
|
||||||
|
|
||||||
WITH_CREDENTIALS = None
|
|
||||||
NO_CREDENTIALS = None
|
|
||||||
PARTIAL_CREDENTIALS = None
|
|
||||||
|
|
||||||
USER_CREDENTIALS = UserCreds(username="test", password_hash="1231234")
|
|
||||||
|
|
||||||
|
|
||||||
# This fixture is a dirty hack that can be removed once these tests are converted from
|
|
||||||
# unittest to pytest. Instead, the appropriate fixtures from conftest.py can be used.
|
|
||||||
@pytest.fixture(scope="module", autouse=True)
|
|
||||||
def configure_resources(server_configs_dir):
|
|
||||||
global WITH_CREDENTIALS
|
|
||||||
global NO_CREDENTIALS
|
|
||||||
global PARTIAL_CREDENTIALS
|
|
||||||
|
|
||||||
WITH_CREDENTIALS = os.path.join(server_configs_dir, "server_config_with_credentials.json")
|
|
||||||
NO_CREDENTIALS = os.path.join(server_configs_dir, "server_config_no_credentials.json")
|
|
||||||
PARTIAL_CREDENTIALS = os.path.join(server_configs_dir, "server_config_partial_credentials.json")
|
|
||||||
|
|
||||||
|
|
||||||
def get_tmp_file():
|
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
||||||
return f.name
|
|
||||||
|
|
||||||
|
|
||||||
class StubEnvironmentConfig(EnvironmentConfig):
|
|
||||||
def __init__(self, server_config, deployment, user_creds):
|
|
||||||
self.server_config = server_config
|
|
||||||
self.deployment = deployment
|
|
||||||
self.user_creds = user_creds
|
|
||||||
self.server_config_path = get_tmp_file()
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
os.remove(self.server_config_path)
|
|
||||||
|
|
||||||
|
|
||||||
class TestEnvironment(TestCase):
|
class TestEnvironment(TestCase):
|
||||||
class EnvironmentCredentialsRequired(Environment):
|
|
||||||
def __init__(self):
|
|
||||||
config = StubEnvironmentConfig("test", "test", None)
|
|
||||||
super().__init__(config)
|
|
||||||
|
|
||||||
class EnvironmentAlreadyRegistered(Environment):
|
|
||||||
def __init__(self):
|
|
||||||
config = StubEnvironmentConfig("test", "test", UserCreds("test_user", "test_secret"))
|
|
||||||
super().__init__(config)
|
|
||||||
|
|
||||||
@patch.object(target=EnvironmentConfig, attribute="save_to_file", new=MagicMock())
|
|
||||||
def test_try_add_user(self):
|
|
||||||
env = TestEnvironment.EnvironmentCredentialsRequired()
|
|
||||||
credentials = USER_CREDENTIALS
|
|
||||||
env.try_add_user(credentials)
|
|
||||||
|
|
||||||
credentials = UserCreds(username="test", password_hash="")
|
|
||||||
with self.assertRaises(InvalidRegistrationCredentialsError):
|
|
||||||
env.try_add_user(credentials)
|
|
||||||
|
|
||||||
def test_try_needs_registration(self):
|
|
||||||
env = TestEnvironment.EnvironmentAlreadyRegistered()
|
|
||||||
with self.assertRaises(AlreadyRegisteredError):
|
|
||||||
env._try_needs_registration()
|
|
||||||
|
|
||||||
env = TestEnvironment.EnvironmentCredentialsRequired()
|
|
||||||
self.assertTrue(env._try_needs_registration())
|
|
||||||
|
|
||||||
def test_needs_registration(self):
|
|
||||||
env = TestEnvironment.EnvironmentCredentialsRequired()
|
|
||||||
self._test_bool_env_method("needs_registration", env, WITH_CREDENTIALS, False)
|
|
||||||
self._test_bool_env_method("needs_registration", env, NO_CREDENTIALS, True)
|
|
||||||
self._test_bool_env_method("needs_registration", env, PARTIAL_CREDENTIALS, True)
|
|
||||||
|
|
||||||
def test_is_registered(self):
|
|
||||||
env = TestEnvironment.EnvironmentCredentialsRequired()
|
|
||||||
self._test_bool_env_method("_is_registered", env, WITH_CREDENTIALS, True)
|
|
||||||
self._test_bool_env_method("_is_registered", env, NO_CREDENTIALS, False)
|
|
||||||
self._test_bool_env_method("_is_registered", env, PARTIAL_CREDENTIALS, False)
|
|
||||||
|
|
||||||
def _test_bool_env_method(
|
def _test_bool_env_method(
|
||||||
self, method_name: str, env: Environment, config: Dict, expected_result: bool
|
self, method_name: str, env: Environment, config: Dict, expected_result: bool
|
||||||
):
|
):
|
||||||
|
|
|
@ -5,7 +5,6 @@ import shutil
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from monkey_island.cc.environment.environment_config import EnvironmentConfig
|
from monkey_island.cc.environment.environment_config import EnvironmentConfig
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -13,15 +12,6 @@ def config_file(tmpdir):
|
||||||
return os.path.join(tmpdir, "test_config.json")
|
return os.path.join(tmpdir, "test_config.json")
|
||||||
|
|
||||||
|
|
||||||
def test_get_with_credentials(with_credentials):
|
|
||||||
config_dict = EnvironmentConfig(with_credentials).to_dict()
|
|
||||||
|
|
||||||
assert len(config_dict.keys()) == 3
|
|
||||||
assert config_dict["server_config"] == "password"
|
|
||||||
assert config_dict["user"] == "test"
|
|
||||||
assert config_dict["password_hash"] == "abcdef"
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_with_no_credentials(no_credentials):
|
def test_get_with_no_credentials(no_credentials):
|
||||||
config_dict = EnvironmentConfig(no_credentials).to_dict()
|
config_dict = EnvironmentConfig(no_credentials).to_dict()
|
||||||
|
|
||||||
|
@ -29,16 +19,8 @@ def test_get_with_no_credentials(no_credentials):
|
||||||
assert config_dict["server_config"] == "password"
|
assert config_dict["server_config"] == "password"
|
||||||
|
|
||||||
|
|
||||||
def test_get_with_partial_credentials(partial_credentials):
|
def test_save_to_file(config_file, no_credentials):
|
||||||
config_dict = EnvironmentConfig(partial_credentials).to_dict()
|
shutil.copyfile(no_credentials, config_file)
|
||||||
|
|
||||||
assert len(config_dict.keys()) == 2
|
|
||||||
assert config_dict["server_config"] == "password"
|
|
||||||
assert config_dict["user"] == "test"
|
|
||||||
|
|
||||||
|
|
||||||
def test_save_to_file(config_file, with_credentials):
|
|
||||||
shutil.copyfile(with_credentials, config_file)
|
|
||||||
|
|
||||||
environment_config = EnvironmentConfig(config_file)
|
environment_config = EnvironmentConfig(config_file)
|
||||||
environment_config.aws = "test_aws"
|
environment_config.aws = "test_aws"
|
||||||
|
@ -48,43 +30,3 @@ def test_save_to_file(config_file, with_credentials):
|
||||||
from_file = json.load(f)
|
from_file = json.load(f)
|
||||||
|
|
||||||
assert environment_config.to_dict() == from_file["environment"]
|
assert environment_config.to_dict() == from_file["environment"]
|
||||||
|
|
||||||
|
|
||||||
def test_save_to_file_preserve_log_level(config_file, with_credentials):
|
|
||||||
shutil.copyfile(with_credentials, config_file)
|
|
||||||
|
|
||||||
environment_config = EnvironmentConfig(config_file)
|
|
||||||
environment_config.aws = "test_aws"
|
|
||||||
environment_config.save_to_file()
|
|
||||||
|
|
||||||
with open(config_file, "r") as f:
|
|
||||||
from_file = json.load(f)
|
|
||||||
|
|
||||||
assert "log_level" in from_file
|
|
||||||
assert from_file["log_level"] == "NOTICE"
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_user(config_file, with_credentials):
|
|
||||||
new_user = "new_user"
|
|
||||||
new_password_hash = "fedcba"
|
|
||||||
new_user_creds = UserCreds(new_user, new_password_hash)
|
|
||||||
|
|
||||||
shutil.copyfile(with_credentials, config_file)
|
|
||||||
|
|
||||||
environment_config = EnvironmentConfig(config_file)
|
|
||||||
environment_config.add_user(new_user_creds)
|
|
||||||
|
|
||||||
with open(config_file, "r") as f:
|
|
||||||
from_file = json.load(f)
|
|
||||||
|
|
||||||
assert len(from_file["environment"].keys()) == 3
|
|
||||||
assert from_file["environment"]["user"] == new_user
|
|
||||||
assert from_file["environment"]["password_hash"] == new_password_hash
|
|
||||||
|
|
||||||
|
|
||||||
def test_user(with_credentials):
|
|
||||||
environment_config = EnvironmentConfig(with_credentials)
|
|
||||||
user = environment_config.user_creds
|
|
||||||
|
|
||||||
assert user.username == "test"
|
|
||||||
assert user.password_hash == "abcdef"
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from common.utils.exceptions import InvalidRegistrationCredentialsError, RegistrationNotNeededError
|
from common.utils.exceptions import AlreadyRegisteredError, InvalidRegistrationCredentialsError
|
||||||
|
|
||||||
REGISTRATION_URL = "/api/registration"
|
REGISTRATION_URL = "/api/registration"
|
||||||
|
|
||||||
|
@ -59,9 +59,7 @@ def test_invalid_credentials(make_registration_request, mock_authentication_serv
|
||||||
|
|
||||||
|
|
||||||
def test_registration_not_needed(make_registration_request, mock_authentication_service):
|
def test_registration_not_needed(make_registration_request, mock_authentication_service):
|
||||||
mock_authentication_service.register_new_user = MagicMock(
|
mock_authentication_service.register_new_user = MagicMock(side_effect=AlreadyRegisteredError())
|
||||||
side_effect=RegistrationNotNeededError()
|
|
||||||
)
|
|
||||||
|
|
||||||
registration_request_body = "{}"
|
registration_request_body = "{}"
|
||||||
response = make_registration_request(registration_request_body)
|
response = make_registration_request(registration_request_body)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from monkey_island.cc.environment.user_creds import UserCreds
|
from monkey_island.cc.services.authentication.user_creds import UserCreds
|
||||||
|
|
||||||
TEST_USER = "Test"
|
TEST_USER = "Test"
|
||||||
TEST_HASH = "abc1231234"
|
TEST_HASH = "abc1231234"
|
|
@ -0,0 +1,184 @@
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from common.utils.exceptions import (
|
||||||
|
AlreadyRegisteredError,
|
||||||
|
IncorrectCredentialsError,
|
||||||
|
InvalidRegistrationCredentialsError,
|
||||||
|
UnknownUserError,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.services import AuthenticationService
|
||||||
|
from monkey_island.cc.services.authentication import authentication_service
|
||||||
|
from monkey_island.cc.services.authentication.i_user_datastore import IUserDatastore
|
||||||
|
from monkey_island.cc.services.authentication.user_creds import UserCreds
|
||||||
|
|
||||||
|
USERNAME = "user1"
|
||||||
|
PASSWORD = "test"
|
||||||
|
PASSWORD_HASH = "$2b$12$YsGjjuJFddYJ6z5S5/nMCuKkCzKHB1AWY9SXkQ02i25d8TgdhIRS2"
|
||||||
|
|
||||||
|
|
||||||
|
class MockUserDatastore(IUserDatastore):
|
||||||
|
def __init__(self, has_registered_users, add_user, get_user_credentials):
|
||||||
|
self._has_registered_users = has_registered_users
|
||||||
|
self._add_user = add_user
|
||||||
|
self._get_user_credentials = get_user_credentials
|
||||||
|
|
||||||
|
def has_registered_users(self):
|
||||||
|
return self._has_registered_users()
|
||||||
|
|
||||||
|
def add_user(self, credentials: UserCreds):
|
||||||
|
return self._add_user(credentials)
|
||||||
|
|
||||||
|
def get_user_credentials(self, username: str) -> UserCreds:
|
||||||
|
return self._get_user_credentials(username)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_reset_datastore_encryptor():
|
||||||
|
return MagicMock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_reset_database():
|
||||||
|
return MagicMock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_unlock_datastore_encryptor():
|
||||||
|
return MagicMock()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_datastore_utils(
|
||||||
|
monkeypatch,
|
||||||
|
mock_reset_datastore_encryptor,
|
||||||
|
mock_reset_database,
|
||||||
|
mock_unlock_datastore_encryptor,
|
||||||
|
):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
authentication_service, "reset_datastore_encryptor", mock_reset_datastore_encryptor
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(authentication_service, "reset_database", mock_reset_database)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
authentication_service, "unlock_datastore_encryptor", mock_unlock_datastore_encryptor
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_needs_registration__true(tmp_path):
|
||||||
|
has_registered_users = False
|
||||||
|
mock_user_datastore = MockUserDatastore(lambda: has_registered_users, None, None)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
assert a_s.needs_registration()
|
||||||
|
|
||||||
|
|
||||||
|
def test_needs_registration__false(tmp_path):
|
||||||
|
has_registered_users = True
|
||||||
|
mock_user_datastore = MockUserDatastore(lambda: has_registered_users, None, None)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
assert not a_s.needs_registration()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("error", [InvalidRegistrationCredentialsError, AlreadyRegisteredError])
|
||||||
|
def test_register_new_user__fails(
|
||||||
|
tmp_path, mock_reset_datastore_encryptor, mock_reset_database, error
|
||||||
|
):
|
||||||
|
mock_user_datastore = MockUserDatastore(lambda: True, MagicMock(side_effect=error), None)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
with pytest.raises(error):
|
||||||
|
a_s.register_new_user(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
mock_reset_datastore_encryptor.assert_not_called()
|
||||||
|
mock_reset_database.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_register_new_user__empty_password_fails(
|
||||||
|
tmp_path, mock_reset_datastore_encryptor, mock_reset_database
|
||||||
|
):
|
||||||
|
mock_user_datastore = MockUserDatastore(lambda: False, None, None)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
with pytest.raises(InvalidRegistrationCredentialsError):
|
||||||
|
a_s.register_new_user(USERNAME, "")
|
||||||
|
|
||||||
|
mock_reset_datastore_encryptor.assert_not_called()
|
||||||
|
mock_reset_database.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_register_new_user(tmp_path, mock_reset_datastore_encryptor, mock_reset_database):
|
||||||
|
mock_add_user = MagicMock()
|
||||||
|
mock_user_datastore = MockUserDatastore(lambda: False, mock_add_user, None)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
a_s.register_new_user(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
assert mock_add_user.call_args[0][0].username == USERNAME
|
||||||
|
assert mock_add_user.call_args[0][0].password_hash != PASSWORD
|
||||||
|
|
||||||
|
mock_reset_datastore_encryptor.assert_called_once()
|
||||||
|
assert mock_reset_datastore_encryptor.call_args[0][1] != USERNAME
|
||||||
|
|
||||||
|
mock_reset_database.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticate__success(tmp_path, mock_unlock_datastore_encryptor):
|
||||||
|
mock_user_datastore = MockUserDatastore(
|
||||||
|
lambda: True,
|
||||||
|
None,
|
||||||
|
lambda _: UserCreds(USERNAME, PASSWORD_HASH),
|
||||||
|
)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
# If authentication fails, this function will raise an exception and the test will fail.
|
||||||
|
a_s.authenticate(USERNAME, PASSWORD)
|
||||||
|
mock_unlock_datastore_encryptor.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("username", "password"), [("wrong_username", PASSWORD), (USERNAME, "wrong_password")]
|
||||||
|
)
|
||||||
|
def test_authenticate__failed_wrong_credentials(
|
||||||
|
tmp_path, mock_unlock_datastore_encryptor, username, password
|
||||||
|
):
|
||||||
|
mock_user_datastore = MockUserDatastore(
|
||||||
|
lambda: True,
|
||||||
|
None,
|
||||||
|
lambda _: UserCreds(USERNAME, PASSWORD_HASH),
|
||||||
|
)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
with pytest.raises(IncorrectCredentialsError):
|
||||||
|
a_s.authenticate(username, password)
|
||||||
|
|
||||||
|
mock_unlock_datastore_encryptor.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticate__failed_no_registered_user(tmp_path, mock_unlock_datastore_encryptor):
|
||||||
|
mock_user_datastore = MockUserDatastore(
|
||||||
|
lambda: True, None, MagicMock(side_effect=UnknownUserError)
|
||||||
|
)
|
||||||
|
|
||||||
|
a_s = AuthenticationService()
|
||||||
|
a_s.initialize(tmp_path, mock_user_datastore)
|
||||||
|
|
||||||
|
with pytest.raises(IncorrectCredentialsError):
|
||||||
|
a_s.authenticate(USERNAME, PASSWORD)
|
||||||
|
|
||||||
|
mock_unlock_datastore_encryptor.assert_not_called()
|
|
@ -0,0 +1,111 @@
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from tests.monkey_island.utils import assert_windows_permissions
|
||||||
|
|
||||||
|
from common.utils.exceptions import (
|
||||||
|
AlreadyRegisteredError,
|
||||||
|
InvalidRegistrationCredentialsError,
|
||||||
|
UnknownUserError,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.server_utils.file_utils import is_windows_os
|
||||||
|
from monkey_island.cc.services.authentication.json_file_user_datastore import (
|
||||||
|
CREDENTIALS_FILE,
|
||||||
|
JsonFileUserDatastore,
|
||||||
|
)
|
||||||
|
from monkey_island.cc.services.authentication.user_creds import UserCreds
|
||||||
|
|
||||||
|
USERNAME = "test"
|
||||||
|
PASSWORD_HASH = "DEADBEEF"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def empty_datastore(tmp_path):
|
||||||
|
return JsonFileUserDatastore(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def populated_datastore(data_for_tests_dir):
|
||||||
|
return JsonFileUserDatastore(data_for_tests_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def credentials_file_path(tmp_path):
|
||||||
|
return tmp_path / CREDENTIALS_FILE
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_registered_users_pre_registration(empty_datastore):
|
||||||
|
assert not empty_datastore.has_registered_users()
|
||||||
|
|
||||||
|
|
||||||
|
def test_has_registered_users_after_registration(populated_datastore):
|
||||||
|
assert populated_datastore.has_registered_users()
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_user(empty_datastore, credentials_file_path):
|
||||||
|
datastore = empty_datastore
|
||||||
|
|
||||||
|
datastore.add_user(UserCreds(USERNAME, PASSWORD_HASH))
|
||||||
|
assert datastore.has_registered_users()
|
||||||
|
assert credentials_file_path.exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(is_windows_os(), reason="Tests Posix (not Windows) permissions.")
|
||||||
|
def test_add_user__term_posix(empty_datastore, credentials_file_path):
|
||||||
|
empty_datastore.add_user(UserCreds(USERNAME, PASSWORD_HASH))
|
||||||
|
st = os.stat(credentials_file_path)
|
||||||
|
|
||||||
|
expected_mode = stat.S_IRUSR | stat.S_IWUSR
|
||||||
|
actual_mode = st.st_mode & (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
||||||
|
|
||||||
|
assert expected_mode == actual_mode
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not is_windows_os(), reason="Tests Windows (not Posix) permissions.")
|
||||||
|
def test_add_user__term_windows(empty_datastore, credentials_file_path):
|
||||||
|
datastore = empty_datastore
|
||||||
|
|
||||||
|
datastore.add_user(UserCreds(USERNAME, PASSWORD_HASH))
|
||||||
|
assert_windows_permissions(str(credentials_file_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_user__None_creds(empty_datastore):
|
||||||
|
with pytest.raises(InvalidRegistrationCredentialsError):
|
||||||
|
empty_datastore.add_user(None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_user__empty_username(empty_datastore):
|
||||||
|
with pytest.raises(InvalidRegistrationCredentialsError):
|
||||||
|
empty_datastore.add_user(UserCreds("", PASSWORD_HASH))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_user__empty_password_hash(empty_datastore):
|
||||||
|
with pytest.raises(InvalidRegistrationCredentialsError):
|
||||||
|
empty_datastore.add_user(UserCreds(USERNAME, ""))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_user__already_registered(populated_datastore):
|
||||||
|
with pytest.raises(AlreadyRegisteredError):
|
||||||
|
populated_datastore.add_user(UserCreds("new_user", "new_hash"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_user_credentials_from_file(tmp_path):
|
||||||
|
empty_datastore = JsonFileUserDatastore(tmp_path)
|
||||||
|
empty_datastore.add_user(UserCreds(USERNAME, PASSWORD_HASH))
|
||||||
|
|
||||||
|
populated_datastore = JsonFileUserDatastore(tmp_path)
|
||||||
|
stored_user = populated_datastore.get_user_credentials(USERNAME)
|
||||||
|
|
||||||
|
assert stored_user.username == USERNAME
|
||||||
|
assert stored_user.password_hash == PASSWORD_HASH
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_unknown_user(populated_datastore):
|
||||||
|
with pytest.raises(UnknownUserError):
|
||||||
|
populated_datastore.get_user_credentials("unregistered_user")
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_user_credentials__no_user_registered(empty_datastore):
|
||||||
|
with pytest.raises(UnknownUserError):
|
||||||
|
empty_datastore.get_user_credentials("unregistered_user")
|
Loading…
Reference in New Issue