• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SeaweedbrainCY / zero-totp / 16126695263

07 Jul 2025 08:02PM UTC coverage: 92.953% (+0.04%) from 92.918%
16126695263

Pull #270

github

SeaweedbrainCY
Fix test
Pull Request #270: UI improvement

174 of 189 new or added lines in 15 files covered. (92.06%)

139 existing lines in 4 files now uncovered.

12637 of 13595 relevant lines covered (92.95%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.74
api/environment.py
1
import os
1✔
2
import logging
1✔
3
import yaml 
1✔
4
import Utils.env_requirements_check as env_requirements_check
1✔
5
from CryptoClasses.serverRSAKeys import ServerRSAKeys
1✔
6
from Crypto.Protocol.KDF import PBKDF2
1✔
7
from Crypto.Hash import SHA512
1✔
8
import ipaddress
1✔
9
import re
1✔
10

11
class EnvironmentConfig:
1✔
12
    required_keys = ["type", "config_version", "domain"]
1✔
13
    def __init__(self, data) -> None:
1✔
14
        self.config_version = data["config_version"]
1✔
15
        LOGGING_FORMAT = '%(asctime)s %(levelname)-8s %(filename)s:%(lineno)d %(funcName)s %(message)s'
1✔
16
        
17
        for key in self.required_keys:
1✔
18
            if key not in data:
1✔
19
                logging.error(f"[FATAL] Load config fail. Was expecting the key environment.{key}")
×
20
                exit(1)
×
21
        self.domain = data["domain"]
1✔
22
        if data["type"] == "local":
1✔
23
            self.type = "local"
×
24
            logging.basicConfig(
×
25
                format=LOGGING_FORMAT,
26
                level=logging.DEBUG,
27
                datefmt='%Y-%m-%dT%H:%M:%SZ%z')
28
            logging.debug("Environment set to development")
×
29
            if "frontend_URI" not in data:
×
30
                logging.error("[FATAL] Load config fail. In local environement, was expecting the key environment.frontend_URI")
×
31
                exit(1)
×
32
            if "API_URI" not in data:
×
33
                logging.error("[FATAL] Load config fail. In local environement, was expecting the key environment.API_URI")
×
34
            self.frontend_URI = data["frontend_URI"]
×
35
            self.callback_URI = f'{data["API_URI"]}/api/v1/google-drive/oauth/callback'
×
36
            os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
×
37
        elif data["type"] == "development":
1✔
38
            self.type = "development"
1✔
39
            logging.basicConfig(
1✔
40
                 filename="/api/logs/api.log",
41
                filemode='a',
42
                format=LOGGING_FORMAT,
43
                level=logging.INFO,
44
                datefmt='%Y-%m-%dT%H:%M:%SZ%z')
45
            logging.info("Environment set to development")
1✔
46
            self.frontend_URI = f"https://{data['domain']}"
1✔
47
            self.callback_URI = f"https://{data['domain']}/api/v1/google-drive/oauth/callback"
1✔
48
        else:
49
            self.type = "production"
×
50
            logging.basicConfig(
×
51
                filename="/api/logs/api.log",
52
                filemode='a',
53
                format=LOGGING_FORMAT,
54
                level=logging.INFO,
55
                datefmt='%Y-%m-%dT%H:%M:%SZ%z')
56
            self.frontend_URI = f"https://{data['domain']}"
×
57
            self.callback_URI = f"https://{data['domain']}/api/v1/google-drive/oauth/callback"
×
58

59

60
        
61

62
class APIConfig:
1✔
63
    required_keys = ["private_key_path", "public_key_path", "flask_secret_key", "server_side_encryption_key"]
1✔
64

65
    def __init__(self, data, config_version):
1✔
66
        for key in self.required_keys:
1✔
67
            if key not in data:
1✔
68
                logging.error(f"[FATAL] Load config fail. Was expecting the key api.{key}")
×
UNCOV
69
                exit(1)
×
70
        
71
        if "port" in data:
1✔
UNCOV
72
            try:
×
UNCOV
73
                self.port = int(data["port"])
×
UNCOV
74
            except Exception as e:
×
UNCOV
75
                logging.error(f"[FATAL] Load config fail. api.port is not valid. {e}")
×
UNCOV
76
                exit(1)
×
77
        else:
78
            logging.info("API will listen on port 8080")
1✔
79
            self.port = 8080
1✔
80
                 
81
        self.private_key_path = data["private_key_path"]
1✔
82
        self.public_key_path = data["public_key_path"]
1✔
83
        self.flask_secret_key = data["flask_secret_key"]
1✔
84
        try:
1✔
85
            if config_version >= 1.0:
1✔
86
                self.server_side_encryption_key = PBKDF2(data["server_side_encryption_key"].encode("utf-8"), '4ATK7mA8aKgT6768' , count=2000000, dkLen=32, hmac_hash_module=SHA512)
1✔
87
            else:
88
                logging.error(f"[FATAL] Load config fail. config version {config_version} is not supported.")
×
89
                exit(1)
×
UNCOV
90
        except Exception as e:
×
UNCOV
91
            logging.error(f"[FATAL] Load config fail. {e}")
×
UNCOV
92
            exit(1)
×
93

94
        self.trusted_proxy = None
1✔
95
        if "trusted_proxy" in data:
1✔
UNCOV
96
            self.trusted_proxy = []
×
UNCOV
97
            for ip in data["trusted_proxy"]:
×
UNCOV
98
                try:
×
UNCOV
99
                    self.trusted_proxy.append(ipaddress.ip_network(ip))
×
UNCOV
100
                except Exception as e:
×
101
                    logging.error(f"[FATAL] Load config fail. api.trusted_proxy contains an invalid ip address. {e}")
×
102
                    exit(1)
×
103
        self.session_token_validity = 600
1✔
104
        if "session_token_validity" in data:
1✔
105
            try:
×
UNCOV
106
                self.session_token_validity = int(data["session_token_validity"])
×
UNCOV
107
            except Exception as e:
×
UNCOV
108
                logging.error(f"[FATAL] Load config fail. api.session_token_validity is not valid. {e}")
×
109
                exit(1)
×
110
        
111
        self.refresh_token_validity = 86400
1✔
112
        if "refresh_token_validity" in data:
1✔
113
            try:
×
114
                self.refresh_token_validity = int(data["refresh_token_validity"])
×
115
            except Exception as e:
×
116
                logging.error(f"[FATAL] Load config fail. api.refresh_token_validity is not valid. {e}")
×
117
                exit(1)
×
118
        
119
        if "health_check" in data:
1✔
UNCOV
120
            if 'node_check_enabled':
×
UNCOV
121
                self.node_check_enabled = data["health_check"]["node_check_enabled"]
×
122
                if self.node_check_enabled:
×
123
                    required_node_health_check_keys = ["node_name", "node_name_hmac_secret"]
×
124
                    for key in required_node_health_check_keys:
×
125
                        if key not in data["health_check"]:
×
126
                            logging.error(f"[FATAL] Load config fail. api.health_check.node_check_enabled is True so api.health_check require the key {key} to exist.")
×
UNCOV
127
                            exit(1)
×
UNCOV
128
                    self.node_name = data["health_check"]["node_name"]
×
UNCOV
129
                    self.node_name_hmac_secret = data["health_check"]["node_name_hmac_secret"]
×
130
            else:
131
                logging.error(f"[FATAL] Load config fail. api.health_check require the key node_check_enabled to exist. {e}")
132
                exit(1)
133
        else: 
134
            self.node_check_enabled = False
1✔
135

136
        self.version = "0.0.0"
1✔
137
        self.build = "0000000"
1✔
138
        with open("VERSION", "r") as f:
1✔
139
            version = f.readline().strip()
1✔
140
            if re.match(r"^b?v?\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?$", version) or version == "dev":
1✔
141
                self.version = version
1✔
142
            else:
143
                logging.warning(f"VERSION file is not in the correct format. Using default value: 0.0.0")
×
144
            build = f.readline().strip() # github short sha1
1✔
145
            if re.match(r"^[a-f0-9]{7}$", build):
1✔
146
                self.build = build
1✔
147
            else:
UNCOV
148
                logging.warning(f"VERSION file is not in the correct format. Using default build: 0000000")
×
149

150
                
151
        
152
class DatabaseConfig:
1✔
153
    required_keys = ["database_uri"]
1✔
154
    def __init__(self, data):
1✔
155
        for key in self.required_keys:
1✔
156
            if key not in data:
1✔
UNCOV
157
                logging.error(f"[FATAL] Load config fail. Was expecting the key database.{key}")
×
UNCOV
158
                exit(1)
×
159
        self.database_uri = data["database_uri"]
1✔
160
        self.are_all_tables_created = False
1✔
161

162

163

164
class EmailsConfig:
1✔
165
    required_keys = ["email_sender_address", "email_smtp_password", "email_smtp_server", "email_smtp_port", "email_smtp_username"]
1✔
166
    def __init__(self, data):
1✔
167
        if "require_email_validation" not in data:
1✔
UNCOV
168
            data["require_email_validation"] = False
×
169
        if data["require_email_validation"] == False:
1✔
UNCOV
170
            logging.warning("require_email_validation is disabled. Users will not be asked to verify their email address. You can enable this option by setting the require_email_validation  variable to true at any moment.")
×
UNCOV
171
            self.require_email_validation = False
×
172
        else :
173
            self.require_email_validation = True
1✔
174
            for key in self.required_keys:
1✔
175
                if key not in data:
1✔
UNCOV
176
                    logging.error(f"[FATAL] Load config fail. Was expecting the key features.emails.{key} because require_email_validation is set to true")
×
UNCOV
177
                    exit(1)
×
178
            self.sender_address = data["email_sender_address"]
1✔
179
            self.sender_password = data["email_smtp_password"]
1✔
180
            self.smtp_server = data["email_smtp_server"]
1✔
181
            self.smtp_port = data["email_smtp_port"]
1✔
182
            self.smtp_username = data["email_smtp_username"]
1✔
183

184
class RateLimitingConfig:
1✔
185
    def __init__(self, data):
1✔
186
        try :
1✔
187
            self.login_attempts_limit_per_ip = int(data["login_attempts_limit_per_ip"]) if "login_attempts_limit_per_ip" in data else 10
1✔
188
            self.send_email_attempts_limit_per_user = int(data["send_email_attempts_limit_per_user"]) if "send_email_attempts_limit_per_user" in data else 5
1✔
189
            self.login_ban_time = int(data["login_ban_time"]) if "login_ban_time" in data else 15
1✔
190
            self.email_ban_time = int(data["email_ban_time"]) if "email_ban_time" in data else 60
1✔
UNCOV
191
        except Exception as e:
×
UNCOV
192
            logging.error(f"[FATAL] Load config fail. {e}")
×
193
            exit(1)
×
194

195

196
class SentryConfig:
1✔
197
    required_keys = ["dsn"]
1✔
198
    def __init__(self, data):
1✔
UNCOV
199
        for key in self.required_keys:
×
UNCOV
200
            if key not in data:
×
UNCOV
201
                logging.error(f"[FATAL] Load config fail. Was expecting the key features.sentry.{key}")
×
UNCOV
202
                exit(1)
×
UNCOV
203
        self.dsn = data["dsn"]
×
204

205
class BackupConfig:
1✔
206
    # Default value 
207
    backup_minimum_count = 20
1✔
208
    max_age_in_days = 30
1✔
209

210

211
    def __init__(self, data):
1✔
212
        if "backup_minimum_count" in data:
1✔
UNCOV
213
            try:
×
UNCOV
214
                self.backup_minimum_count = int(data["backup_minimum_count"])
×
UNCOV
215
            except Exception as e:
×
216
                logging.error(f"[FATAL] Load config fail. default_backup_configuration.backup_minimum_count is not valid. {e}")
×
217
                exit(1)
×
218
        else:
219
            logging.info("default_backup_configuration.backup_minimum_count is not set. Using default value: 20")
1✔
220
        if "max_age_in_days" in data:
1✔
UNCOV
221
            try:
×
UNCOV
222
                self.max_age_in_days = int(data["max_age_in_days"])
×
UNCOV
223
            except Exception as e:
×
UNCOV
224
                logging.error(f"[FATAL] Load config fail. default_backup_configuration.max_age_in_days is not valid. {e}")
×
UNCOV
225
                exit(1)
×
226
        else:
227
            logging.info("default_backup_configuration.max_age_in_days is not set. Using default value: 30")
1✔
228

229

230
class PrivacyPolicyConfig:
1✔
231
    def __init__(self):
1✔
232
        self.available_languages = ["en", "fr"]
1✔
233
        self.privacy_policy_mk_file_path = {}
1✔
234
        for lang in self.available_languages:
1✔
235
            if os.path.exists(f"./config/assets/privacy_policy/privacy_policy_{lang}.md"):
1✔
236
                self.privacy_policy_mk_file_path[lang] = f"./config/assets/privacy_policy/privacy_policy_{lang}.md"
1✔
237
            else:
238
                self.privacy_policy_mk_file_path[lang] = f"./assets/privacy_policy/privacy_policy_{lang}.md"
1✔
239
        
240

241
class GoogleDriveConfig:
1✔
242
    def __init__(self, data):
1✔
243
        self.enabled = data["enabled"] if "enabled" in data else False
1✔
244
        if self.enabled:
1✔
245
            if "client_secret_file_path" not in data:
1✔
UNCOV
246
                logging.error("[FATAL] Load config fail. Was expecting the key features.google_drive.client_secret_file_path when google drive is enabled")
×
UNCOV
247
                exit(1)
×
248
            self.client_secret_file_path = data["client_secret_file_path"]
1✔
249

250
class FeaturesConfig:
1✔
251
    def __init__(self, data):
1✔
252
                
253
        self.emails = EmailsConfig(data["emails"]) if "emails" in data else None
1✔
254
        self.rate_limiting = RateLimitingConfig(data["rate_limiting"] if "rate_limiting" in data else [])
1✔
255
        self.sentry = SentryConfig(data["sentry"]) if "sentry" in data else None
1✔
256
        self.backup_config = BackupConfig(data["default_backup_configuration"] if "default_backup_configuration" in data else [])
1✔
257
        self.privacy_policy = PrivacyPolicyConfig()
1✔
258
        self.signup_enabled = data["signup_enabled"] if "signup_enabled" in data else True
1✔
259
        self.google_drive = GoogleDriveConfig(data["google_drive_backup"] if "google_drive_backup" in data else [])
1✔
260

261

262
class Config:
1✔
263
    required_keys = ["api", "environment", "database", "features"]
1✔
264
    def __init__(self, data):
1✔
265
        for key in self.required_keys:
1✔
266
            if key not in data:
1✔
UNCOV
267
                logging.error(f"[FATAL] Load config fail. Was expecting the key {key}")
×
UNCOV
268
                exit(1)
×
269
        self.environment = EnvironmentConfig(data["environment"] if data["environment"] != None else [])
1✔
270
        self.api = APIConfig(data["api"] if data["api"] != None else [], self.environment.config_version)
1✔
271
        self.database = DatabaseConfig(data["database"] if data["database"] != None else [])
1✔
272
        self.features = FeaturesConfig(data["features"] if data["features"] != None else [])
1✔
273

274

275

276

277
try:
1✔
278
    with open("./config/config.yml") as config_yml:
1✔
279
        try:
1✔
280
            raw_conf = yaml.safe_load(config_yml)
1✔
281
            conf = Config(raw_conf)
1✔
282
        
UNCOV
283
        except yaml.YAMLError as exc:
×
UNCOV
284
            raise Exception(exc)
×
UNCOV
285
except Exception as e :
×
UNCOV
286
    logging.error(f"[FATAL] API will stop now. Error while checking /api/config/config.yml, {e}")
×
UNCOV
287
    exit(1)
×
288

289

290
env_requirements_check.test_conf(conf) 
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc