Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
aab4750
extended configuration manager with optional OIDC sections
sjanssen2 Mar 20, 2024
49b0448
flake8
sjanssen2 Mar 20, 2024
2840601
also provide a label for a speaking name of the identity provider
sjanssen2 Mar 20, 2024
f1c9149
start implementing the OIDC dance
sjanssen2 Mar 20, 2024
2eb6d08
modal not necessary, if only one provider was defined
sjanssen2 Mar 20, 2024
48ca02a
error handling of provider not in config file
sjanssen2 Mar 20, 2024
dc4bd20
adding pycurl package to enable tornado curl_httpclients
sjanssen2 Mar 20, 2024
e1f3c13
a new method to create a user, if information do not need to be enter…
sjanssen2 Mar 20, 2024
48f09a5
full OIDC dance implemented
sjanssen2 Mar 20, 2024
baf40df
add an admin page to activate users which requested authorization thr…
sjanssen2 Mar 20, 2024
670a55a
flake8
sjanssen2 Mar 20, 2024
091ffc6
adding menu entry for user authorization
sjanssen2 Mar 20, 2024
1feefc0
do not expose traditional qiita internal user authentication, if OIDC…
sjanssen2 Mar 21, 2024
29ce7dd
use Qiita typical modal for OIDC login
sjanssen2 Mar 21, 2024
2ca5bb8
wrong menu entrie affected
sjanssen2 Mar 21, 2024
1b787cb
always allow logout
sjanssen2 Mar 21, 2024
88319b2
improved error handling
sjanssen2 Mar 21, 2024
02d9af0
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Mar 22, 2024
b1e1b6b
revert: let user change their profile, but not password - if provided…
sjanssen2 Mar 22, 2024
a7d3b84
speaking button names + move into correct div to always get displayed
sjanssen2 Mar 22, 2024
125835a
use email from config + loop user_info from OIDC to fill DB
sjanssen2 Mar 22, 2024
5f28092
use OIDC info to prefil user information
sjanssen2 Mar 22, 2024
19b4d7b
drop admin user authorization
sjanssen2 Apr 4, 2024
33f2879
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Apr 4, 2024
c9d413a
using the well-known json dict instead of manually providing multiple…
sjanssen2 Jun 5, 2024
6bfafcb
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Jun 5, 2024
9a5e7cc
flake8
sjanssen2 Jun 5, 2024
b2fc279
flake8
sjanssen2 Jun 5, 2024
5cc0896
add ability to display OIDC logos
sjanssen2 Jun 5, 2024
949084d
add OIDC logo
sjanssen2 Jun 5, 2024
c3b040b
revert to dev branch
sjanssen2 Jun 5, 2024
d96bbae
fixing config manager tests
sjanssen2 Jun 5, 2024
a491870
Merge pull request #7 from jlab/auth_oidc_wellknown
sjanssen2 Jun 5, 2024
b1baece
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Jun 6, 2024
81fdcbf
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Jun 20, 2024
e0c4002
add missing template
sjanssen2 Jun 20, 2024
bb9c685
Merge branch 'add_admin_purge_template' of github.com:jlab/qiita into…
sjanssen2 Jun 20, 2024
79e794a
Merge branch 'dev' of https://github.com/qiita-spots/qiita into auth_…
sjanssen2 Jun 21, 2024
c9aacec
Merge branch 'master' of github.com:qiita-spots/qiita into auth_oidc_…
sjanssen2 Mar 4, 2025
a5deb83
Merge pull request #10 from jlab/auth_oidc_merged
sjanssen2 Mar 4, 2025
7693c5e
extended configuration manager with optional OIDC sections
sjanssen2 Mar 20, 2024
b4ab605
flake8
sjanssen2 Mar 4, 2025
baa7230
also provide a label for a speaking name of the identity provider
sjanssen2 Mar 20, 2024
52e57ca
start implementing the OIDC dance
sjanssen2 Mar 20, 2024
4061373
modal not necessary, if only one provider was defined
sjanssen2 Mar 20, 2024
51307d1
error handling of provider not in config file
sjanssen2 Mar 20, 2024
7a0ec9f
adding pycurl package to enable tornado curl_httpclients
sjanssen2 Mar 20, 2024
0c365a1
a new method to create a user, if information do not need to be enter…
sjanssen2 Mar 20, 2024
e993a99
full OIDC dance implemented
sjanssen2 Mar 20, 2024
ca5f7f6
add an admin page to activate users which requested authorization thr…
sjanssen2 Mar 20, 2024
4d5c6a2
flake8
sjanssen2 Mar 20, 2024
fd6d15e
adding menu entry for user authorization
sjanssen2 Mar 20, 2024
9c8b824
do not expose traditional qiita internal user authentication, if OIDC…
sjanssen2 Mar 21, 2024
a654e48
use Qiita typical modal for OIDC login
sjanssen2 Mar 21, 2024
27f6d35
always allow logout
sjanssen2 Mar 21, 2024
85bf1fa
improved error handling
sjanssen2 Mar 21, 2024
8a504cc
revert: let user change their profile, but not password - if provided…
sjanssen2 Mar 22, 2024
ef05eed
speaking button names + move into correct div to always get displayed
sjanssen2 Mar 22, 2024
a5270a0
use email from config + loop user_info from OIDC to fill DB
sjanssen2 Mar 22, 2024
2efb70f
use OIDC info to prefil user information
sjanssen2 Mar 22, 2024
c8b1198
drop admin user authorization
sjanssen2 Apr 4, 2024
3d6f718
using the well-known json dict instead of manually providing multiple…
sjanssen2 Jun 5, 2024
3957030
flake8
sjanssen2 Jun 5, 2024
648f2f9
flake8
sjanssen2 Jun 5, 2024
73f92b9
add ability to display OIDC logos
sjanssen2 Jun 5, 2024
0dc243d
add OIDC logo
sjanssen2 Jun 5, 2024
9b81163
fixing config manager tests
sjanssen2 Jun 5, 2024
bb03167
Merge branch 'auth_oidc' of github.com:jlab/qiita into auth_oidc
sjanssen2 Mar 4, 2025
a76288b
Merge branch 'master' of github.com:qiita-spots/qiita into auth_oidc
sjanssen2 Mar 11, 2025
8cc718f
Merge branch 'dev' of github.com:qiita-spots/qiita into auth_oidc
sjanssen2 Mar 11, 2025
89cab41
Merge branch 'master' of github.com:qiita-spots/qiita into auth_oidc
sjanssen2 Mar 11, 2025
5c3fa7b
Merge branch 'dev' of github.com:jlab/qiita into dev
sjanssen2 Mar 11, 2025
769e382
multiple validation jobs should be submitted as lead and dependent jo…
sjanssen2 Mar 11, 2025
1a61930
Merge branch 'dev' of github.com:qiita-spots/qiita into auth_oidc
sjanssen2 Mar 12, 2025
1b55c47
Merge branch 'dev' of github.com:qiita-spots/qiita into dev
sjanssen2 Mar 12, 2025
fa26cff
expose additional HTTP endpoints to send and retrieve data over http …
sjanssen2 Aug 15, 2025
d377393
add another configuration variable to decide if HTTPS filetransfer en…
sjanssen2 Aug 18, 2025
5e34bab
aim to subtract OIDC changes
sjanssen2 Aug 18, 2025
931bc4d
remove image
sjanssen2 Aug 18, 2025
b0de5c7
restore blank line
sjanssen2 Aug 18, 2025
6c1fd0e
Merge branch 'master' of github.com:jlab/qiita into uncouplePlugins
sjanssen2 Aug 18, 2025
5499e04
Merge branch 'dev' of github.com:jlab/qiita into uncouplePlugins
sjanssen2 Aug 18, 2025
44d4315
revert changes
sjanssen2 Aug 18, 2025
2b70f7c
revert
sjanssen2 Aug 18, 2025
dd622c6
revert
sjanssen2 Aug 18, 2025
85d30f4
return info as "reason"
sjanssen2 Aug 18, 2025
9858b42
initial set of tests
sjanssen2 Aug 18, 2025
2f43e89
codestyle
sjanssen2 Aug 18, 2025
3f77f85
more codestyle
sjanssen2 Aug 18, 2025
ad06f27
enable endpoints
sjanssen2 Aug 18, 2025
28cbb98
tests pass locally
sjanssen2 Aug 19, 2025
3edd07e
adding in more functionallity for testing, i.e. operate on token base…
sjanssen2 Aug 19, 2025
5bad769
avoid name collisions
sjanssen2 Aug 19, 2025
d5f00d7
debug
sjanssen2 Aug 19, 2025
e9028a8
more debug
sjanssen2 Aug 19, 2025
7efc716
change debug
sjanssen2 Aug 19, 2025
91bbbe4
continue debug
sjanssen2 Aug 19, 2025
2342304
adapt filepaths to github runner
sjanssen2 Aug 19, 2025
3fce312
remove debug and fix codestyle
sjanssen2 Aug 19, 2025
be38c10
remove debug step
sjanssen2 Aug 19, 2025
6cdbbe1
after talking with Antonio, we decided that no additional config para…
sjanssen2 Aug 27, 2025
53adaf0
Update config_test.cfg
sjanssen2 Aug 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/qiita-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ jobs:

