Skip to content

Commit 68bf4d7

Browse files
Updated unit tests for the recentchanges, rename secret and environment variable functions for clarity and consistency
1 parent c6ca360 commit 68bf4d7

File tree

6 files changed

+131
-89
lines changed

6 files changed

+131
-89
lines changed

janux_auth_gateway/__init__.py

+13
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,17 @@
1616
Author: FOX Techniques <[email protected]>
1717
"""
1818

19+
from .config import Config, _read_secret, _read_jwt_key, _get_env_variable
20+
21+
# Only allow these functions to be imported within the package
22+
__module_exports__ = ["Config", "_read_secret", "_read_jwt_key", "_get_env_variable"]
23+
24+
25+
# Make sure internal functions can only be imported within package
26+
def __getattr__(name):
27+
if name in __module_exports__:
28+
return globals()[name] # Allow internal access within the package
29+
raise AttributeError(f"Module '{__name__}' has no attribute '{name}'")
30+
31+
1932
__version__ = "0.1.0"

janux_auth_gateway/config/__init__.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Author: FOX Techniques <[email protected]>
1414
"""
1515

16-
from .config import Config, get_env_variable
16+
from .config import Config, _read_secret, _read_jwt_key, _get_env_variable
1717

18-
__all__ = ["Config", "get_env_variable"]
18+
# Expose the Config class for external use.
19+
__all__ = ["Config"]

janux_auth_gateway/config/config.py

+25-29
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from typing import Optional, List
1717

1818

19-
def read_secret(secret_name):
19+
def _read_secret(secret_name):
2020
"""
2121
Reads secrets from:
2222
1. `/run/secrets/` (Docker Secrets in Production)
@@ -36,11 +36,7 @@ def read_secret(secret_name):
3636
return os.getenv(secret_name) # Fallback to environment variable
3737

3838

39-
import os
40-
import glob
41-
42-
43-
def read_jwt_key(key_type: str) -> str:
39+
def _read_jwt_key(key_type: str) -> str:
4440
"""
4541
Reads a private or public key from Docker Secrets or local development folder.
4642
@@ -77,7 +73,7 @@ def read_jwt_key(key_type: str) -> str:
7773
raise ValueError(f"No {key_type} key file found in {search_paths}")
7874

7975

80-
def get_env_variable(var_name: str, default: Optional[str] = None) -> str:
76+
def _get_env_variable(var_name: str, default: Optional[str] = None) -> str:
8177
"""
8278
Retrieve non-sensitive environment variables with an optional default.
8379
@@ -107,48 +103,48 @@ class Config:
107103
"""
108104

109105
# Application settings
110-
ENVIRONMENT = get_env_variable("ENVIRONMENT", "local")
111-
ALLOWED_ORIGINS: List[str] = get_env_variable("ALLOWED_ORIGINS", "*").split(",")
106+
ENVIRONMENT = _get_env_variable("ENVIRONMENT", "local")
107+
ALLOWED_ORIGINS: List[str] = _get_env_variable("ALLOWED_ORIGINS", "*").split(",")
112108

113109
# 🔐 Encryption Key (AES)
114-
JANUX_ENCRYPTION_KEY = read_secret("janux_encryption_key")
110+
JANUX_ENCRYPTION_KEY = _read_secret("janux_encryption_key")
115111

116112
# 🔑 JWT Authentication Keys
117-
JWT_PRIVATE_KEY = read_jwt_key(key_type="private")
118-
JWT_PUBLIC_KEY = read_jwt_key(key_type="public")
113+
JWT_PRIVATE_KEY = _read_jwt_key(key_type="private")
114+
JWT_PUBLIC_KEY = _read_jwt_key(key_type="public")
119115

120116
JWT_ALGORITHM = "RS256"
121117

