Skip to content

Commit

Permalink
v 1.1.0: Secure /info with auth (tableau#415)
Browse files Browse the repository at this point in the history
- Authorization is now required for the /info API method.
  This method did not check authentication previously. This change is
  backwards compatible with Tableau clients.

- Improved config parsing flexibility. Previously the
  TABPY_EVALUATE_TIMEOUT setting would be set to a default if
  tabpy couldn't parse the value. Now it will throw an exception
  at startup.
  • Loading branch information
0golovatyi authored Apr 23, 2020
1 parent 2d3a5f9 commit 3cd106b
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 134 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Code coverage

on: [push, pull_request]

jobs:
build:
name: ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
matrix:
python-version: [3.7]
os: [ubuntu-latest]

steps:
- uses: actions/checkout@v1

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements_test.txt
pip install -r requirements_dev.txt
- name: Test with pytest
run: |
pytest tests --cov=tabpy --cov-config=setup.cfg
coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
- name: Test with pytest
run: |
pytest tests
12 changes: 12 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## v1.1.0

### Improvements

- Authorization is now required for the /info API method.
This method did not check authentication previously. This change is
backwards compatible with Tableau clients.
- Improved config parsing flexibility. Previously the
TABPY_EVALUATE_TIMEOUT setting would be set to a default if
tabpy couldn't parse the value. Now it will throw an exception
at startup.

## v1.0.0

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![GitHub](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://raw.githubusercontent.com/Tableau/TabPy/master/LICENSE)

[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tableau/tabpy/Test%20Run%20on%20Push)](https://github.com/tableau/TabPy/actions?query=workflow%3A%22Test+Run+on+Push%22)
![Scrutinizer coverage (GitHub/BitBucket)](https://img.shields.io/scrutinizer/coverage/g/tableau/tabpy)
![Scrutinizer coverage](https://img.shields.io/scrutinizer/coverage/g/tableau/tabpy)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/tableau/TabPy/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/tableau/TabPy/?branch=master)

[![Python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://www.python.org/downloads/release/python-360/)
Expand Down
10 changes: 7 additions & 3 deletions docs/server-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ Passwords in the password file are hashed with PBKDF2.
with hosting OS mechanisms. Ideally the file should only be accessible
for reading with the account under which TabPy runs and TabPy admin account.**

There is a `tabpy-user` command provided with `tabpy`
package to operate with accounts in the password file. Run
`tabpy-user -h` to see how to use it.
There is a `tabpy-user` command provided with `tabpy` package to
operate with accounts in the password file. Run `tabpy-user -h`
to see how to use it.

After making any changes to the password file, TabPy needs to be restarted.

Expand Down Expand Up @@ -245,6 +245,10 @@ will be generated and displayed in the command line.
To delete an account open password file in any text editor and delete the
line with the user name.

### Endpoint Security

All endpoints require authentication if it is enabled for the server.

## Logging

Logging for TabPy is implemented with Python's standard logger and can be configured
Expand Down
1 change: 1 addition & 0 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
coveralls
hypothesis
pytest
pytest-cov
Expand Down
2 changes: 1 addition & 1 deletion tabpy/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
46 changes: 19 additions & 27 deletions tabpy/tabpy_server/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,17 @@ def try_exit(self):

return application

def _set_parameter(self, parser, settings_key, config_key, default_val):
def _set_parameter(self, parser, settings_key, config_key, default_val, parse_function):
key_is_set = False

if (
config_key is not None
and parser.has_section("TabPy")
and parser.has_option("TabPy", config_key)
):
self.settings[settings_key] = parser.get("TabPy", config_key)
if parse_function is None:
parse_function = parser.get
self.settings[settings_key] = parse_function("TabPy", config_key)
key_is_set = True
logger.debug(
f"Parameter {settings_key} set to "
Expand Down Expand Up @@ -252,40 +254,30 @@ def _parse_config(self, config_file):
)

settings_parameters = [
(SettingsParameters.Port, ConfigParameters.TABPY_PORT, 9004),
(SettingsParameters.ServerVersion, None, __version__),
(SettingsParameters.EvaluateTimeout, ConfigParameters.TABPY_EVALUATE_TIMEOUT, 30),
(SettingsParameters.Port, ConfigParameters.TABPY_PORT, 9004, None),
(SettingsParameters.ServerVersion, None, __version__, None),
(SettingsParameters.EvaluateTimeout, ConfigParameters.TABPY_EVALUATE_TIMEOUT,
30, parser.getfloat),
(SettingsParameters.UploadDir, ConfigParameters.TABPY_QUERY_OBJECT_PATH,
os.path.join(pkg_path, "tmp", "query_objects")),
os.path.join(pkg_path, "tmp", "query_objects"), None),
(SettingsParameters.TransferProtocol, ConfigParameters.TABPY_TRANSFER_PROTOCOL,
"http"),
"http", None),
(SettingsParameters.CertificateFile, ConfigParameters.TABPY_CERTIFICATE_FILE,
None),
(SettingsParameters.KeyFile, ConfigParameters.TABPY_KEY_FILE, None),
None, None),
(SettingsParameters.KeyFile, ConfigParameters.TABPY_KEY_FILE, None, None),
(SettingsParameters.StateFilePath, ConfigParameters.TABPY_STATE_PATH,
os.path.join(pkg_path, "tabpy_server")),
os.path.join(pkg_path, "tabpy_server"), None),
(SettingsParameters.StaticPath, ConfigParameters.TABPY_STATIC_PATH,
os.path.join(pkg_path, "tabpy_server", "static")),
(ConfigParameters.TABPY_PWD_FILE, ConfigParameters.TABPY_PWD_FILE, None),
os.path.join(pkg_path, "tabpy_server", "static"), None),
(ConfigParameters.TABPY_PWD_FILE, ConfigParameters.TABPY_PWD_FILE, None, None),
(SettingsParameters.LogRequestContext, ConfigParameters.TABPY_LOG_DETAILS,
"false"),
"false", None),
(SettingsParameters.MaxRequestSizeInMb, ConfigParameters.TABPY_MAX_REQUEST_SIZE_MB,
100),
100, None),
]

