From 1b4eba3e5bc7d1854e2e23e3921743e771c829f0 Mon Sep 17 00:00:00 2001 From: LadderOperator Date: Sat, 14 Mar 2026 15:01:57 +0800 Subject: [PATCH] feat: support Seafile 11.0 Bearer auth and account token generated from web UI - Auto-switch Authorization header: 'Bearer' for 11.0+, 'Token' for legacy - Add SeafileAPI.from_auth_token() for web-generated auth tokens --- .gitignore | 3 +++ README.md | 15 +++++++++++- demo/repo_demo.py | 43 +++++++++++++++++++++++++--------- seafileapi/main.py | 57 +++++++++++++++++++++++++++++++++++----------- 4 files changed, 93 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 5367bbe..0746356 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ target/ .idea/ + +# test configuration +demo/test.ini diff --git a/README.md b/README.md index 1af584b..ac657cc 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Two basic objects are used in Seafile Python SDK. You can initialize a SeafileAPI object with user's login name and password. After authentication, you can get library objects for specific libraries to manipulate on libraries. +The SeafileAPI object can also be initialized with `SeafileAPI.from_auth_token(auth_token, server_url)` if you already have a valid auth token generated from Web UI. + #### Repo A repo object represents a library in Seafile. You can use it to manipulate files and folders inside a library. @@ -37,18 +39,29 @@ seafile_api = SeafileAPI(login_name, pwd, server_url) seafile_api.auth() ``` +You can also get an authenticated SeafileAPI object by using an existing auth token. + +``` +from seafileapi import SeafileAPI +server_url = "https://cloud.seafile.com/" +auth_token = "your_auth_token" +seafile_api = SeafileAPI.from_auth_token(auth_token, server_url) +``` + ## Repo authentication There are two ways to get an authenticated repo object: -#### By username and password +#### By username and password (or auth token) ```python from seafileapi import SeafileAPI server_url = "https://cloud.seafile.com/" login_name = "example@examle.com" pwd = "password" +#auth_token = "your_auth_token" seafile_api = SeafileAPI(login_name, pwd, server_url) +# using SeafileAPI.from_auth_token(auth_token, server_url) is also supported seafile_api.auth() repo_id = "xxxxxxxxxxxx" repo = seafile_api.get_repo(repo_id) # return object diff --git a/demo/repo_demo.py b/demo/repo_demo.py index 5969688..5367c23 100644 --- a/demo/repo_demo.py +++ b/demo/repo_demo.py @@ -1,22 +1,43 @@ -from seafileapi import SeafileAPI, Repo +import sys +sys.path.append('..') +from seafileapi import SeafileAPI, Repo +from configparser import ConfigParser -server_url = "http://127.0.0.1:8000/" -login_name = "example@examle.com" -pwd = "password" +config = ConfigParser() +config.read('test.ini') -api_token = '6de40d8456b06bdb4c9eabbf658175bdc4084050' +server_url = config.get('account', 'server_url') +login_name = config.get('account', 'login_name') +pwd = config.get('account', 'password') +account_token = config.get('account', 'account_token') +test_uuid = config.get('account', 'test_repo_uuid') +repo_token = config.get('repo', 'repo_token') -seafile_api = SeafileAPI(login_name, pwd, server_url) -seafile_api.auth() +# Auth with password +print('Auth with password...') +seafile_api_pwd = SeafileAPI(login_name, pwd, server_url) +seafile_api_pwd.auth() +user_repo_pwd = seafile_api_pwd.get_repo(test_uuid) +print(user_repo_pwd.list_dir()) -user_repo = seafile_api.get_repo('2ce3ec78-d347-412c-b157-fce3c0d30ebb') +# Auth with account_token +print('Auth with account_token...') +seafile_api_token = SeafileAPI.from_auth_token(account_token, server_url) +seafile_api_token.auth() +user_repo_token = seafile_api_token.get_repo(test_uuid) +print(user_repo_token.list_dir()) -api_repo = Repo(api_token, server_url) +# Using repo_token +print('Using repo_token...') +api_repo = Repo(repo_token, server_url) api_repo.auth() +print(api_repo.list_dir()) + + '''seafileAPI''' # print(seafile_api.list_repos()) @@ -46,8 +67,8 @@ # print(user_repo.create_file("")) # print(api_repo.create_file("q")) -print(user_repo.rename_file('/d','d2')) -print(api_repo.rename_file('','f2')) +# print(user_repo.rename_file('/d','d2')) +# print(api_repo.rename_file('','f2')) # print(user_repo.delete_file('')) # print(api_repo.delete_file('/')) \ No newline at end of file diff --git a/seafileapi/main.py b/seafileapi/main.py index 3210e70..6b3bd8a 100644 --- a/seafileapi/main.py +++ b/seafileapi/main.py @@ -6,9 +6,10 @@ from seafileapi.utils import urljoin -def parse_headers(token): +def parse_headers(token, bearer=False): + AUTH_WORD = 'Bearer ' if bearer else 'Token ' return { - 'Authorization': 'Token ' + token, + 'Authorization': AUTH_WORD + token, 'Content-Type': 'application/json', } @@ -33,13 +34,23 @@ def __init__(self, token, server_url): self.repo_id = None self.timeout = 30 self.headers = None + self.version = None self._by_api_token = True def auth(self, by_api_token=True): if not by_api_token: self._by_api_token = False - self.headers = parse_headers(self.token) + self._get_server_version() + using_bearer = int(self.version.split('.')[0]) >= 11 # From version 11.0 + self.headers = parse_headers(self.token, using_bearer) + + def _get_server_version(self): + url = urljoin(self.server_url, '/api2/server-info/') + res = requests.get(url, timeout=self.timeout) + if res.status_code != 200: + raise ClientHttpError(res.status_code, res.content) + self.version = res.json()['version'] def _repo_info_url(self): if self._by_api_token: @@ -202,22 +213,42 @@ def __init__(self, login_name, password, server_url): self.server_url = server_url.strip().strip('/') self.token = None self.timeout = 30 + self.version = None self.headers = None + @classmethod + def from_auth_token(cls, auth_token, server_url): + # allow auth token generated via web interface + instance = cls(None, None, server_url) + instance.token = auth_token + + return instance + def auth(self): - data = { - 'username': self.login_name, - 'password': self.password, - } - url = "%s/%s" % (self.server_url.rstrip('/'), 'api2/auth-token/') - res = requests.post(url, data=data, timeout=self.timeout) + + if self.password: + data = { + 'username': self.login_name, + 'password': self.password, + } + url = "%s/%s" % (self.server_url.rstrip('/'), 'api2/auth-token/') + res = requests.post(url, data=data, timeout=self.timeout) + if res.status_code != 200: + raise ClientHttpError(res.status_code, res.content) + token = res.json()['token'] + assert len(token) == 40, 'The length of seahub api auth token should be 40' + self.token = token + self._get_server_version() + using_bearer = int(self.version.split('.')[0]) >= 11 # From version 11.0 + self.headers = parse_headers(self.token, using_bearer) + + def _get_server_version(self): + url = urljoin(self.server_url, '/api2/server-info/') + res = requests.get(url, timeout=self.timeout) if res.status_code != 200: raise ClientHttpError(res.status_code, res.content) - token = res.json()['token'] - assert len(token) == 40, 'The length of seahub api auth token should be 40' - self.token = token - self.headers = parse_headers(token) + self.version = res.json()['version'] def _repo_obj(self, repo_id): repo = Repo(self.token, self.server_url)