Skip to content

Commit 89d686e

Browse files
bobokungithub-actions[bot]dependabot[bot]actions-user
authored
4.6.2 (#950)
# Improvements - Adds better validation for security passwords # Bug Fixes - Conditionally skip permission validation and setting on Windows systems - Improve cross-platform compatibility for authentication settings handling - Fixes bug where users cannot set up initial security through the webUI **Full Changelog**: v4.6.1...v4.6.2 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Actionbot <actions@github.com>
1 parent 28f1a3b commit 89d686e

File tree

13 files changed

+91
-71
lines changed

13 files changed

+91
-71
lines changed

CHANGELOG

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
# Requirements Updated
2-
- "argon2-cffi==25.1.0"
3-
- "slowapi==0.1.9"
4-
- "ruff==0.12.12"
5-
6-
# New Features
7-
- Adds authentication support for the webUI and webAPI (Fixes #867)
8-
91
# Improvements
10-
- Enhanced `--web-server` option to support disabling with `--web-server=False` while maintaining backward compatibility
11-
- The `schedule.yml` is now renamed to `qbm_settings.yml` in order to support security features (Automatic migration)
12-
- Makes hyperlinks clickable in the webUI (Fixes #938)
2+
- Adds better validation for security passwords
133

144
# Bug Fixes
15-
- Better support for windows paths when using remote_dir
16-
- Fix `QBT_CONFIG_DIR` not supporting folders with subdirectories (Fixes #934)
17-
- Fixes webUI not being packaged with PyPi builds
18-
- Fix bug in remove_orphaned where it's not able to start a new thread in concurrent runs
5+
- Conditionally skip permission validation and setting on Windows systems
6+
- Improve cross-platform compatibility for authentication settings handling
7+
- Fixes bug where users cannot set up initial security through the webUI
198

20-
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.6.0...v4.6.1
9+
**Full Changelog**: https://github.com/StuffAnThings/qbit_manage/compare/v4.6.1...v4.6.2

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ prep-release:
300300
patch=$$(echo "$$new_version" | cut -d. -f3); \
301301
prev_patch=$$((patch - 1)); \
302302
prev_version="$$major.$$minor.$$prev_patch"; \
303+
git fetch origin master:master \
303304
updated_deps=$$(git diff master..HEAD -- pyproject.toml | grep '^+' | grep '==' | sed 's/^+//' | sed 's/^ *//' | sed 's/,$$//' | sed 's/^/- /'); \
304305
echo "# Requirements Updated" > CHANGELOG; \
305306
if [ -n "$$updated_deps" ]; then \

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.6.1
1+
4.6.2

desktop/tauri/src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/tauri/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ license = "MIT"
4343
name = "qbit-manage-desktop"
4444
repository = ""
4545
rust-version = "1.70"
46-
version = "4.6.1"
46+
version = "4.6.2"
4747

4848
[target."cfg(unix)".dependencies]
4949
glib = "0.20.0"

desktop/tauri/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,5 @@
6868
},
6969
"identifier": "com.qbitmanage.desktop",
7070
"productName": "qBit Manage",
71-
"version": "4.6.1"
71+
"version": "4.6.2"
7272
}

docs/Commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
| `-ru` or `--rem-unregistered` | QBT_REM_UNREGISTERED | rem_unregistered | Use this if you would like to remove unregistered torrents. (It will the delete data & torrent if it is not being cross-seeded, otherwise it will just remove the torrent without deleting data). Trackers that have an error and not covered by the remove unregistered logic will also be tagged as `issue` for manual review. | False |
1818
| `-tte` or `--tag-tracker-error` | QBT_TAG_TRACKER_ERROR | tag_tracker_error | Use this if you would like to tag torrents that do not have a working tracker. | False |
1919
| `-ro` or `--rem-orphaned` | QBT_REM_ORPHANED | rem_orphaned | Use this if you would like to remove orphaned files from your `root_dir` directory that are not referenced by any torrents. It will scan your `root_dir` directory and compare it with what is in qBittorrent. Any data not referenced in qBittorrent will be moved into `/data/torrents/orphaned_data` folder for you to review/delete. | False |
20-
| `-tnhl` or `--tag-nohardlinks` | QBT_TAG_NOHARDLINKS | tag_nohardlinks | Use this to tag any torrents that do not have any hard links associated with any of the files. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. | False |
20+
| `-tnhl` or `--tag-nohardlinks` | QBT_TAG_NOHARDLINKS | tag_nohardlinks | Use this to tag any torrents where the torrent's largest file does not have any hardlinks associated with any of the files outside of your Qbit_Manage root directory. This is useful for those that use Sonarr/Radarr that hard links your media files with the torrents for seeding. When files get upgraded they no longer become linked with your media therefore will be tagged with a new tag noHL. You can then safely delete/remove these torrents to free up any extra space that is not being used by your media folder. Refer to the qbm hardlinks functionality documentation for details. | False |
2121
| `-sl` or `--share-limits` | QBT_SHARE_LIMITS | share_limits | Control how torrent share limits are set depending on the priority of your grouping. Each torrent will be matched with the share limit group with the highest priority that meets the group filter criteria. Each torrent can only be matched with one share limit group. | False |
2222
| `-sc` or `--skip-cleanup` | QBT_SKIP_CLEANUP | skip_cleanup | Use this to skip emptying the Recycle Bin folder (`/root_dir/.RecycleBin`) and Orphaned directory. (`/root_dir/orphaned_data`) | False |
2323
| `-dr` or `--dry-run` | QBT_DRY_RUN | dry_run | If you would like to see what is gonna happen but not actually move/delete or tag/categorize anything. | False |

docs/Config-Setup.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ This section defines the tags used based upon the tracker's URL.
144144
| `cat` | Set the category based on tracker URL. This category option takes priority over the category defined in [cat](#cat) | None | <center>❌</center> |
145145
| `notifiarr` | Set this to the notifiarr react name. This is used to add indexer reactions to the notifications sent by Notifiarr | None | <center>❌</center> |
146146

147-
If you are unsure what key word to use. Simply select a torrent within qB and down at the bottom you should see a tab that says `Trackers` within the list that is populated there are ea list of trackers that are associated with this torrent, select a keyword from there and add it to the config file. Make sure this key word is unique enough that the script will not get confused with any other tracker.
147+
If you are unsure what key word to use. Simply select a torrent within qB and down at the bottom you should see a tab that says `Trackers` within the list that is populated there are ea list of trackers that are associated with this torrent, select a keyword from there and add it to the config file. Make sure this keyword is unique enough that the script will not get confused with any other tracker.
148148

149149
>[!TIP]
150150
> The `other` key is a special keyword and if defined will tag any other trackers that don't match the above trackers into this tag.
@@ -168,6 +168,9 @@ If you're needing information regarding hardlinks here are some excellent resour
168168
> Mandatory to fill out [directory parameter](#directory) above to use this function (root_dir/remote_dir)
169169
Beyond this you'll need to use one of the [categories](#cat) above as the key.
170170

171+
This functionality will tag any torrent's whose file (or largest file if multi-file) does not have any hardlinks outside the qbm root_dir.
172+
Note that `ignore_root_dir` (Default: True) will ignore any hardlinks detected in the same root_dir.
173+
171174
| Configuration | Definition | Required |
172175
| :------------ | :-------------------------------------------------------- | :----------------- |
173176
| `key` | Category name to check for nohardlinked torrents in qbit. | <center>✅</center> |

modules/auth.py

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import hashlib
55
import ipaddress
66
import os
7+
import platform
78
import re
89
import secrets
910
from collections import defaultdict
@@ -338,22 +339,25 @@ def _load_auth_settings(self) -> AuthSettings:
338339

339340
try:
340341
if self.settings_path.exists():
341-
# Check file permissions for security
342-
try:
343-
stat_info = self.settings_path.stat()
344-
# Check if file is readable/writable by group or others (should be 600 or similar)
345-
if stat_info.st_mode & 0o077: # Check if group/other has any permissions
346-
logger.warning(
347-
f"Settings file {self.settings_path} has overly permissive permissions. "
348-
f"Fixing to 600 (owner read/write only)."
349-
)
350-
try:
351-
os.chmod(str(self.settings_path), 0o600)
352-
logger.info(f"Fixed permissions for settings file {self.settings_path} to 600.")
353-
except OSError as e:
354-
logger.error(f"Could not fix permissions for settings file: {e}")
355-
except (OSError, AttributeError) as e:
356-
logger.warning(f"Could not check permissions for settings file: {e}")
342+
# Check file permissions for security (skip on Windows as it uses different permission model)
343+
if platform.system() != "Windows":
344+
try:
345+
stat_info = self.settings_path.stat()
346+
# Check if file is readable/writable by group or others (should be 600 or similar)
347+
if stat_info.st_mode & 0o077: # Check if group/other has any permissions
348+
logger.warning(
349+
f"Settings file {self.settings_path} has overly permissive permissions. "
350+
f"Fixing to 600 (owner read/write only)."
351+
)
352+
try:
353+
os.chmod(str(self.settings_path), 0o600)
354+
logger.info(f"Fixed permissions for settings file {self.settings_path} to 600.")
355+
except OSError as e:
356+
logger.error(f"Could not fix permissions for settings file: {e}")
357+
except (OSError, AttributeError) as e:
358+
logger.warning(f"Could not check permissions for settings file: {e}")
359+
else:
360+
logger.debug("Skipping file permission check on Windows (uses different permission model)")
357361

358362
with open(self.settings_path, encoding="utf-8") as f:
359363
yaml_loader = ruamel.yaml.YAML(typ="safe")
@@ -574,11 +578,14 @@ def save_auth_settings(settings_path: Path, settings: AuthSettings) -> bool:
574578
with open(settings_path, "w", encoding="utf-8") as f:
575579
yaml.dump(data, f)
576580

577-
# Set restrictive permissions (600 - owner read/write only)
578-
try:
579-
os.chmod(str(settings_path), 0o600)
580-
except OSError as e:
581-
logger.error(f"Could not set permissions for settings file: {e}")
581+
# Set restrictive permissions (600 - owner read/write only) - skip on Windows
582+
if platform.system() != "Windows":
583+
try:
584+
os.chmod(str(settings_path), 0o600)
585+
except OSError as e:
586+
logger.error(f"Could not set permissions for settings file: {e}")
587+
else:
588+
logger.debug("Skipping file permission setting on Windows (uses different permission model)")
582589

583590
return True
584591
except Exception as e:

modules/web_api.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,17 +1725,26 @@ async def update_security_settings(self, request: SecuritySettingsRequest, req:
17251725
f"has_username={bool(current_settings.username)}"
17261726
)
17271727

1728+
# Check if this is initial setup (no authentication currently configured)
1729+
is_initial_setup = not current_settings.enabled or (
1730+
current_settings.method == "none" and not current_settings.username and not current_settings.api_key
1731+
)
1732+
17281733
# Check if client is local and bypass_auth_for_local is enabled
17291734
if current_settings.bypass_auth_for_local and is_local_ip(req, current_settings.trusted_proxies):
17301735
logger.trace("Local client with bypass_auth_for_local enabled, skipping credential verification")
17311736
auth_verified = True
1737+
elif is_initial_setup:
1738+
# Allow initial setup without credentials when no authentication is configured
1739+
logger.trace("Initial authentication setup detected, skipping credential verification")
1740+
auth_verified = True
17321741
else:
17331742
# Verify current credentials for reauthentication
17341743
auth_verified = False
17351744

17361745
# First, try credentials provided in the request body
17371746
# Try API key verification first
1738-
if request.current_api_key and current_settings.api_key:
1747+
if not auth_verified and request.current_api_key and current_settings.api_key:
17391748
logger.trace("Attempting API key verification from request body")
17401749
if verify_api_key(request.current_api_key, current_settings.api_key):
17411750
auth_verified = True
@@ -1786,8 +1795,8 @@ async def update_security_settings(self, request: SecuritySettingsRequest, req:
17861795
except Exception as e:
17871796
logger.warning(f"Error parsing Basic auth header: {e}")
17881797

1789-
# If neither method worked, require authentication
1790-
if not auth_verified:
1798+
# If neither method worked and it's not initial setup, require authentication
1799+
if not auth_verified and not is_initial_setup:
17911800
logger.warning("No valid current credentials provided for security settings update")
17921801
return {"success": False, "message": "Current credentials required to update security settings"}
17931802

0 commit comments

Comments
 (0)