122118
# 🔥 JWT Token Settings
123119
ACCESS_TOKEN_EXPIRE_MINUTES = int(
124-
get_env_variable("ACCESS_TOKEN_EXPIRE_MINUTES", "20")
120+
_get_env_variable("ACCESS_TOKEN_EXPIRE_MINUTES", "20")
125121
)
126-
TOKEN_ISSUER = get_env_variable("TOKEN_ISSUER", "JANUX-server")
127-
TOKEN_AUDIENCE = get_env_variable("TOKEN_AUDIENCE", "JANUX-application")
122+
ISSUER = _get_env_variable("TOKEN_ISSUER", "JANUX-server")
123+
AUDIENCE = _get_env_variable("TOKEN_AUDIENCE", "JANUX-application")
128124

129125
# 🗝️ Token Endpoints
130-
USER_TOKEN_URL = get_env_variable("USER_TOKEN_URL", "/auth/login")
131-
ADMIN_TOKEN_URL = get_env_variable("ADMIN_TOKEN_URL", "/auth/login")
126+
USER_TOKEN_URL = _get_env_variable("USER_TOKEN_URL", "/auth/login")
127+
ADMIN_TOKEN_URL = _get_env_variable("ADMIN_TOKEN_URL", "/auth/login")
132128

133129
# 🛢️ Database (MongoDB)
134-
MONGO_URI = read_secret("mongo_uri")
135-
MONGO_DATABASE_NAME = get_env_variable("MONGO_DATABASE_NAME", "users_db")
130+
MONGO_URI = _read_secret("mongo_uri")
131+
MONGO_DATABASE_NAME = _get_env_variable("MONGO_DATABASE_NAME", "users_db")
136132

137133
# 👤 MongoDB Initial Admin Credentials
138-
MONGO_ADMIN_EMAIL = read_secret("mongo_admin_email")
139-
MONGO_ADMIN_PASSWORD = read_secret("mongo_admin_password")
140-
MONGO_ADMIN_FULLNAME = read_secret("mongo_admin_fullname")
141-
MONGO_ADMIN_ROLE = read_secret("mongo_admin_role")
134+
MONGO_ADMIN_EMAIL = _read_secret("mongo_admin_email")
135+
MONGO_ADMIN_PASSWORD = _read_secret("mongo_admin_password")
136+
MONGO_ADMIN_FULLNAME = _read_secret("mongo_admin_fullname")
137+
MONGO_ADMIN_ROLE = _read_secret("mongo_admin_role")
142138

143139
# 👤 MongoDB Initial User Credentials
144-
MONGO_USER_EMAIL = read_secret("mongo_user_email")
145-
MONGO_USER_PASSWORD = read_secret("mongo_user_password")
146-
MONGO_USER_FULLNAME = read_secret("mongo_user_fullname")
147-
MONGO_USER_ROLE = read_secret("mongo_user_role")
140+
MONGO_USER_EMAIL = _read_secret("mongo_user_email")
141+
MONGO_USER_PASSWORD = _read_secret("mongo_user_password")
142+
MONGO_USER_FULLNAME = _read_secret("mongo_user_fullname")
143+
MONGO_USER_ROLE = _read_secret("mongo_user_role")
148144

149145
# 🔄 Redis Configuration
150-
REDIS_HOST = get_env_variable("REDIS_HOST", "localhost")
151-
REDIS_PORT = int(get_env_variable("REDIS_PORT", "6379"))
146+
REDIS_HOST = _get_env_variable("REDIS_HOST", "localhost")
147+
REDIS_PORT = int(_get_env_variable("REDIS_PORT", "6379"))
152148

153149
@staticmethod
154150
def validate():

janux_auth_gateway/database/mongoDB.py

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from janux_auth_gateway.auth.passwords import verify_password, hash_password
2121
from janux_auth_gateway.config import Config
2222
from janux_auth_gateway.debug.custom_logger import get_logger
23-
from janux_auth_gateway.utils.email_utils import mask_email
2423
from janux_auth_gateway.models.user_model import User
2524
from janux_auth_gateway.models.admin_model import Admin
2625

tests/auth/test_jwt.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def mock_keys(mocker):
5757
)
5858

