skyplatform-iam 1.0.5__py3-none-any.whl → 1.2.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.
@@ -4,28 +4,8 @@ SkyPlatform IAM SDK
4
4
  """
5
5
 
6
6
  from .config import AuthConfig
7
- from .middleware import (
8
- AuthMiddleware,
9
- AuthService,
10
- setup_auth_middleware,
11
- get_current_user,
12
- get_optional_user,
13
- create_auth_middleware
14
- )
7
+ from .middleware import AuthMiddleware, AuthService, setup_auth_middleware, get_current_user, get_optional_user
15
8
  from .connect_agenterra_iam import ConnectAgenterraIam
16
- from .global_manager import GlobalIAMManager, get_global_manager
17
- from .api import (
18
- init_skyplatform_iam,
19
- get_iam_client,
20
- create_lazy_iam_client,
21
- LazyIAMClient,
22
- get_current_user_info,
23
- verify_permission,
24
- get_config,
25
- get_sdk_status,
26
- reset_sdk,
27
- setup_auth # 向后兼容的别名
28
- )
29
9
  from .exceptions import (
30
10
  SkyPlatformAuthException,
31
11
  AuthenticationError,
@@ -37,39 +17,28 @@ from .exceptions import (
37
17
  NetworkError
38
18
  )
39
19
 
40
- __version__ = "2.0.0"
20
+ __version__ = "1.2.0"
41
21
  __author__ = "x9"
42
22
  __description__ = "SkyPlatform IAM认证SDK,提供FastAPI中间件和IAM服务连接功能"
43
23
 
24
+ # 全局IAM客户端实例
25
+ _global_iam_client = None
26
+
44
27
  # 导出主要类和函数
45
28
  __all__ = [
46
29
  # 配置
47
30
  "AuthConfig",
48
31
 
49
- # 新的统一API(推荐使用)
50
- "init_skyplatform_iam",
51
- "get_iam_client",
52
- "create_lazy_iam_client",
53
- "LazyIAMClient",
54
- "get_current_user_info",
55
- "verify_permission",
56
- "get_config",
57
- "get_sdk_status",
58
- "reset_sdk",
59
-
60
- # 全局管理器
61
- "GlobalIAMManager",
62
- "get_global_manager",
63
-
64
32
  # 中间件
65
33
  "AuthMiddleware",
66
34
  "AuthService",
67
- "create_auth_middleware",
35
+ "setup_auth_middleware",
68
36
  "get_current_user",
69
37
  "get_optional_user",
70
38
 
71
39
  # 客户端
72
40
  "ConnectAgenterraIam",
41
+ "get_iam_client",
73
42
 
74
43
  # 异常
75
44
  "SkyPlatformAuthException",
@@ -81,10 +50,6 @@ __all__ = [
81
50
  "IAMServiceError",
82
51
  "NetworkError",
83
52
 
84
- # 向后兼容(已废弃)
85
- "setup_auth_middleware",
86
- "setup_auth",
87
-
88
53
  # 版本信息
89
54
  "__version__",
90
55
  "__author__",
@@ -92,10 +57,9 @@ __all__ = [
92
57
  ]
93
58
 
94
59
 
95
- # 向后兼容的便捷函数(已废弃)
96
- def create_auth_middleware_legacy(config: AuthConfig = None, **kwargs) -> AuthMiddleware:
60
+ def create_auth_middleware(config: AuthConfig = None, **kwargs) -> AuthMiddleware:
97
61
  """
98
- 创建认证中间件的便捷函数(已废弃)
62
+ 创建认证中间件的便捷函数
99
63
 
100
64
  Args:
101
65
  config: 认证配置,如果为None则从环境变量创建
@@ -104,25 +68,19 @@ def create_auth_middleware_legacy(config: AuthConfig = None, **kwargs) -> AuthMi
104
68
  Returns:
105
69
  AuthMiddleware: 认证中间件实例
106
70
 
107
- Deprecated:
108
- 请使用 init_skyplatform_iam() + create_auth_middleware() 替代
71
+ Note:
72
+ 此函数用于创建中间件实例,用于请求拦截和鉴权。
73
+ 客户端应用需要自己实现具体的业务接口。
109
74
  """
110
- import warnings
111
- warnings.warn(
112
- "create_auth_middleware_legacy()已废弃,请使用init_skyplatform_iam() + create_auth_middleware()替代",
113
- DeprecationWarning,
114
- stacklevel=2
115
- )
116
-
117
75
  if config is None:
118
76
  config = AuthConfig.from_env()
119
77
 
120
- return AuthMiddleware(config=config, use_global_manager=False, **kwargs)
78
+ return AuthMiddleware(config=config, **kwargs)
121
79
 
122
80
 
123
- def setup_auth_legacy(app, config: AuthConfig = None):
81
+ def init_skyplatform_iam(app, config: AuthConfig = None):
124
82
  """
125
- 一键设置认证中间件的便捷函数(已废弃)
83
+ 一键设置认证中间件的便捷函数
126
84
 
127
85
  Args:
128
86
  app: FastAPI应用实例
@@ -131,16 +89,11 @@ def setup_auth_legacy(app, config: AuthConfig = None):
131
89
  Returns:
132
90
  AuthMiddleware: 认证中间件实例
133
91
 
134
- Deprecated:
135
- 请使用 init_skyplatform_iam() 替代
92
+ Note:
93
+ 此函数只设置认证中间件,不包含预制路由。
94
+ 客户端应用需要根据业务需求自己实现认证相关的API接口。
95
+ 建议传入完整的AuthConfig对象以避免环境变量配置问题。
136
96
  """
137
- import warnings
138
- warnings.warn(
139
- "setup_auth_legacy()已废弃,请使用init_skyplatform_iam()替代",
140
- DeprecationWarning,
141
- stacklevel=2
142
- )
143
-
144
97
  if config is None:
145
98
  config = AuthConfig.from_env()
146
99
 
@@ -151,7 +104,58 @@ def setup_auth_legacy(app, config: AuthConfig = None):
151
104
  setup_auth_middleware(config)
152
105
 
153
106
  # 添加中间件
154
- middleware = AuthMiddleware(app=app, config=config, use_global_manager=False)
155
- app.add_middleware(AuthMiddleware, config=config, use_global_manager=False)
107
+ middleware = AuthMiddleware(app=app, config=config)
108
+ app.add_middleware(AuthMiddleware, config=config)
156
109
 
157
- return middleware
110
+ return middleware
111
+
112
+
113
+ def get_iam_client(config: AuthConfig = None) -> ConnectAgenterraIam:
114
+ """
115
+ 获取全局IAM客户端实例
116
+
117
+ Args:
118
+ config: 认证配置,如果为None则从环境变量创建
119
+
120
+ Returns:
121
+ ConnectAgenterraIam: IAM客户端实例
122
+
123
+ Note:
124
+ 此函数使用单例模式,确保整个应用中只有一个IAM客户端实例。
125
+ 第一次调用时会创建实例,后续调用会返回同一个实例。
126
+ 如果需要更新配置,可以传入新的config参数。
127
+
128
+ Example:
129
+ # 使用默认配置(从环境变量)
130
+ iam_client = get_iam_client()
131
+
132
+ # 使用自定义配置
133
+ config = AuthConfig(
134
+ agenterra_iam_host="https://iam.example.com",
135
+ server_name="my_server",
136
+ access_key="my_access_key"
137
+ )
138
+ iam_client = get_iam_client(config)
139
+
140
+ # 调用IAM服务方法
141
+ result = iam_client.register(
142
+ cred_type="username",
143
+ cred_value="test_user",
144
+ password="password123"
145
+ )
146
+ """
147
+ global _global_iam_client
148
+
149
+ # 如果传入了新的配置,或者实例不存在,则创建/更新实例
150
+ if config is not None:
151
+ if _global_iam_client is None:
152
+ _global_iam_client = ConnectAgenterraIam(config=config)
153
+ else:
154
+ # 重新加载配置
155
+ _global_iam_client.reload_config(config)
156
+ elif _global_iam_client is None:
157
+ # 使用默认配置创建实例
158
+ default_config = AuthConfig.from_env()
159
+ _global_iam_client = ConnectAgenterraIam(config=default_config)
160
+
161
+ return _global_iam_client
skyplatform_iam/config.py CHANGED
@@ -1,28 +1,30 @@
1
1
  """
