mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
feat(users): users.json v2 JSON Schema + validation tests (ref #120)
This commit is contained in:
parent
a2507c6559
commit
47fcaa6584
74
packages/secubox-users/schema/users.json.schema.json
Normal file
74
packages/secubox-users/schema/users.json.schema.json
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://secubox.in/schemas/users-v2.json",
|
||||
"title": "SecuBox Users v2",
|
||||
"type": "object",
|
||||
"required": ["version", "users", "groups"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {"type": "string"},
|
||||
"version": {"const": 2},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/user"}
|
||||
},
|
||||
"groups": {"type": "array"}
|
||||
},
|
||||
"definitions": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username", "role", "enabled", "must_change_password",
|
||||
"services", "created"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"username": {"type": "string", "pattern": "^[a-z0-9_-]{2,32}$"},
|
||||
"email": {"type": ["string", "null"], "format": "email"},
|
||||
"role": {"enum": ["admin", "operator", "viewer"]},
|
||||
"enabled": {"type": "boolean"},
|
||||
"password_hash": {"type": ["string", "null"]},
|
||||
"must_change_password": {"type": "boolean"},
|
||||
"totp": {"oneOf": [{"type": "null"}, {"$ref": "#/definitions/totp"}]},
|
||||
"google": {"oneOf": [{"type": "null"}, {"$ref": "#/definitions/google"}]},
|
||||
"services": {"type": "array", "items": {"type": "string"}},
|
||||
"created": {"type": "string", "format": "date-time"},
|
||||
"last_login": {"type": ["string", "null"], "format": "date-time"},
|
||||
"provision_results": {"type": "object"}
|
||||
}
|
||||
},
|
||||
"totp": {
|
||||
"type": "object",
|
||||
"required": ["secret", "enabled", "enrolled_at", "backup_codes"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"secret": {"type": "string", "minLength": 16},
|
||||
"enabled": {"type": "boolean"},
|
||||
"enrolled_at": {"type": "string", "format": "date-time"},
|
||||
"last_step": {"type": ["integer", "null"]},
|
||||
"backup_codes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["hash", "used_at"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"hash": {"type": "string"},
|
||||
"used_at": {"type": ["string", "null"], "format": "date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"google": {
|
||||
"type": "object",
|
||||
"required": ["sub", "email", "linked_at"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"sub": {"type": "string"},
|
||||
"email": {"type": "string", "format": "email"},
|
||||
"linked_at": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
packages/secubox-users/tests/test_schema.py
Normal file
77
packages/secubox-users/tests/test_schema.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""Validate users.json v2 against the JSON Schema."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import jsonschema
|
||||
import pytest
|
||||
|
||||
SCHEMA = json.loads(
|
||||
(Path(__file__).resolve().parents[1] / "schema" / "users.json.schema.json").read_text()
|
||||
)
|
||||
|
||||
|
||||
def _doc(**overrides):
|
||||
base = {
|
||||
"version": 2,
|
||||
"users": [
|
||||
{
|
||||
"username": "admin",
|
||||
"email": "admin@example.local",
|
||||
"role": "admin",
|
||||
"enabled": True,
|
||||
"password_hash": None,
|
||||
"must_change_password": True,
|
||||
"totp": None,
|
||||
"google": None,
|
||||
"services": [],
|
||||
"created": "2026-05-13T00:00:00+00:00",
|
||||
"last_login": None,
|
||||
}
|
||||
],
|
||||
"groups": [],
|
||||
}
|
||||
base.update(overrides)
|
||||
return base
|
||||
|
||||
|
||||
def test_minimal_valid_doc():
|
||||
jsonschema.validate(_doc(), SCHEMA)
|
||||
|
||||
|
||||
def test_version_must_be_2():
|
||||
with pytest.raises(jsonschema.ValidationError):
|
||||
jsonschema.validate(_doc(version=1), SCHEMA)
|
||||
|
||||
|
||||
def test_username_pattern_enforced():
|
||||
doc = _doc()
|
||||
doc["users"][0]["username"] = "BadName!"
|
||||
with pytest.raises(jsonschema.ValidationError):
|
||||
jsonschema.validate(doc, SCHEMA)
|
||||
|
||||
|
||||
def test_role_enum_enforced():
|
||||
doc = _doc()
|
||||
doc["users"][0]["role"] = "superuser"
|
||||
with pytest.raises(jsonschema.ValidationError):
|
||||
jsonschema.validate(doc, SCHEMA)
|
||||
|
||||
|
||||
def test_totp_block_shape():
|
||||
doc = _doc()
|
||||
doc["users"][0]["totp"] = {
|
||||
"secret": "JBSWY3DPEHPK3PXP",
|
||||
"enabled": True,
|
||||
"enrolled_at": "2026-05-13T00:00:00+00:00",
|
||||
"last_step": None,
|
||||
"backup_codes": [
|
||||
{"hash": "$argon2id$...", "used_at": None}
|
||||
],
|
||||
}
|
||||
jsonschema.validate(doc, SCHEMA)
|
||||
|
||||
|
||||
def test_password_hash_may_be_null():
|
||||
doc = _doc()
|
||||
doc["users"][0]["password_hash"] = None
|
||||
jsonschema.validate(doc, SCHEMA)
|
||||
Loading…
Reference in New Issue
Block a user