echo "4. Setting up nginx"
mkdir -p /usr/share/miniconda/envs/qiita/var/run/nginx/
sed -i "s|alias /Users/username|alias /home/runner/work/qiita|" ${PWD}/qiita_pet/nginx_example.conf
nginx -c ${PWD}/qiita_pet/nginx_example.conf

echo "5. Setting up qiita"
Expand Down
51 changes: 50 additions & 1 deletion qiita_db/handlers/tests/oauthbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
# The full license is in the file LICENSE, distributed with this software.
# -----------------------------------------------------------------------------

from qiita_core.qiita_settings import r_client
import requests
import os
import sys
from qiita_core.qiita_settings import r_client, qiita_config

from qiita_pet.test.tornado_test_base import TestHandlerBase

Expand All @@ -19,3 +22,49 @@ def setUp(self):
r_client.hset(self.token, 'grant_type', 'client')
r_client.expire(self.token, 20)
super(OauthTestingBase, self).setUp()
self._session = requests.Session()
# should point to client certificat file:
# /qiita/qiita_core/support_files/ci_rootca.crt
self._verify = os.environ['QIITA_ROOTCA_CERT']
self._fetch_token()

self._files_to_remove = []

def tearDown(self):
for fp in self._files_to_remove:
if os.path.exists(fp):
os.remove(fp)