2
2
  SkyPlatform IAM SDK 配置模块
3
3
  """
4
+ import logging
4
5
  import os
5
6
  import fnmatch
6
- import logging
7
- from typing import Optional, List, Dict, Any
8
- from pydantic import BaseModel, Field, validator
7
+ from typing import List, Optional, Dict
8
+
9
+ import jwt
10
+ from pydantic import BaseModel, Field
9
11
  from dotenv import load_dotenv
10
12
 
11
13
  # 加载环境变量
12
14
  load_dotenv()
13
-
14
15
  logger = logging.getLogger(__name__)
15
16
 
16
17
 
17
18
  class AuthConfig(BaseModel):
18
19
  """
19
20
  认证配置类
20
- 支持环境变量和代码配置,增强配置验证和管理功能
21
+ 支持环境变量和代码配置
21
22
  """
22
23
  # IAM服务配置
23
24
  agenterra_iam_host: str
24
25
  server_name: str
25
26
  access_key: str
27
+ machine_token: str
26
28
 
27
29
  # Token配置
28
30
  token_header: str = "Authorization"
@@ -33,184 +35,33 @@ class AuthConfig(BaseModel):
33
35
 
34
36
  # 白名单路径配置(实例变量)
35
37
  whitelist_paths: List[str] = Field(default_factory=list)
36
-
37
- # 连接配置
38
- timeout: int = 30
39
- max_retries: int = 3
40
-
41
- # 缓存配置
42
- enable_cache: bool = True
43
- cache_ttl: int = 300 # 5分钟
44
38
 
45
39
  class Config:
46
- env_prefix = "SKYPLATFORM_"
47
- validate_assignment = True
48
-
49
- @validator('agenterra_iam_host')
50
- def validate_iam_host(cls, v):
51
- """验证IAM主机地址"""
52
- if not v:
53
- raise ValueError("agenterra_iam_host不能为空")
54
- if not (v.startswith('http://') or v.startswith('https://')):
55
- raise ValueError("agenterra_iam_host必须以http://或https://开头")
56
- return v.rstrip('/') # 移除末尾的斜杠
57
-
58
- @validator('server_name')
59
- def validate_server_name(cls, v):
60
- """验证服务名称"""
61
- if not v or not v.strip():
62
- raise ValueError("server_name不能为空")
63
- return v.strip()
64
-
65
- @validator('access_key')
66
- def validate_access_key(cls, v):
67
- """验证访问密钥"""
68
- if not v or not v.strip():
69
- raise ValueError("access_key不能为空")
70
- if len(v.strip()) < 8:
71
- raise ValueError("access_key长度不能少于8个字符")
72
- return v.strip()
73
-
74
- @validator('timeout')
75
- def validate_timeout(cls, v):
76
- """验证超时时间"""
77
- if v <= 0:
78
- raise ValueError("timeout必须大于0")
79
- return v
80
-
81
- @validator('max_retries')
82
- def validate_max_retries(cls, v):
83
- """验证最大重试次数"""
84
- if v < 0:
85
- raise ValueError("max_retries不能小于0")
86
- return v
87
-
88
- @validator('cache_ttl')
89
- def validate_cache_ttl(cls, v):
90
- """验证缓存TTL"""
91
- if v <= 0:
92
- raise ValueError("cache_ttl必须大于0")
93
- return v
40
+ env_prefix = "AGENTERRA_"
94
41
 
95
42
  @classmethod
96
- def from_env(cls, prefix: str = "SKYPLATFORM_") -> "AuthConfig":
43
+ def from_env(cls) -> "AuthConfig":
97
44
  """
