Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ The use of environment variables allow you to provide overrides to default setti
| `APPRISE_CONFIG_LOCK` | Locks down your API hosting so that you can no longer delete/update/access stateful information. Your configuration is still referenced when stateful calls are made to `/notify`. The idea of this switch is to allow someone to set their (Apprise) configuration up and then as an added security tactic, they may choose to lock their configuration down (in a read-only state). Those who use the Apprise CLI tool may still do it, however the `--config` (`-c`) switch will not successfully reference this access point anymore. You can however use the `apprise://` plugin without any problem ([see here for more details](https://appriseit.com/services/apprise_api/)). This defaults to `no` and can however be set to `yes` by simply defining the global variable as such.
| `APPRISE_ADMIN` | Enables admin mode. This removes the distinction between users and admins and allows listing stored configuration keys (when `STATEFUL_MODE` is set to `simple`). This defaults to `no` and can be set to `yes`.
| `APPRISE_INTERPRET_EMOJIS` | Override the Apprise `interpret-emojis` setting. This defaults to `none` (not set), but can be enforced to `no` or `yes`.
| `APPRISE_HTTP_REDIRECTS` | By default, Apprise follows HTTP 3xx redirects, matching the behaviour of the underlying requests library. Set to `no` to disable redirect following globally across all plugins without having to add `redirect=no` to every individual URL. Individual URLs can always override this with `?redirect=yes` or `?redirect=no` regardless of this setting. This defaults to `yes`.
| `APPRISE_DENY_SERVICES` | A comma separated set of entries identifying what plugins to deny access to. You only need to identify one schema entry associated with a plugin to in turn disable all of it. Hence, if you wanted to disable the `glib` plugin, you do not need to additionally include `qt` as well since it's included as part of the (`dbus`) package; consequently specifying `qt` would in turn disable the `glib` module as well (another way to accomplish the same task). To exclude/disable more the one upstream service, simply specify additional entries separated by a `,` (comma) or ` ` (space). The `APPRISE_DENY_SERVICES` entries are ignored if the `APPRISE_ALLOW_SERVICES` is identified. By default, this is initialized to `windows, dbus, gnome, macosx, syslog` (blocking local actions from being issued inside of the docker container)
| `APPRISE_ALLOW_SERVICES` | A comma separated set of entries identifying what plugins to allow access to. You may only use alpha-numeric characters as is the restriction of Apprise Schemas (schema://) anyway. To exclusively include more the one upstream service, simply specify additional entries separated by a `,` (comma) or ` ` (space). The `APPRISE_DENY_SERVICES` entries are ignored if the `APPRISE_ALLOW_SERVICES` is identified.
| `APPRISE_API_ONLY` | This option will disable the entire access to web administration interface and only the API will be available for use. By default, this option will be set to `no`, or you can omit this variable if you wish.
Expand Down
7 changes: 7 additions & 0 deletions apprise_api/api/tests/test_json_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def test_priority_tag_detail_shape(self):
plain = tag_detail(_Tag("family"))
prioritized = tag_detail(_Tag("family", priority=1, has_priority=True))
prioritized_string = tag_detail("2:email")
plain_string = tag_detail("alerts")

assert plain == {
"name": "family",
Expand All @@ -201,6 +202,12 @@ def test_priority_tag_detail_shape(self):
"token": "2:email",
"exact": "2:email",
}
assert plain_string == {
"name": "alerts",
"priority": 0,
"token": "alerts",
"exact": "0:alerts",
}
assert tag_names(["2:email", _Tag("family")]) == {"email", "family"}

def test_service_retry_falls_back_to_url_parameter(self):
Expand Down
119 changes: 119 additions & 0 deletions apprise_api/api/tests/test_redirects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Copyright (C) 2026 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import json
from unittest import mock

import apprise
from django.test import SimpleTestCase, override_settings

from ..forms import NotifyForm

# Grant access to our Notification Manager Singleton
N_MGR = apprise.manager_plugins.NotificationManager()


class NotifyWithRedirectTests(SimpleTestCase):
"""
Test that APPRISE_HTTP_REDIRECTS is wired correctly to AppriseAsset.

The apprise package installed in the test environment may pre-date the
http_redirects attribute, so these tests mock AppriseAsset directly to
verify that views.py passes the correct value rather than running the
full notification stack.
"""

def _make_asset_spy(self):
"""Return a mock that patches AppriseAsset and captures its kwargs."""
real_asset_cls = apprise.AppriseAsset

captured = {}

class SpyAsset(real_asset_cls):
def __init__(self, **kwargs):
# Capture what was passed before forwarding
captured.update(kwargs)
# Remove our new kwarg if the installed apprise doesn't
# know about it yet so the real __init__ doesn't raise
kwargs.pop("http_redirects", None)
super().__init__(**kwargs)

return SpyAsset, captured

def test_stateful_notify_passes_redirects_to_asset(self):
"""
Stateful /notify/{key} passes APPRISE_HTTP_REDIRECTS to AppriseAsset.
"""
spy_cls, captured = self._make_asset_spy()

key = "test_redirect_stateful"

# Seed stateful config
with mock.patch("apprise.AppriseAsset", spy_cls):
self.client.post("/add/{}".format(key), {"urls": "json://user:pass@localhost"})

form_data = {"title": "Test", "body": "Body"}
form = NotifyForm(data=form_data)
assert form.is_valid()
del form.cleaned_data["attachment"]

# Redirects disabled (default)
spy_cls, captured = self._make_asset_spy()
with mock.patch("apprise.AppriseAsset", spy_cls), override_settings(APPRISE_HTTP_REDIRECTS=False):
self.client.post("/notify/{}".format(key), form.cleaned_data)
assert captured.get("http_redirects") is False

# Redirects enabled
spy_cls, captured = self._make_asset_spy()
with mock.patch("apprise.AppriseAsset", spy_cls), override_settings(APPRISE_HTTP_REDIRECTS=True):
self.client.post("/notify/{}".format(key), form.cleaned_data)
assert captured.get("http_redirects") is True

def test_stateless_notify_passes_redirects_to_asset(self):
"""
Stateless /notify passes APPRISE_HTTP_REDIRECTS to AppriseAsset.
"""
json_data = {
"urls": "json://user:pass@localhost",
"title": "Test",
"body": "Body",
}

# Redirects disabled (default)
spy_cls, captured = self._make_asset_spy()
with mock.patch("apprise.AppriseAsset", spy_cls), override_settings(APPRISE_HTTP_REDIRECTS=False):
self.client.post(
"/notify",
data=json.dumps(json_data),
content_type="application/json",
)
assert captured.get("http_redirects") is False

# Redirects enabled
spy_cls, captured = self._make_asset_spy()
with mock.patch("apprise.AppriseAsset", spy_cls), override_settings(APPRISE_HTTP_REDIRECTS=True):
self.client.post(
"/notify",
data=json.dumps(json_data),
content_type="application/json",
)
assert captured.get("http_redirects") is True
4 changes: 4 additions & 0 deletions apprise_api/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,8 @@ def post(self, request, key):
"storage_mode": settings.APPRISE_STORAGE_MODE,
# Emoji configuration (values are None, True, or False)
"interpret_emojis": settings.APPRISE_INTERPRET_EMOJIS,
# HTTP redirect behaviour (values are None, True, or False)
"http_redirects": settings.APPRISE_HTTP_REDIRECTS,
}

if body_format:
Expand Down Expand Up @@ -2111,6 +2113,8 @@ def post(self, request):
"plugin_paths": settings.APPRISE_PLUGIN_PATHS,
# Emoji configuration (values are None, True, or False)
"interpret_emojis": settings.APPRISE_INTERPRET_EMOJIS,
# HTTP redirect behaviour (values are None, True, or False)
"http_redirects": settings.APPRISE_HTTP_REDIRECTS,
}
if settings.APPRISE_STATELESS_STORAGE:
# Persistent Storage is allowed with Stateless queries
Expand Down
7 changes: 7 additions & 0 deletions apprise_api/core/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,10 @@
"+",
)
)

# Allow HTTP Redirects override
# By default Apprise follows HTTP 3xx redirects, matching the behaviour of
# the underlying requests library. Set APPRISE_HTTP_REDIRECTS=no to disable
# redirect following globally across all plugins without touching individual
# URLs.
APPRISE_HTTP_REDIRECTS = os.environ.get("APPRISE_HTTP_REDIRECTS", "yes")[0].lower() in ("a", "y", "1", "t", "e", "+")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ version = "1.4.1"
authors = [{ name = "Chris Caron", email = "lead2gold@gmail.com" }]
license = "MIT"
dependencies = [
"apprise == 1.10.0",
"apprise == 1.11.0",
"django",
"gevent",
"gunicorn",
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# apprise @ git+https://github.com/caronc/apprise@custom-tag-or-version

## 3. The below grabs our stable version (generally the best choice):
apprise == 1.10.0
apprise == 1.11.0

## Apprise API Minimum Requirements
django
Expand Down
Loading