def _fetch_token(self):
data = {
'client_id': '4MOBzUBHBtUmwhaC258H7PS0rBBLyGQrVxGPgc9g305bvVhf6h',
'client_secret':
('rFb7jwAb3UmSUN57Bjlsi4DTl2owLwRpwCc0SggRN'
'EVb2Ebae2p5Umnq20rNMhmqN'),
'grant_type': 'client'}
Comment on lines +39 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be part of the configuration file, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are credentials necessary to be present in qiitaDB to obtain a token ... finally on the plugin side. But here it is within the db side, just for testing.

For real plugins, client_id and client_secret get initialized with random values and the "plugin registry" process is when qiita admins "trust" this plugin and add their credentials into the DB.

So yes, these are values stored in the plugin configuration file

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened an issue about this: #3481

resp = self._session.post(
"%s/qiita_db/authenticate/" % qiita_config.base_url,
verify=self._verify, data=data, timeout=80)
if resp.status_code != 200:
raise ValueError("_fetchToken() POST request failed")
self._token = resp.json()['access_token']
print('obtained access_token = %s' % self._token, file=sys.stderr)

def post_authed(self, url, **kwargs):
if 'headers' not in kwargs:
kwargs['headers'] = {}
if 'Authorization' not in kwargs['headers']:
kwargs['headers']['Authorization'] = 'Bearer %s' % self._token

r = self._session.post(
qiita_config.base_url + url, verify=self._verify, **kwargs)
r.close()

return r

def get_authed(self, url):
r = self._session.get(qiita_config.base_url + url, verify=self._verify,
headers={'Authorization': 'Bearer %s' %
self._token})
r.close()
return r
9 changes: 9 additions & 0 deletions qiita_pet/handlers/cloud_handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .file_transfer_handlers import (FetchFileFromCentralHandler,
PushFileToCentralHandler)

