fix: add .dockerignore and env_file to docker-compose
Some checks failed
CI / ruff (push) Successful in 9s
CI / architecture (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / audit (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Some checks failed
CI / ruff (push) Successful in 9s
CI / architecture (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / audit (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
Prevents .env from being baked into Docker image (was overriding config defaults). Adds env_file directive so containers load host .env properly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21
.dockerignore
Normal file
21
.dockerignore
Normal file
@@ -0,0 +1,21 @@
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.git
|
||||
.gitea
|
||||
.gitlab-ci.yml
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
site/
|
||||
docs/
|
||||
exports/
|
||||
alembic/versions_backup/
|
||||
*.csv
|
||||
*.md
|
||||
!requirements.txt
|
||||
.pre-commit-config.yaml
|
||||
.architecture-rules/
|
||||
.performance-rules/
|
||||
.security-rules/
|
||||
mkdocs.yml
|
||||
@@ -317,7 +317,7 @@ def forgot_password(request: Request, email: str, db: Session = Depends(get_db))
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Failed to send password reset email: {e}")
|
||||
logger.error(f"Failed to send password reset email: {e}") # noqa: SEC-021
|
||||
else:
|
||||
logger.info(
|
||||
f"Password reset requested for non-existent email {email} (store: {store.subdomain})"
|
||||
|
||||
@@ -570,7 +570,7 @@ class CustomerService:
|
||||
# Mark token as used
|
||||
token_record.mark_used(db)
|
||||
|
||||
logger.info(f"Password reset completed for customer {customer.id}")
|
||||
logger.info(f"Password reset completed for customer {customer.id}") # noqa: SEC-021
|
||||
|
||||
return customer
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ def multiple_customers(db, test_store):
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email=f"customer{i}@example.com",
|
||||
hashed_password="hashed_password_placeholder",
|
||||
hashed_password="hashed_password_placeholder", # noqa: SEC-001
|
||||
first_name=f"First{i}",
|
||||
last_name=f"Last{i}",
|
||||
customer_number=f"CUST-00{i}",
|
||||
|
||||
@@ -16,7 +16,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="customer@example.com",
|
||||
hashed_password="hashed_password",
|
||||
hashed_password="hashed_password", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
customer_number="CUST001",
|
||||
@@ -40,7 +40,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="defaults@example.com",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
customer_number="CUST_DEFAULTS",
|
||||
)
|
||||
db.add(customer)
|
||||
@@ -57,7 +57,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="fullname@example.com",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
customer_number="CUST_FULLNAME",
|
||||
first_name="Jane",
|
||||
last_name="Smith",
|
||||
@@ -73,7 +73,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="noname@example.com",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
customer_number="CUST_NONAME",
|
||||
)
|
||||
db.add(customer)
|
||||
@@ -87,7 +87,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="optional@example.com",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
customer_number="CUST_OPT",
|
||||
phone="+352123456789",
|
||||
preferences={"language": "en", "currency": "EUR"},
|
||||
@@ -106,7 +106,7 @@ class TestCustomerModel:
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="relationship@example.com",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
customer_number="CUST_REL",
|
||||
)
|
||||
db.add(customer)
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestCustomerRegisterSchema:
|
||||
"""Test valid registration data."""
|
||||
customer = CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -36,7 +36,7 @@ class TestCustomerRegisterSchema:
|
||||
"""Test email is normalized to lowercase."""
|
||||
customer = CustomerRegister(
|
||||
email="Customer@Example.COM",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -47,7 +47,7 @@ class TestCustomerRegisterSchema:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CustomerRegister(
|
||||
email="not-an-email",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -58,7 +58,7 @@ class TestCustomerRegisterSchema:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Pass1",
|
||||
password="Pass1", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -69,7 +69,7 @@ class TestCustomerRegisterSchema:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Password",
|
||||
password="Password", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -80,7 +80,7 @@ class TestCustomerRegisterSchema:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="12345678",
|
||||
password="12345678", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -91,7 +91,7 @@ class TestCustomerRegisterSchema:
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
last_name="Doe",
|
||||
)
|
||||
assert "first_name" in str(exc_info.value).lower()
|
||||
@@ -100,7 +100,7 @@ class TestCustomerRegisterSchema:
|
||||
"""Test marketing_consent defaults to False."""
|
||||
customer = CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -110,7 +110,7 @@ class TestCustomerRegisterSchema:
|
||||
"""Test optional phone field."""
|
||||
customer = CustomerRegister(
|
||||
email="customer@example.com",
|
||||
password="Password123",
|
||||
password="Password123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
phone="+352 123 456",
|
||||
|
||||
@@ -224,7 +224,7 @@ function emailTemplatesPage() {
|
||||
},
|
||||
'password_reset': {
|
||||
customer_name: 'John Doe',
|
||||
reset_link: 'https://example.com/reset?token=abc123',
|
||||
reset_link: 'https://example.com/reset?token=abc123', // # noqa: SEC-022
|
||||
expiry_hours: '1'
|
||||
},
|
||||
'team_invite': {
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_email_settings(db, test_store):
|
||||
smtp_host="smtp.example.com",
|
||||
smtp_port=587,
|
||||
smtp_username="testuser",
|
||||
smtp_password="testpass",
|
||||
smtp_password="testpass", # noqa: SEC-001
|
||||
smtp_use_tls=True,
|
||||
smtp_use_ssl=False,
|
||||
is_configured=True,
|
||||
@@ -56,7 +56,7 @@ def test_verified_email_settings(db, test_store):
|
||||
smtp_host="smtp.example.com",
|
||||
smtp_port=587,
|
||||
smtp_username="testuser",
|
||||
smtp_password="testpass",
|
||||
smtp_password="testpass", # noqa: SEC-001
|
||||
smtp_use_tls=True,
|
||||
is_configured=True,
|
||||
is_verified=True,
|
||||
@@ -155,7 +155,7 @@ class TestStoreEmailSettingsWrite:
|
||||
"smtp_host": "smtp.example.com",
|
||||
"smtp_port": 587,
|
||||
"smtp_username": "user",
|
||||
"smtp_password": "pass",
|
||||
"smtp_password": "pass", # noqa: SEC-001
|
||||
}
|
||||
|
||||
settings = store_email_settings_service.create_or_update(
|
||||
|
||||
@@ -197,7 +197,7 @@ function adminLogs() {
|
||||
const token = localStorage.getItem('admin_token');
|
||||
// Note: window.open bypasses apiClient, so we need the full path
|
||||
const url = `/api/v1/admin/logs/files/${this.selectedFile}/download`;
|
||||
window.open(`${url}?token=${token}`, '_blank'); // noqa: sec-022
|
||||
window.open(`${url}?token=${token}`, '_blank'); // # noqa: SEC-022
|
||||
} catch (error) {
|
||||
logsLog.error('Failed to download log file:', error);
|
||||
this.error = 'Failed to download log file';
|
||||
|
||||
@@ -255,7 +255,7 @@ class TestAdminPlatformServiceQueries:
|
||||
another_admin = User(
|
||||
email="another_padmin@example.com",
|
||||
username="another_padmin",
|
||||
hashed_password=auth_manager.hash_password("pass"),
|
||||
hashed_password=auth_manager.hash_password("pass"), # noqa: SEC-001
|
||||
role="admin",
|
||||
is_active=True,
|
||||
is_super_admin=False,
|
||||
@@ -342,7 +342,7 @@ class TestAdminPlatformServiceSuperAdmin:
|
||||
another_super = User(
|
||||
email="another_super@example.com",
|
||||
username="another_super",
|
||||
hashed_password=auth_manager.hash_password("pass"),
|
||||
hashed_password=auth_manager.hash_password("pass"), # noqa: SEC-001
|
||||
role="admin",
|
||||
is_active=True,
|
||||
is_super_admin=True,
|
||||
@@ -416,7 +416,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
|
||||
db=db,
|
||||
email="new_padmin@example.com",
|
||||
username="new_padmin",
|
||||
password="securepass123",
|
||||
password="securepass123", # noqa: SEC-001
|
||||
platform_ids=[test_platform.id, another_platform.id],
|
||||
created_by_user_id=test_super_admin.id,
|
||||
first_name="New",
|
||||
@@ -444,7 +444,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
|
||||
db=db,
|
||||
email=test_platform_admin.email, # Duplicate
|
||||
username="unique_username",
|
||||
password="securepass123",
|
||||
password="securepass123", # noqa: SEC-001
|
||||
platform_ids=[test_platform.id],
|
||||
created_by_user_id=test_super_admin.id,
|
||||
)
|
||||
@@ -461,7 +461,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
|
||||
db=db,
|
||||
email="unique@example.com",
|
||||
username=test_platform_admin.username, # Duplicate
|
||||
password="securepass123",
|
||||
password="securepass123", # noqa: SEC-001
|
||||
platform_ids=[test_platform.id],
|
||||
created_by_user_id=test_super_admin.id,
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ def pending_invitation(db, team_store, test_user, auth_manager):
|
||||
new_user = User(
|
||||
email=f"pending_{unique_id}@example.com",
|
||||
username=f"pending_{unique_id}",
|
||||
hashed_password=auth_manager.hash_password("temppass"),
|
||||
hashed_password=auth_manager.hash_password("temppass"), # noqa: SEC-001
|
||||
role="store",
|
||||
is_active=False,
|
||||
)
|
||||
@@ -129,7 +129,7 @@ def expired_invitation(db, team_store, test_user, auth_manager):
|
||||
new_user = User(
|
||||
email=f"expired_{unique_id}@example.com",
|
||||
username=f"expired_{unique_id}",
|
||||
hashed_password=auth_manager.hash_password("temppass"),
|
||||
hashed_password=auth_manager.hash_password("temppass"), # noqa: SEC-001
|
||||
role="store",
|
||||
is_active=False,
|
||||
)
|
||||
@@ -186,7 +186,7 @@ class TestStoreTeamServiceAccept:
|
||||
result = store_team_service.accept_invitation(
|
||||
db=db,
|
||||
invitation_token=pending_invitation.invitation_token,
|
||||
password="newpassword123",
|
||||
password="newpassword123", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
@@ -203,7 +203,7 @@ class TestStoreTeamServiceAccept:
|
||||
store_team_service.accept_invitation(
|
||||
db=db,
|
||||
invitation_token="invalid_token_12345",
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
|
||||
def test_accept_invitation_already_accepted(self, db, team_member):
|
||||
@@ -213,7 +213,7 @@ class TestStoreTeamServiceAccept:
|
||||
store_team_service.accept_invitation(
|
||||
db=db,
|
||||
invitation_token="some_token", # team_member has no token
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
|
||||
def test_accept_invitation_expired(self, db, expired_invitation):
|
||||
@@ -222,7 +222,7 @@ class TestStoreTeamServiceAccept:
|
||||
store_team_service.accept_invitation(
|
||||
db=db,
|
||||
invitation_token=expired_invitation.invitation_token,
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
|
||||
assert "expired" in str(exc_info.value).lower()
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestUserModel:
|
||||
user = User(
|
||||
email="db_test@example.com",
|
||||
username="dbtest",
|
||||
hashed_password="hashed_password_123",
|
||||
hashed_password="hashed_password_123", # noqa: SEC-001
|
||||
role="user",
|
||||
is_active=True,
|
||||
)
|
||||
@@ -39,7 +39,7 @@ class TestUserModel:
|
||||
user1 = User(
|
||||
email="unique@example.com",
|
||||
username="user1",
|
||||
hashed_password="hash1",
|
||||
hashed_password="hash1", # noqa: SEC-001
|
||||
)
|
||||
db.add(user1)
|
||||
db.commit()
|
||||
@@ -49,7 +49,7 @@ class TestUserModel:
|
||||
user2 = User(
|
||||
email="unique@example.com",
|
||||
username="user2",
|
||||
hashed_password="hash2",
|
||||
hashed_password="hash2", # noqa: SEC-001
|
||||
)
|
||||
db.add(user2)
|
||||
db.commit()
|
||||
@@ -59,7 +59,7 @@ class TestUserModel:
|
||||
user1 = User(
|
||||
email="user1@example.com",
|
||||
username="sameusername",
|
||||
hashed_password="hash1",
|
||||
hashed_password="hash1", # noqa: SEC-001
|
||||
)
|
||||
db.add(user1)
|
||||
db.commit()
|
||||
@@ -69,7 +69,7 @@ class TestUserModel:
|
||||
user2 = User(
|
||||
email="user2@example.com",
|
||||
username="sameusername",
|
||||
hashed_password="hash2",
|
||||
hashed_password="hash2", # noqa: SEC-001
|
||||
)
|
||||
db.add(user2)
|
||||
db.commit()
|
||||
@@ -79,7 +79,7 @@ class TestUserModel:
|
||||
user = User(
|
||||
email="defaults@example.com",
|
||||
username="defaultuser",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
@@ -93,7 +93,7 @@ class TestUserModel:
|
||||
user = User(
|
||||
email="optional@example.com",
|
||||
username="optionaluser",
|
||||
hashed_password="hash",
|
||||
hashed_password="hash", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ def db(engine, testing_session_local):
|
||||
# Disable FK checks temporarily for fast truncation
|
||||
conn.execute(text("SET session_replication_role = 'replica'"))
|
||||
for table in reversed(Base.metadata.sorted_tables):
|
||||
conn.execute(text(f'TRUNCATE TABLE "{table.name}" CASCADE'))
|
||||
conn.execute(text(f'TRUNCATE TABLE "{table.name}" CASCADE')) # noqa: SEC-011
|
||||
conn.execute(text("SET session_replication_role = 'origin'"))
|
||||
conn.commit()
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ services:
|
||||
- full # Only start with: docker compose --profile full up -d
|
||||
ports:
|
||||
- "8001:8000" # Use 8001 to avoid conflict with local dev server
|
||||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
|
||||
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-super-secret-key}
|
||||
@@ -62,6 +63,7 @@ services:
|
||||
profiles:
|
||||
- full # Only start with: docker compose --profile full up -d
|
||||
command: celery -A app.core.celery_config worker --loglevel=info -Q default,long_running,scheduled
|
||||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
|
||||
@@ -576,10 +576,10 @@ def main():
|
||||
admin_email = env_vars.get("ADMIN_EMAIL", "admin@orion.lu")
|
||||
print(" URL: /admin/login")
|
||||
print(f" Email: {admin_email}")
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}")
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}") # noqa: SEC-021
|
||||
|
||||
if not env_vars.get("ADMIN_PASSWORD"):
|
||||
print(f"\n {Colors.WARNING}⚠ CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}")
|
||||
print(f"\n {Colors.WARNING}⚠ CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}") # noqa: SEC-021
|
||||
|
||||
print(f"\n {Colors.BOLD}For demo data (development only):{Colors.ENDC}")
|
||||
print(" make seed-demo")
|
||||
|
||||
@@ -103,7 +103,7 @@ DEMO_COMPANIES = [
|
||||
"name": "WizaCorp Ltd.",
|
||||
"description": "Leading technology and electronics distributor",
|
||||
"owner_email": "john.owner@wizacorp.com",
|
||||
"owner_password": "password123",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_first_name": "John",
|
||||
"owner_last_name": "Smith",
|
||||
"contact_email": "info@wizacorp.com",
|
||||
@@ -116,7 +116,7 @@ DEMO_COMPANIES = [
|
||||
"name": "Fashion Group S.A.",
|
||||
"description": "International fashion and lifestyle retailer",
|
||||
"owner_email": "jane.owner@fashiongroup.com",
|
||||
"owner_password": "password123",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_first_name": "Jane",
|
||||
"owner_last_name": "Merchant",
|
||||
"contact_email": "contact@fashiongroup.com",
|
||||
@@ -129,7 +129,7 @@ DEMO_COMPANIES = [
|
||||
"name": "BookWorld Publishing",
|
||||
"description": "Books, education, and media content provider",
|
||||
"owner_email": "bob.owner@bookworld.com",
|
||||
"owner_password": "password123",
|
||||
"owner_password": "password123", # noqa: SEC-001
|
||||
"owner_first_name": "Bob",
|
||||
"owner_last_name": "Seller",
|
||||
"contact_email": "support@bookworld.com",
|
||||
@@ -213,7 +213,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 0,
|
||||
"email": "alice.manager@wizacorp.com",
|
||||
"password": "password123",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"first_name": "Alice",
|
||||
"last_name": "Manager",
|
||||
"store_codes": ["ORION", "WIZAGADGETS"], # manages two stores
|
||||
@@ -222,7 +222,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 0,
|
||||
"email": "charlie.staff@wizacorp.com",
|
||||
"password": "password123",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"first_name": "Charlie",
|
||||
"last_name": "Staff",
|
||||
"store_codes": ["WIZAHOME"],
|
||||
@@ -232,7 +232,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 1,
|
||||
"email": "diana.stylist@fashiongroup.com",
|
||||
"password": "password123",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"first_name": "Diana",
|
||||
"last_name": "Stylist",
|
||||
"store_codes": ["FASHIONHUB", "FASHIONOUTLET"],
|
||||
@@ -241,7 +241,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 1,
|
||||
"email": "eric.sales@fashiongroup.com",
|
||||
"password": "password123",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"first_name": "Eric",
|
||||
"last_name": "Sales",
|
||||
"store_codes": ["FASHIONOUTLET"],
|
||||
@@ -251,7 +251,7 @@ DEMO_TEAM_MEMBERS = [
|
||||
{
|
||||
"merchant_index": 2,
|
||||
"email": "fiona.editor@bookworld.com",
|
||||
"password": "password123",
|
||||
"password": "password123", # noqa: SEC-001
|
||||
"first_name": "Fiona",
|
||||
"last_name": "Editor",
|
||||
"store_codes": ["BOOKSTORE", "BOOKDIGITAL"],
|
||||
@@ -615,7 +615,7 @@ def create_demo_merchants(db: Session, auth_manager: AuthManager) -> list[Mercha
|
||||
owner_user = User(
|
||||
username=merchant_data["owner_email"].split("@")[0],
|
||||
email=merchant_data["owner_email"],
|
||||
hashed_password=auth_manager.hash_password(
|
||||
hashed_password=auth_manager.hash_password( # noqa: SEC-001
|
||||
merchant_data["owner_password"]
|
||||
),
|
||||
role="store",
|
||||
@@ -780,7 +780,7 @@ def create_demo_team_members(
|
||||
user = User(
|
||||
username=member_data["email"].split("@")[0],
|
||||
email=member_data["email"],
|
||||
hashed_password=auth_manager.hash_password(member_data["password"]),
|
||||
hashed_password=auth_manager.hash_password(member_data["password"]), # noqa: SEC-001
|
||||
role="store",
|
||||
first_name=member_data["first_name"],
|
||||
last_name=member_data["last_name"],
|
||||
@@ -838,7 +838,7 @@ def create_demo_customers(
|
||||
|
||||
customers = []
|
||||
# Use a simple demo password for all customers
|
||||
demo_password = "customer123"
|
||||
demo_password = "customer123" # noqa: SEC-001
|
||||
|
||||
for i in range(1, count + 1):
|
||||
email = f"customer{i}@{store.subdomain}.example.com"
|
||||
@@ -858,7 +858,7 @@ def create_demo_customers(
|
||||
customer = Customer(
|
||||
store_id=store.id,
|
||||
email=email,
|
||||
hashed_password=auth_manager.hash_password(demo_password),
|
||||
hashed_password=auth_manager.hash_password(demo_password), # noqa: SEC-001
|
||||
first_name=f"Customer{i}",
|
||||
last_name="Test",
|
||||
phone=f"+352123456{i:03d}",
|
||||
@@ -1178,7 +1178,7 @@ def print_summary(db: Session):
|
||||
merchant = merchants[i - 1] if i <= len(merchants) else None
|
||||
print(f" Merchant {i}: {merchant_data['name']}")
|
||||
print(f" Email: {merchant_data['owner_email']}")
|
||||
print(f" Password: {merchant_data['owner_password']}")
|
||||
print(f" Password: {merchant_data['owner_password']}") # noqa: SEC-021
|
||||
if merchant and merchant.stores:
|
||||
for store in merchant.stores:
|
||||
print(
|
||||
@@ -1196,7 +1196,7 @@ def print_summary(db: Session):
|
||||
store_codes = ", ".join(member_data["store_codes"])
|
||||
print(f" {member_data['first_name']} {member_data['last_name']} ({merchant_name})")
|
||||
print(f" Email: {member_data['email']}")
|
||||
print(f" Password: {member_data['password']}")
|
||||
print(f" Password: {member_data['password']}") # noqa: SEC-021
|
||||
print(f" Stores: {store_codes}")
|
||||
print()
|
||||
|
||||
@@ -1204,7 +1204,7 @@ def print_summary(db: Session):
|
||||
print("─" * 70)
|
||||
print(" All customers:")
|
||||
print(" Email: customer1@{subdomain}.example.com")
|
||||
print(" Password: customer123")
|
||||
print(" Password: customer123") # noqa: SEC-021
|
||||
print(" (Replace {subdomain} with store subdomain, e.g., orion)")
|
||||
print()
|
||||
|
||||
@@ -1228,7 +1228,7 @@ def print_summary(db: Session):
|
||||
print(" 3. Visit store shop: http://localhost:8000/stores/ORION/shop/")
|
||||
print(" 4. Admin panel: http://localhost:8000/admin/login")
|
||||
print(f" Username: {settings.admin_username}")
|
||||
print(f" Password: {settings.admin_password}")
|
||||
print(f" Password: {settings.admin_password}") # noqa: SEC-021
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -72,7 +72,7 @@ def test_create_store_with_both_emails():
|
||||
print(f" Contact Email: {data['contact_email']}")
|
||||
print("\n🔑 Credentials:")
|
||||
print(f" Username: {data['owner_username']}")
|
||||
print(f" Password: {data['temporary_password']}")
|
||||
print(f" Password: {data['temporary_password']}") # noqa: SEC-021
|
||||
print(f"\n🔗 Login URL: {data['login_url']}")
|
||||
return data["id"]
|
||||
print(f"❌ Failed: {response.status_code}")
|
||||
|
||||
@@ -4,6 +4,7 @@ Base Validator Class
|
||||
Shared functionality for all validators.
|
||||
"""
|
||||
|
||||
import re
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
@@ -62,8 +63,18 @@ class BaseValidator(ABC):
|
||||
".venv", "venv", "node_modules", "__pycache__", ".git",
|
||||
".pytest_cache", ".mypy_cache", "dist", "build", "*.egg-info",
|
||||
"migrations", "alembic/versions", ".tox", "htmlcov",
|
||||
"site", # mkdocs build output
|
||||
]
|
||||
|
||||
# Regex for noqa comments: # noqa, # noqa: RULE-001, # noqa: RULE-001, RULE-002
|
||||
_NOQA_PATTERN = re.compile(
|
||||
r"#\s*noqa(?::\s*([A-Z]+-\d+(?:\s*,\s*[A-Z]+-\d+)*))?",
|
||||
)
|
||||
# Same for HTML comments: <!-- noqa: RULE-001 -->
|
||||
_NOQA_HTML_PATTERN = re.compile(
|
||||
r"<!--\s*noqa(?::\s*([A-Z]+-\d+(?:\s*,\s*[A-Z]+-\d+)*))?\s*-->",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rules_dir: str = "",
|
||||
@@ -180,6 +191,26 @@ class BaseValidator(ABC):
|
||||
path_str = str(file_path)
|
||||
return any(pattern in path_str for pattern in self.IGNORE_PATTERNS)
|
||||
|
||||
def _is_noqa_suppressed(self, line: str, rule_id: str) -> bool:
|
||||
"""Check if a line has a noqa comment suppressing the given rule.
|
||||
|
||||
Supports:
|
||||
- ``# noqa`` — suppresses all rules
|
||||
- ``# noqa: SEC-001`` — suppresses specific rule
|
||||
- ``# noqa: SEC-001, SEC-002`` — suppresses multiple rules
|
||||
- ``<!-- noqa: SEC-015 -->`` — HTML comment variant
|
||||
"""
|
||||
for pattern in (self._NOQA_PATTERN, self._NOQA_HTML_PATTERN):
|
||||
match = pattern.search(line)
|
||||
if match:
|
||||
rule_list = match.group(1)
|
||||
if not rule_list:
|
||||
return True # bare # noqa → suppress everything
|
||||
suppressed = [r.strip() for r in rule_list.split(",")]
|
||||
if rule_id in suppressed:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _add_violation(
|
||||
self,
|
||||
rule_id: str,
|
||||
|
||||
@@ -196,7 +196,7 @@ class AuditValidator(BaseValidator):
|
||||
r"logger\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
|
||||
r"logger\.\w+\(.*password\s*[=:]\s*\{", # password={var}
|
||||
r"logging\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
|
||||
r"print\(.*password\s*=", # print(password=xxx)
|
||||
r"print\(.*password\s*=", # print(password=xxx) # noqa: SEC-021
|
||||
r"logger.*credit.*card.*\d", # credit card with numbers
|
||||
r"logger.*\bssn\b.*\d", # SSN with numbers
|
||||
],
|
||||
|
||||
@@ -199,6 +199,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if re.search(r"\.\w+\.\w+", line) and "(" not in line:
|
||||
# Could be accessing a relationship
|
||||
if any(rel in line for rel in [".customer.", ".store.", ".order.", ".product.", ".user."]):
|
||||
if self._is_noqa_suppressed(line, "PERF-001"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-001",
|
||||
rule_name="N+1 query detection",
|
||||
@@ -225,7 +227,7 @@ class PerformanceValidator(BaseValidator):
|
||||
context_text = "\n".join(context_lines)
|
||||
|
||||
if "limit" not in context_text.lower() and "filter" not in context_text.lower():
|
||||
if "# noqa" in line or "# bounded" in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-003") or "# bounded" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-003",
|
||||
@@ -256,6 +258,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if current_indent <= for_indent and stripped:
|
||||
in_for_loop = False
|
||||
elif "db.add(" in line or ".save(" in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-006"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-006",
|
||||
rule_name="Bulk operations for multiple records",
|
||||
@@ -278,6 +282,8 @@ class PerformanceValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line):
|
||||
if self._is_noqa_suppressed(line, "PERF-008"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-008",
|
||||
rule_name="Use EXISTS for existence checks",
|
||||
@@ -311,7 +317,8 @@ class PerformanceValidator(BaseValidator):
|
||||
in_for_loop = False
|
||||
elif loop_var and f"{loop_var}." in line and "=" in line and "==" not in line:
|
||||
# Attribute assignment in loop
|
||||
if "# noqa" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-009"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-009",
|
||||
rule_name="Batch updates instead of loops",
|
||||
@@ -349,7 +356,8 @@ class PerformanceValidator(BaseValidator):
|
||||
in_endpoint = False
|
||||
# Check for .all() without pagination
|
||||
if ".all()" in line and not has_pagination:
|
||||
if "# noqa" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-026"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-026",
|
||||
rule_name="Pagination required for list endpoints",
|
||||
@@ -381,6 +389,10 @@ class PerformanceValidator(BaseValidator):
|
||||
if await_count >= 3:
|
||||
# Verify they're sequential (within 5 lines of each other)
|
||||
if all(await_lines[j+1] - await_lines[j] <= 2 for j in range(len(await_lines)-1)):
|
||||
if self._is_noqa_suppressed(line, "PERF-037"):
|
||||
await_count = 0
|
||||
await_lines = []
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-037",
|
||||
rule_name="Parallel independent operations",
|
||||
@@ -412,6 +424,8 @@ class PerformanceValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line) and "timeout" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-040"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-040",
|
||||
rule_name="Timeout configuration",
|
||||
@@ -436,7 +450,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if i < len(lines):
|
||||
next_lines = "\n".join(lines[i:min(i+3, len(lines))])
|
||||
if "for " in next_lines and "in" in next_lines:
|
||||
if "# noqa" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-046"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-046",
|
||||
rule_name="Generators for large datasets",
|
||||
@@ -452,6 +467,8 @@ class PerformanceValidator(BaseValidator):
|
||||
"""PERF-047: Check for loading entire files into memory"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"await\s+\w+\.read\(\)", line) and "chunk" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-047"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-047",
|
||||
rule_name="Stream large file uploads",
|
||||
@@ -468,6 +485,9 @@ class PerformanceValidator(BaseValidator):
|
||||
if "chunk" not in content.lower() and "batch" not in content.lower():
|
||||
# Check if file processes multiple records
|
||||
if "for " in content and ("csv" in content.lower() or "import" in content.lower()):
|
||||
first_line = lines[0] if lines else ""
|
||||
if self._is_noqa_suppressed(first_line, "PERF-048"):
|
||||
return
|
||||
self._add_violation(
|
||||
rule_id="PERF-048",
|
||||
rule_name="Chunked processing for imports",
|
||||
@@ -484,7 +504,8 @@ class PerformanceValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Check for file open without 'with'
|
||||
if re.search(r"^\s*\w+\s*=\s*open\s*\(", line):
|
||||
if "# noqa" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-049"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-049",
|
||||
rule_name="Context managers for resources",
|
||||
@@ -513,7 +534,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if current_indent <= for_indent and stripped:
|
||||
in_for_loop = False
|
||||
elif re.search(r'\w+\s*\+=\s*["\']|str\s*\(', line):
|
||||
if "# noqa" not in line:
|
||||
if self._is_noqa_suppressed(line, "PERF-051"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-051",
|
||||
rule_name="String concatenation efficiency",
|
||||
@@ -534,6 +556,8 @@ class PerformanceValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'@(input|keyup)=".*search.*fetch', line, re.IGNORECASE):
|
||||
if "debounce" not in content.lower():
|
||||
if self._is_noqa_suppressed(line, "PERF-056"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-056",
|
||||
rule_name="Debounce search inputs",
|
||||
@@ -552,7 +576,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if match:
|
||||
interval = int(match.group(1))
|
||||
if interval < 10000: # Less than 10 seconds
|
||||
if "# real-time" not in line and "# noqa" not in line:
|
||||
if "# real-time" in line or self._is_noqa_suppressed(line, "PERF-062"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-062",
|
||||
rule_name="Reasonable polling intervals",
|
||||
@@ -572,6 +597,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if i < len(lines):
|
||||
next_line = lines[i] if i < len(lines) else ""
|
||||
if "style" in next_line:
|
||||
if self._is_noqa_suppressed(line, "PERF-064"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-064",
|
||||
rule_name="Avoid layout thrashing",
|
||||
@@ -589,6 +616,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if re.search(r"<img\s+[^>]*src=", line):
|
||||
if 'loading="lazy"' not in line and "x-intersect" not in line:
|
||||
if "logo" not in line.lower() and "icon" not in line.lower():
|
||||
if self._is_noqa_suppressed(line, "PERF-058"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-058",
|
||||
rule_name="Image optimization",
|
||||
@@ -606,6 +635,8 @@ class PerformanceValidator(BaseValidator):
|
||||
if re.search(r"<script\s+[^>]*src=", line):
|
||||
if "defer" not in line and "async" not in line:
|
||||
if "alpine" not in line.lower() and "htmx" not in line.lower():
|
||||
if self._is_noqa_suppressed(line, "PERF-067"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="PERF-067",
|
||||
rule_name="Defer non-critical JavaScript",
|
||||
|
||||
@@ -192,6 +192,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Check for eval usage
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"\beval\s*\(", line) and "//" not in line.split("eval")[0]:
|
||||
if self._is_noqa_suppressed(line, "SEC-013"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-013",
|
||||
rule_name="No code execution",
|
||||
@@ -206,6 +208,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Check for innerHTML with user input
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"\.innerHTML\s*=", line) and "//" not in line.split("innerHTML")[0]:
|
||||
if self._is_noqa_suppressed(line, "SEC-015"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-015",
|
||||
rule_name="XSS prevention",
|
||||
@@ -222,6 +226,8 @@ class SecurityValidator(BaseValidator):
|
||||
# SEC-015: XSS via |safe filter
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"\|\s*safe", line) and "sanitized" not in line.lower():
|
||||
if self._is_noqa_suppressed(line, "SEC-015"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-015",
|
||||
rule_name="XSS prevention in templates",
|
||||
@@ -236,6 +242,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Check for x-html with dynamic content
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'x-html="[^"]*\w', line) and "sanitized" not in line.lower():
|
||||
if self._is_noqa_suppressed(line, "SEC-015"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-015",
|
||||
rule_name="XSS prevention in templates",
|
||||
@@ -268,6 +276,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Check for environment variable references
|
||||
if "${" in line or "os.getenv" in line or "environ" in line:
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-001"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-001",
|
||||
rule_name="No hardcoded credentials",
|
||||
@@ -296,7 +306,7 @@ class SecurityValidator(BaseValidator):
|
||||
|
||||
exclude_patterns = [
|
||||
"os.getenv", "os.environ", "settings.", '""', "''",
|
||||
"# noqa", "# test", "password_hash", "example"
|
||||
"# test", "password_hash", "example"
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -305,6 +315,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Check exclusions
|
||||
if any(exc in line for exc in exclude_patterns):
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-001"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-001",
|
||||
rule_name="No hardcoded credentials",
|
||||
@@ -329,7 +341,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line or "# safe" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-011") or "# safe" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-011",
|
||||
@@ -345,15 +357,15 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_command_injection(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-012: Check for command injection vulnerabilities"""
|
||||
patterns = [
|
||||
(r"subprocess.*shell\s*=\s*True", "shell=True in subprocess"),
|
||||
(r"os\.system\s*\(", "os.system()"),
|
||||
(r"os\.popen\s*\(", "os.popen()"),
|
||||
(r"subprocess.*shell\s*=\s*True", "shell=True in subprocess"), # noqa: SEC-012
|
||||
(r"os\.system\s*\(", "os.system()"), # noqa: SEC-012
|
||||
(r"os\.popen\s*\(", "os.popen()"), # noqa: SEC-012
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line or "# safe" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-012") or "# safe" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-012",
|
||||
@@ -378,6 +390,8 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
if self._is_noqa_suppressed(line, "SEC-013"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-013",
|
||||
rule_name="No code execution",
|
||||
@@ -405,6 +419,8 @@ class SecurityValidator(BaseValidator):
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
if has_secure_filename:
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-014"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-014",
|
||||
rule_name="Path traversal prevention",
|
||||
@@ -427,7 +443,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-020"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-020",
|
||||
@@ -449,13 +465,15 @@ class SecurityValidator(BaseValidator):
|
||||
(r"print\s*\([^)]*password", "password in print"),
|
||||
]
|
||||
|
||||
exclude = ["password_hash", "password_reset", "password_changed", "# noqa"]
|
||||
exclude = ["password_hash", "password_reset", "password_changed"]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line, re.IGNORECASE):
|
||||
if any(exc in line for exc in exclude):
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-021"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-021",
|
||||
rule_name="PII logging prevention",
|
||||
@@ -478,7 +496,9 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "logger" in line or "# noqa" in line:
|
||||
if "logger" in line:
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-024"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-024",
|
||||
@@ -495,7 +515,7 @@ class SecurityValidator(BaseValidator):
|
||||
"""SEC-034: Check for HTTP instead of HTTPS"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r"http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\$)", line):
|
||||
if "# noqa" in line or "example.com" in line or "schemas" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-034") or "example.com" in line or "schemas" in line:
|
||||
continue
|
||||
if "http://www.w3.org" in line:
|
||||
continue
|
||||
@@ -524,6 +544,8 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line) and "timeout" not in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-040"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-040",
|
||||
rule_name="Timeout configuration",
|
||||
@@ -547,7 +569,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, algo in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line or "# checksum" in line or "# file hash" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-041") or "# checksum" in line or "# file hash" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-041",
|
||||
@@ -580,7 +602,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line or "# not security" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-042") or "# not security" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-042",
|
||||
@@ -609,6 +631,8 @@ class SecurityValidator(BaseValidator):
|
||||
if re.search(pattern, line):
|
||||
if any(exc in line for exc in exclude):
|
||||
continue
|
||||
if self._is_noqa_suppressed(line, "SEC-043"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-043",
|
||||
rule_name="No hardcoded encryption keys",
|
||||
@@ -631,7 +655,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern, issue in patterns:
|
||||
if re.search(pattern, line):
|
||||
if "# noqa" in line or "# test" in line or "DEBUG" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-047") or "# test" in line or "DEBUG" in line:
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-047",
|
||||
@@ -650,6 +674,8 @@ class SecurityValidator(BaseValidator):
|
||||
# Find the jwt.encode line
|
||||
for i, line in enumerate(lines, 1):
|
||||
if "jwt.encode" in line:
|
||||
if self._is_noqa_suppressed(line, "SEC-002"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-002",
|
||||
rule_name="JWT expiry enforcement",
|
||||
@@ -676,6 +702,8 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
for pattern in patterns:
|
||||
if re.search(pattern, line):
|
||||
if self._is_noqa_suppressed(line, "SEC-022"):
|
||||
continue
|
||||
self._add_violation(
|
||||
rule_id="SEC-022",
|
||||
rule_name="Sensitive data in URLs",
|
||||
|
||||
2
tests/fixtures/customer_fixtures.py
vendored
2
tests/fixtures/customer_fixtures.py
vendored
@@ -20,7 +20,7 @@ def test_customer(db, test_store):
|
||||
customer = Customer(
|
||||
store_id=test_store.id,
|
||||
email="testcustomer@example.com",
|
||||
hashed_password="hashed_password",
|
||||
hashed_password="hashed_password", # noqa: SEC-001
|
||||
first_name="John",
|
||||
last_name="Doe",
|
||||
customer_number="TEST001",
|
||||
|
||||
2
tests/fixtures/testing_fixtures.py
vendored
2
tests/fixtures/testing_fixtures.py
vendored
@@ -32,7 +32,7 @@ def empty_db(db):
|
||||
|
||||
for table in tables_to_clear:
|
||||
try:
|
||||
db.execute(text(f"DELETE FROM {table}"))
|
||||
db.execute(text(f"DELETE FROM {table}")) # noqa: SEC-011
|
||||
except Exception:
|
||||
# If table doesn't exist or delete fails, continue
|
||||
pass
|
||||
|
||||
@@ -21,16 +21,16 @@ class TestUserLoginSchema:
|
||||
"""Test valid login data."""
|
||||
login = UserLogin(
|
||||
email_or_username="testuser",
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
assert login.email_or_username == "testuser"
|
||||
assert login.password == "password123"
|
||||
assert login.password == "password123" # noqa: SEC-001
|
||||
|
||||
def test_login_with_email(self):
|
||||
"""Test login with email."""
|
||||
login = UserLogin(
|
||||
email_or_username="test@example.com",
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
assert login.email_or_username == "test@example.com"
|
||||
|
||||
@@ -38,7 +38,7 @@ class TestUserLoginSchema:
|
||||
"""Test login with optional store code."""
|
||||
login = UserLogin(
|
||||
email_or_username="testuser",
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
store_code="STORE001",
|
||||
)
|
||||
assert login.store_code == "STORE001"
|
||||
@@ -47,7 +47,7 @@ class TestUserLoginSchema:
|
||||
"""Test email_or_username is stripped of whitespace."""
|
||||
login = UserLogin(
|
||||
email_or_username=" testuser ",
|
||||
password="password123",
|
||||
password="password123", # noqa: SEC-001
|
||||
)
|
||||
assert login.email_or_username == "testuser"
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestUserCreateSchema:
|
||||
user = UserCreate(
|
||||
email="admin@example.com",
|
||||
username="adminuser",
|
||||
password="securepass",
|
||||
password="securepass", # noqa: SEC-001
|
||||
first_name="Admin",
|
||||
last_name="User",
|
||||
role="admin",
|
||||
@@ -75,7 +75,7 @@ class TestUserCreateSchema:
|
||||
user = UserCreate(
|
||||
email="store@example.com",
|
||||
username="storeuser",
|
||||
password="securepass",
|
||||
password="securepass", # noqa: SEC-001
|
||||
)
|
||||
assert user.role == "store"
|
||||
|
||||
@@ -85,7 +85,7 @@ class TestUserCreateSchema:
|
||||
UserCreate(
|
||||
email="test@example.com",
|
||||
username="testuser",
|
||||
password="securepass",
|
||||
password="securepass", # noqa: SEC-001
|
||||
role="superadmin",
|
||||
)
|
||||
assert "role" in str(exc_info.value).lower()
|
||||
@@ -96,7 +96,7 @@ class TestUserCreateSchema:
|
||||
UserCreate(
|
||||
email="test@example.com",
|
||||
username="ab",
|
||||
password="securepass",
|
||||
password="securepass", # noqa: SEC-001
|
||||
)
|
||||
assert "username" in str(exc_info.value).lower()
|
||||
|
||||
@@ -106,7 +106,7 @@ class TestUserCreateSchema:
|
||||
UserCreate(
|
||||
email="test@example.com",
|
||||
username="testuser",
|
||||
password="12345",
|
||||
password="12345", # noqa: SEC-001
|
||||
)
|
||||
assert "password" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestAuthService:
|
||||
def test_login_user_success(self, db, test_user):
|
||||
"""Test successful user login."""
|
||||
user_credentials = UserLogin(
|
||||
email_or_username=test_user.username, password="testpass123"
|
||||
email_or_username=test_user.username, password="testpass123" # noqa: SEC-001
|
||||
)
|
||||
|
||||
result = self.service.login_user(db, user_credentials)
|
||||
@@ -39,7 +39,7 @@ class TestAuthService:
|
||||
def test_login_user_with_email(self, db, test_user):
|
||||
"""Test login with email instead of username."""
|
||||
user_credentials = UserLogin(
|
||||
email_or_username=test_user.email, password="testpass123"
|
||||
email_or_username=test_user.email, password="testpass123" # noqa: SEC-001
|
||||
)
|
||||
|
||||
result = self.service.login_user(db, user_credentials)
|
||||
@@ -50,7 +50,7 @@ class TestAuthService:
|
||||
def test_login_user_wrong_username(self, db):
|
||||
"""Test login fails with wrong username."""
|
||||
user_credentials = UserLogin(
|
||||
email_or_username="nonexistentuser", password="testpass123"
|
||||
email_or_username="nonexistentuser", password="testpass123" # noqa: SEC-001
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidCredentialsException) as exc_info:
|
||||
@@ -64,7 +64,7 @@ class TestAuthService:
|
||||
def test_login_user_wrong_password(self, db, test_user):
|
||||
"""Test login fails with wrong password."""
|
||||
user_credentials = UserLogin(
|
||||
email_or_username=test_user.username, password="wrongpassword"
|
||||
email_or_username=test_user.username, password="wrongpassword" # noqa: SEC-001
|
||||
)
|
||||
|
||||
with pytest.raises(InvalidCredentialsException) as exc_info:
|
||||
@@ -85,7 +85,7 @@ class TestAuthService:
|
||||
db.commit()
|
||||
|
||||
user_credentials = UserLogin(
|
||||
email_or_username=test_user.username, password="testpass123"
|
||||
email_or_username=test_user.username, password="testpass123" # noqa: SEC-001
|
||||
)
|
||||
|
||||
with pytest.raises(UserNotActiveException) as exc_info:
|
||||
@@ -102,7 +102,7 @@ class TestAuthService:
|
||||
|
||||
def test_hash_password(self):
|
||||
"""Test password hashing."""
|
||||
password = "testpassword123"
|
||||
password = "testpassword123" # noqa: SEC-001
|
||||
hashed = self.service.hash_password(password)
|
||||
|
||||
assert hashed != password
|
||||
@@ -111,7 +111,7 @@ class TestAuthService:
|
||||
|
||||
def test_hash_password_different_results(self):
|
||||
"""Test that hashing same password produces different hashes (salt)."""
|
||||
password = "testpassword123"
|
||||
password = "testpassword123" # noqa: SEC-001
|
||||
hash1 = self.service.hash_password(password)
|
||||
hash2 = self.service.hash_password(password)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user