enable policies
This commit is contained in:
@@ -12,6 +12,9 @@ from gatehouse_app.models.oidc_refresh_token import OIDCRefreshToken
|
||||
from gatehouse_app.models.oidc_session import OIDCSession
|
||||
from gatehouse_app.models.oidc_token_metadata import OIDCTokenMetadata
|
||||
from gatehouse_app.models.oidc_audit_log import OIDCAuditLog
|
||||
from gatehouse_app.models.organization_security_policy import OrganizationSecurityPolicy
|
||||
from gatehouse_app.models.user_security_policy import UserSecurityPolicy
|
||||
from gatehouse_app.models.mfa_policy_compliance import MfaPolicyCompliance
|
||||
|
||||
__all__ = [
|
||||
"BaseModel",
|
||||
@@ -27,4 +30,7 @@ __all__ = [
|
||||
"OIDCSession",
|
||||
"OIDCTokenMetadata",
|
||||
"OIDCAuditLog",
|
||||
"OrganizationSecurityPolicy",
|
||||
"UserSecurityPolicy",
|
||||
"MfaPolicyCompliance",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""MfaPolicyCompliance model."""
|
||||
from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.base import BaseModel
|
||||
from gatehouse_app.utils.constants import MfaComplianceStatus
|
||||
|
||||
|
||||
class MfaPolicyCompliance(BaseModel):
|
||||
"""MFA policy compliance tracking per user per organization.
|
||||
|
||||
Tracks each user's MFA compliance state separately for each organization membership.
|
||||
"""
|
||||
|
||||
__tablename__ = "mfa_policy_compliance"
|
||||
|
||||
user_id = db.Column(
|
||||
db.String(36), db.ForeignKey("users.id"), nullable=False, index=True
|
||||
)
|
||||
organization_id = db.Column(
|
||||
db.String(36), db.ForeignKey("organizations.id"), nullable=False, index=True
|
||||
)
|
||||
|
||||
status = db.Column(
|
||||
db.Enum(MfaComplianceStatus),
|
||||
nullable=False,
|
||||
default=MfaComplianceStatus.NOT_APPLICABLE,
|
||||
)
|
||||
|
||||
# Snapshot of org policy at the time this record became active
|
||||
policy_version = db.Column(db.Integer, nullable=False)
|
||||
|
||||
# When policy started applying to this user
|
||||
applied_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# Final deadline for this user to comply (per user, not global)
|
||||
deadline_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# When they became compliant under this policy_version
|
||||
compliant_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# When suspended enforcement started for this user
|
||||
suspended_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# Notification tracking
|
||||
last_notified_at = db.Column(db.DateTime, nullable=True)
|
||||
notification_count = db.Column(db.Integer, nullable=False, default=0)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"user_id", "organization_id", name="uix_user_org_compliance"
|
||||
),
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user = db.relationship(
|
||||
"User", back_populates="mfa_compliance", foreign_keys=[user_id]
|
||||
)
|
||||
organization = db.relationship("Organization", foreign_keys=[organization_id])
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of MfaPolicyCompliance."""
|
||||
return f"<MfaPolicyCompliance user={self.user_id} org={self.organization_id} status={self.status}>"
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert to dictionary."""
|
||||
exclude = exclude or []
|
||||
return super().to_dict(exclude=exclude)
|
||||
@@ -24,6 +24,13 @@ class Organization(BaseModel):
|
||||
oidc_clients = db.relationship(
|
||||
"OIDCClient", back_populates="organization", cascade="all, delete-orphan"
|
||||
)
|
||||
security_policy = db.relationship(
|
||||
"OrganizationSecurityPolicy",
|
||||
back_populates="organization",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan",
|
||||
foreign_keys="OrganizationSecurityPolicy.organization_id",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of Organization."""
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""OrganizationSecurityPolicy model."""
|
||||
from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.base import BaseModel
|
||||
from gatehouse_app.utils.constants import MfaPolicyMode
|
||||
|
||||
|
||||
class OrganizationSecurityPolicy(BaseModel):
|
||||
"""Organization security policy model for MFA configuration.
|
||||
|
||||
One row per organization capturing its current security requirements.
|
||||
"""
|
||||
|
||||
__tablename__ = "organization_security_policies"
|
||||
|
||||
organization_id = db.Column(
|
||||
db.String(36),
|
||||
db.ForeignKey("organizations.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
# MFA policy configuration
|
||||
mfa_policy_mode = db.Column(
|
||||
db.Enum(MfaPolicyMode), nullable=False, default=MfaPolicyMode.OPTIONAL
|
||||
)
|
||||
|
||||
# Grace period for members in days
|
||||
mfa_grace_period_days = db.Column(db.Integer, nullable=False, default=14)
|
||||
|
||||
# Notification settings (in days before individual user deadline)
|
||||
notify_days_before = db.Column(db.Integer, nullable=False, default=7)
|
||||
|
||||
# Versioning for compatibility tracking
|
||||
policy_version = db.Column(db.Integer, nullable=False, default=1)
|
||||
|
||||
# Audit metadata
|
||||
updated_by_user_id = db.Column(db.String(36), db.ForeignKey("users.id"), nullable=True)
|
||||
|
||||
# Relationships
|
||||
organization = db.relationship(
|
||||
"Organization", back_populates="security_policy", foreign_keys=[organization_id]
|
||||
)
|
||||
updated_by_user = db.relationship("User", foreign_keys=[updated_by_user_id])
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of OrganizationSecurityPolicy."""
|
||||
return f"<OrganizationSecurityPolicy org={self.organization_id} mode={self.mfa_policy_mode}>"
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert to dictionary."""
|
||||
exclude = exclude or []
|
||||
return super().to_dict(exclude=exclude)
|
||||
@@ -25,6 +25,9 @@ class Session(BaseModel):
|
||||
revoked_at = db.Column(db.DateTime, nullable=True)
|
||||
revoked_reason = db.Column(db.String(255), nullable=True)
|
||||
|
||||
# Compliance session flag
|
||||
is_compliance_only = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
# Relationships
|
||||
user = db.relationship("User", back_populates="sessions")
|
||||
|
||||
|
||||
@@ -31,6 +31,18 @@ class User(BaseModel):
|
||||
foreign_keys="OrganizationMember.user_id",
|
||||
)
|
||||
audit_logs = db.relationship("AuditLog", back_populates="user", cascade="all, delete-orphan")
|
||||
security_policies = db.relationship(
|
||||
"UserSecurityPolicy",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
foreign_keys="UserSecurityPolicy.user_id",
|
||||
)
|
||||
mfa_compliance = db.relationship(
|
||||
"MfaPolicyCompliance",
|
||||
back_populates="user",
|
||||
cascade="all, delete-orphan",
|
||||
foreign_keys="MfaPolicyCompliance.user_id",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of User."""
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""UserSecurityPolicy model."""
|
||||
from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.base import BaseModel
|
||||
from gatehouse_app.utils.constants import MfaRequirementOverride
|
||||
|
||||
|
||||
class UserSecurityPolicy(BaseModel):
|
||||
"""User security policy model for per-user MFA overrides.
|
||||
|
||||
Stores per user overrides of organization level MFA requirements.
|
||||
"""
|
||||
|
||||
__tablename__ = "user_security_policies"
|
||||
|
||||
user_id = db.Column(
|
||||
db.String(36), db.ForeignKey("users.id"), nullable=False, index=True
|
||||
)
|
||||
organization_id = db.Column(
|
||||
db.String(36), db.ForeignKey("organizations.id"), nullable=False, index=True
|
||||
)
|
||||
|
||||
mfa_override_mode = db.Column(
|
||||
db.Enum(MfaRequirementOverride),
|
||||
nullable=False,
|
||||
default=MfaRequirementOverride.INHERIT,
|
||||
)
|
||||
|
||||
# If override is REQUIRED and you want to force a specific factor set
|
||||
force_totp = db.Column(db.Boolean, nullable=False, default=False)
|
||||
force_webauthn = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"user_id", "organization_id", name="uix_user_org_policy"
|
||||
),
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user = db.relationship(
|
||||
"User", back_populates="security_policies", foreign_keys=[user_id]
|
||||
)
|
||||
organization = db.relationship(
|
||||
"Organization", foreign_keys=[organization_id]
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of UserSecurityPolicy."""
|
||||
return f"<UserSecurityPolicy user={self.user_id} org={self.organization_id} mode={self.mfa_override_mode}>"
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert to dictionary."""
|
||||
exclude = exclude or []
|
||||
return super().to_dict(exclude=exclude)
|
||||
Reference in New Issue
Block a user