98
45
  从环境变量创建配置
99
-
100
- Args:
101
- prefix: 环境变量前缀,默认为SKYPLATFORM_
102
-
103
- Returns:
104
- AuthConfig: 配置实例
105
-
106
- Raises:
107
- ValueError: 配置验证失败
108
46
  """
109
- logger.info(f"从环境变量加载配置,前缀: {prefix}")
110
-
111
- # 支持多种环境变量前缀(向后兼容)
112
- def get_env_value(key: str, default: str = '') -> str:
113
- # 优先使用新前缀
114
- value = os.environ.get(f"{prefix}{key}", '')
115
- if not value:
116
- # 回退到旧前缀
117
- value = os.environ.get(f"AGENTERRA_{key}", default)
118
- return value
119
-
120
- # 解析白名单路径
121
- whitelist_paths_str = get_env_value('WHITELIST_PATHS', '')
122
- whitelist_paths = []
123
- if whitelist_paths_str:
124
- whitelist_paths = [path.strip() for path in whitelist_paths_str.split(',') if path.strip()]
125
-
126
- config = cls(
127
- agenterra_iam_host=get_env_value('IAM_HOST'),
128
- server_name=get_env_value('SERVER_NAME'),
129
- access_key=get_env_value('ACCESS_KEY'),
130
- enable_debug=get_env_value('ENABLE_DEBUG', 'false').lower() == 'true',
131
- whitelist_paths=whitelist_paths,
132
- timeout=int(get_env_value('TIMEOUT', '30')),
133
- max_retries=int(get_env_value('MAX_RETRIES', '3')),
134
- enable_cache=get_env_value('ENABLE_CACHE', 'true').lower() == 'true',
135
- cache_ttl=int(get_env_value('CACHE_TTL', '300'))
47
+ return cls(
48
+ agenterra_iam_host=os.environ.get('AGENTERRA_IAM_HOST', ''),
49
+ server_name=os.environ.get('AGENTERRA_SERVER_NAME', ''),
50
+ access_key=os.environ.get('AGENTERRA_ACCESS_KEY', ''),
51
+ enable_debug=os.environ.get('AGENTERRA_ENABLE_DEBUG', 'false').lower() == 'true',
52
+ machine_token=os.environ.get('MACHINE_TOKEN', ''),
53
+ whitelist_paths=[] # 初始化空的白名单路径列表
136
54
  )
137
-
138
- logger.info(f"配置加载完成: server_name={config.server_name}, "
139
- f"iam_host={config.agenterra_iam_host}, "
140
- f"whitelist_paths_count={len(config.whitelist_paths)}")
141
-
142
- return config
143
55
 
144
- def validate_config(self) -> None:
56
+ def validate_config(self) -> bool:
145
57
  """
