API - fix user agent infos in emails after dependencies update
(user-agent parsing has been removed in Werkzeug 2.1)
This commit is contained in:
parent
a9064d5bef
commit
db8c9df33c
@ -19,6 +19,7 @@ from flask_sqlalchemy import SQLAlchemy
|
|||||||
from sqlalchemy.exc import ProgrammingError
|
from sqlalchemy.exc import ProgrammingError
|
||||||
|
|
||||||
from fittrackee.emails.email import EmailService
|
from fittrackee.emails.email import EmailService
|
||||||
|
from fittrackee.request import CustomRequest
|
||||||
|
|
||||||
VERSION = __version__ = '0.6.1'
|
VERSION = __version__ = '0.6.1'
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
@ -35,9 +36,17 @@ logging.basicConfig(
|
|||||||
appLog = logging.getLogger('fittrackee')
|
appLog = logging.getLogger('fittrackee')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFlask(Flask):
|
||||||
|
# add custom Request to handle user-agent parsing
|
||||||
|
# (removed in Werkzeug 2.1)
|
||||||
|
request_class = CustomRequest
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> Flask:
|
def create_app() -> Flask:
|
||||||
# instantiate the app
|
# instantiate the app
|
||||||
app = Flask(__name__, static_folder='dist/static', template_folder='dist')
|
app = CustomFlask(
|
||||||
|
__name__, static_folder='dist/static', template_folder='dist'
|
||||||
|
)
|
||||||
|
|
||||||
# set config
|
# set config
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
@ -105,7 +114,7 @@ def create_app() -> Flask:
|
|||||||
appLog.setLevel(logging.DEBUG)
|
appLog.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
# Enable CORS
|
# Enable CORS
|
||||||
@app.after_request
|
@app.after_request # type: ignore
|
||||||
def after_request(response: Response) -> Response:
|
def after_request(response: Response) -> Response:
|
||||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||||
response.headers.add(
|
response.headers.add(
|
||||||
|
24
fittrackee/request.py
Normal file
24
fittrackee/request.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
from flask import Request
|
||||||
|
from ua_parser import user_agent_parser
|
||||||
|
from werkzeug.user_agent import UserAgent as IUserAgent
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgent(IUserAgent):
|
||||||
|
def __init__(self, string: str):
|
||||||
|
super().__init__(string)
|
||||||
|
self.platform, self.browser = self._parse_user_agent(self.string)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_user_agent(
|
||||||
|
user_agent: str,
|
||||||
|
) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
parsed_string = user_agent_parser.Parse(user_agent)
|
||||||
|
platform = parsed_string.get('os', {}).get('family')
|
||||||
|
browser = parsed_string.get('user_agent', {}).get('family')
|
||||||
|
return platform, browser
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRequest(Request):
|
||||||
|
user_agent_class = UserAgent
|
@ -4,6 +4,7 @@ from uuid import uuid4
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from fittrackee.files import display_readable_file_size
|
from fittrackee.files import display_readable_file_size
|
||||||
|
from fittrackee.request import UserAgent
|
||||||
from fittrackee.utils import get_readable_duration
|
from fittrackee.utils import get_readable_duration
|
||||||
|
|
||||||
|
|
||||||
@ -42,3 +43,28 @@ class TestReadableDuration:
|
|||||||
readable_duration = get_readable_duration(30, locale)
|
readable_duration = get_readable_duration(30, locale)
|
||||||
|
|
||||||
assert readable_duration == expected_duration
|
assert readable_duration == expected_duration
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseUserAgent:
|
||||||
|
string = (
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64; rv:98.0) '
|
||||||
|
'Gecko/20100101 Firefox/98.0'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_it_returns_browser_name(self) -> None:
|
||||||
|
user_agent = UserAgent(self.string)
|
||||||
|
assert user_agent.browser == 'Firefox'
|
||||||
|
|
||||||
|
def test_it_returns_other_as_brother_name_when_empty_string_provided(
|
||||||
|
self,
|
||||||
|
) -> None:
|
||||||
|
user_agent = UserAgent('')
|
||||||
|
assert user_agent.browser == 'Other'
|
||||||
|
|
||||||
|
def test_it_returns_operating_system(self) -> None:
|
||||||
|
user_agent = UserAgent(self.string)
|
||||||
|
assert user_agent.platform == 'Linux'
|
||||||
|
|
||||||
|
def test_it_returns_other_as_os_when_empty_string_provided(self) -> None:
|
||||||
|
user_agent = UserAgent('')
|
||||||
|
assert user_agent.platform == 'Other'
|
||||||
|
@ -285,8 +285,8 @@ class TestUserRegistration(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': username,
|
'username': username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
'account_confirmation_url': (
|
'account_confirmation_url': (
|
||||||
'http://0.0.0.0:5000/account-confirmation'
|
'http://0.0.0.0:5000/account-confirmation'
|
||||||
f'?token={expected_token}'
|
f'?token={expected_token}'
|
||||||
@ -807,8 +807,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': user_1.username,
|
'username': user_1.username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
'new_email_address': new_email,
|
'new_email_address': new_email,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -849,8 +849,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': user_1.username,
|
'username': user_1.username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
'email_confirmation_url': (
|
'email_confirmation_url': (
|
||||||
f'http://0.0.0.0:5000/email-update?token={expected_token}'
|
f'http://0.0.0.0:5000/email-update?token={expected_token}'
|
||||||
),
|
),
|
||||||
@ -1009,8 +1009,8 @@ class TestUserAccountUpdate(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': user_1.username,
|
'username': user_1.username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1690,8 +1690,8 @@ class TestPasswordResetRequest(ApiTestCaseMixin):
|
|||||||
f'http://0.0.0.0:5000/password-reset?token={token}'
|
f'http://0.0.0.0:5000/password-reset?token={token}'
|
||||||
),
|
),
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1903,8 +1903,8 @@ class TestPasswordUpdate(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': user_1.username,
|
'username': user_1.username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2130,8 +2130,8 @@ class TestResendAccountConfirmationEmail(ApiTestCaseMixin):
|
|||||||
{
|
{
|
||||||
'username': inactive_user.username,
|
'username': inactive_user.username,
|
||||||
'fittrackee_url': 'http://0.0.0.0:5000',
|
'fittrackee_url': 'http://0.0.0.0:5000',
|
||||||
'operating_system': 'linux',
|
'operating_system': 'Linux',
|
||||||
'browser_name': 'firefox',
|
'browser_name': 'Firefox',
|
||||||
'account_confirmation_url': (
|
'account_confirmation_url': (
|
||||||
'http://0.0.0.0:5000/account-confirmation'
|
'http://0.0.0.0:5000/account-confirmation'
|
||||||
f'?token={expected_token}'
|
f'?token={expected_token}'
|
||||||
|
14
poetry.lock
generated
14
poetry.lock
generated
@ -1315,6 +1315,14 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ua-parser"
|
||||||
|
version = "0.10.0"
|
||||||
|
description = "Python port of Browserscope's user agent parser"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "1.26.9"
|
version = "1.26.9"
|
||||||
@ -1380,7 +1388,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "adc2a4d8457e207233c64b835fbfc1e38dbf49dc2d807657a80b53bd8a2c622d"
|
content-hash = "e130b306957d6577ecd21cc1a19daa81073d5bbe77ba9f6a60ff83d8af0a2f05"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alabaster = [
|
alabaster = [
|
||||||
@ -2193,6 +2201,10 @@ typing-extensions = [
|
|||||||
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
{file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
|
||||||
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
{file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
|
||||||
]
|
]
|
||||||
|
ua-parser = [
|
||||||
|
{file = "ua-parser-0.10.0.tar.gz", hash = "sha256:47b1782ed130d890018d983fac37c2a80799d9e0b9c532e734c67cf70f185033"},
|
||||||
|
{file = "ua_parser-0.10.0-py2.py3-none-any.whl", hash = "sha256:46ab2e383c01dbd2ab284991b87d624a26a08f72da4d7d413f5bfab8b9036f8a"},
|
||||||
|
]
|
||||||
urllib3 = [
|
urllib3 = [
|
||||||
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
{file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
|
||||||
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
|
{file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
|
||||||
|
@ -40,6 +40,7 @@ shortuuid = "^1.0.8"
|
|||||||
staticmap = "^0.5.4"
|
staticmap = "^0.5.4"
|
||||||
SQLAlchemy = "1.4.32"
|
SQLAlchemy = "1.4.32"
|
||||||
pyOpenSSL = "^22.0"
|
pyOpenSSL = "^22.0"
|
||||||
|
ua-parser = "^0.10.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^22.3"
|
black = "^22.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user