Coverage for src / lexigram / admin / auth / integration.py: 50%

64 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-13 22:14 +0800

1"""Auth integration for lexigram-admin. 

2 

3This module provides the ``AdminUser`` dataclass — the primary admin user 

4representation that satisfies ``AuthenticatedUserProtocol`` — plus re-exports 

5of relevant contracts types for consumer convenience. 

6 

7CROSS-EXT-02: Uses protocols from lexigram-contracts for dependency injection, 

8eliminating direct cross-extension imports from lexigram-auth. 

9""" 

10 

11from __future__ import annotations 

12 

13from dataclasses import dataclass, field 

14 

15from lexigram.contracts import AuthenticatedUserProtocol as AuthenticatedUserProtocol 

16from lexigram.contracts.auth import PasswordHasherProtocol 

17 

18# ============================================================================ 

19# Admin User 

20# ============================================================================ 

21 

22 

23@dataclass 

24class AdminUser: 

25 """Admin user implementing AuthenticatedUserProtocol protocol. 

26 

27 This class represents an authenticated admin user with 

28 roles and permissions. 

29 """ 

30 

31 id: int | str 

32 email: str 

33 name: str 

34 password_hash: str | None = None 

35 is_active: bool = True 

36 is_superuser: bool = False 

37 

38 roles: list[str] = field(default_factory=list) 

39 permissions: set[str] = field(default_factory=set) 

40 

41 # Metadata 

42 created_at: str | None = None 

43 last_login: str | None = None 

44 

45 # Reference to framework auth user (set when delegated auth is active) 

46 framework_user: AuthenticatedUserProtocol | None = None 

47 

48 @property 

49 def user_id(self) -> str: 

50 """Unique user identifier.""" 

51 if self.framework_user is not None: 

52 return self.framework_user.user_id 

53 return str(self.id) 

54 

55 @property 

56 def username(self) -> str: 

57 """Username of the authenticated user.""" 

58 if self.framework_user is not None: 

59 return getattr(self.framework_user, "username", self.framework_user.name) 

60 return self.name 

61 

62 @property 

63 def is_verified(self) -> bool: 

64 """Whether user is verified.""" 

65 if self.framework_user is not None: 

66 return getattr(self.framework_user, "is_verified", self.is_active) 

67 return self.is_active 

68 

69 @property 

70 def is_authenticated(self) -> bool: 

71 """Check if user is authenticated.""" 

72 if self.framework_user is not None: 

73 return getattr(self.framework_user, "is_authenticated", self.is_active) 

74 return self.is_active 

75 

76 @property 

77 def is_admin(self) -> bool: 

78 """Check if user is admin.""" 

79 if self.framework_user is not None: 

80 return ( 

81 getattr(self.framework_user, "is_admin", False) or "admin" in self.roles 

82 ) 

83 return self.is_superuser or "admin" in self.roles 

84 

85 def has_role(self, role: str) -> bool: 

86 """Check if user has a specific role.""" 

87 if self.framework_user is not None and hasattr(self.framework_user, "has_role"): 

88 return self.framework_user.has_role(role) 

89 return role in self.roles or self.is_superuser 

90 

91 def has_permission(self, permission: str) -> bool: 

92 """Check if user has a specific permission.""" 

93 if self.framework_user is not None and hasattr( 

94 self.framework_user, "has_permission" 

95 ): 

96 return self.framework_user.has_permission(permission) 

97 if self.is_superuser: 

98 return True 

99 if permission in self.permissions: 

100 return True 

101 parts = permission.split(":") 

102 for i in range(len(parts)): 

103 wildcard = ":".join(parts[: i + 1]) + ":*" 

104 if wildcard in self.permissions: 

105 return True 

106 return False 

107 

108 def has_any_permission(self, permissions: list[str]) -> bool: 

109 """Check if user has any of the given permissions.""" 

110 return any(self.has_permission(p) for p in permissions) 

111 

112 def has_all_permissions(self, permissions: list[str]) -> bool: 

113 """Check if user has all of the given permissions.""" 

114 return all(self.has_permission(p) for p in permissions) 

115 

116 

117__all__ = [ 

118 "AdminUser", 

119 "AuthenticatedUserProtocol", 

120 "PasswordHasherProtocol", 

121]