146
- 验证配置完整性
147
-
148
- Raises:
149
- ValueError: 配置验证失败
58
+ 验证配置是否完整
150
59
  """
151
- logger.debug("开始验证配置完整性")
152
-
153
- # Pydantic会自动调用validator,这里只需要检查业务逻辑
154
- if not self.agenterra_iam_host:
155
- raise ValueError("agenterra_iam_host不能为空")
156
- if not self.server_name:
157
- raise ValueError("server_name不能为空")
158
- if not self.access_key:
159
- raise ValueError("access_key不能为空")
160
-
161
- logger.info("配置验证通过")
162
-
163
- def merge_config(self, other: "AuthConfig") -> "AuthConfig":
164
- """
165
- 合并配置,other的非空值会覆盖当前配置
166
-
167
- Args:
168
- other: 要合并的配置
169
-
170
- Returns:
171
- AuthConfig: 合并后的新配置实例
172
- """
173
- logger.debug("开始合并配置")
174
-
175
- # 获取当前配置的字典表示
176
- current_dict = self.dict()
177
- other_dict = other.dict()
178
-
179
- # 合并配置
180
- merged_dict = current_dict.copy()
181
- for key, value in other_dict.items():
182
- if key == 'whitelist_paths':
183
- # 白名单路径需要合并而不是覆盖
184
- merged_paths = list(set(current_dict[key] + value))
185
- merged_dict[key] = merged_paths
186
- elif value: # 只有非空值才覆盖
187
- merged_dict[key] = value
188
-
189
- logger.debug(f"配置合并完成,合并后的配置: {merged_dict}")
190
- return AuthConfig(**merged_dict)
191
-
192
- def to_dict(self) -> Dict[str, Any]:
193
- """
194
- 转换为字典格式
195
-
196
- Returns:
197
- Dict: 配置字典
198
- """
199
- return self.dict()
200
-
201
- def copy_with_updates(self, **updates) -> "AuthConfig":
202
- """
203
- 创建配置副本并更新指定字段
204
-
205
- Args:
206
- **updates: 要更新的字段
207
-
208
- Returns:
209
- AuthConfig: 更新后的配置副本
210
- """
211
- config_dict = self.dict()
212
- config_dict.update(updates)
213
- return AuthConfig(**config_dict)
60
+ required_fields = ['agenterra_iam_host', 'server_name', 'access_key']
61
+ for field in required_fields:
62
+ if not getattr(self, field):
63
+ raise ValueError(f"配置项 {field} 不能为空")
64
+ return True
214
65
 
215
66
  def _normalize_path(self, path: str) -> str:
216
67
  """
@@ -218,15 +69,15 @@ class AuthConfig(BaseModel):
218
69
  """
219
70
  if not path:
220
71
  return path
221
-
72
+
222
73
  # 确保路径以 / 开头
223
74
  if not path.startswith('/'):
224
75
  path = '/' + path
225
-
76
+
226
77
  # 移除重复的斜杠
227
78
  while '//' in path:
228
79
  path = path.replace('//', '/')
229
-
80
+
230
81
  return path
231
82
 
232
83
  def add_whitelist_path(self, path: str) -> None:
@@ -235,7 +86,7 @@ class AuthConfig(BaseModel):
235
86
  """
236
87
  if not path:
237
88
  return
238
-
89
+
239
90
  normalized_path = self._normalize_path(path)
240
91
  if normalized_path not in self.whitelist_paths:
241
92
  self.whitelist_paths.append(normalized_path)
@@ -253,7 +104,7 @@ class AuthConfig(BaseModel):
253
104
  """
254
105
  if not path:
255
106
  return
256
-
107
+
257
108
  normalized_path = self._normalize_path(path)
258
109
  if normalized_path in self.whitelist_paths:
259
110
  self.whitelist_paths.remove(normalized_path)
@@ -276,12 +127,27 @@ class AuthConfig(BaseModel):
276
127
  """
277
128
  if not path:
278
129
  return False
279
-
130
+
280
131
  normalized_path = self._normalize_path(path)
281
-
132
+
282
133
  for whitelist_path in self.whitelist_paths:
283
134
  # 支持通配符匹配
284
135
  if fnmatch.fnmatch(normalized_path, whitelist_path):
285
136
  return True
286
-
137
+
287
138
  return False
139
+
140
+
141
+ def decode_jwt_token(token: str) -> Optional[Dict]:
142
+ """直接解析JWT token获取payload"""
143
+ try:
144
+ # 不验证签名,只解析payload(因为token已经通过verify_token验证过)
145
+ decoded_payload = jwt.decode(token, options={"verify_signature": False})
146
+ logger.debug(f"JWT token解析成功: {decoded_payload}")
147
+ return decoded_payload
148
+ except jwt.InvalidTokenError as e:
149
+ logger.error(f"JWT token解析失败: {str(e)}")
150
+ return None
151
+ except Exception as e:
152
+ logger.error(f"JWT token解析异常: {str(e)}")
153
+ return None