5959
# Mock Config values with valid RSA keys
60-
mocker.patch.object(Config, "PRIVATE_KEY", private_pem)
61-
mocker.patch.object(Config, "PUBLIC_KEY", public_pem)
60+
mocker.patch.object(Config, "JWT_PRIVATE_KEY", private_pem)
61+
mocker.patch.object(Config, "JWT_PUBLIC_KEY", public_pem)
6262

6363

6464
@pytest.fixture()
@@ -130,7 +130,7 @@ def test_verify_expired_jwt(mock_keys, fake_redis):
130130
"""
131131
expired_token = jwt.encode(
132132
{"sub": "testuser", "exp": 0},
133-
Config.PRIVATE_KEY,
133+
Config.JWT_PRIVATE_KEY,
134134
algorithm="RS256",
135135
)
136136

tests/config/test_config.py

+87-54
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,27 @@
66
Tests:
77
- Retrieval of environment variables with defaults.
88
- Handling of missing required environment variables.
9-
- Config validation without exposing secret values.
9+
- Secure handling of secrets using file-based and environment-based approaches.
10+
- Validation of critical configuration settings.
11+
- Proper loading of JWT private and public keys.
12+
- Ensuring Redis and MongoDB configurations are correctly loaded.
1013
1114
Features:
1215
- Uses `pytest-mock` to override environment variables.
13-
- Mocks private/public key file reads to avoid file system dependencies.
14-
- Ensures secure validation without printing sensitive information.
16+
- Mocks file reading operations to avoid filesystem dependencies.
17+
- Ensures security best practices for managing secrets and keys.
18+
- Validates behavior when secrets are missing or invalid.
1519
1620
Author: FOX Techniques <[email protected]>
1721
"""
1822

1923
import pytest
2024
import os
2125
from unittest.mock import patch, mock_open
22-
from janux_auth_gateway.config import get_env_variable, Config
26+
from janux_auth_gateway.config import Config
27+
28+
# In your test file
29+
from janux_auth_gateway.config import _read_secret, _read_jwt_key, _get_env_variable
2330

2431

2532
def test_get_env_variable_with_existing_value(mocker):
@@ -31,7 +38,7 @@ def test_get_env_variable_with_existing_value(mocker):
3138
"""
3239
mocker.patch.dict(os.environ, {"TEST_ENV_VAR": "test_value"})
3340

34-
assert get_env_variable("TEST_ENV_VAR") == "test_value"
41+
assert _get_env_variable("TEST_ENV_VAR") == "test_value"
3542

3643

3744
def test_get_env_variable_with_default_value():
@@ -41,7 +48,7 @@ def test_get_env_variable_with_default_value():
4148
Expected Outcome:
4249
- The function should return the default value if the variable is not set.
4350
"""
44-
assert get_env_variable("NON_EXISTENT_VAR", "default_value") == "default_value"
51+
assert _get_env_variable("NON_EXISTENT_VAR", "default_value") == "default_value"
4552

4653