for setting, parameter, default_val in settings_parameters:
self._set_parameter(parser, setting, parameter, default_val)

try:
self.settings[SettingsParameters.EvaluateTimeout] = float(
self.settings[SettingsParameters.EvaluateTimeout]
)
except ValueError:
logger.warning(
"Evaluate timeout must be a float type. Defaulting "
"to evaluate timeout of 30 seconds."
)
self.settings[SettingsParameters.EvaluateTimeout] = 30
for setting, parameter, default_val, parse_function in settings_parameters:
self._set_parameter(parser, setting, parameter, default_val, parse_function)

if not os.path.exists(self.settings[SettingsParameters.UploadDir]):
os.makedirs(self.settings[SettingsParameters.UploadDir])
Expand Down
7 changes: 4 additions & 3 deletions tabpy/tabpy_server/handlers/service_info_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ def initialize(self, app):
super(ServiceInfoHandler, self).initialize(app)

def get(self):
# do not check for authentication - this method
# is the only way for client to collect info about
# supported API versions and required features
if self.should_fail_with_not_authorized():
self.fail_with_not_authorized()
return

self._add_CORS_header()
info = {}
info["description"] = self.tabpy_state.get_description()
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/server_tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ def test_custom_evaluate_timeout_invalid(
)
config_file.close()

app = TabPyApp(self.config_file.name)
self.assertEqual(app.settings["evaluate_timeout"], 30.0)
with self.assertRaises(ValueError) as err:
TabPyApp(self.config_file.name)

@patch("tabpy.tabpy_server.app.app.os")
@patch("tabpy.tabpy_server.app.app.os.path.exists", return_value=True)
Expand Down
Loading

0 comments on commit 3cd106b

Please sign in to comment.