__all__ = ['FetchFileFromCentralHandler']

ENDPOINTS = [
(r"/cloud/fetch_file_from_central/(.*)", FetchFileFromCentralHandler),
(r"/cloud/push_file_to_central/", PushFileToCentralHandler)
]
94 changes: 94 additions & 0 deletions qiita_pet/handlers/cloud_handlers/file_transfer_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import os

from tornado.web import HTTPError, RequestHandler
from tornado.gen import coroutine

from qiita_core.util import execute_as_transaction
from qiita_db.handlers.oauth2 import authenticate_oauth
from qiita_core.qiita_settings import qiita_config


class FetchFileFromCentralHandler(RequestHandler):
@authenticate_oauth
@coroutine
@execute_as_transaction
def get(self, requested_filepath):
# ensure we have an absolute path, i.e. starting at /
filepath = os.path.join(os.path.sep, requested_filepath)
# use a canonic version of the filepath
filepath = os.path.abspath(filepath)

# canonic version of base_data_dir
basedatadir = os.path.abspath(qiita_config.base_data_dir)

# TODO: can we somehow check, if the requesting client (which should be
# one of the plugins) was started from a user that actually has
# access to the requested file?
Comment on lines +24 to +26
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, a user can only start a job if they have access to that artifact. However, I don't see why this shouldn't be added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also undecided at the moment. I think of multiple "runner" locations for plugins, e.g. UCSD, JLU Gießen, .... Do we really want to trust these runners to be nice and access only files of the central filesystem necessary for their job?!
What if one of those plugins iterate over all artifacts even from private studies?!


if not filepath.startswith(basedatadir):
# attempt to access files outside of the BASE_DATA_DIR
# intentionally NOT reporting the actual location to avoid exposing
# instance internal information
raise HTTPError(403, reason=(
"You cannot access files outside of "
"the BASE_DATA_DIR of Qiita!"))

if not os.path.exists(filepath):
raise HTTPError(403, reason=(
"The requested file is not present in Qiita's BASE_DATA_DIR!"))

# delivery of the file via nginx requires replacing the basedatadir
# with the prefix defined in the nginx configuration for the
# base_data_dir, '/protected/' by default
protected_filepath = filepath.replace(basedatadir, '/protected')

self.set_header('Content-Type', 'application/octet-stream')
self.set_header('Content-Transfer-Encoding', 'binary')
self.set_header('X-Accel-Redirect', protected_filepath)
self.set_header('Content-Description', 'File Transfer')
self.set_header('Expires', '0')
self.set_header('Cache-Control', 'no-cache')
self.set_header('Content-Disposition',
'attachment; filename=%s' % os.path.basename(
protected_filepath))
self.finish()


class PushFileToCentralHandler(RequestHandler):
@authenticate_oauth
@coroutine
@execute_as_transaction
def post(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this is a web blocking operation? If yes, I guess we should add a new block to the nginx/supervisord files so they have their own workers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, shouldn't the @coroutine decorator spawn a new "thread" / "worker"? But this might only be true for python/tornado, not for the nginx itself. But doesn't come nginx with load balancing?

if not self.request.files:
raise HTTPError(400, reason='No files to upload defined!')

# canonic version of base_data_dir
basedatadir = os.path.abspath(qiita_config.base_data_dir)
stored_files = []

for filespath, filelist in self.request.files.items():
if filespath.startswith(basedatadir):
filespath = filespath[len(basedatadir):]

for file in filelist:
filepath = os.path.join(filespath, file['filename'])
# remove leading /
if filepath.startswith(os.sep):
filepath = filepath[len(os.sep):]
filepath = os.path.abspath(os.path.join(basedatadir, filepath))

if os.path.exists(filepath):
raise HTTPError(403, reason=(
"The requested file is already "
"present in Qiita's BASE_DATA_DIR!"))

os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "wb") as f:
f.write(file['body'])
stored_files.append(filepath)

self.write("Stored %i files into BASE_DATA_DIR of Qiita:\n%s\n" % (
len(stored_files),
'\n'.join(map(lambda x: ' - %s' % x, stored_files))))

self.finish()
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from unittest import main
from os.path import exists, basename
from os import remove
import filecmp

