Repository navigation
fix: instantiate SeaTableAPI directly with access-token instead of temp-api-token #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -89,25 +89,20 @@ def _get_msg_sender_by_account(self, account_name): | |
| msg_sender = None | ||
| return msg_sender | ||
|
|
||
| def auth(self, with_socket_io=False): | ||
| """Auth to SeaTable | ||
| """ | ||
| def _auth(self, workspace_id, dtable_name, dtable_uuid, access_token, dtable_server_url, dtable_db_url, use_api_gateway, with_socket_io=False): | ||
| self.jwt_exp = datetime.now() + timedelta(days=3) | ||
| url = self.server_url + '/api/v2.1/dtable/app-access-token/' | ||
| headers = parse_headers(self.token) | ||
| response = requests.get(url, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
|
|
||
| self.dtable_server_url = parse_server_url(data.get('dtable_server')) | ||
| self.dtable_db_url = parse_server_url(data.get('dtable_db', '')) | ||
| self.jwt_token = data.get('access_token') | ||
| self.dtable_server_url = parse_server_url(dtable_server_url) | ||
| self.dtable_db_url = parse_server_url(dtable_db_url) | ||
|
|
||
| self.jwt_token = access_token | ||
| self.headers = parse_headers(self.jwt_token) | ||
| self.workspace_id = data.get('workspace_id') | ||
| self.dtable_uuid = data.get('dtable_uuid') | ||
| self.dtable_name = data.get('dtable_name') | ||
|
|
||
| # api gateway entry | ||
| self.use_api_gateway = data.get('use_api_gateway') | ||
| self.workspace_id = workspace_id | ||
| self.dtable_name = dtable_name | ||
| self.dtable_uuid = dtable_uuid | ||
|
|
||
| self.use_api_gateway = use_api_gateway | ||
| if self.use_api_gateway: | ||
| self.api_gateway = APIGateway( | ||
| token = self.token, | ||
|
|
@@ -116,13 +111,34 @@ def auth(self, with_socket_io=False): | |
| headers=self.headers, | ||
| dtable_uuid=self.dtable_uuid | ||
| ) | ||
| self.dtable_server_url = parse_server_url(self.server_url + '/api-gateway') | ||
|
|
||
| self.is_authed = True | ||
|
|
||
| if with_socket_io is True: | ||
| base = self._clone() | ||
| self.socketIO = SocketIO(base) | ||
| self.socketIO._connect() | ||
|
|
||
| self.is_authed = True | ||
| def auth(self, with_socket_io=False): | ||
| """Auth to SeaTable | ||
| """ | ||
| self.jwt_exp = datetime.now() + timedelta(days=3) | ||
| url = self.server_url + '/api/v2.1/dtable/app-access-token/' | ||
| headers = parse_headers(self.token) | ||
| response = requests.get(url, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
|
|
||
| self._auth( | ||
| data.get('workspace_id'), | ||
| data.get('dtable_name'), | ||
| data.get('dtable_uuid'), | ||
| data.get('access_token'), | ||
| data.get('dtable_server', ''), | ||
| data.get('dtable_db', ''), | ||
| data.get('use_api_gateway'), | ||
| with_socket_io=with_socket_io | ||
| ) | ||
|
|
||
| def _metadata_server_url(self): | ||
| return self.dtable_server_url + '/api/v1/dtables/' + self.dtable_uuid + '/metadata/' | ||
|
|
@@ -219,7 +235,7 @@ def _get_account_detail(self, account_name): | |
| params = { | ||
| 'account_name': account_name | ||
| } | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
|
|
||
| data = parse_response(response) | ||
|
|
@@ -601,7 +617,7 @@ def get_file_download_link(self, path): | |
| """ | ||
| url = self._app_download_link_url() | ||
| params = {'path': path} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data.get('download_link') | ||
|
|
@@ -612,7 +628,7 @@ def get_file_upload_link(self): | |
| :return: dict | ||
| """ | ||
| url = self._app_upload_link_url() | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data | ||
|
|
@@ -1231,7 +1247,7 @@ def get_custom_file_download_link(self, path): | |
| """ | ||
| url = self._app_custom_download_link_url() | ||
| params = {'path': path} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data.get('download_link') | ||
|
|
@@ -1240,7 +1256,7 @@ def get_custom_file_download_link(self, path): | |
| def get_custom_file_upload_link(self, path): | ||
| url = self._app_custom_upload_link_url() | ||
| params = {'path': path} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data | ||
|
|
@@ -1282,7 +1298,7 @@ def upload_local_file_to_custom_folder(self, local_path, custom_folder_path = No | |
| def get_custom_file_info(self, path, name): | ||
| url = self._app_custom_asset_file_url() | ||
| params = {'path': path, 'name': name} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| d = data['dirent'] | ||
|
|
@@ -1301,7 +1317,7 @@ def get_custom_file_info(self, path, name): | |
| def list_custom_assets(self, path): | ||
| url = self._app_custom_asset_dir_url() | ||
| params = {'path': path} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data | ||
|
|
@@ -1310,7 +1326,7 @@ def list_custom_assets(self, path): | |
| def get_user_info(self, username): | ||
| url = self._app_user_info_url() | ||
| params = {'username': username} | ||
| headers = parse_headers(self.token) | ||
| headers = self.headers | ||
| response = requests.get(url, params=params, headers=headers, timeout=self.timeout) | ||
| data = parse_response(response) | ||
| return data | ||
|
|
@@ -1351,6 +1367,13 @@ def _get_temp_api_token_url(self, workspace_id, name): | |
| 'name': name | ||
| } | ||
|
|
||
| def _get_base_access_token_url(self, workspace_id, name): | ||
| return '%(server_url)s/api/v2.1/workspace/%(workspace_id)s/dtable/%(name)s/access-token/' % { | ||
| 'server_url': self.server_url, | ||
| 'workspace_id': workspace_id, | ||
| 'name': name | ||
| } | ||
|
|
||
| @property | ||
| def token_headers(self): | ||
| return { | ||
|
|
@@ -1407,9 +1430,21 @@ def copy_base(self, src_workspace_id, base_name, dst_workspace_id): | |
| return parse_response(response).get('dtable') | ||
|
|
||
| def get_base(self, workspace_id, base_name, with_socket_io=False): | ||
| response = requests.get(self._get_temp_api_token_url(workspace_id, base_name), | ||
| headers=self.token_headers, timeout=self.timeout) | ||
| api_token = parse_response(response).get('api_token') | ||
| base = SeaTableAPI(api_token, self.server_url) | ||
| base.auth(with_socket_io=with_socket_io) | ||
| response = requests.get(self._get_base_access_token_url(workspace_id, base_name), | ||
| headers=self.token_headers, | ||
| timeout=self.timeout) | ||
| resp_json = response.json() | ||
| access_token = resp_json['access_token'] | ||
| dtable_uuid = resp_json['dtable_uuid'] | ||
| base = SeaTableAPI('', self.server_url) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] 保留资产接口可用的认证方式 影响:Account.get_base 返回的 Base 现在 token 为空,但 get_file_upload_link、get_file_download_link 等方法仍以 self.token 请求 app-* 接口。隔离 HTTP 对照中网关查询正常,上传链接及 upload_bytes_file 却发送空 Token 凭据并返回 403;旧版上传链接成功。 建议修复:将这些方法同步迁移到支持 access-token 或账号认证的资源接口,保留授权范围;不能直接把 access JWT 当 API token,也不应恢复对已弃用 temp-api-token 的依赖。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 补充当前设计及配套服务端的修复方向:Web b58180ff 的 _resolve_api_token 已接受 dtable access JWT,因此本评论中“不能直接把 access JWT 当 API token”的笼统限制不再适用。SDK 60ab602 的空凭据问题仍可复现,且同样影响 custom assets、第三方凭据和用户信息;应让这些方法使用已初始化的 access JWT headers,并保留普通 API-token Base 的兼容行为,不恢复 temp-api-token 依赖。服务端必须先上线该兼容支持。Socket.IO 的刷新来源丢失仍是另一条独立问题。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| base._auth( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] 为账号创建的 Base 保留续期能力 影响:这里仅初始化 access JWT,没有保留 Account 的刷新来源。SocketIO._on_io_disconnect 会无条件调用克隆 Base.auth(),后者仍拿空 self.token 请求 app-access-token;首次连接正常,但服务端主动断开后立即收到 403,无法重连,即使当前 JWT 尚未过期。 建议修复:保存 Account/workspace/Base 对应的 access-token 刷新提供者,让 auth() 和 Socket.IO 克隆通过它续期,并为普通 API-token Base 保留原行为;补充主动断开重连回归。 |
||
| workspace_id, | ||
| base_name, | ||
| dtable_uuid, | ||
| access_token, | ||
| '', | ||
| '', | ||
| True, | ||
| with_socket_io=with_socket_io | ||
| ) | ||
| return base | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Warning] 保留统一响应解析的异常契约
影响:access-token 接口返回 401/403、Base 不存在、限流或服务错误时,直接解析并索引字段会把原 ConnectionError 变成 KeyError('access_token'),HTML 网关错误则变成 JSONDecodeError。403 Token expired. 也不再抛出 AuthExpiredError,已有调用方的重新登录和错误处理分支因此无法正常接管;精确 base/head 的回环 HTTP 对照已复现。
建议修复:改用现有 parse_response(response),成功后再读取字段,并补充过期、无权限、不存在和非 JSON 错误响应的回归。