skyplatform-iam 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ """
2
+ SkyPlatform IAM SDK
3
+ 提供FastAPI认证中间件和IAM服务连接功能
4
+ """
5
+
6
+ from .config import AuthConfig
7
+ from .middleware import AuthMiddleware
8
+ from .connect_agenterra_iam import ConnectAgenterraIam
9
+ from .exceptions import (
10
+ SkyPlatformAuthException,
11
+ AuthenticationError,
12
+ AuthorizationError,
13
+ TokenExpiredError,
14
+ TokenInvalidError,
15
+ ConfigurationError,
16
+ IAMServiceError,
17
+ NetworkError
18
+ )
19
+
20
+ __version__ = "1.0.0"
21
+ __author__ = "x9"
22
+ __description__ = "SkyPlatform IAM认证SDK,提供FastAPI中间件和IAM服务连接功能"
23
+
24
+ # 导出主要类和函数
25
+ __all__ = [
26
+ # 配置
27
+ "AuthConfig",
28
+
29
+ # 中间件
30
+ "AuthMiddleware",
31
+
32
+ # 客户端
33
+ "ConnectAgenterraIam",
34
+
35
+ # 异常
36
+ "SkyPlatformAuthException",
37
+ "AuthenticationError",
38
+ "AuthorizationError",
39
+ "TokenExpiredError",
40
+ "TokenInvalidError",
41
+ "ConfigurationError",
42
+ "IAMServiceError",
43
+ "NetworkError",
44
+
45
+ # 版本信息
46
+ "__version__",
47
+ "__author__",
48
+ "__description__"
49
+ ]
50
+
51
+
52
+ def create_auth_middleware(config: AuthConfig = None, **kwargs) -> AuthMiddleware:
53
+ """
54
+ 创建认证中间件的便捷函数
55
+
56
+ Args:
57
+ config: 认证配置,如果为None则从环境变量创建
58
+ **kwargs: 其他中间件参数
59
+
60
+ Returns:
61
+ AuthMiddleware: 认证中间件实例
62
+
63
+ Note:
64
+ 此函数用于创建中间件实例,用于请求拦截和鉴权。
65
+ 客户端应用需要自己实现具体的业务接口。
66
+ """
67
+ if config is None:
68
+ config = AuthConfig.from_env()
69
+
70
+ return AuthMiddleware(config=config, **kwargs)
71
+
72
+
73
+ def setup_auth(app, config: AuthConfig = None):
74
+ """
75
+ 一键设置认证中间件的便捷函数
76
+
77
+ Args:
78
+ app: FastAPI应用实例
79
+ config: 认证配置,如果为None则从环境变量创建
80
+
81
+ Returns:
82
+ AuthMiddleware: 认证中间件实例
83
+
84
+ Note:
85
+ 此函数只设置认证中间件,不包含预制路由。
86
+ 客户端应用需要根据业务需求自己实现认证相关的API接口。
87
+ """
88
+ if config is None:
89
+ config = AuthConfig.from_env()
90
+
91
+ # 添加中间件
92
+ middleware = AuthMiddleware(app=app, config=config)
93
+ app.add_middleware(AuthMiddleware, config=config)
94
+
95
+ return middleware
@@ -0,0 +1,173 @@
1
+ from fastapi import Request, HTTPException, status
2
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3
+ from typing import Optional, Dict
4
+ import jwt
5
+
6
+ from .connect_agenterra_iam import ConnectAgenterraIam
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class AuthMiddleware:
13
+ def __init__(self):
14
+ self.security = HTTPBearer(auto_error=False)
15
+ self.iam_client = ConnectAgenterraIam()
16
+
17
+ async def verify_token(self, request: Request):
18
+ # 通过token, server_ak, server_sk判断是否有权限
19
+ credentials: HTTPAuthorizationCredentials = await self.security(request)
20
+ api_path = request.url.path
21
+ method = request.method
22
+
23
+ server_ak = request.headers.get("SERVER-AK", "")
24
+ server_sk = request.headers.get("SERVER-SK", "")
25
+
26
+ token = ""
27
+ if credentials is not None:
28
+ token = credentials.credentials
29
+ user_info_by_iam = self.iam_client.verify_token(token, api_path, method, server_ak, server_sk)
30
+ if user_info_by_iam:
31
+ return True
32
+ return False
33
+
34
+ async def get_current_user(self, request: Request) -> Optional[Dict]:
35
+ """获取当前用户信息"""
36
+ try:
37
+ # 直接调用verify_token方法进行token验证
38
+ if not await self.verify_token(request):
39
+ return None
40
+
41
+ # 获取token用于后续用户信息获取
42
+ credentials: HTTPAuthorizationCredentials = await self.security(request)
43
+ token = credentials.credentials
44
+
45
+ # 直接解析JWT token获取payload
46
+ payload = self.decode_jwt_token(token)
47
+ if not payload:
48
+ logger.error("JWT token解析失败")
49
+ return None
50
+
51
+ # 从payload中提取用户信息
52
+ iam_user_id = payload.get("sub") # JWT标准中用户ID存储在sub字段
53
+ username = None
54
+
55
+ # 解析新的凭证信息结构
56
+ all_credentials = payload.get("all_credentials", [])
57
+ total_credentials = payload.get("total_credentials", 0)
58
+
59
+ # 从all_credentials中提取username(向后兼容)
60
+ for cred in all_credentials:
61
+ if cred.get("type") == "username":
62
+ username = cred.get("value")
63
+ break
64
+
65
+ # 向后兼容性:如果没有all_credentials,尝试从payload的其他字段构建
66
+ if not all_credentials:
67
+ credentials_list = []
68
+ # 检查payload中是否有直接的username字段
69
+ if payload.get("username"):
70
+ username = payload.get("username")
71
+ credentials_list.append({"type": "username", "value": username})
72
+ if payload.get("email"):
73
+ credentials_list.append({"type": "email", "value": payload.get("email")})
74
+ if payload.get("phone"):
75
+ credentials_list.append({"type": "phone", "value": payload.get("phone")})
76
+ all_credentials = credentials_list
77
+ total_credentials = len(credentials_list)
78
+
79
+ if not username:
80
+ return None
81
+
82
+ # 构建用户信息字典
83
+ user_info = {
84
+ "id": iam_user_id,
85
+ "username": username,
86
+ "all_credentials": all_credentials,
87
+ "total_credentials": total_credentials,
88
+ "microservice": payload.get("microservice") # 添加微服务信息
89
+ }
90
+
91
+ # 向后兼容:添加传统字段映射
92
+ for cred in all_credentials:
93
+ if cred.get("type") == "email":
94
+ user_info["email"] = cred.get("value")
95
+ elif cred.get("type") == "phone":
96
+ user_info["phone"] = cred.get("value")
97
+ elif cred.get("type") == "username" and not user_info.get("username"):
98
+ user_info["username"] = cred.get("value")
99
+
100
+ # 统计凭证类型分布
101
+ cred_types = [cred.get("type") for cred in all_credentials]
102
+ cred_type_count = {cred_type: cred_types.count(cred_type) for cred_type in set(cred_types)}
103
+
104
+ logger.info(
105
+ f"用户认证成功: user_id={iam_user_id}, username={username}, 凭证数量={total_credentials}, 凭证类型分布={cred_type_count}")
106
+ logger.debug(f"JWT payload: {payload}")
107
+
108
+ # 将用户信息添加到请求状态中
109
+ request.state.user = user_info
110
+ return user_info
111
+
112
+
113
+ except HTTPException as e:
114
+ print(403)
115
+ logger.error(f"获取当前用户信息失败: {str(e)}")
116
+ # 重新抛出HTTP异常(403权限不足)
117
+ return None
118
+ except Exception as e:
119
+ logger.error(f"获取当前用户信息失败: {str(e)}")
120
+ return None
121
+
122
+ async def require_auth(self, request: Request) -> Dict:
123
+ """要求用户必须登录"""
124
+ try:
125
+ user_info = await self.get_current_user(request)
126
+ if not user_info:
127
+ raise HTTPException(
128
+ status_code=status.HTTP_401_UNAUTHORIZED,
129
+ detail="需要登录认证",
130
+ headers={"WWW-Authenticate": "Bearer"},
131
+ )
132
+ return user_info
133
+ except HTTPException:
134
+ # 重新抛出HTTP异常(可能是403权限不足或401未认证)
135
+ raise
136
+
137
+ async def optional_auth(self, request: Request) -> Optional[Dict]:
138
+ """可选的用户认证(不强制要求登录)"""
139
+ try:
140
+ return await self.get_current_user(request)
141
+ except HTTPException:
142
+ # 对于可选认证,如果是403权限不足,仍然抛出异常
143
+ # 如果是401未认证,返回None
144
+ raise
145
+
146
+ def decode_jwt_token(self, token: str) -> Optional[Dict]:
147
+ """直接解析JWT token获取payload"""
148
+ try:
149
+ # 不验证签名,只解析payload(因为token已经通过verify_token验证过)
150
+ decoded_payload = jwt.decode(token, options={"verify_signature": False})
151
+ logger.debug(f"JWT token解析成功: {decoded_payload}")
152
+ return decoded_payload
153
+ except jwt.InvalidTokenError as e:
154
+ logger.error(f"JWT token解析失败: {str(e)}")
155
+ return None
156
+ except Exception as e:
157
+ logger.error(f"JWT token解析异常: {str(e)}")
158
+ return None
159
+
160
+
161
+ # 创建全局认证中间件实例
162
+ auth_middleware = AuthMiddleware()
163
+
164
+
165
+ # 便捷的依赖函数
166
+ async def get_current_user(request: Request) -> Dict:
167
+ """获取当前用户的依赖函数"""
168
+ return await auth_middleware.require_auth(request)
169
+
170
+
171
+ async def get_optional_user(request: Request) -> Optional[Dict]:
172
+ """获取可选当前用户的依赖函数"""
173
+ return await auth_middleware.optional_auth(request)
@@ -0,0 +1,68 @@
1
+ """
2
+ SkyPlatform IAM SDK 配置模块
3
+ """
4
+ import os
5
+ from typing import Optional, List
6
+ from pydantic import BaseModel
7
+ from dotenv import load_dotenv
8
+
9
+ # 加载环境变量
10
+ load_dotenv()
11
+
12
+
13
+ class AuthConfig(BaseModel):
14
+ """
15
+ 认证配置类
16
+ 支持环境变量和代码配置
17
+ """
18
+ # IAM服务配置
19
+ agenterra_iam_host: str
20
+ server_name: str
21
+ access_key: str
22
+
23
+
24
+ # Token配置
25
+ token_header: str = "Authorization"
26
+ token_prefix: str = "Bearer "
27
+
28
+ # 错误处理配置
29
+ enable_debug: bool = False
30
+
31
+ class Config:
32
+ env_prefix = "AGENTERRA_"
33
+
34
+ @classmethod
35
+ def from_env(cls) -> "AuthConfig":
36
+ """
37
+ 从环境变量创建配置
38
+ """
39
+ return cls(
40
+ agenterra_iam_host=os.environ.get('AGENTERRA_IAM_HOST', ''),
41
+ server_name=os.environ.get('AGENTERRA_SERVER_NAME', ''),
42
+ access_key=os.environ.get('AGENTERRA_ACCESS_KEY', ''),
43
+ enable_debug=os.environ.get('AGENTERRA_ENABLE_DEBUG', 'false').lower() == 'true'
44
+ )
45
+
46
+ def validate_config(self) -> bool:
47
+ """
48
+ 验证配置是否完整
49
+ """
50
+ required_fields = ['agenterra_iam_host', 'server_name', 'access_key']
51
+ for field in required_fields:
52
+ if not getattr(self, field):
53
+ raise ValueError(f"配置项 {field} 不能为空")
54
+ return True
55
+
56
+ def add_whitelist_path(self, path: str) -> None:
57
+ """
58
+ 添加白名单路径
59
+ """
60
+ if path not in self.whitelist_paths:
61
+ self.whitelist_paths.append(path)
62
+
63
+ def remove_whitelist_path(self, path: str) -> None:
64
+ """
65
+ 移除白名单路径
66
+ """
67
+ if path in self.whitelist_paths:
68
+ self.whitelist_paths.remove(path)