from qiita_db.handlers.tests.oauthbase import OauthTestingBase
import qiita_db as qdb


class FetchFileFromCentralHandlerTests(OauthTestingBase):
def setUp(self):
super(FetchFileFromCentralHandlerTests, self).setUp()

def test_get(self):
endpoint = '/cloud/fetch_file_from_central/'
base_data_dir = qdb.util.get_db_files_base_dir()

obs = self.get_authed(endpoint + 'nonexistingfile')
self.assertEqual(obs.status_code, 403)
self.assertIn('outside of the BASE_DATA_DIR', obs.reason)

obs = self.get_authed(
endpoint + base_data_dir[1:] + '/nonexistingfile')
self.assertEqual(obs.status_code, 403)
self.assertIn('The requested file is not present', obs.reason)

obs = self.get_authed(
endpoint + base_data_dir[1:] +
'/raw_data/FASTA_QUAL_preprocessing.fna')
self.assertEqual(obs.status_code, 200)
self.assertIn('FLP3FBN01ELBSX length=250 xy=1766_01', str(obs.content))


class PushFileToCentralHandlerTests(OauthTestingBase):
def setUp(self):
super(PushFileToCentralHandlerTests, self).setUp()

def test_post(self):
endpoint = '/cloud/push_file_to_central/'
base_data_dir = qdb.util.get_db_files_base_dir()

# create a test file "locally", i.e. in current working directory
fp_source = 'foo.bar'
with open(fp_source, 'w') as f:
f.write("this is a test\n")
self._files_to_remove.append(fp_source)

# if successful, expected location of the file in BASE_DATA_DIR
fp_target = base_data_dir + '/bar/' + basename(fp_source)
self._files_to_remove.append(fp_target)

# create a second test file
fp_source2 = 'foo_two.bar'
with open(fp_source2, 'w') as f:
f.write("this is another test\n")
self._files_to_remove.append(fp_source2)
fp_target2 = base_data_dir + '/barr/' + basename(fp_source2)
self._files_to_remove.append(fp_target2)

# test raise error if no file is given
obs = self.post_authed(endpoint)
self.assertEqual(obs.reason, "No files to upload defined!")

# test correct mechanism
with open(fp_source, 'rb') as fh:
obs = self.post_authed(endpoint, files={'bar/': fh})
self.assertIn('Stored 1 files into BASE_DATA_DIR of Qiita',
str(obs.content))
self.assertTrue(filecmp.cmp(fp_source, fp_target, shallow=False))

# check if error is raised, if file already exists
with open(fp_source, 'rb') as fh:
obs = self.post_authed(endpoint, files={'bar/': fh})
self.assertIn("already present in Qiita's BASE_DATA_DIR!",
obs.reason)

# test transfer of multiple files
if exists(fp_target):
remove(fp_target)
with open(fp_source, 'rb') as fh1:
with open(fp_source2, 'rb') as fh2:
obs = self.post_authed(
endpoint, files={'bar/': fh1, 'barr/': fh2})
self.assertIn('Stored 2 files into BASE_DATA_DIR of Qiita',
str(obs.content))
self.assertTrue(filecmp.cmp(fp_source, fp_target,
shallow=False))
self.assertTrue(filecmp.cmp(fp_source2, fp_target2,
shallow=False))


if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions qiita_pet/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
from qiita_pet.handlers.rest import ENDPOINTS as REST_ENDPOINTS
from qiita_pet.handlers.qiita_redbiom import RedbiomPublicSearch
from qiita_pet.handlers.public import PublicHandler
from qiita_pet.handlers.cloud_handlers import ENDPOINTS as CLOUD_ENDPOINTS

if qiita_config.portal == "QIITA":
from qiita_pet.handlers.portal import (
Expand Down Expand Up @@ -244,6 +245,11 @@ def __init__(self):
(r"/qiita_db/studies/(.*)", APIStudiesListing)
]

# expose endpoints necessary for https file communication between
# master and plugins IF no shared file system for base_data_dir is
# intended
handlers.extend(CLOUD_ENDPOINTS)

# rest endpoints
handlers.extend(REST_ENDPOINTS)

Expand Down
Loading