matlab-proxy 0.5.3__py3-none-any.whl → 0.30.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matlab_proxy/app.py +578 -205
- matlab_proxy/app_state.py +1061 -431
- matlab_proxy/constants.py +37 -0
- matlab_proxy/default_configuration.py +39 -4
- matlab_proxy/devel.py +18 -22
- matlab_proxy/gui/index.html +20 -1
- matlab_proxy/gui/static/css/index.BedVwcEg.css +10 -0
- matlab_proxy/gui/static/js/index.pQwV1obF.js +64 -0
- matlab_proxy/gui/static/media/MATLAB-env-blur.NupTbPv_.png +0 -0
- matlab_proxy/matlab/evaluateUserMatlabCode.m +51 -0
- matlab_proxy/matlab/startup.m +3 -28
- matlab_proxy/settings.py +543 -112
- matlab_proxy/util/__init__.py +187 -59
- matlab_proxy/util/cookie_jar.py +72 -0
- matlab_proxy/util/event_loop.py +28 -10
- matlab_proxy/util/list_servers.py +71 -26
- matlab_proxy/util/mw.py +16 -15
- matlab_proxy/util/mwi/download.py +136 -0
- matlab_proxy/util/mwi/embedded_connector/__init__.py +1 -1
- matlab_proxy/util/mwi/embedded_connector/helpers.py +12 -4
- matlab_proxy/util/mwi/embedded_connector/request.py +78 -12
- matlab_proxy/util/mwi/environment_variables.py +120 -27
- matlab_proxy/util/mwi/exceptions.py +63 -9
- matlab_proxy/util/mwi/logger.py +141 -27
- matlab_proxy/util/mwi/session_name.py +28 -0
- matlab_proxy/util/mwi/token_auth.py +264 -121
- matlab_proxy/util/mwi/validators.py +231 -88
- matlab_proxy/util/system.py +9 -0
- matlab_proxy/util/windows.py +32 -6
- {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/METADATA +94 -49
- matlab_proxy-0.30.1.dist-info/RECORD +88 -0
- {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/WHEEL +1 -2
- {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info}/entry_points.txt +1 -1
- matlab_proxy_manager/README.md +85 -0
- matlab_proxy_manager/__init__.py +6 -0
- matlab_proxy_manager/lib/README.md +53 -0
- matlab_proxy_manager/lib/__init__.py +1 -0
- matlab_proxy_manager/lib/api.py +419 -0
- matlab_proxy_manager/storage/README.md +54 -0
- matlab_proxy_manager/storage/__init__.py +1 -0
- matlab_proxy_manager/storage/file_repository.py +144 -0
- matlab_proxy_manager/storage/interface.py +62 -0
- matlab_proxy_manager/storage/server.py +172 -0
- matlab_proxy_manager/utils/__init__.py +1 -0
- matlab_proxy_manager/utils/auth.py +77 -0
- matlab_proxy_manager/utils/constants.py +8 -0
- matlab_proxy_manager/utils/decorators.py +37 -0
- matlab_proxy_manager/utils/environment_variables.py +51 -0
- matlab_proxy_manager/utils/exceptions.py +45 -0
- matlab_proxy_manager/utils/helpers.py +314 -0
- matlab_proxy_manager/utils/logger.py +76 -0
- matlab_proxy_manager/web/README.md +37 -0
- matlab_proxy_manager/web/__init__.py +1 -0
- matlab_proxy_manager/web/app.py +536 -0
- matlab_proxy_manager/web/monitor.py +45 -0
- matlab_proxy_manager/web/watcher.py +65 -0
- matlab_proxy/gui/asset-manifest.json +0 -23
- matlab_proxy/gui/authorization.html +0 -115
- matlab_proxy/gui/bootstrap.3.4.1.min.css +0 -6
- matlab_proxy/gui/navbar.css +0 -8
- matlab_proxy/gui/signin.css +0 -42
- matlab_proxy/gui/static/css/main.d890078a.chunk.css +0 -13
- matlab_proxy/gui/static/css/main.d890078a.chunk.css.map +0 -1
- matlab_proxy/gui/static/js/2.13be6544.chunk.js +0 -3
- matlab_proxy/gui/static/js/2.13be6544.chunk.js.LICENSE.txt +0 -59
- matlab_proxy/gui/static/js/2.13be6544.chunk.js.map +0 -1
- matlab_proxy/gui/static/js/main.c311d854.chunk.js +0 -2
- matlab_proxy/gui/static/js/main.c311d854.chunk.js.map +0 -1
- matlab_proxy/gui/static/js/runtime-main.f70e4d5f.js +0 -2
- matlab_proxy/gui/static/js/runtime-main.f70e4d5f.js.map +0 -1
- matlab_proxy/gui/static/media/arrow.0c2968b9.svg +0 -4
- matlab_proxy/gui/static/media/feedback.6e8d50eb.svg +0 -1
- matlab_proxy/gui/static/media/gripper.9defbc5e.svg +0 -1
- matlab_proxy/gui/static/media/help.15e5bfab.svg +0 -1
- matlab_proxy/gui/static/media/ico-header-contact-hover.0958c442.svg +0 -17
- matlab_proxy/gui/static/media/ico-header-contact.ae9169c8.svg +0 -17
- matlab_proxy/gui/static/media/restart.7987508a.svg +0 -1
- matlab_proxy/gui/static/media/sign-out.08356b67.svg +0 -1
- matlab_proxy/gui/static/media/start.50c4596f.svg +0 -1
- matlab_proxy/gui/static/media/stop.30c9a9ab.svg +0 -1
- matlab_proxy/gui/static/media/terminate.7ea1363e.svg +0 -1
- matlab_proxy/gui/token.html +0 -123
- matlab_proxy-0.5.3.dist-info/RECORD +0 -84
- matlab_proxy-0.5.3.dist-info/top_level.txt +0 -1
- /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.82b1212e.woff → glyphicons-halflings-regular.BKjkU69z.woff} +0 -0
- /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.5be1347c.eot → glyphicons-halflings-regular.BUJKDMgK.eot} +0 -0
- /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.060b2710.svg → glyphicons-halflings-regular.CSehLiBc.svg} +0 -0
- /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.4692b9ec.ttf → glyphicons-halflings-regular.DrwTMapi.ttf} +0 -0
- /matlab_proxy/gui/static/media/{glyphicons-halflings-regular.be810be3.woff2 → glyphicons-halflings-regular.DzqM6ju8.woff2} +0 -0
- /matlab_proxy/gui/static/media/{ico-header-account-hover.89438e91.svg → ico-header-account-hover.-jQHo6Wx.svg} +0 -0
- /matlab_proxy/gui/static/media/{ico-header-account.86b10d7b.svg → ico-header-account.CJCFoo5a.svg} +0 -0
- /matlab_proxy/gui/static/media/{ico-sprite.cbdb66c0.png → ico-sprite.DXGLgzq9.png} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-eps.4d20e0ee.ttf → mathworks-eps.CGNQALa9.ttf} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-eps.df1428df.svg → mathworks-eps.DrkCtQtG.svg} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-eps.e5c41e84.woff → mathworks-eps.Ds7lQbql.woff} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-pictograms.3fc6513a.woff → mathworks-pictograms.BdqxEfBR.woff} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-pictograms.f6f087b0.svg → mathworks-pictograms.CCLweoD4.svg} +0 -0
- /matlab_proxy/gui/static/media/{mathworks-pictograms.6e128c0e.ttf → mathworks-pictograms.DZhFdRSm.ttf} +0 -0
- /matlab_proxy/gui/static/media/{mathworks.80a3218e.svg → mathworks.C-qsbhDy.svg} +0 -0
- /matlab_proxy/gui/static/media/{mathworks.c422935b.ttf → mathworks.Ceplx86V.ttf} +0 -0
- /matlab_proxy/gui/static/media/{mathworks.37a563ef.woff → mathworks.D08X1Vp8.woff} +0 -0
- /matlab_proxy/gui/static/media/{trigger-error.3f1c4ef2.svg → trigger-error.QEdsGL-m.svg} +0 -0
- /matlab_proxy/gui/static/media/{trigger-ok.7b9c238b.svg → trigger-ok.Dzg8OIrk.svg} +0 -0
- {matlab_proxy-0.5.3.dist-info → matlab_proxy-0.30.1.dist-info/licenses}/LICENSE.md +0 -0
matlab_proxy/util/mwi/logger.py
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
# Copyright
|
|
2
|
-
"""Functions to access & control the logging behavior of the app
|
|
3
|
-
"""
|
|
1
|
+
# Copyright 2020-2025 The MathWorks, Inc.
|
|
2
|
+
"""Functions to access & control the logging behavior of the app"""
|
|
4
3
|
|
|
5
4
|
import logging
|
|
6
5
|
import os
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
from rich.table import Table
|
|
7
12
|
|
|
8
13
|
from . import environment_variables as mwi_env
|
|
9
14
|
|
|
15
|
+
logging.getLogger("aiohttp_session").setLevel(logging.ERROR)
|
|
16
|
+
|
|
10
17
|
|
|
11
18
|
def get(init=False):
|
|
12
19
|
"""Get the logger used by this application.
|
|
@@ -44,28 +51,65 @@ def __set_logging_configuration():
|
|
|
44
51
|
Returns:
|
|
45
52
|
Logger: Logger object with the set configuration.
|
|
46
53
|
"""
|
|
47
|
-
#
|
|
48
|
-
|
|
54
|
+
# Create the Logger for MATLABProxy
|
|
55
|
+
logger = __get_mw_logger()
|
|
56
|
+
|
|
57
|
+
# log_level is either set by environment or is the default value.
|
|
58
|
+
log_level = os.getenv(
|
|
59
|
+
mwi_env.get_env_name_logging_level(), __get_default_log_level()
|
|
60
|
+
).upper()
|
|
61
|
+
|
|
62
|
+
if __is_invalid_log_level(log_level):
|
|
63
|
+
default_log_level = __get_default_log_level()
|
|
64
|
+
logging.warning(
|
|
65
|
+
f"Unknown log level '{log_level}' set. Defaulting to log level '{default_log_level}'..."
|
|
66
|
+
)
|
|
67
|
+
log_level = default_log_level
|
|
49
68
|
|
|
50
69
|
## Set logging object
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
70
|
+
if mwi_env.Experimental.use_rich_logger():
|
|
71
|
+
from rich.logging import RichHandler
|
|
72
|
+
|
|
73
|
+
rich_handler = RichHandler(
|
|
74
|
+
keywords=[__get_mw_logger_name()],
|
|
57
75
|
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
rich_handler.setFormatter(logging.Formatter("%(name)s %(message)s"))
|
|
77
|
+
logger.addHandler(rich_handler)
|
|
78
|
+
else:
|
|
79
|
+
colored_formatter = _ColoredFormatter(
|
|
80
|
+
"%(color)s[%(levelname)1.1s %(asctime)s %(name)s]%(end_color)s %(message)s"
|
|
81
|
+
)
|
|
82
|
+
stream_handler = logging.StreamHandler()
|
|
83
|
+
stream_handler.setFormatter(colored_formatter)
|
|
84
|
+
logger.addHandler(stream_handler)
|
|
61
85
|
|
|
62
|
-
# log_level is either set by environment or is the default value.
|
|
63
|
-
logger.info(f"Initializing logger with log_level: {log_level}")
|
|
64
86
|
logger.setLevel(log_level)
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
log_file = os.getenv(mwi_env.get_env_name_log_file(), None)
|
|
89
|
+
if log_file:
|
|
90
|
+
try:
|
|
91
|
+
log_file = Path(log_file)
|
|
92
|
+
# Need to create the file if it doesn't exist or else logging.FileHandler
|
|
93
|
+
# would open it in 'write' mode instead of 'append' mode.
|
|
94
|
+
log_file.touch(exist_ok=True)
|
|
95
|
+
logger.info(f"Initializing logger with log file:{log_file}")
|
|
96
|
+
file_handler = logging.FileHandler(filename=log_file, mode="a")
|
|
97
|
+
formatter = logging.Formatter(
|
|
98
|
+
fmt="[%(levelname)s %(asctime)s %(name)s] %(message)s"
|
|
99
|
+
)
|
|
100
|
+
file_handler.setFormatter(formatter)
|
|
101
|
+
file_handler.setLevel(log_level)
|
|
102
|
+
logger.addHandler(file_handler)
|
|
103
|
+
|
|
104
|
+
except PermissionError:
|
|
105
|
+
print(
|
|
106
|
+
f"PermissionError: Permission denied to create log file at: {log_file}"
|
|
107
|
+
)
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
except Exception as err:
|
|
111
|
+
print(f"Failed to use log file: {log_file} with error: {err}")
|
|
112
|
+
sys.exit(1)
|
|
69
113
|
|
|
70
114
|
return logger
|
|
71
115
|
|
|
@@ -91,14 +135,84 @@ def __get_default_log_level():
|
|
|
91
135
|
return "INFO"
|
|
92
136
|
|
|
93
137
|
|
|
94
|
-
def
|
|
95
|
-
"""
|
|
96
|
-
to control logging
|
|
138
|
+
def __is_invalid_log_level(log_level):
|
|
139
|
+
"""Helper to check if the log level is valid.
|
|
97
140
|
|
|
98
141
|
Returns:
|
|
99
|
-
|
|
142
|
+
Boolean: Whether log level exists
|
|
100
143
|
"""
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
|
|
145
|
+
return not hasattr(logging, log_level)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def log_startup_info(title=None, matlab_urls=[]):
|
|
149
|
+
"""Logs the startup information to the console and log file if specified."""
|
|
150
|
+
logger = __get_mw_logger()
|
|
151
|
+
print_as_table = False
|
|
152
|
+
header_info = "Access MATLAB at:"
|
|
153
|
+
|
|
154
|
+
if sys.stdout.isatty():
|
|
155
|
+
# Width cannot be determined in non-interactive sessions
|
|
156
|
+
console = Console()
|
|
157
|
+
# Number of additional characters used by the table
|
|
158
|
+
padding = 4
|
|
159
|
+
print_as_table = all(len(url) + padding <= console.width for url in matlab_urls)
|
|
160
|
+
|
|
161
|
+
if print_as_table:
|
|
162
|
+
table = Table(
|
|
163
|
+
caption=title,
|
|
164
|
+
show_header=False,
|
|
165
|
+
show_lines=True,
|
|
166
|
+
show_edge=True,
|
|
167
|
+
highlight=True,
|
|
168
|
+
expand=True,
|
|
169
|
+
)
|
|
170
|
+
table.add_column(overflow="fold", style="bold green", justify="center")
|
|
171
|
+
table.add_row(header_info)
|
|
172
|
+
for url in matlab_urls:
|
|
173
|
+
table.add_row(url)
|
|
174
|
+
console.print(table)
|
|
175
|
+
|
|
176
|
+
if os.getenv(mwi_env.get_env_name_log_file(), None) or not print_as_table:
|
|
177
|
+
urls = "\n\t".join(matlab_urls)
|
|
178
|
+
logger.critical(f"{header_info}\n\t{urls}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class _ColoredFormatter(logging.Formatter):
|
|
182
|
+
"""Custom formatter to add colors based on log level and modify time format."""
|
|
183
|
+
|
|
184
|
+
def format(self, record):
|
|
185
|
+
# Example: Add 'color' and 'end_color' attributes based on log level
|
|
186
|
+
if record.levelno == logging.INFO:
|
|
187
|
+
record.color = "\033[32m" # Green
|
|
188
|
+
record.end_color = "\033[0m"
|
|
189
|
+
elif record.levelno == logging.DEBUG:
|
|
190
|
+
record.color = "\033[94m" # Blue
|
|
191
|
+
record.end_color = "\033[0m"
|
|
192
|
+
elif record.levelno == logging.WARNING:
|
|
193
|
+
record.color = "\033[93m" # Yellow
|
|
194
|
+
record.end_color = "\033[0m"
|
|
195
|
+
elif record.levelno == logging.ERROR:
|
|
196
|
+
record.color = "\033[91m" # Red
|
|
197
|
+
record.end_color = "\033[0m"
|
|
198
|
+
elif record.levelno == logging.CRITICAL:
|
|
199
|
+
record.color = "\033[35m" # Magenta
|
|
200
|
+
record.end_color = "\033[0m"
|
|
201
|
+
else:
|
|
202
|
+
record.color = ""
|
|
203
|
+
record.end_color = ""
|
|
204
|
+
|
|
205
|
+
# Call the original format method
|
|
206
|
+
return super().format(record)
|
|
207
|
+
|
|
208
|
+
def formatTime(self, record, datefmt=None):
|
|
209
|
+
# Default behavior of formatTime
|
|
210
|
+
ct = self.converter(record.created)
|
|
211
|
+
if datefmt:
|
|
212
|
+
s = time.strftime(datefmt, ct)
|
|
213
|
+
else:
|
|
214
|
+
t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
|
|
215
|
+
s = "%s,%03d" % (t, record.msecs)
|
|
216
|
+
|
|
217
|
+
# Replace the comma with a period
|
|
218
|
+
return s.replace(",", ".")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright 2025 The MathWorks, Inc.
|
|
2
|
+
"""This file provides functions to set a session name for the MATLAB Proxy instance."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
6
|
+
from matlab_proxy.util.mwi import logger as mwi_logger
|
|
7
|
+
|
|
8
|
+
logger = mwi_logger.get()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_session_name():
|
|
12
|
+
"""Get the session name for the MATLAB Proxy instance.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
str: returns the user-defined session name if set, otherwise returns None.
|
|
16
|
+
"""
|
|
17
|
+
return os.getenv(mwi_env.get_env_name_session_name(), None)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_browser_title(matlab_version) -> str:
|
|
21
|
+
"""Get the browser title for the MATLAB Proxy instance."""
|
|
22
|
+
|
|
23
|
+
browser_title = "MATLAB " + (matlab_version or "")
|
|
24
|
+
session_name = _get_session_name()
|
|
25
|
+
if session_name:
|
|
26
|
+
browser_title = session_name + " - " + browser_title
|
|
27
|
+
logger.info("Session Name set to : %s", session_name)
|
|
28
|
+
return browser_title
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2020-2025 The MathWorks, Inc.
|
|
2
2
|
|
|
3
3
|
# This file contains functions required to enable token based authentication in the server.
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import secrets
|
|
7
|
+
from hashlib import sha256
|
|
8
|
+
from hmac import compare_digest
|
|
9
|
+
from urllib.parse import parse_qs
|
|
7
10
|
|
|
8
11
|
from aiohttp import web
|
|
9
|
-
from aiohttp_session import get_session, new_session
|
|
10
|
-
from aiohttp_session.cookie_storage import EncryptedCookieStorage
|
|
11
|
-
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
12
|
+
from aiohttp_session import get_session, new_session
|
|
12
13
|
|
|
13
|
-
from . import
|
|
14
|
+
from matlab_proxy.util.mwi import environment_variables as mwi_env
|
|
15
|
+
from matlab_proxy.util.mwi import logger as mwi_logger
|
|
14
16
|
|
|
15
17
|
logger = mwi_logger.get()
|
|
16
18
|
|
|
19
|
+
## Module Public Methods:
|
|
17
20
|
|
|
18
|
-
def decorator_authenticate_access(endpoint):
|
|
19
|
-
"""Decorates any endpoint function with token authentication checks."""
|
|
20
|
-
logger.debug("inside decorator_authenticate_access")
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
If token is provided and matches the expected secret, then the request is considered authentic,
|
|
27
|
-
and the token is saved into the session cookie.
|
|
28
|
-
"""
|
|
29
|
-
logger.debug(f" inside authenticate_access for request:{request}")
|
|
30
|
-
app_settings = request.app["settings"]
|
|
31
|
-
base_url = app_settings["base_url"]
|
|
22
|
+
def generate_mwi_auth_token_and_hash():
|
|
23
|
+
"""
|
|
24
|
+
Generate the MWI Token and a hash for that token to be used by the server,
|
|
25
|
+
based on the environment variables that control it.
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
logger.debug(
|
|
35
|
-
f" Request is authenticated, proceed to endpoint:{endpoint}{request}"
|
|
36
|
-
)
|
|
37
|
-
return await endpoint(request)
|
|
38
|
-
else:
|
|
39
|
-
# This branch intends to redirect users to the error page asking for TOKEN.
|
|
40
|
-
# However, we need to tell this page which page to redirect to on successful validation.
|
|
27
|
+
Token Auth is enabled by default, unless MWI_ENABLE_TOKEN_AUTH is explicitly set to False.
|
|
41
28
|
|
|
42
|
-
|
|
29
|
+
If MWI_ENABLE_TOKEN_AUTH is set, and MWI_AUTH_TOKEN is unset, then generate a token
|
|
30
|
+
else if MWI_AUTH_TOKEN is set, use that token for authentication.
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
32
|
+
Returns the Token and its hash to be used for authentication if enabled.
|
|
33
|
+
Returns None, if Token-Based Authentication is not enabled by user.
|
|
34
|
+
"""
|
|
35
|
+
env_name_enable_mwi_token_auth = mwi_env.get_env_name_enable_mwi_auth_token()
|
|
36
|
+
env_name_mwi_auth_token = mwi_env.get_env_name_mwi_auth_token()
|
|
37
|
+
enable_token_auth = os.getenv(env_name_enable_mwi_token_auth, "").lower()
|
|
38
|
+
auth_token = os.getenv(env_name_mwi_auth_token, "")
|
|
50
39
|
|
|
51
|
-
|
|
40
|
+
if enable_token_auth == "false":
|
|
41
|
+
if auth_token:
|
|
42
|
+
logger.warning(
|
|
43
|
+
f"Ignoring {env_name_mwi_auth_token}, as {env_name_enable_mwi_token_auth} explicitly set to false"
|
|
44
|
+
)
|
|
45
|
+
return _format_token_as_dictionary(None)
|
|
52
46
|
|
|
47
|
+
if auth_token:
|
|
48
|
+
auth_token = auth_token.strip()
|
|
49
|
+
logger.debug(f"Using provided {env_name_mwi_auth_token}.")
|
|
50
|
+
return _format_token_as_dictionary(auth_token)
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
# default catch-all for when the env variables are not set or above conditions are not met.
|
|
53
|
+
# This path will be executed if MWI_ENABLE_TOKEN_AUTH is set to true/Any value (default workflow)
|
|
54
|
+
generated_token = secrets.token_urlsafe()
|
|
55
|
+
logger.debug("Using auto-generated token.")
|
|
56
|
+
return _format_token_as_dictionary(generated_token)
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def get_mwi_auth_token_access_str(app_settings):
|
|
60
60
|
"""Returns formatted string with mwi token for use with server URL"""
|
|
61
|
-
if
|
|
62
|
-
mwi_auth_token_name = app_settings["
|
|
61
|
+
if app_settings["mwi_is_token_auth_enabled"]:
|
|
62
|
+
mwi_auth_token_name = app_settings["mwi_auth_token_name_for_http"]
|
|
63
63
|
mwi_auth_token = app_settings["mwi_auth_token"]
|
|
64
64
|
return f"?{mwi_auth_token_name}={mwi_auth_token}"
|
|
65
65
|
|
|
@@ -68,93 +68,236 @@ def get_mwi_auth_token_access_str(app_settings):
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
async def authenticate_request(request):
|
|
71
|
-
"""
|
|
71
|
+
"""Authenticates incoming request by verifying whether the expected token is in the request.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if token_name in session:
|
|
94
|
-
stored_session_token = session[token_name]
|
|
95
|
-
logger.debug(f"Found token with value: {stored_session_token}")
|
|
96
|
-
# Verify that token contains expected value
|
|
97
|
-
if stored_session_token == the_secret_token:
|
|
98
|
-
logger.debug("Token validation success!")
|
|
99
|
-
return True
|
|
100
|
-
else:
|
|
101
|
-
logger.info("Invalid Token found in session!")
|
|
102
|
-
logger.debug(f"Expected: {the_secret_token} ")
|
|
103
|
-
logger.debug(f"Actual : {stored_session_token}")
|
|
104
|
-
return False
|
|
105
|
-
else:
|
|
106
|
-
logger.info(f"{token_name} not found in session cookie.")
|
|
107
|
-
return False
|
|
73
|
+
The mwi_auth_token must be present either in:
|
|
74
|
+
a. session cookie
|
|
75
|
+
b. request's query parameters
|
|
76
|
+
c. or the request's headers.
|
|
77
|
+
|
|
78
|
+
Returns True/False based on whether the request is authenticated.
|
|
79
|
+
Returns True when authentication is disabled.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
logger.debug(f"<======== Authenticate request: {request}")
|
|
83
|
+
|
|
84
|
+
if _is_mwi_token_auth_enabled(request):
|
|
85
|
+
logger.debug("Authentication is Enabled.")
|
|
86
|
+
is_authenticated = (
|
|
87
|
+
await _is_valid_token_in_session_cookie(request)
|
|
88
|
+
or await _is_valid_token_in_headers(request)
|
|
89
|
+
or await _is_valid_token_in_url_query(request)
|
|
90
|
+
)
|
|
91
|
+
if is_authenticated:
|
|
92
|
+
logger.debug("Authentication successful. ========>")
|
|
108
93
|
else:
|
|
109
|
-
logger.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
94
|
+
logger.error("Token Authentication failed. ========>")
|
|
95
|
+
|
|
96
|
+
return is_authenticated
|
|
97
|
+
|
|
98
|
+
logger.debug("Token Authentication disabled.========>")
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def authenticate_access_decorator(endpoint):
|
|
103
|
+
"""This decorator verifies that the request to an endpoint exposed by matlab-proxy
|
|
104
|
+
contains the correct MWI_AUTH_TOKEN before servicing an endpoint."""
|
|
105
|
+
|
|
106
|
+
async def protect_endpoint(request):
|
|
107
|
+
"""Passes request to the endpoint after validating the token
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
request (HTTPRequest) : Web Request to endpoint
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
web.HTTPForbidden: Thrown when validation of token fails
|
|
114
|
+
"""
|
|
115
|
+
if await authenticate_request(request):
|
|
116
|
+
# request is authentic, proceed to execute the endpoint
|
|
117
|
+
return await endpoint(request)
|
|
118
|
+
else:
|
|
119
|
+
raise web.HTTPForbidden(reason="Unauthorized access to matlab-proxy.")
|
|
120
|
+
|
|
121
|
+
return protect_endpoint
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
## Module Private Methods:
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def _get_token_name(request):
|
|
128
|
+
"""Gets the name of the token from settings.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
request (HTTPRequest) : Used to get to app settings
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
str : token name
|
|
132
135
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
app_settings = request.app["settings"]
|
|
137
|
+
return app_settings["mwi_auth_token_name_for_env"]
|
|
135
138
|
|
|
136
|
-
|
|
139
|
+
|
|
140
|
+
def _get_token_name_for_http(request):
|
|
141
|
+
"""Gets the name of the token from settings.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
request (HTTPRequest) : Used to get to app settings
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
str : token name
|
|
148
|
+
"""
|
|
149
|
+
app_settings = request.app["settings"]
|
|
150
|
+
return app_settings["mwi_auth_token_name_for_http"]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def _get_token(request):
|
|
154
|
+
"""Gets the value of secret token from settings.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
request (HTTPRequest) : Used to get to app settings
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
str : token value
|
|
161
|
+
"""
|
|
162
|
+
app_settings = request.app["settings"]
|
|
163
|
+
return app_settings[await _get_token_name(request)]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def _get_token_hash(request):
|
|
167
|
+
"""Gets the hashed value of secret token from settings.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
request (HTTPRequest) : Used to get to app settings
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
str : token hash
|
|
174
|
+
"""
|
|
175
|
+
app_settings = request.app["settings"]
|
|
176
|
+
return app_settings["mwi_auth_token_hash"]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def _store_token_hash_into_session(request):
|
|
180
|
+
"""Stores the token hash into the session cookie."""
|
|
181
|
+
# Always use `new_session` during login to guard against
|
|
182
|
+
# Session Fixation. See aiohttp-session#281
|
|
183
|
+
session = await new_session(request)
|
|
184
|
+
|
|
185
|
+
# Stash token hash in session for other endpoints
|
|
186
|
+
session[await _get_token_name(request)] = await _get_token_hash(request)
|
|
187
|
+
logger.debug(f"Created session and saved cookie.")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _is_mwi_token_auth_enabled(request):
|
|
191
|
+
"""Returns True/False based on whether the mwi_auth_token_auth is enabled in app settings
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
request (HTTPRequest) : Used to get access to app settings
|
|
195
|
+
"""
|
|
196
|
+
app_settings = request.app["settings"]
|
|
197
|
+
return app_settings["mwi_is_token_auth_enabled"]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
async def _is_valid_token(token, request):
|
|
201
|
+
"""Checks if token contains expected value.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
token (str): Token string to validate
|
|
205
|
+
request (HTTPRequest) : Used to access app settings
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
_type_: True is token is valid, false otherwise.
|
|
137
209
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
210
|
+
# Check if the token provided in the request matches the hash or the original token
|
|
211
|
+
# equivalent to a == b, but protects against timing attacks
|
|
212
|
+
is_valid = compare_digest(token, await _get_token_hash(request)) or compare_digest(
|
|
213
|
+
token, await _get_token(request)
|
|
141
214
|
)
|
|
142
|
-
|
|
215
|
+
logger.debug("Token validation " + ("successful." if is_valid else "failed."))
|
|
216
|
+
return is_valid
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
async def _is_valid_token_in_session_cookie(request):
|
|
220
|
+
"""Checks the session cookie for auth token
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
request (HTTPRequest) : Used to access app settings
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Boolean : True if valid token is found
|
|
227
|
+
"""
|
|
228
|
+
logger.debug("Checking for token in session cookie...")
|
|
229
|
+
session = await get_session(request)
|
|
230
|
+
logger.debug(f"Got session cookie.")
|
|
231
|
+
token_name = await _get_token_name(request)
|
|
232
|
+
if token_name in session:
|
|
233
|
+
stored_session_token = session[token_name]
|
|
234
|
+
logger.debug(f"Found token in session cookie, validating...")
|
|
235
|
+
return await _is_valid_token(stored_session_token, request)
|
|
236
|
+
|
|
237
|
+
logger.debug("Token not found in session cookie.")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
async def _is_valid_token_in_url_query(request):
|
|
242
|
+
"""Checks the url_query parameter for auth token
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
request (HTTPRequest) : Used to access app settings
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Boolean : True if valid token is found
|
|
249
|
+
"""
|
|
250
|
+
logger.debug("Checking for token in url query...")
|
|
251
|
+
query_string = request.query_string
|
|
252
|
+
logger.debug(f"url query parameters found:{query_string}")
|
|
253
|
+
if query_string:
|
|
254
|
+
token_name = _get_token_name_for_http(request)
|
|
255
|
+
parsed_token = parse_qs(request.query_string).get(token_name)
|
|
256
|
+
if parsed_token:
|
|
257
|
+
parsed_token = parsed_token[0]
|
|
258
|
+
logger.debug("parsed_token from url query string.")
|
|
259
|
+
return await _is_valid_token(parsed_token, request)
|
|
260
|
+
|
|
261
|
+
logger.debug("Token not found in url query.")
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async def _is_valid_token_in_headers(request):
|
|
266
|
+
"""Checks the request headers for auth token
|
|
267
|
+
Additionally, save token into session cookie when a token is found.
|
|
268
|
+
This is done to avoid the front end from having to send the token in every header.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
request (HTTPRequest) : Used to access app settings
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Boolean : True if valid token is found
|
|
275
|
+
"""
|
|
276
|
+
logger.debug("Checking for token in request headers...")
|
|
277
|
+
headers = request.headers
|
|
278
|
+
token_name = _get_token_name_for_http(request)
|
|
279
|
+
if token_name in headers:
|
|
280
|
+
logger.debug(f"Token found in headers: {token_name}")
|
|
281
|
+
is_valid_token = await _is_valid_token(headers[token_name], request)
|
|
282
|
+
if is_valid_token:
|
|
283
|
+
await _store_token_hash_into_session(request)
|
|
284
|
+
return is_valid_token
|
|
285
|
+
|
|
286
|
+
logger.debug("Token not found in request headers.")
|
|
287
|
+
return False
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _generate_hash(message):
|
|
291
|
+
"""Util function to generate a sha256 hash for a message
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
message (str): message to be hashed
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
str: sha256 hash for a given message
|
|
298
|
+
"""
|
|
299
|
+
return sha256(message.encode()).hexdigest() if message is not None else None
|
|
143
300
|
|
|
144
|
-
if is_mwi_auth_token_enabled:
|
|
145
|
-
if mwi_auth_token and mwi_auth_token.strip():
|
|
146
|
-
# Use the provided MWI token, after stripping leading and trailing whitespaces
|
|
147
|
-
mwi_auth_token = mwi_auth_token.strip()
|
|
148
|
-
logger.debug(f"Using provided mwi_auth_token: {mwi_auth_token.strip()}")
|
|
149
|
-
else:
|
|
150
|
-
# Generate a token
|
|
151
|
-
mwi_auth_token = secrets.token_urlsafe()
|
|
152
|
-
else:
|
|
153
|
-
# Token Authentication must be enabled to provide custom tokens.
|
|
154
|
-
if mwi_auth_token is not None:
|
|
155
|
-
logger.error(
|
|
156
|
-
"Ignoring MWI_AUTH_TOKEN. To enable, set MWI_ENABLE_TOKEN_AUTH to True !!!"
|
|
157
|
-
)
|
|
158
|
-
mwi_auth_token = None
|
|
159
301
|
|
|
160
|
-
|
|
302
|
+
def _format_token_as_dictionary(token):
|
|
303
|
+
return {"token": token, "token_hash": _generate_hash(token)}
|