4754
def test_get_env_variable_missing_without_default():
@@ -52,10 +59,10 @@ def test_get_env_variable_missing_without_default():
5259
- The function should raise a ValueError if no default is provided.
5360
"""
5461
with pytest.raises(ValueError, match="Missing environment variable: 'MISSING_VAR'"):
55-
get_env_variable("MISSING_VAR")
62+
_get_env_variable("MISSING_VAR")
5663

5764

58-
@pytest.fixture()
65+
@pytest.fixture
5966
def mock_config(mocker):
6067
"""
6168
Mock the entire Config class properties without exposing secrets.
@@ -65,35 +72,34 @@ def mock_config(mocker):
6572
{
6673
"ENVIRONMENT": "test",
6774
"ALLOWED_ORIGINS": "http://localhost,http://127.0.0.1",
68-
"CONTAINER": "False",
69-
"AUTH_PRIVATE_KEY_PATH": "/fake/private.pem",
70-
"AUTH_PUBLIC_KEY_PATH": "/fake/public.pem",
7175
"ACCESS_TOKEN_EXPIRE_MINUTES": "30",
72-
"USER_TOKEN_URL": "http://localhost/token",
73-
"ADMIN_TOKEN_URL": "http://localhost/admin-token",
74-
"MONGO_URI": "mongodb://localhost:27017/test_db",
75-
"MONGO_DATABASE_NAME": "test_db",
76-
"ISSUER": "JANUX-server",
77-
"AUDIENCE": "JANUX-application",
78-
"MONGO_ADMIN_EMAIL": "[email protected]",
79-
"MONGO_ADMIN_PASSWORD": "adminpassword",
80-
"MONGO_ADMIN_FULLNAME": "Admin User",
81-
"MONGO_ADMIN_ROLE": "super_admin",
82-
"MONGO_USER_EMAIL": "[email protected]",
83-
"MONGO_USER_PASSWORD": "userpassword",
84-
"MONGO_USER_FULLNAME": "Test User",
85-
"MONGO_USER_ROLE": "user",
76+
"TOKEN_ISSUER": "JANUX-server",
77+
"TOKEN_AUDIENCE": "JANUX-application",
78+
"USER_TOKEN_URL": "/auth/login",
79+
"ADMIN_TOKEN_URL": "/auth/login",
8680
"REDIS_HOST": "localhost",
8781
"REDIS_PORT": "6379",
8882
},
8983
)
9084

85+
# Mock secrets
86+
mocker.patch(
87+
"janux_auth_gateway.config._read_secret",
88+
side_effect=lambda key: f"mocked_{key}",
89+
)
90+
91+
# Mock JWT key reading
9192
fake_private_key = "-----BEGIN PRIVATE KEY-----\nFAKEKEY\n-----END PRIVATE KEY-----"
9293
fake_public_key = "-----BEGIN PUBLIC KEY-----\nFAKEKEY\n-----END PUBLIC KEY-----"
9394

94-
mocker.patch("builtins.open", mock_open(read_data=fake_private_key), create=True)
95-
with patch("builtins.open", mock_open(read_data=fake_public_key)):
96-
yield
95+
mocker.patch(
96+
"janux_auth_gateway.config._read_jwt_key",
97+
side_effect=lambda key_type: (
98+
fake_private_key if key_type == "private" else fake_public_key
99+
),
100+
)
101+
102+
yield
97103

98104

99105
def test_config_validation_success(mock_config):
@@ -116,10 +122,12 @@ def test_config_validation_failure_invalid_keys(mocker):
116122
Expected Outcome:
117123
- Should raise a ValueError if private or public keys are invalid.
118124
"""
119-
mocker.patch.object(Config, "PRIVATE_KEY", "INVALID_KEY")
120-
mocker.patch.object(Config, "PUBLIC_KEY", "INVALID_KEY")
125+
mocker.patch.object(Config, "JWT_PRIVATE_KEY", "INVALID_KEY")
126+
mocker.patch.object(Config, "JWT_PUBLIC_KEY", "INVALID_KEY")
121127

122-
with pytest.raises(ValueError, match="Invalid configuration for PRIVATE_KEY"):
128+
with pytest.raises(
129+
ValueError, match="Invalid or missing `jwt_private_key` for signing JWTs."
130+
):
123131
Config.validate()
124132

125133

@@ -128,56 +136,81 @@ def test_config_validation_failure_invalid_mongo_uri(mocker):
128136
Test that Config.validate() raises an error if MongoDB URI is invalid.
129137
130138
Expected Outcome:
131-
- Should raise a ValueError if the MongoDB URI does not start with the expected prefix.
139+
- Should raise a ValueError if the MongoDB URI is missing.
132140
"""
133-
mocker.patch.object(Config, "MONGO_URI", "invalid_uri")
141+
mocker.patch.object(Config, "MONGO_URI", "")
134142

