Skip to content

Commit

Permalink
!! Breaking Change !! The Token Authentication key name is updated fr…
Browse files Browse the repository at this point in the history
…om `mwi_auth_token` to `mwi-auth-token`.

Underscore separated header names are not recognized by reverse proxies like NGINX, and require additional settings to allow them to pass through. For more information, see: https://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
  • Loading branch information
Krishan Sharma authored and Prabhakar Kumar committed May 3, 2024
1 parent d8b0a88 commit c8808f4
Show file tree
Hide file tree
Showing 15 changed files with 120 additions and 74 deletions.
45 changes: 23 additions & 22 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,29 +65,30 @@ $ env MWI_ENABLE_SSL=True MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_K

`matlab-proxy` is a web server and that allows one to start and access MATLAB on the machine the server is running on. Anyone with access to the server can access MATLAB and thereby the machine on which its running.

`Token-Based Authentication` is enabled by default and the server will require a token to authenticate access. This token can be provided to the server in a few ways:
`Token-Based Authentication` is enabled by default and the server requires a token to authenticate access. Users can provide this token to the server in the following ways:

1. Through the [URL parameter](https://www.rfc-editor.org/rfc/rfc3986#section-3.4) : `mwi_auth_token`. Example:
1. Use the [URL parameter](https://www.rfc-editor.org/rfc/rfc3986#section-3.4) : `mwi-auth-token`. Example:
```html
https://localhost:8888/?mwi_auth_token=abcdef...
https://localhost:8888/?mwi-auth-token=abcdef...
```
Once provided, this information is cached in the browser and will be used in subsequent interactions
The browser caches the token for subsequent interactions.

2. Through the auth token input field in the Status Information dialogue box that is presented when the user is not already logged in.
2. Use the auth token input field in the Status Information dialogue box that appears when the user is not already logged in.
<p align="center">
<img width="800" src="./img/token_authentication_page.png">
</p>

3. Through a `mwi_auth_token` header. Example:
3. Use a `mwi-auth-token` header. Example:
``` html
mwi_auth_token:abcdef..
mwi-auth-token:abcdef..
```
> :warning: `matlab-proxy` version v0.16.0 and later require you to provide the token name in the URL or header with hyphens instead of underscores, for example `mwi-auth-token` instead of `mwi_auth_token`.

**NOTE** : Its highly recommended to use this feature along with SSL enabled as shown [here](#use-token-authentication-with-ssl-enabled).

### **Use with auto-generated tokens**
### **Use auto-generated tokens**

When enabled, `matlab-proxy` will require the URL to specify the access token using the [query component](https://www.rfc-editor.org/rfc/rfc3986#section-3.4) `mwi_auth_token`.
When enabled, `matlab-proxy` requires the URL to specify the access token using the [query component](https://www.rfc-editor.org/rfc/rfc3986#section-3.4) `mwi-auth-token`.

Example:

Expand All @@ -96,13 +97,13 @@ Example:
# Start matlab-proxy with Token-Based Authentication enabled by default
$ matlab-proxy-app

# The access link is presented in the terminal upon startup like follows:
# The access link appears in the terminal:
==================================================================================================
Access MATLAB at:
http://127.0.0.1:37109?mwi_auth_token=SY78vUw5qyf0JTJzGK4mKJlk_exkzL_SMFJyilbGtNI
http://127.0.0.1:37109?mwi-auth-token=SY78vUw5qyf0JTJzGK4mKJlk_exkzL_SMFJyilbGtNI
==================================================================================================
```
In this example `SY78vUw5qyf0JTJzGK4mKJlk_exkzL_SMFJyilbGtNI` is the token that the server would need for all future communication.
In this example `SY78vUw5qyf0JTJzGK4mKJlk_exkzL_SMFJyilbGtNI` is the token that the server needs for future communication.

After initial access, this token is cached by the browser, and all subsequent access from the same browser to the server will not require this token.
You will however need this token to access the server from a new browser session or if you have cleared cookies or have cookies disabled.
Expand All @@ -121,10 +122,10 @@ Example:
# Start matlab-proxy with Token-Based Authentication enabled, and with custom token with a value of "MyCustomSecretToken"
$ env MWI_ENABLE_TOKEN_AUTH=True MWI_AUTH_TOKEN=MyCustomSecretToken matlab-proxy-app

# The access link is presented in the terminal upon startup like follows:
# The access link appears in the terminal:
==================================================================================================
Access MATLAB at:
http://127.0.0.1:37109?mwi_auth_token=MyCustomSecretToken
http://127.0.0.1:37109?mwi-auth-token=MyCustomSecretToken
==================================================================================================
```

Expand All @@ -137,13 +138,13 @@ For example, the following command starts the server to deliver content on `HTTP
# Start matlab-proxy with Token-Based Authentication & SSL enabled with custom token with a value of "asdf"
$ env MWI_SSL_CERT_FILE="/path/to/certificate.pem" MWI_SSL_KEY_FILE="/path/to/keyfile.key" MWI_ENABLE_TOKEN_AUTH=True MWI_AUTH_TOKEN=asdf matlab-proxy-app

# The access link is presented in the terminal upon startup like follows:
# The access link appears in the terminal:
==================================================================================================
Access MATLAB at:
https://127.0.0.1:37109?mwi_auth_token=asdf
https://127.0.0.1:37109?mwi-auth-token=asdf
==================================================================================================

# NOTE: This server is running HTTP(S) !
# NOTE: This server is running HTTP(S)

```

Expand Down Expand Up @@ -173,10 +174,10 @@ $ ssh test-user@usermachine
-------------------------------------------------------------------------------------------------------------------
Your running servers are:
-------------------------------------------------------------------------------------------------------------------
1. https://127.0.0.1:46525/asdf?mwi_auth_token=asdfasdf
2. http://127.0.0.1:39057/test?mwi_auth_token=_qNJIXEbnXwrj9nxZwbJiWno0YqYSh8BMdQOR6K67y0
3. http://127.0.0.1:35647/test?mwi_auth_token=r6djdrcf591PttYlDZcVL78xIa1XgCviM9dQD-BrqDE
4. http://127.0.0.1:36537/test?mwi_auth_token=HdQ-9tooAzA0A0CrpUxP1e5crQBErMQC3tPGTkTtrVo
1. https://127.0.0.1:46525/asdf?mwi-auth-token=asdfasdf
2. http://127.0.0.1:39057/test?mwi-auth-token=_qNJIXEbnXwrj9nxZwbJiWno0YqYSh8BMdQOR6K67y0
3. http://127.0.0.1:35647/test?mwi-auth-token=r6djdrcf591PttYlDZcVL78xIa1XgCviM9dQD-BrqDE
4. http://127.0.0.1:36537/test?mwi-auth-token=HdQ-9tooAzA0A0CrpUxP1e5crQBErMQC3tPGTkTtrVo
5. http://127.0.0.1:35433/test
-------------------------------------------------------------------------------------------------------------------
Thank you.
Expand Down Expand Up @@ -209,7 +210,7 @@ Example:
# Start matlab-proxy with Token-Based Authentication disabled
$ env MWI_ENABLE_TOKEN_AUTH="False" matlab-proxy-app

# The access link is presented in the terminal upon startup like follows:
# The access link appears in the terminal:
==================================================================================================
Access MATLAB at:
http://127.0.0.1:37110
Expand Down
3 changes: 2 additions & 1 deletion gui/src/actionCreators/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
selectIsAuthenticated
} from '../selectors';
import sha256 from 'crypto-js/sha256';
import { MWI_AUTH_TOKEN_NAME_FOR_HTTP } from '../constants';

export function setAuthStatus (authentication) {
return {
Expand Down Expand Up @@ -296,7 +297,7 @@ export function updateAuthStatus (token) {
const options = {
method: 'POST',
headers: {
mwi_auth_token: tokenHash
[MWI_AUTH_TOKEN_NAME_FOR_HTTP]: tokenHash
}
};
const response = await fetchWithTimeout(dispatch, './authenticate', options, 15000);
Expand Down
23 changes: 12 additions & 11 deletions gui/src/components/App/App.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import App from './index';
import * as actionCreators from '../../actionCreators';
import state from '../../test/utils/state';
import { MAX_REQUEST_FAIL_COUNT } from '../../constants';
import { MWI_AUTH_TOKEN_NAME_FOR_HTTP } from '../../constants';

const _ = require('lodash');

Expand Down Expand Up @@ -47,7 +48,7 @@ describe('App Component', () => {
});

it('should render overlayTrigger (after closing the tutorial)', () => {
// Hide the tutorial before rendering the component.
// Hide the tutorial before rendering the component.
initialState.tutorialHidden = true;
initialState.overlayVisibility = false;

Expand All @@ -64,7 +65,7 @@ describe('App Component', () => {
});

it('should render LicensingGatherer component within the App component when no licensing is provided and user is authenticated', () => {
// Set lincensingInfo to empty object.
// Set licensingInfo to empty object.
initialState.overlayVisibility = true;
initialState.serverStatus.licensingInfo = {};
initialState.authentication.enabled = true;
Expand All @@ -79,7 +80,7 @@ describe('App Component', () => {
});

it('should render LicensingGatherer component within the App component when no licensing is provided and authentication is disabled', () => {
// Set lincensingInfo to empty object.
// Set licensingInfo to empty object.
initialState.overlayVisibility = true;
initialState.serverStatus.licensingInfo = {};
initialState.authentication.enabled = false;
Expand All @@ -93,7 +94,7 @@ describe('App Component', () => {
});

it('should render Information Component within App Component after licensing is provided and user is authenticated', () => {
// Hide the tutorial and make the overlay visible.
// Hide the tutorial and make the overlay visible.
initialState.tutorialHidden = true;
initialState.overlayVisibility = true;

Expand All @@ -112,7 +113,7 @@ describe('App Component', () => {
});

it('should render Information Component within App Component after licensing is provided and auth is not enabled', () => {
// Hide the tutorial and make the overlay visible.
// Hide the tutorial and make the overlay visible.
initialState.tutorialHidden = true;
initialState.overlayVisibility = true;

Expand All @@ -131,7 +132,7 @@ describe('App Component', () => {
});

it('should display integration terminated error', () => {
// Hide the tutorial, make the overlay visible and set fetchFailCount to MAX_REQUEST_FAIL_COUNT
// Hide the tutorial, make the overlay visible and set fetchFailCount to MAX_REQUEST_FAIL_COUNT
initialState.tutorialHidden = true;
initialState.overlayVisibility = true;
initialState.serverStatus.fetchFailCount = MAX_REQUEST_FAIL_COUNT;
Expand Down Expand Up @@ -173,7 +174,7 @@ describe('App Component', () => {
});

it('should display Confirmation component ', () => {
// Hide the tutorial and make the overlay visible
// Hide the tutorial and make the overlay visible
initialState.tutorialHidden = true;
initialState.overlayVisibility = true;

Expand All @@ -196,7 +197,7 @@ describe('App Component', () => {
});

it('should display Help Component', () => {
// Hide the tutorial and make the overlay visible
// Hide the tutorial and make the overlay visible
initialState.tutorialHidden = true;
initialState.overlayVisibility = true;

Expand Down Expand Up @@ -230,9 +231,9 @@ describe('App Component', () => {

const tokenInQuery = '12345';
it.each([
[`?mwi_auth_token=${tokenInQuery}&test1=1&test2=2`, tokenInQuery],
[`?test1=1&mwi_auth_token=${tokenInQuery}&test2=2`, tokenInQuery],
[`?test1=1&test2=2&mwi_auth_token=${tokenInQuery}`, tokenInQuery]
[`?${MWI_AUTH_TOKEN_NAME_FOR_HTTP}=${tokenInQuery}&test1=1&test2=2`, tokenInQuery],
[`?test1=1&${MWI_AUTH_TOKEN_NAME_FOR_HTTP}=${tokenInQuery}&test2=2`, tokenInQuery],
[`?test1=1&test2=2&${MWI_AUTH_TOKEN_NAME_FOR_HTTP}=${tokenInQuery}`, tokenInQuery]
])("should pick the token correctly when the query parameters are '%s'", (queryParams, expectedToken) => {
const url = 'http://localhost.com:5555';
const mockUpdateAuthStatus = jest.spyOn(actionCreators, 'updateAuthStatus');
Expand Down
7 changes: 4 additions & 3 deletions gui/src/components/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
} from '../../actionCreators';
import blurredBackground from './MATLAB-env-blur.png';
import EntitlementSelector from '../EntitlementSelector';
import { MWI_AUTH_TOKEN_NAME_FOR_HTTP } from '../../constants';

function App () {
const dispatch = useDispatch();
Expand Down Expand Up @@ -165,10 +166,10 @@ function App () {
cancel={endSession}
title='MATLAB is currently open in another window'
cancelButton={wasEverActive ? ('Cancel') : ('Continue in existing window')}
confirmButton = {wasEverActive ? ('Confirm') : ('Continue in this window')}>
confirmButton={wasEverActive ? ('Confirm') : ('Continue in this window')}>
{wasEverActive
? 'You have been disconnected because MATLAB is open in another window. Click on Confirm to continue using MATLAB here.'
: <div>MATLAB is open in another window and cannot be opened in a second window or tab at the same time.<br></br>Would you like to continue in this window?</div> }
: <div>MATLAB is open in another window and cannot be opened in a second window or tab at the same time.<br></br>Would you like to continue in this window?</div>}
</Confirmation>
);
}
Expand Down Expand Up @@ -204,7 +205,7 @@ function App () {

useEffect(() => {
const queryParams = parseQueryParams(window.location);
const token = queryParams.get('mwi_auth_token');
const token = queryParams.get(MWI_AUTH_TOKEN_NAME_FOR_HTTP);

if (token) {
dispatch(updateAuthStatus(token));
Expand Down
1 change: 1 addition & 0 deletions gui/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
export const STATUS_REQUEST_INTERVAL_MS = 1000;
// Maximum number of consecutive failed requests allowed before triggering a connection error
export const MAX_REQUEST_FAIL_COUNT = 60;
export const MWI_AUTH_TOKEN_NAME_FOR_HTTP = 'mwi-auth-token';
15 changes: 9 additions & 6 deletions matlab_proxy/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
from typing import Final, Optional

from matlab_proxy import util
from matlab_proxy.settings import (
get_process_startup_timeout,
)
from matlab_proxy.constants import (
CONNECTOR_SECUREPORT_FILENAME,
MATLAB_LOGS_FILE_NAME,
)
from matlab_proxy.settings import (
get_process_startup_timeout,
)
from matlab_proxy.util import mw, mwi, system, windows
from matlab_proxy.util.mwi import environment_variables as mwi_env
from matlab_proxy.util.mwi import token_auth
Expand All @@ -28,12 +28,11 @@
LicensingError,
MatlabError,
OnlineLicensingError,
XvfbError,
UIVisibleFatalError,
XvfbError,
log_error,
)


logger = mwi.logger.get()


Expand Down Expand Up @@ -314,7 +313,11 @@ def _get_token_auth_headers(self) -> Optional[dict]:
[Dict | None]: Returns token authentication headers if any.
"""
return (
{self.settings["mwi_auth_token_name"]: self.settings["mwi_auth_token_hash"]}
{
self.settings["mwi_auth_token_name_for_http"]: self.settings[
"mwi_auth_token_hash"
]
}
if self.settings["mwi_is_token_auth_enabled"]
else None
)
Expand Down
1 change: 1 addition & 0 deletions matlab_proxy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@

# This constant when set to True restricts the number of active sessions to one
IS_CONCURRENCY_CHECK_ENABLED: Final[bool] = True
MWI_AUTH_TOKEN_NAME_FOR_HTTP = "mwi-auth-token"
11 changes: 7 additions & 4 deletions matlab_proxy/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import matlab_proxy
from matlab_proxy import constants
from matlab_proxy.constants import MWI_AUTH_TOKEN_NAME_FOR_HTTP
from matlab_proxy.util import mwi, system
from matlab_proxy.util.mwi import environment_variables as mwi_env
from matlab_proxy.util.mwi import token_auth
Expand Down Expand Up @@ -197,11 +198,12 @@ def get_dev_settings(config):
"mwi_logs_root_dir": get_mwi_logs_root_dir(dev=True),
"mw_context_tags": get_mw_context_tags(matlab_proxy.get_default_config_name()),
"mwi_server_url": None,
"mwi_is_token_auth_enabled": mwi_auth_token != None,
"mwi_is_token_auth_enabled": mwi_auth_token is not None,
"mwi_auth_status": False,
"mwi_auth_token": mwi_auth_token,
"mwi_auth_token_hash": mwi_auth_token_hash,
"mwi_auth_token_name": mwi_env.get_env_name_mwi_auth_token().lower(),
"mwi_auth_token_name_for_http": MWI_AUTH_TOKEN_NAME_FOR_HTTP,
"mwi_auth_token_name_for_env": mwi_env.get_env_name_mwi_auth_token().lower(),
"mwi_use_existing_license": mwi.validators.validate_use_existing_licensing(
os.getenv(mwi_env.get_env_name_mwi_use_existing_license(), "")
),
Expand Down Expand Up @@ -322,11 +324,12 @@ def get_server_settings(config_name):
"mw_context_tags": get_mw_context_tags(config_name),
# The url where the matlab-proxy server is accessible at
"mwi_server_url": None,
"mwi_is_token_auth_enabled": mwi_auth_token != None,
"mwi_is_token_auth_enabled": mwi_auth_token is not None,
"mwi_auth_status": False,
"mwi_auth_token": mwi_auth_token,
"mwi_auth_token_hash": mwi_auth_token_hash,
"mwi_auth_token_name": mwi_env.get_env_name_mwi_auth_token().lower(),
"mwi_auth_token_name_for_http": MWI_AUTH_TOKEN_NAME_FOR_HTTP,
"mwi_auth_token_name_for_env": mwi_env.get_env_name_mwi_auth_token().lower(),
"mwi_use_existing_license": mwi.validators.validate_use_existing_licensing(
os.getenv(mwi_env.get_env_name_mwi_use_existing_license(), "")
),
Expand Down
24 changes: 19 additions & 5 deletions matlab_proxy/util/mwi/token_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def generate_mwi_auth_token_and_hash():
def get_mwi_auth_token_access_str(app_settings):
"""Returns formatted string with mwi token for use with server URL"""
if app_settings["mwi_is_token_auth_enabled"]:
mwi_auth_token_name = app_settings["mwi_auth_token_name"]
mwi_auth_token_name = app_settings["mwi_auth_token_name_for_http"]
mwi_auth_token = app_settings["mwi_auth_token"]
return f"?{mwi_auth_token_name}={mwi_auth_token}"

Expand Down Expand Up @@ -134,7 +134,20 @@ async def _get_token_name(request):
str : token name
"""
app_settings = request.app["settings"]
return app_settings["mwi_auth_token_name"]
return app_settings["mwi_auth_token_name_for_env"]


def _get_token_name_for_http(request):
"""Gets the name of the token from settings.
Args:
request (HTTPRequest) : Used to get to app settings
Returns:
str : token name
"""
app_settings = request.app["settings"]
return app_settings["mwi_auth_token_name_for_http"]


async def _get_token(request):
Expand Down Expand Up @@ -238,11 +251,11 @@ async def _is_valid_token_in_url_query(request):
query_string = request.query_string
logger.debug(f"url query parameters found:{query_string}")
if query_string:
token_name = await _get_token_name(request)
token_name = _get_token_name_for_http(request)
parsed_token = parse_qs(request.query_string).get(token_name)
if parsed_token:
parsed_token = parsed_token[0]
logger.debug(f"parsed_token from url query string.")
logger.debug("parsed_token from url query string.")
return await _is_valid_token(parsed_token, request)

logger.debug("Token not found in url query.")
Expand All @@ -262,8 +275,9 @@ async def _is_valid_token_in_headers(request):
"""
logger.debug("Checking for token in request headers...")
headers = request.headers
token_name = await _get_token_name(request)
token_name = _get_token_name_for_http(request)
if token_name in headers:
logger.debug(f"Token found in headers: {token_name}")
is_valid_token = await _is_valid_token(headers[token_name], request)
if is_valid_token:
await _store_token_hash_into_session(request)
Expand Down
Loading

0 comments on commit c8808f4

Please sign in to comment.