2018-02-20 11:22:17 +00:00
|
|
|
from functools import wraps
|
|
|
|
|
from itertools import chain
|
2016-10-27 17:31:13 +01:00
|
|
|
|
2022-01-26 14:44:33 +00:00
|
|
|
from flask import abort, g, make_response, request
|
2021-06-09 13:19:05 +01:00
|
|
|
from flask_login import current_user
|
2018-08-23 16:11:08 +01:00
|
|
|
from notifications_utils.field import Field
|
2024-03-08 15:00:48 -08:00
|
|
|
from ordered_set import OrderedSet
|
2017-12-30 16:54:39 +00:00
|
|
|
from werkzeug.datastructures import MultiDict
|
2019-03-21 16:41:22 +00:00
|
|
|
from werkzeug.routing import RequestRedirect
|
2016-02-19 16:38:04 +00:00
|
|
|
|
2023-08-25 09:12:23 -07:00
|
|
|
SENDING_STATUSES = ["created", "pending", "sending"]
|
|
|
|
|
DELIVERED_STATUSES = ["delivered", "sent"]
|
|
|
|
|
FAILURE_STATUSES = [
|
|
|
|
|
"failed",
|
|
|
|
|
"temporary-failure",
|
|
|
|
|
"permanent-failure",
|
|
|
|
|
"technical-failure",
|
|
|
|
|
"validation-failed",
|
|
|
|
|
]
|
2017-01-30 17:27:09 +00:00
|
|
|
REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES
|
|
|
|
|
|
2022-12-05 15:33:44 -05:00
|
|
|
NOTIFICATION_TYPES = ["sms", "email"]
|
2020-08-11 15:44:17 +01:00
|
|
|
|
2019-10-01 17:16:15 +01:00
|
|
|
|
2020-06-30 15:32:00 +01:00
|
|
|
def service_has_permission(permission):
|
|
|
|
|
from app import current_service
|
2020-07-03 10:00:55 +01:00
|
|
|
|
2020-06-30 15:32:00 +01:00
|
|
|
def wrap(func):
|
|
|
|
|
@wraps(func)
|
|
|
|
|
def wrap_func(*args, **kwargs):
|
|
|
|
|
if not current_service or not current_service.has_permission(permission):
|
|
|
|
|
abort(403)
|
|
|
|
|
return func(*args, **kwargs)
|
2023-08-25 09:12:23 -07:00
|
|
|
|
2020-06-30 15:32:00 +01:00
|
|
|
return wrap_func
|
2023-08-25 09:12:23 -07:00
|
|
|
|
2020-06-30 15:32:00 +01:00
|
|
|
return wrap
|
|
|
|
|
|
|
|
|
|
|
2016-07-05 11:39:07 +01:00
|
|
|
def get_help_argument():
|
2023-08-25 09:12:23 -07:00
|
|
|
return (
|
|
|
|
|
request.args.get("help")
|
|
|
|
|
if request.args.get("help") in ("1", "2", "3")
|
|
|
|
|
else None
|
|
|
|
|
)
|
2016-10-25 18:10:15 +01:00
|
|
|
|
|
|
|
|
|
2017-12-30 16:54:39 +00:00
|
|
|
def parse_filter_args(filter_dict):
|
|
|
|
|
if not isinstance(filter_dict, MultiDict):
|
|
|
|
|
filter_dict = MultiDict(filter_dict)
|
|
|
|
|
|
|
|
|
|
return MultiDict(
|
2023-08-25 09:12:23 -07:00
|
|
|
(key, (",".join(filter_dict.getlist(key))).split(","))
|
2017-12-30 16:54:39 +00:00
|
|
|
for key in filter_dict.keys()
|
2023-08-25 09:12:23 -07:00
|
|
|
if "".join(filter_dict.getlist(key))
|
2017-12-30 16:54:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_status_filters(filter_args):
|
2023-08-25 09:12:23 -07:00
|
|
|
status_filters = filter_args.get("status", [])
|
|
|
|
|
return list(
|
|
|
|
|
OrderedSet(
|
|
|
|
|
chain(
|
|
|
|
|
(status_filters or REQUESTED_STATUSES),
|
|
|
|
|
DELIVERED_STATUSES if "delivered" in status_filters else [],
|
|
|
|
|
SENDING_STATUSES if "sending" in status_filters else [],
|
|
|
|
|
FAILURE_STATUSES if "failed" in status_filters else [],
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-02-06 09:29:11 +00:00
|
|
|
|
|
|
|
|
|
2018-04-30 10:46:39 +01:00
|
|
|
def unicode_truncate(s, length):
|
2023-08-25 09:12:23 -07:00
|
|
|
encoded = s.encode("utf-8")[:length]
|
|
|
|
|
return encoded.decode("utf-8", "ignore")
|
2018-07-02 09:08:21 +01:00
|
|
|
|
|
|
|
|
|
2021-07-21 16:06:23 +01:00
|
|
|
def should_skip_template_page(db_template):
|
2018-08-09 16:29:51 +01:00
|
|
|
return (
|
2023-08-25 09:12:23 -07:00
|
|
|
current_user.has_permissions("send_messages")
|
|
|
|
|
and not current_user.has_permissions("manage_templates", "manage_api_keys")
|
|
|
|
|
and not db_template["archived"]
|
2018-08-09 16:29:51 +01:00
|
|
|
)
|
2018-08-23 16:11:08 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_default_sms_sender(sms_senders):
|
2023-08-25 09:12:23 -07:00
|
|
|
return str(
|
|
|
|
|
next(
|
|
|
|
|
(
|
|
|
|
|
Field(x["sms_sender"], html="escape")
|
|
|
|
|
for x in sms_senders
|
|
|
|
|
if x["is_default"]
|
|
|
|
|
),
|
|
|
|
|
"None",
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-11-27 16:49:01 +00:00
|
|
|
|
|
|
|
|
|
2019-03-21 16:41:22 +00:00
|
|
|
class PermanentRedirect(RequestRedirect):
|
|
|
|
|
"""
|
|
|
|
|
In Werkzeug 0.15.0 the status code for RequestRedirect changed from 301 to 308.
|
|
|
|
|
308 status codes are not supported when Internet Explorer is used with Windows 7
|
|
|
|
|
and Windows 8.1, so this class keeps the original status code of 301.
|
|
|
|
|
"""
|
2023-08-25 09:12:23 -07:00
|
|
|
|
2019-03-21 16:41:22 +00:00
|
|
|
code = 301
|
2019-10-18 16:09:39 +01:00
|
|
|
|
|
|
|
|
|
2020-05-26 17:39:25 +01:00
|
|
|
def hide_from_search_engines(f):
|
|
|
|
|
@wraps(f)
|
|
|
|
|
def decorated_function(*args, **kwargs):
|
|
|
|
|
g.hide_from_search_engines = True
|
2020-05-27 09:03:14 +01:00
|
|
|
response = make_response(f(*args, **kwargs))
|
2023-08-25 09:12:23 -07:00
|
|
|
response.headers["X-Robots-Tag"] = "noindex"
|
2020-05-27 09:03:14 +01:00
|
|
|
return response
|
2023-08-25 09:12:23 -07:00
|
|
|
|
2020-05-26 17:39:25 +01:00
|
|
|
return decorated_function
|
2020-08-13 12:38:12 +01:00
|
|
|
|
|
|
|
|
|
2023-12-01 11:14:19 -08:00
|
|
|
# Function used for debugging.
|
|
|
|
|
# Do print(hilite(message)) while debugging, then remove your print statements
|
|
|
|
|
def hilite(message):
|
|
|
|
|
ansi_green = "\033[32m"
|
|
|
|
|
ansi_reset = "\033[0m"
|
|
|
|
|
return f"{ansi_green}{message}{ansi_reset}"
|
|
|
|
|
|
|
|
|
|
|
2020-08-13 12:38:12 +01:00
|
|
|
# Function to merge two dict or lists with a JSON-like structure into one.
|
|
|
|
|
# JSON-like means they can contain all types JSON can: all the main primitives
|
|
|
|
|
# plus nested lists or dictionaries.
|
|
|
|
|
# Merge is additive. New values overwrite old and collections are added to.
|
|
|
|
|
def merge_jsonlike(source, destination):
|
|
|
|
|
def merge_items(source_item, destination_item):
|
|
|
|
|
if isinstance(source_item, dict) and isinstance(destination_item, dict):
|
|
|
|
|
merge_dicts(source_item, destination_item)
|
|
|
|
|
elif isinstance(source_item, list) and isinstance(destination_item, list):
|
|
|
|
|
merge_lists(source_item, destination_item)
|
|
|
|
|
else: # primitive value
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def merge_lists(source, destination):
|
2021-01-26 12:16:08 +00:00
|
|
|
last_src_idx = len(source) - 1
|
2021-01-21 16:49:12 +00:00
|
|
|
for idx, item in enumerate(destination):
|
2021-01-26 12:16:08 +00:00
|
|
|
if idx <= last_src_idx:
|
2021-01-21 16:49:12 +00:00
|
|
|
# assign destination value if can't be merged into source
|
|
|
|
|
if merge_items(source[idx], destination[idx]) is False:
|
|
|
|
|
source[idx] = destination[idx]
|
|
|
|
|
else:
|
2020-08-13 12:38:12 +01:00
|
|
|
source.append(item)
|
|
|
|
|
|
|
|
|
|
def merge_dicts(source, destination):
|
|
|
|
|
for key, value in destination.items():
|
|
|
|
|
if key in source:
|
|
|
|
|
# assign destination value if can't be merged into source
|
|
|
|
|
if merge_items(source[key], value) is False:
|
|
|
|
|
source[key] = value
|
|
|
|
|
else:
|
|
|
|
|
source[key] = value
|
|
|
|
|
|
|
|
|
|
merge_items(source, destination)
|