135-
with pytest.raises(ValueError, match="Invalid configuration for MONGO_URI"):
143+
with pytest.raises(
144+
ValueError, match="Missing `mongo_uri` for database connection."
145+
):
136146
Config.validate()
137147

138148

139-
def test_config_missing_critical_vars(mocker):
149+
def test_config_missing_critical_secrets(mocker):
140150
"""
141-
Test that Config.validate() raises an error when a required variable is missing.
151+
Test that Config.validate() raises an error when a required secret is missing.
142152
143153
Expected Outcome:
144-
- Should raise a ValueError when a critical variable like PRIVATE_KEY is missing.
154+
- Should raise a ValueError when a critical secret is missing.
145155
"""
146-
mocker.patch.object(Config, "PRIVATE_KEY", "")
156+
mocker.patch.object(Config, "JANUX_ENCRYPTION_KEY", "")
147157

148-
with pytest.raises(ValueError, match="Invalid configuration for PRIVATE_KEY"):
158+
with pytest.raises(
159+
ValueError, match="Missing `janux_encryption_key` for encryption."
160+
):
149161
Config.validate()
150162

151163

152-
def test_config_redis_connection(mocker):
164+
def test_read_secret_from_file(mocker):
153165
"""
154-
Test that Redis host and port are set correctly.
166+
Test that secrets are correctly read from files.
155167
156168
Expected Outcome:
157-
- Config should properly set REDIS_HOST and REDIS_PORT.
169+
- The function should return the secret value from a file.
158170
"""
159-
mocker.patch.object(Config, "REDIS_HOST", "fake_redis_host")
160-
mocker.patch.object(Config, "REDIS_PORT", "6380")
171+
secret_value = "mocked_secret_value"
172+
mock_open_file = mock_open(read_data=secret_value)
161173

162-
assert Config.REDIS_HOST == "fake_redis_host"
163-
assert Config.REDIS_PORT == "6380"
174+
with patch("builtins.open", mock_open_file):
175+
with patch("os.path.exists", return_value=True):
176+
assert _read_secret("test_secret") == secret_value
164177

165178

166-
def test_config_secure_admin_defaults(mock_config):
179+
def test_read_secret_from_env(mocker):
167180
"""
168-
Test that the default admin role is set correctly.
181+
Test that secrets are read from environment variables if files are unavailable.
169182
170183
Expected Outcome:
171-
- Default admin role should be 'super_admin'.
184+
- The function should return the secret value from environment variables.
172185
"""
173-
assert Config.MONGO_ADMIN_ROLE == "super_admin"
186+
mocker.patch.dict(os.environ, {"test_secret": "mocked_secret_from_env"})
187+
188+
with patch("os.path.exists", return_value=False):
189+
assert _read_secret("test_secret") == "mocked_secret_from_env"
190+
191+
192+
def test_read_jwt_key_from_file(mocker):
193+
"""
194+
Test that JWT keys are correctly read from files.
195+
196+
Expected Outcome:
197+
- The function should return the key content from a file.
198+
"""
199+
jwt_key_value = "mocked_jwt_key"
200+
mock_open_file = mock_open(read_data=jwt_key_value)
201+
202+
with patch("builtins.open", mock_open_file):
203+
with patch("os.path.exists", return_value=True):
204+
assert _read_jwt_key("private") == jwt_key_value
174205

175206

176-
def test_config_secure_user_defaults(mock_config):
207+
def test_read_jwt_key_missing(mocker):
177208
"""
178-
Test that the default user role is set correctly.
209+
Test that a ValueError is raised when no JWT key is found.
179210
180211
Expected Outcome:
181-
- Default user role should be 'user'.
212+
- The function should raise a ValueError if no key file is found.
182213
"""
183-
assert Config.MONGO_USER_ROLE == "user"
214+
with patch("os.path.exists", return_value=False):
215+
with pytest.raises(ValueError, match="No private key file found"):
216+
_read_jwt_key("private")

0 commit comments

Comments
 (0)