skyplatform-iam 1.0.3__py3-none-any.whl → 1.0.5__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.
- skyplatform_iam/__init__.py +68 -17
- skyplatform_iam/api.py +366 -0
- skyplatform_iam/config.py +173 -18
- skyplatform_iam/connect_agenterra_iam.py +209 -0
- skyplatform_iam/global_manager.py +272 -0
- skyplatform_iam/middleware.py +138 -31
- skyplatform_iam-1.0.5.dist-info/METADATA +461 -0
- skyplatform_iam-1.0.5.dist-info/RECORD +10 -0
- skyplatform_iam-1.0.3.dist-info/METADATA +0 -261
- skyplatform_iam-1.0.3.dist-info/RECORD +0 -8
- {skyplatform_iam-1.0.3.dist-info → skyplatform_iam-1.0.5.dist-info}/WHEEL +0 -0
skyplatform_iam/config.py
CHANGED
|
@@ -3,18 +3,21 @@ SkyPlatform IAM SDK 配置模块
|
|
|
3
3
|
"""
|
|
4
4
|
import os
|
|
5
5
|
import fnmatch
|
|
6
|
-
|
|
7
|
-
from
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional, List, Dict, Any
|
|
8
|
+
from pydantic import BaseModel, Field, validator
|
|
8
9
|
from dotenv import load_dotenv
|
|
9
10
|
|
|
10
11
|
# 加载环境变量
|
|
11
12
|
load_dotenv()
|
|
12
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class AuthConfig(BaseModel):
|
|
15
18
|
"""
|
|
16
19
|
认证配置类
|
|
17
|
-
|
|
20
|
+
支持环境变量和代码配置,增强配置验证和管理功能
|
|
18
21
|
"""
|
|
19
22
|
# IAM服务配置
|
|
20
23
|
agenterra_iam_host: str
|
|
@@ -30,32 +33,184 @@ class AuthConfig(BaseModel):
|
|
|
30
33
|
|
|
31
34
|
# 白名单路径配置(实例变量)
|
|
32
35
|
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分钟
|
|
33
44
|
|
|
34
45
|
class Config:
|
|
35
|
-
env_prefix = "
|
|
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
|
|
36
94
|
|
|
37
95
|
@classmethod
|
|
38
|
-
def from_env(cls) -> "AuthConfig":
|
|
96
|
+
def from_env(cls, prefix: str = "SKYPLATFORM_") -> "AuthConfig":
|
|
39
97
|
"""
|
|
40
98
|
从环境变量创建配置
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
prefix: 环境变量前缀,默认为SKYPLATFORM_
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
AuthConfig: 配置实例
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
ValueError: 配置验证失败
|
|
41
108
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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'))
|
|
48
136
|
)
|
|
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
|
+
|
|
144
|
+
def validate_config(self) -> None:
|
|
145
|
+
"""
|
|
146
|
+
验证配置完整性
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: 配置验证失败
|
|
150
|
+
"""
|
|
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("配置验证通过")
|
|
49
162
|
|
|
50
|
-
def
|
|
163
|
+
def merge_config(self, other: "AuthConfig") -> "AuthConfig":
|
|
164
|
+
"""
|
|
165
|
+
合并配置,other的非空值会覆盖当前配置
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
other: 要合并的配置
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
AuthConfig: 合并后的新配置实例
|
|
51
172
|
"""
|
|
52
|
-
|
|
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: 更新后的配置副本
|
|
53
210
|
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raise ValueError(f"配置项 {field} 不能为空")
|
|
58
|
-
return True
|
|
211
|
+
config_dict = self.dict()
|
|
212
|
+
config_dict.update(updates)
|
|
213
|
+
return AuthConfig(**config_dict)
|
|
59
214
|
|
|
60
215
|
def _normalize_path(self, path: str) -> str:
|
|
61
216
|
"""
|
|
@@ -83,6 +83,53 @@ class ConnectAgenterraIam(object):
|
|
|
83
83
|
# 标记为已初始化
|
|
84
84
|
self._initialized = True
|
|
85
85
|
|
|
86
|
+
@classmethod
|
|
87
|
+
def get_instance(cls):
|
|
88
|
+
"""
|
|
89
|
+
获取已初始化的ConnectAgenterraIam单例实例
|
|
90
|
+
|
|
91
|
+
如果实例尚未初始化,会抛出异常提示用户先进行配置
|
|
92
|
+
这个方法通常在用户已经通过setup_auth()进行配置后使用
|
|
93
|
+
|
|
94
|
+
返回:
|
|
95
|
+
- ConnectAgenterraIam: 已初始化的单例实例
|
|
96
|
+
|
|
97
|
+
异常:
|
|
98
|
+
- RuntimeError: 当实例未初始化时抛出
|
|
99
|
+
"""
|
|
100
|
+
if cls._instance is None or not cls._initialized:
|
|
101
|
+
raise RuntimeError(
|
|
102
|
+
"ConnectAgenterraIam实例尚未初始化。请先使用以下方式之一进行配置:\n"
|
|
103
|
+
"1. 使用setup_auth()进行一键配置\n"
|
|
104
|
+
"2. 手动创建ConnectAgenterraIam实例:ConnectAgenterraIam(config=your_config)"
|
|
105
|
+
)
|
|
106
|
+
return cls._instance
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def get_instance_lazy(cls):
|
|
110
|
+
"""
|
|
111
|
+
获取已初始化的单例实例(延迟模式)
|
|
112
|
+
|
|
113
|
+
此方法支持延迟初始化,当实例未初始化时返回 None 而不是抛出异常。
|
|
114
|
+
适用于在模块导入时需要获取实例但可能尚未初始化的场景。
|
|
115
|
+
|
|
116
|
+
返回:
|
|
117
|
+
- ConnectAgenterraIam: 已初始化的单例实例,如果未初始化则返回 None
|
|
118
|
+
"""
|
|
119
|
+
if cls._instance is None or not cls._initialized:
|
|
120
|
+
return None
|
|
121
|
+
return cls._instance
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def is_initialized(cls):
|
|
125
|
+
"""
|
|
126
|
+
检查单例实例是否已初始化
|
|
127
|
+
|
|
128
|
+
返回:
|
|
129
|
+
- bool: 如果实例已初始化返回 True,否则返回 False
|
|
130
|
+
"""
|
|
131
|
+
return cls._instance is not None and cls._initialized
|
|
132
|
+
|
|
86
133
|
def reload_config(self, config):
|
|
87
134
|
"""
|
|
88
135
|
重新加载配置
|
|
@@ -760,6 +807,168 @@ class ConnectAgenterraIam(object):
|
|
|
760
807
|
self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
|
|
761
808
|
return False
|
|
762
809
|
|
|
810
|
+
def add_custom_config(self, user_id, config_name, config_value=None):
|
|
811
|
+
"""
|
|
812
|
+
机机接口:添加用户自定义配置
|
|
813
|
+
|
|
814
|
+
为指定用户添加或更新自定义属性配置。
|
|
815
|
+
|
|
816
|
+
参数:
|
|
817
|
+
- user_id: 用户ID
|
|
818
|
+
- config_name: 配置项名称
|
|
819
|
+
- config_value: 配置项值(可选)
|
|
820
|
+
|
|
821
|
+
返回:
|
|
822
|
+
- 成功: 返回响应对象
|
|
823
|
+
- 失败: 返回False
|
|
824
|
+
"""
|
|
825
|
+
method_name = "add_custom_config"
|
|
826
|
+
self.logger.info(f"[{method_name}] 开始添加用户自定义配置 - user_id: {user_id}, config_name: {config_name}")
|
|
827
|
+
|
|
828
|
+
try:
|
|
829
|
+
body = {
|
|
830
|
+
"server_name": self.server_name,
|
|
831
|
+
"access_key": self.access_key,
|
|
832
|
+
"user_id": user_id,
|
|
833
|
+
"config_name": config_name
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
# 添加可选参数
|
|
837
|
+
if config_value is not None:
|
|
838
|
+
body["config_value"] = config_value
|
|
839
|
+
|
|
840
|
+
uri = "/api/v2/service/add_custom_config"
|
|
841
|
+
url = self.agenterra_iam_host + uri
|
|
842
|
+
|
|
843
|
+
# 记录请求信息
|
|
844
|
+
self._log_request(method_name, url, self.headers, body)
|
|
845
|
+
|
|
846
|
+
response = requests.post(
|
|
847
|
+
url=url,
|
|
848
|
+
headers=self.headers,
|
|
849
|
+
json=body,
|
|
850
|
+
verify=False
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
# 记录响应信息
|
|
854
|
+
self._log_response(method_name, response)
|
|
855
|
+
|
|
856
|
+
if response.status_code == 200:
|
|
857
|
+
self.logger.info(f"[{method_name}] 添加用户自定义配置成功")
|
|
858
|
+
return response
|
|
859
|
+
else:
|
|
860
|
+
self.logger.warning(f"[{method_name}] 添加用户自定义配置失败 - 状态码: {response.status_code}")
|
|
861
|
+
|
|
862
|
+
return False
|
|
863
|
+
except Exception as e:
|
|
864
|
+
self.logger.error(f"[{method_name}] 添加用户自定义配置请求异常: {str(e)}")
|
|
865
|
+
self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
|
|
866
|
+
return False
|
|
867
|
+
|
|
868
|
+
def get_custom_configs(self, user_id):
|
|
869
|
+
"""
|
|
870
|
+
机机接口:获取用户自定义配置
|
|
871
|
+
|
|
872
|
+
获取指定用户的所有自定义属性配置。
|
|
873
|
+
|
|
874
|
+
参数:
|
|
875
|
+
- user_id: 用户ID
|
|
876
|
+
|
|
877
|
+
返回:
|
|
878
|
+
- 成功: 返回响应对象
|
|
879
|
+
- 失败: 返回False
|
|
880
|
+
"""
|
|
881
|
+
method_name = "get_custom_configs"
|
|
882
|
+
self.logger.info(f"[{method_name}] 开始获取用户自定义配置 - user_id: {user_id}")
|
|
883
|
+
|
|
884
|
+
try:
|
|
885
|
+
body = {
|
|
886
|
+
"server_name": self.server_name,
|
|
887
|
+
"access_key": self.access_key,
|
|
888
|
+
"user_id": user_id
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
uri = "/api/v2/service/get_custom_configs"
|
|
892
|
+
url = self.agenterra_iam_host + uri
|
|
893
|
+
|
|
894
|
+
# 记录请求信息
|
|
895
|
+
self._log_request(method_name, url, self.headers, body)
|
|
896
|
+
|
|
897
|
+
response = requests.post(
|
|
898
|
+
url=url,
|
|
899
|
+
headers=self.headers,
|
|
900
|
+
json=body,
|
|
901
|
+
verify=False
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# 记录响应信息
|
|
905
|
+
self._log_response(method_name, response)
|
|
906
|
+
|
|
907
|
+
if response.status_code == 200:
|
|
908
|
+
self.logger.info(f"[{method_name}] 获取用户自定义配置成功")
|
|
909
|
+
return response
|
|
910
|
+
else:
|
|
911
|
+
self.logger.warning(f"[{method_name}] 获取用户自定义配置失败 - 状态码: {response.status_code}")
|
|
912
|
+
|
|
913
|
+
return False
|
|
914
|
+
except Exception as e:
|
|
915
|
+
self.logger.error(f"[{method_name}] 获取用户自定义配置请求异常: {str(e)}")
|
|
916
|
+
self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
|
|
917
|
+
return False
|
|
918
|
+
|
|
919
|
+
def delete_custom_config(self, user_id, config_name):
|
|
920
|
+
"""
|
|
921
|
+
机机接口:删除用户自定义配置
|
|
922
|
+
|
|
923
|
+
删除指定用户的指定自定义属性配置。
|
|
924
|
+
|
|
925
|
+
参数:
|
|
926
|
+
- user_id: 用户ID
|
|
927
|
+
- config_name: 配置项名称
|
|
928
|
+
|
|
929
|
+
返回:
|
|
930
|
+
- 成功: 返回响应对象
|
|
931
|
+
- 失败: 返回False
|
|
932
|
+
"""
|
|
933
|
+
method_name = "delete_custom_config"
|
|
934
|
+
self.logger.info(f"[{method_name}] 开始删除用户自定义配置 - user_id: {user_id}, config_name: {config_name}")
|
|
935
|
+
|
|
936
|
+
try:
|
|
937
|
+
body = {
|
|
938
|
+
"server_name": self.server_name,
|
|
939
|
+
"access_key": self.access_key,
|
|
940
|
+
"user_id": user_id,
|
|
941
|
+
"config_name": config_name
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
uri = "/api/v2/service/delete_custom_config"
|
|
945
|
+
url = self.agenterra_iam_host + uri
|
|
946
|
+
|
|
947
|
+
# 记录请求信息
|
|
948
|
+
self._log_request(method_name, url, self.headers, body)
|
|
949
|
+
|
|
950
|
+
response = requests.post(
|
|
951
|
+
url=url,
|
|
952
|
+
headers=self.headers,
|
|
953
|
+
json=body,
|
|
954
|
+
verify=False
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
# 记录响应信息
|
|
958
|
+
self._log_response(method_name, response)
|
|
959
|
+
|
|
960
|
+
if response.status_code == 200:
|
|
961
|
+
self.logger.info(f"[{method_name}] 删除用户自定义配置成功")
|
|
962
|
+
return response
|
|
963
|
+
else:
|
|
964
|
+
self.logger.warning(f"[{method_name}] 删除用户自定义配置失败 - 状态码: {response.status_code}")
|
|
965
|
+
|
|
966
|
+
return False
|
|
967
|
+
except Exception as e:
|
|
968
|
+
self.logger.error(f"[{method_name}] 删除用户自定义配置请求异常: {str(e)}")
|
|
969
|
+
self.logger.error(f"[{method_name}] 异常堆栈: {traceback.format_exc()}")
|
|
970
|
+
return False
|
|
971
|
+
|
|
763
972
|
def merge_credential(self, target_user_id, cred_type, cred_value, merge_reason=None):
|
|
764
973
|
"""
|
|
765
974
|
机机接口:凭证合并
|