pki_ca/ca_core/entity.py

182 lines
5.3 KiB
Python

from db_logging import log_change
def ensure_entity_active(cursor, entity_id):
"""
Ensure an entity exists and is active.
Revoked entities are immutable.
"""
cursor.execute("SELECT status FROM entity WHERE id = %s", (entity_id,))
row = cursor.fetchone()
if row is None:
raise ValueError("Entity does not exist")
if row["status"] != "active":
raise ValueError("Entity is not active")
def _validate_ca_reference_for_group(ca_reference):
if ca_reference is None:
raise ValueError("ca_reference is required for groups")
if not isinstance(ca_reference, str):
raise ValueError("ca_reference must be a string")
if len(ca_reference) == 0:
raise ValueError("ca_reference cannot be empty")
if len(ca_reference) > 100:
raise ValueError("ca_reference must be at most 100 characters")
def is_creator(cursor, entity_id):
"""
Return True if the entity is a creator.
A creator is:
- an entity of type 'person'
- with a row in property where property_name = 'creator'
"""
cursor.execute("SELECT type FROM entity WHERE id = %s", (entity_id,))
row = cursor.fetchone()
if row is None:
return False
if row["type"] != "person":
return False
cursor.execute(
"SELECT 1 FROM property WHERE id = %s AND property_name = %s",
(entity_id, "creator"),
)
return cursor.fetchone() is not None
def ensure_creator(cursor, creator_id):
"""
Ensure creator_id exists, is active, and references a creator.
A creator is a 'person' entity that has the 'creator' property.
"""
ensure_entity_active(cursor, creator_id)
if not is_creator(cursor, creator_id):
raise ValueError("creator_id must reference a creator")
def insert_creator(cursor, name, public_key):
"""
Create a creator.
Creators are persons with property 'creator' in the property table.
"""
cursor.execute(
"""
INSERT INTO entity (name, type, public_key, status, ca_reference)
VALUES (%s, 'person', %s, 'active', NULL)
RETURNING id
""",
(name, public_key),
)
creator_id = cursor.fetchone()["id"]
# Mark as creator via property table (schema: property(id, property_name))
cursor.execute(
"""
INSERT INTO property (id, property_name)
VALUES (%s, %s)
ON CONFLICT (id, property_name) DO NOTHING
""",
(creator_id, "creator"),
)
log_change(cursor, f"Created creator entity {creator_id} with name {name}")
return creator_id
def enroll_person(cursor, name, public_key, creator_id):
"""
Enroll a new person under a creator.
creator_id must refer to an active creator (person + 'creator' property).
"""
ensure_creator(cursor, creator_id)
cursor.execute(
"""
INSERT INTO entity (name, type, public_key, creator, status, ca_reference)
VALUES (%s, 'person', %s, %s, 'active', NULL)
RETURNING id
""",
(name, public_key, creator_id),
)
person_id = cursor.fetchone()["id"]
log_change(cursor, f"Enrolled person {person_id} under creator {creator_id}")
return person_id
def create_group(cursor, name, public_key, creator_id, ca_reference):
"""
Create a group under a creator.
creator_id must refer to an active creator (person + 'creator' property).
Groups must define a non-empty ca_reference.
"""
ensure_creator(cursor, creator_id)
_validate_ca_reference_for_group(ca_reference)
cursor.execute(
"""
INSERT INTO entity (name, type, public_key, creator, status, ca_reference)
VALUES (%s, 'group', %s, %s, 'active', %s)
RETURNING id
""",
(name, public_key, creator_id, ca_reference),
)
group_id = cursor.fetchone()["id"]
log_change(
cursor,
f"Created group {group_id} under creator {creator_id} with ca_reference {ca_reference}",
)
return group_id
def get_entity(cursor, entity_id):
cursor.execute("SELECT * FROM entity WHERE id = %s", (entity_id,))
return cursor.fetchone()
def set_entity_status(cursor, entity_id, status, changed_by):
"""
Update entity status.
Only active entities can change status. Once revoked, immutable.
"""
ensure_entity_active(cursor, entity_id)
cursor.execute("UPDATE entity SET status = %s WHERE id = %s", (status, entity_id))
log_change(cursor, f"Set status of entity {entity_id} to {status} by {changed_by}")
def set_symmetrical_key(cursor, entity_id, key_value, changed_by):
ensure_entity_active(cursor, entity_id)
cursor.execute(
"UPDATE entity SET symmetrical_key = %s WHERE id = %s",
(key_value, entity_id),
)
log_change(cursor, f"Set symmetrical_key for entity {entity_id} by {changed_by}")
def get_symmetrical_key(cursor, entity_id):
cursor.execute("SELECT symmetrical_key FROM entity WHERE id = %s", (entity_id,))
row = cursor.fetchone()
return row["symmetrical_key"] if row else None
def set_entity_keys(cursor, entity_id, public_key, changed_by):
ensure_entity_active(cursor, entity_id)
cursor.execute(
"UPDATE entity SET public_key = %s WHERE id = %s",
(public_key, entity_id),
)
log_change(cursor, f"Updated public key for entity {entity_id} by {changed_by}")