From 96f964acd215e95ed780c9d34a37cf5c53ffdd1c Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Thu, 22 Aug 2024 12:14:45 -0700 Subject: [PATCH 01/33] transferred AAD changes (not SxS) --- seqr/utils/social_auth_pipeline.py | 6 ++++++ seqr/views/apis/data_manager_api.py | 5 +++-- seqr/views/utils/terra_api_utils.py | 5 +++-- settings.py | 33 ++++++++++++++++++++++------- ui/shared/utils/constants.js | 2 +- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/seqr/utils/social_auth_pipeline.py b/seqr/utils/social_auth_pipeline.py index b747cdd5be..c51e552a34 100644 --- a/seqr/utils/social_auth_pipeline.py +++ b/seqr/utils/social_auth_pipeline.py @@ -37,3 +37,9 @@ def log_signed_in(backend, response, is_new=False, *args, **kwargs): logger.info('Logged in {} ({})'.format(response['email'], backend.name), extra={'user_email': response['email']}) if is_new: logger.info('Created user {} ({})'.format(response['email'], backend.name), extra={'user_email': response['email']}) + + +def log_azure_signed_in(backend, details, is_new=False, *args, **kwargs): + logger.info('Logged in {} ({})'.format(details['email'], backend.name), extra={'user_email': details['email']}) + if is_new: + logger.info('Created user {} ({})'.format(details['email'], backend.name), extra={'user_email': details['email']}) diff --git a/seqr/views/apis/data_manager_api.py b/seqr/views/apis/data_manager_api.py index 1ad54f5d99..c834756e1b 100644 --- a/seqr/views/apis/data_manager_api.py +++ b/seqr/views/apis/data_manager_api.py @@ -32,7 +32,8 @@ from seqr.models import Sample, RnaSample, Individual, Project, PhenotypePrioritization -from settings import KIBANA_SERVER, KIBANA_ELASTICSEARCH_PASSWORD, SEQR_SLACK_LOADING_NOTIFICATION_CHANNEL, BASE_URL +from settings import KIBANA_SERVER, KIBANA_ELASTICSEARCH_PASSWORD, KIBANA_ELASTICSEARCH_USER, \ + SEQR_SLACK_LOADING_NOTIFICATION_CHANNEL, BASE_URL logger = SeqrLogger(__name__) @@ -621,7 +622,7 @@ def proxy_to_kibana(request): headers = _convert_django_meta_to_http_headers(request.META) headers['Host'] = KIBANA_SERVER if KIBANA_ELASTICSEARCH_PASSWORD: - token = base64.b64encode('kibana:{}'.format(KIBANA_ELASTICSEARCH_PASSWORD).encode('utf-8')) + token = base64.b64encode('{}:{}'.format(KIBANA_ELASTICSEARCH_USER, KIBANA_ELASTICSEARCH_PASSWORD).encode('utf-8')) headers['Authorization'] = 'Basic {}'.format(token.decode('utf-8')) url = "http://{host}{path}".format(host=KIBANA_SERVER, path=request.get_full_path()) diff --git a/seqr/views/utils/terra_api_utils.py b/seqr/views/utils/terra_api_utils.py index 9ec6427254..95bcbf8deb 100644 --- a/seqr/views/utils/terra_api_utils.py +++ b/seqr/views/utils/terra_api_utils.py @@ -14,7 +14,8 @@ from seqr.utils.redis_utils import safe_redis_get_json, safe_redis_set_json from settings import SEQR_VERSION, TERRA_API_ROOT_URL, TERRA_PERMS_CACHE_EXPIRE_SECONDS, SERVICE_ACCOUNT_CREDENTIALS, \ - TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS, SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, SERVICE_ACCOUNT_FOR_ANVIL, SOCIAL_AUTH_PROVIDER + TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS, SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY, \ + SERVICE_ACCOUNT_FOR_ANVIL, SOCIAL_AUTH_PROVIDER SEQR_USER_AGENT = "seqr/" + SEQR_VERSION OWNER_ACCESS_LEVEL = 'OWNER' @@ -59,7 +60,7 @@ def __init__(self, message): def google_auth_enabled(): - return bool(SOCIAL_AUTH_GOOGLE_OAUTH2_KEY) + return bool(SOCIAL_AUTH_GOOGLE_OAUTH2_KEY) or bool(SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY) def anvil_enabled(): diff --git a/settings.py b/settings.py index abfe408e7b..628f86dd6f 100644 --- a/settings.py +++ b/settings.py @@ -202,6 +202,7 @@ AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', + 'social_core.backends.azuread_tenant.AzureADV2TenantOAuth2', 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend', ) @@ -327,7 +328,8 @@ host=ELASTICSEARCH_SERVICE_HOSTNAME, port=ELASTICSEARCH_SERVICE_PORT) SEQR_ELASTICSEARCH_PASSWORD = os.environ.get('SEQR_ES_PASSWORD') -ELASTICSEARCH_CREDENTIALS = ('seqr', SEQR_ELASTICSEARCH_PASSWORD) if SEQR_ELASTICSEARCH_PASSWORD else None +ELASTICSEARCH_USER = os.environ.get('ELASTICSEARCH_USER', 'seqr') +ELASTICSEARCH_CREDENTIALS = (ELASTICSEARCH_USER, SEQR_ELASTICSEARCH_PASSWORD) if SEQR_ELASTICSEARCH_PASSWORD else None ELASTICSEARCH_PROTOCOL = os.environ.get('ELASTICSEARCH_PROTOCOL', 'http') ELASTICSEARCH_CA_PATH = os.environ.get('ELASTICSEARCH_CA_PATH') # if we have a custom CA certificate for elasticsearch, add it to the verification path for connections @@ -340,6 +342,7 @@ host=os.environ.get('KIBANA_SERVICE_HOSTNAME', 'localhost'), port=os.environ.get('KIBANA_SERVICE_PORT', 5601) ) +KIBANA_ELASTICSEARCH_USER = os.environ.get('KIBANA_ELASTICSEARCH_USER', 'kibana') KIBANA_ELASTICSEARCH_PASSWORD = os.environ.get('KIBANA_ES_PASSWORD') HAIL_BACKEND_SERVICE_HOSTNAME = os.environ.get('HAIL_BACKEND_SERVICE_HOSTNAME') @@ -380,7 +383,6 @@ ######################################################### # Social auth specific settings ######################################################### -SOCIAL_AUTH_JSONFIELD_ENABLED = True SOCIAL_AUTH_GOOGLE_OAUTH2_IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.profile', @@ -397,7 +399,15 @@ SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_CLIENT_ID') SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET') -LOGIN_URL = GOOGLE_LOGIN_REQUIRED_URL if SOCIAL_AUTH_GOOGLE_OAUTH2_KEY else '/login' +# LOGIN_URL = GOOGLE_LOGIN_REQUIRED_URL if SOCIAL_AUTH_GOOGLE_OAUTH2_KEY else '/login' + +######################################################### +# Social auth AzureAD specific settings +######################################################### +SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_AZUREAD_V2_OAUTH2_CLIENT_ID') +SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_AZUREAD_V2_OAUTH2_SECRET') +SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_TENANT_ID = os.environ.get('SOCIAL_AUTH_AZUREAD_V2_OAUTH2_TENANT') +LOGIN_URL = '/login/azuread-v2-tenant-oauth2' if SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY else '/login' SOCIAL_AUTH_JSONFIELD_ENABLED = True @@ -414,6 +424,13 @@ SOCIAL_AUTH_PIPELINE_USER_EXIST = ('seqr.utils.social_auth_pipeline.validate_user_exist',) SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER = ('social_core.pipeline.social_auth.associate_user',) SOCIAL_AUTH_PIPELINE_LOG = ('seqr.utils.social_auth_pipeline.log_signed_in',) +SOCIAL_AUTH_PIPELINE_CLOUD_BASE = ( + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.user.create_user', + SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER, + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details' +) TERRA_PERMS_CACHE_EXPIRE_SECONDS = os.environ.get('TERRA_PERMS_CACHE_EXPIRE_SECONDS', 60) TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS = os.environ.get('TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS', 300) @@ -447,12 +464,12 @@ SOCIAL_AUTH_PIPELINE = ('seqr.utils.social_auth_pipeline.validate_anvil_registration',) + \ SOCIAL_AUTH_PIPELINE_BASE + \ - ('social_core.pipeline.user.get_username', - 'social_core.pipeline.user.create_user') + \ - SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER + \ - ('social_core.pipeline.social_auth.load_extra_data', - 'social_core.pipeline.user.user_details') + \ + SOCIAL_AUTH_PIPELINE_CLOUD_BASE + \ SOCIAL_AUTH_PIPELINE_LOG +elif SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY: + SOCIAL_AUTH_PIPELINE = SOCIAL_AUTH_PIPELINE_BASE + \ + SOCIAL_AUTH_PIPELINE_CLOUD_BASE + \ + ('seqr.utils.social_auth_pipeline.log_azure_signed_in',) else: SOCIAL_AUTH_PIPELINE = SOCIAL_AUTH_PIPELINE_BASE + SOCIAL_AUTH_PIPELINE_USER_EXIST + \ SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER + SOCIAL_AUTH_PIPELINE_LOG diff --git a/ui/shared/utils/constants.js b/ui/shared/utils/constants.js index 621472eba7..80ab0edb79 100644 --- a/ui/shared/utils/constants.js +++ b/ui/shared/utils/constants.js @@ -18,7 +18,7 @@ import { ColoredIcon } from '../components/StyledComponents' import HpoPanel from '../components/panel/HpoPanel' export const ANVIL_URL = 'https://anvil.terra.bio' -export const GOOGLE_LOGIN_URL = '/login/google-oauth2' +export const GOOGLE_LOGIN_URL = '/login/azuread-v2-tenant-oauth2' export const LOCAL_LOGIN_URL = '/login' export const VCF_DOCUMENTATION_URL = 'https://storage.googleapis.com/seqr-reference-data/seqr-vcf-info.pdf' From 54cc9f1d0aa8f987bd6a94f79a69ea59ee18a72e Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Mon, 26 Aug 2024 15:03:05 -0700 Subject: [PATCH 02/33] fixed tuple bug --- settings.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/settings.py b/settings.py index 628f86dd6f..6778ce7dcf 100644 --- a/settings.py +++ b/settings.py @@ -426,11 +426,10 @@ SOCIAL_AUTH_PIPELINE_LOG = ('seqr.utils.social_auth_pipeline.log_signed_in',) SOCIAL_AUTH_PIPELINE_CLOUD_BASE = ( 'social_core.pipeline.user.get_username', - 'social_core.pipeline.user.create_user', - SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER, - 'social_core.pipeline.social_auth.load_extra_data', - 'social_core.pipeline.user.user_details' -) + 'social_core.pipeline.user.create_user') + \ + SOCIAL_AUTH_PIPELINE_ASSOCIATE_USER + \ + ('social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details') TERRA_PERMS_CACHE_EXPIRE_SECONDS = os.environ.get('TERRA_PERMS_CACHE_EXPIRE_SECONDS', 60) TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS = os.environ.get('TERRA_WORKSPACE_CACHE_EXPIRE_SECONDS', 300) From 574bd53a39e24d83d2c6acebb87fa2c213f27cca Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Thu, 29 Aug 2024 12:43:09 -0400 Subject: [PATCH 03/33] better inteval filtering and partitioning --- hail_search/constants.py | 2 ++ hail_search/queries/base.py | 7 ++++--- hail_search/queries/mito.py | 7 +++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hail_search/constants.py b/hail_search/constants.py index 148c7f3044..63cca9db31 100644 --- a/hail_search/constants.py +++ b/hail_search/constants.py @@ -88,3 +88,5 @@ ('likely_disease_causing', 'DM?', 'DM?'), ('hgmd_other', 'DP', None), ] + +MAX_LOAD_INTERVALS = 1000 diff --git a/hail_search/queries/base.py b/hail_search/queries/base.py index 434ee49241..31f17e8e73 100644 --- a/hail_search/queries/base.py +++ b/hail_search/queries/base.py @@ -6,7 +6,7 @@ from hail_search.constants import AFFECTED_ID, ALT_ALT, ANNOTATION_OVERRIDE_FIELDS, ANY_AFFECTED, COMP_HET_ALT, \ COMPOUND_HET, GENOME_VERSION_GRCh38, GROUPED_VARIANTS_FIELD, ALLOWED_TRANSCRIPTS, ALLOWED_SECONDARY_TRANSCRIPTS, HAS_ANNOTATION_OVERRIDE, \ - HAS_ALT, HAS_REF,INHERITANCE_FILTERS, PATH_FREQ_OVERRIDE_CUTOFF, MALE, RECESSIVE, REF_ALT, REF_REF, \ + HAS_ALT, HAS_REF,INHERITANCE_FILTERS, PATH_FREQ_OVERRIDE_CUTOFF, MALE, RECESSIVE, REF_ALT, REF_REF, MAX_LOAD_INTERVALS, \ UNAFFECTED_ID, X_LINKED_RECESSIVE, XPOS, OMIM_SORT, FAMILY_GUID_FIELD, GENOTYPES_FIELD, AFFECTED_ID_MAP DATASETS_DIR = os.environ.get('DATASETS_DIR', '/hail_datasets') @@ -14,7 +14,7 @@ # Number of filtered genes at which pre-filtering a table by gene-intervals does not improve performance # Estimated based on behavior for several representative gene lists -MAX_GENE_INTERVALS = int(os.environ.get('MAX_GENE_INTERVALS', 100)) +MAX_GENE_INTERVALS = int(os.environ.get('MAX_GENE_INTERVALS', MAX_LOAD_INTERVALS)) # Optimal number of entry table partitions, balancing parallelization with partition overhead # Experimentally determined based on compound het search performance: @@ -237,7 +237,8 @@ def __init__(self, sample_data, sort=XPOS, sort_metadata=None, num_results=100, self._has_secondary_annotations = False self._is_multi_data_type_comp_het = False self.max_unaffected_samples = None - self._load_table_kwargs = {'_n_partitions': min(MAX_PARTITIONS, (os.cpu_count() or 2)-1)} + self._n_partitions = min(MAX_PARTITIONS, (os.cpu_count() or 2)-1) + self._load_table_kwargs = {'_n_partitions': self._n_partitions} self.entry_samples_by_family_guid = {} if sample_data: diff --git a/hail_search/queries/mito.py b/hail_search/queries/mito.py index 90436bea27..f7dcdab473 100644 --- a/hail_search/queries/mito.py +++ b/hail_search/queries/mito.py @@ -6,10 +6,9 @@ from hail_search.constants import ABSENT_PATH_SORT_OFFSET, CLINVAR_KEY, CLINVAR_MITO_KEY, CLINVAR_LIKELY_PATH_FILTER, CLINVAR_PATH_FILTER, \ CLINVAR_PATH_RANGES, CLINVAR_PATH_SIGNIFICANCES, ALLOWED_TRANSCRIPTS, ALLOWED_SECONDARY_TRANSCRIPTS, PATHOGENICTY_SORT_KEY, CONSEQUENCE_SORT, \ - PATHOGENICTY_HGMD_SORT_KEY + PATHOGENICTY_HGMD_SORT_KEY, MAX_LOAD_INTERVALS from hail_search.queries.base import BaseHailTableQuery, PredictionPath, QualityFilterFormat -MAX_LOAD_INTERVALS = 1000 logger = logging.getLogger(__name__) @@ -215,6 +214,10 @@ def _prefilter_entries_table(self, ht, parsed_intervals=None, exclude_intervals= ht = hl.filter_intervals(ht, parsed_intervals, keep=False) elif len(parsed_intervals or []) >= MAX_LOAD_INTERVALS: ht = hl.filter_intervals(ht, parsed_intervals) + + if '_n_partitions' not in self._load_table_kwargs: + ht = ht.naive_coalesce(self._n_partitions) + return ht def _get_allowed_consequence_ids(self, annotations): From b789e2234cd25550772d4fcf71cdc6a631a4bdef Mon Sep 17 00:00:00 2001 From: Greg Smith Date: Thu, 29 Aug 2024 13:58:38 -0700 Subject: [PATCH 04/33] implemented gcp/azure SxS --- seqr/views/apis/auth_api.py | 4 ++-- seqr/views/apis/users_api.py | 6 +++--- seqr/views/react_app.py | 4 ++-- seqr/views/react_app_tests.py | 6 +++--- seqr/views/utils/terra_api_utils.py | 12 ++++++++++-- ui/pages/Dashboard/Dashboard.jsx | 14 +++++++------- ui/pages/Login/Login.jsx | 10 +++++----- ui/pages/Login/components/Login.test.js | 2 +- ui/pages/Login/components/LoginError.jsx | 20 +++++++++++++------- ui/pages/Public/components/LandingPage.jsx | 14 +++++++------- ui/redux/selectors.js | 3 ++- ui/shared/components/page/Header.jsx | 12 ++++++------ ui/shared/utils/constants.js | 1 - 13 files changed, 61 insertions(+), 47 deletions(-) diff --git a/seqr/views/apis/auth_api.py b/seqr/views/apis/auth_api.py index 9723870668..942ec3dbf3 100644 --- a/seqr/views/apis/auth_api.py +++ b/seqr/views/apis/auth_api.py @@ -12,14 +12,14 @@ from seqr.utils.logging_utils import SeqrLogger from seqr.views.utils.json_utils import create_json_response -from seqr.views.utils.terra_api_utils import google_auth_enabled, remove_token +from seqr.views.utils.terra_api_utils import oauth_enabled, remove_token from settings import LOGIN_URL, POLICY_REQUIRED_URL logger = SeqrLogger(__name__) def login_view(request): - if google_auth_enabled(): + if oauth_enabled(): raise PermissionDenied('Username/ password authentication is disabled') request_json = json.loads(request.body) diff --git a/seqr/views/apis/users_api.py b/seqr/views/apis/users_api.py index 69f55a5b4f..8e21cee8c2 100644 --- a/seqr/views/apis/users_api.py +++ b/seqr/views/apis/users_api.py @@ -16,7 +16,7 @@ get_project_collaborators_by_username, get_json_for_project_collaborator_groups, PROJECT_ACCESS_GROUP_NAMES from seqr.views.utils.permissions_utils import get_project_guids_user_can_view, get_project_and_check_permissions, \ login_and_policies_required, login_active_required, active_user_has_policies_and_passes_test -from seqr.views.utils.terra_api_utils import google_auth_enabled, anvil_enabled +from seqr.views.utils.terra_api_utils import oauth_enabled, anvil_enabled from settings import BASE_URL, SEQR_TOS_VERSION, SEQR_PRIVACY_VERSION logger = SeqrLogger(__name__) @@ -59,7 +59,7 @@ def get_project_collaborator_options(request, project_guid): def forgot_password(request): - if google_auth_enabled(): + if oauth_enabled(): raise PermissionDenied('Username/ password authentication is disabled') request_json = json.loads(request.body) @@ -88,7 +88,7 @@ def forgot_password(request): def set_password(request, username): - if google_auth_enabled(): + if oauth_enabled(): raise PermissionDenied('Username/ password authentication is disabled') user = User.objects.get(username=username) diff --git a/seqr/views/react_app.py b/seqr/views/react_app.py index 2490bf7c6d..7062e42ed1 100644 --- a/seqr/views/react_app.py +++ b/seqr/views/react_app.py @@ -11,7 +11,7 @@ from seqr.utils.search.utils import backend_specific_call from seqr.views.utils.orm_to_json_utils import get_json_for_user, get_json_for_current_user from seqr.views.utils.permissions_utils import login_active_required -from seqr.views.utils.terra_api_utils import google_auth_enabled +from seqr.views.utils.terra_api_utils import oauth_provider_login @login_active_required(login_url=LOGIN_URL) @@ -51,7 +51,7 @@ def render_app_html(request, additional_json=None, include_user=True, status=200 initial_json = {'meta': { 'version': '{}-{}'.format(SEQR_VERSION, ui_version), 'hijakEnabled': DEBUG or False, - 'googleLoginEnabled': google_auth_enabled(), + 'oauthLoginProviderUrl': oauth_provider_login(), 'elasticsearchEnabled': backend_specific_call(True, False), 'warningMessages': [message.json() for message in WarningMessage.objects.all()], 'anvilLoadingDelayDate': ANVIL_LOADING_DELAY_EMAIL_START_DATE if should_show_loading_delay else None, diff --git a/seqr/views/react_app_tests.py b/seqr/views/react_app_tests.py index e5242459ca..a993f7b440 100644 --- a/seqr/views/react_app_tests.py +++ b/seqr/views/react_app_tests.py @@ -23,7 +23,7 @@ def _check_page_html(self, response, user, user_key='user', user_fields=None, g self.assertDictEqual(initial_json['meta'], { 'version': mock.ANY, 'hijakEnabled': False, - 'googleLoginEnabled': self.GOOGLE_ENABLED, + 'oauthLoginProviderUrl': self.OAUTH_PROVIDER, 'elasticsearchEnabled': bool(self.ES_HOSTNAME), 'warningMessages': [{'id': 1, 'header': 'Warning!', 'message': 'A sample warning'}], 'anvilLoadingDelayDate': anvil_loading_date, @@ -99,12 +99,12 @@ def test_react_page_additional_configs(self, mock_datetime): class LocalAppPageTest(AuthenticationTestCase, AppPageTest): fixtures = ['users'] - GOOGLE_ENABLED = False + OAUTH_PROVIDER = '' class AnvilAppPageTest(AnvilAuthenticationTestCase, AppPageTest): fixtures = ['users'] - GOOGLE_ENABLED = True + OAUTH_PROVIDER = 'cloud' def test_react_page(self, *args, **kwargs): super(AnvilAppPageTest, self).test_react_page(*args, **kwargs) diff --git a/seqr/views/utils/terra_api_utils.py b/seqr/views/utils/terra_api_utils.py index 95bcbf8deb..fd2e87a1df 100644 --- a/seqr/views/utils/terra_api_utils.py +++ b/seqr/views/utils/terra_api_utils.py @@ -59,10 +59,18 @@ def __init__(self, message): super(TerraRefreshTokenFailedException, self).__init__(message, 401) -def google_auth_enabled(): +def oauth_enabled(): return bool(SOCIAL_AUTH_GOOGLE_OAUTH2_KEY) or bool(SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY) +def oauth_provider_login(): + if SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: + return '/login/google-oauth2' + if SOCIAL_AUTH_AZUREAD_V2_TENANT_OAUTH2_KEY: + return '/login/azuread-v2-tenant-oauth2' + return '' + + def anvil_enabled(): return bool(TERRA_API_ROOT_URL) @@ -101,7 +109,7 @@ def _get_call_args(path, headers=None, root_url=None): def _safe_get_social(user): - if not google_auth_enabled() or not hasattr(user, 'social_auth'): + if not oauth_enabled() or not hasattr(user, 'social_auth'): return None return user.social_auth.filter(provider=SOCIAL_AUTH_PROVIDER).first() diff --git a/ui/pages/Dashboard/Dashboard.jsx b/ui/pages/Dashboard/Dashboard.jsx index a56704f3c8..ebf33264bf 100644 --- a/ui/pages/Dashboard/Dashboard.jsx +++ b/ui/pages/Dashboard/Dashboard.jsx @@ -7,7 +7,7 @@ import { Link } from 'react-router-dom' import { Popup, Icon } from 'semantic-ui-react' import { fetchProjects } from 'redux/rootReducer' -import { getProjectsIsLoading, getUser, getGoogleLoginEnabled } from 'redux/selectors' +import { getProjectsIsLoading, getUser, getOauthLoginEnabled } from 'redux/selectors' import HorizontalStackedBar from 'shared/components/graph/HorizontalStackedBar' import DataTable from 'shared/components/table/DataTable' import DataLoader from 'shared/components/DataLoader' @@ -186,14 +186,14 @@ COLUMNS_NO_ANVIL.splice(2, 1) const SUPERUSER_COLUMNS_NO_ANVIL = [...SUPERUSER_COLUMNS] SUPERUSER_COLUMNS_NO_ANVIL.splice(2, 1) -const getColumns = (googleLoginEnabled, isAnvil, isSuperuser) => { - if (googleLoginEnabled && isAnvil) { +const getColumns = (oauthLoginEnabled, isAnvil, isSuperuser) => { + if (oauthLoginEnabled && isAnvil) { return isSuperuser ? SUPERUSER_COLUMNS : COLUMNS } return isSuperuser ? SUPERUSER_COLUMNS_NO_ANVIL : COLUMNS_NO_ANVIL } -const ProjectsTable = React.memo(({ visibleProjects, loading, load, user, googleLoginEnabled }) => ( +const ProjectsTable = React.memo(({ visibleProjects, loading, load, user, oauthLoginEnabled }) => ( @@ -210,7 +210,7 @@ const ProjectsTable = React.memo(({ visibleProjects, loading, load, user, google emptyContent="0 projects found" loading={loading} data={visibleProjects} - columns={getColumns(googleLoginEnabled, user.isAnvil, user.isSuperuser)} + columns={getColumns(oauthLoginEnabled, user.isAnvil, user.isSuperuser)} footer={user.isPm ? : null} downloadTableType="Projects" downloadFileName="projects" @@ -224,7 +224,7 @@ ProjectsTable.propTypes = { loading: PropTypes.bool.isRequired, user: PropTypes.object, load: PropTypes.func, - googleLoginEnabled: PropTypes.bool, + oauthLoginEnabled: PropTypes.bool, } export { ProjectsTable as ProjectsTableComponent } @@ -233,7 +233,7 @@ const mapStateToProps = state => ({ visibleProjects: getVisibleProjects(state), loading: getProjectsIsLoading(state), user: getUser(state), - googleLoginEnabled: getGoogleLoginEnabled(state), + oauthLoginEnabled: getOauthLoginEnabled(state), }) const mapDispatchToProps = { diff --git a/ui/pages/Login/Login.jsx b/ui/pages/Login/Login.jsx index 2cfe712c5b..4b877ffb87 100644 --- a/ui/pages/Login/Login.jsx +++ b/ui/pages/Login/Login.jsx @@ -3,16 +3,16 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Route, Switch } from 'react-router-dom' -import { getGoogleLoginEnabled } from 'redux/selectors' +import { getOauthLoginEnabled } from 'redux/selectors' import { Error404 } from 'shared/components/page/Errors' import Login from './components/Login' import LoginError from './components/LoginError' import ForgotPassword from './components/ForgotPassword' import SetPassword from './components/SetPassword' -const LoginPage = ({ match, googleLoginEnabled }) => ( +const LoginPage = ({ match, oauthLoginEnabled }) => ( - {googleLoginEnabled ? + {oauthLoginEnabled ? : [ , @@ -25,11 +25,11 @@ const LoginPage = ({ match, googleLoginEnabled }) => ( LoginPage.propTypes = { match: PropTypes.object, - googleLoginEnabled: PropTypes.bool, + oauthLoginEnabled: PropTypes.bool, } const mapStateToProps = state => ({ - googleLoginEnabled: getGoogleLoginEnabled(state), + oauthLoginEnabled: getOauthLoginEnabled(state), }) export default connect(mapStateToProps)(LoginPage) diff --git a/ui/pages/Login/components/Login.test.js b/ui/pages/Login/components/Login.test.js index 41a6c078dc..25d0bbcaaf 100644 --- a/ui/pages/Login/components/Login.test.js +++ b/ui/pages/Login/components/Login.test.js @@ -8,6 +8,6 @@ import Login from './Login' configure({ adapter: new Adapter() }) test('shallow-render without crashing', () => { - const store = configureStore()({ newUser: { username: 'test' }, meta: { googleLoginEnabled: false } }) + const store = configureStore()({ newUser: { username: 'test' }, meta: { oauthLoginProviderUrl: '' } }) shallow() }) diff --git a/ui/pages/Login/components/LoginError.jsx b/ui/pages/Login/components/LoginError.jsx index b5a331dc38..cf26ce69b1 100644 --- a/ui/pages/Login/components/LoginError.jsx +++ b/ui/pages/Login/components/LoginError.jsx @@ -1,10 +1,11 @@ import React from 'react' import PropTypes from 'prop-types' import { Segment, Message, Button, Icon } from 'semantic-ui-react' +import { getOauthLoginProviderUrl } from 'redux/selectors' -import { ANVIL_URL, GOOGLE_LOGIN_URL } from 'shared/utils/constants' +import { ANVIL_URL } from 'shared/utils/constants' -const GOGGLE_SETUP_URL = 'https://support.terra.bio/hc/en-us/articles/360029186611-Setting-up-a-Google-account-with-a-non-Google-email' +const GOOGLE_SETUP_URL = 'https://support.terra.bio/hc/en-us/articles/360029186611-Setting-up-a-Google-account-with-a-non-Google-email' const ERROR_MESSAGES = { anvil_registration: ( @@ -20,20 +21,20 @@ const ERROR_MESSAGES = {
If the email associated with your account is not managed by google, follow - these instructions + these instructions to register the email with google before registering with AnVIL. ), - no_account: 'No seqr account found for the selected Google user', + no_account: 'No seqr account found for the selected user identity', } -const LoginError = ({ location, match }) => ( +const LoginError = ({ location, match, oauthLoginProviderUrl }) => ( {ERROR_MESSAGES[match.params.error] || `Unknown error occured: ${match.params.error}`}
- @@ -44,6 +45,11 @@ const LoginError = ({ location, match }) => ( LoginError.propTypes = { location: PropTypes.object, match: PropTypes.object, + oauthLoginProviderUrl: PropTypes.string, } -export default LoginError +const mapStateToProps = state => ({ + oauthLoginProviderUrl: getOauthLoginProviderUrl(state), +}) + +export default connect(mapStateToProps)(LoginError) diff --git a/ui/pages/Public/components/LandingPage.jsx b/ui/pages/Public/components/LandingPage.jsx index 257de12c85..f6ddf29101 100644 --- a/ui/pages/Public/components/LandingPage.jsx +++ b/ui/pages/Public/components/LandingPage.jsx @@ -5,10 +5,10 @@ import PropTypes from 'prop-types' import styled from 'styled-components' import { Segment, Header, Grid, Button, List } from 'semantic-ui-react' -import { getGoogleLoginEnabled } from 'redux/selectors' +import { getOauthLoginProviderUrl } from 'redux/selectors' import { VerticalSpacer } from 'shared/components/Spacers' import { SeqrPaperLink } from 'shared/components/page/Footer' -import { LOCAL_LOGIN_URL, GOOGLE_LOGIN_URL } from 'shared/utils/constants' +import { LOCAL_LOGIN_URL } from 'shared/utils/constants' const PageSegment = styled(Segment).attrs({ padded: 'very' })` padding-left: 20% !important; @@ -50,15 +50,15 @@ SeqrAvailability.propTypes = { hasFootnote: PropTypes.bool, } -const LandingPage = ({ googleLoginEnabled }) => ( +const LandingPage = ({ oauthLoginProviderUrl }) => (
seqr} /> An open source software platform for rare disease genomics - {googleLoginEnabled ? -
@@ -45,11 +46,11 @@ const LoginError = ({ location, match, oauthLoginProviderUrl }) => ( LoginError.propTypes = { location: PropTypes.object, match: PropTypes.object, - oauthLoginProviderUrl: PropTypes.string, + oauthLoginProvider: PropTypes.string, } const mapStateToProps = state => ({ - oauthLoginProviderUrl: getOauthLoginProviderUrl(state), + oauthLoginProvider: getOauthLoginProvider(state), }) export default connect(mapStateToProps)(LoginError) diff --git a/ui/pages/Public/components/LandingPage.jsx b/ui/pages/Public/components/LandingPage.jsx index f6ddf29101..7aaeed4031 100644 --- a/ui/pages/Public/components/LandingPage.jsx +++ b/ui/pages/Public/components/LandingPage.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types' import styled from 'styled-components' import { Segment, Header, Grid, Button, List } from 'semantic-ui-react' -import { getOauthLoginProviderUrl } from 'redux/selectors' +import { getOauthLoginProvider } from 'redux/selectors' import { VerticalSpacer } from 'shared/components/Spacers' import { SeqrPaperLink } from 'shared/components/page/Footer' import { LOCAL_LOGIN_URL } from 'shared/utils/constants' @@ -50,15 +50,15 @@ SeqrAvailability.propTypes = { hasFootnote: PropTypes.bool, } -const LandingPage = ({ oauthLoginProviderUrl }) => ( +const LandingPage = ({ oauthLoginProvider }) => (
seqr} /> An open source software platform for rare disease genomics - {oauthLoginProviderUrl ? -