From 76e7c65a6e0030b50bc2e2ea30e8e9f69ee5ce00 Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Sun, 30 Oct 2022 02:58:41 +0800 Subject: [PATCH 1/2] add cas auth strategy --- .env.example | 4 ++++ src/commons/utils/AuthHelper.ts | 14 +++++++++++++- src/commons/utils/Constants.ts | 22 +++++++++++++++++++--- src/pages/login/Login.tsx | 13 ++++++++----- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 11b785b8a0..e08bd96749 100644 --- a/.env.example +++ b/.env.example @@ -37,6 +37,10 @@ REACT_APP_OAUTH2_PROVIDER3_ENDPOINT=http://localhost:8000/login?provider=test&co # REACT_APP_OAUTH2_PROVIDER2_NAME=Cognito # REACT_APP_OAUTH2_PROVIDER2_ENDPOINT= +REACT_APP_CAS_PROVIDER1= +REACT_APP_CAS_PROVIDER1_NAME= +REACT_APP_CAS_PROVIDER1_ENDPOINT= + REACT_APP_MODULE_BACKEND_URL=https://source-academy.github.io/modules REACT_APP_SHAREDB_BACKEND_URL= REACT_APP_SICPJS_BACKEND_URL="http://127.0.0.1:8080/" diff --git a/src/commons/utils/AuthHelper.ts b/src/commons/utils/AuthHelper.ts index 0fef8cddb3..c8ec30669d 100644 --- a/src/commons/utils/AuthHelper.ts +++ b/src/commons/utils/AuthHelper.ts @@ -1,5 +1,10 @@ import Constants from './Constants'; +export enum AuthProviderType { + OAUTH2 = 'OAUTH2', + CAS = 'CAS' +} + export function computeEndpointUrl(providerId: string): string | undefined { const ep = Constants.authProviders.get(providerId); if (!ep) { @@ -7,7 +12,14 @@ export function computeEndpointUrl(providerId: string): string | undefined { } try { const epUrl = new URL(ep.endpoint); - epUrl.searchParams.set('redirect_uri', computeRedirectUri(providerId)!); + switch (ep.type) { + case AuthProviderType.OAUTH2: + epUrl.searchParams.set('redirect_uri', computeRedirectUri(providerId)!); + break; + case AuthProviderType.CAS: + epUrl.searchParams.set('service', computeRedirectUri(providerId)!); + break; + } return epUrl.toString(); } catch (e) { // in dev, sometimes the endpoint is a dummy; allow that diff --git a/src/commons/utils/Constants.ts b/src/commons/utils/Constants.ts index 073fdfab9f..ff9b04ff87 100644 --- a/src/commons/utils/Constants.ts +++ b/src/commons/utils/Constants.ts @@ -1,6 +1,8 @@ import { Chapter, Variant } from 'js-slang/dist/types'; import moment, { Moment } from 'moment'; +import { AuthProviderType } from './AuthHelper'; + function isTrue(value?: string, defaultTo?: boolean): boolean { return typeof value === 'undefined' && typeof defaultTo !== 'undefined' ? defaultTo @@ -44,8 +46,10 @@ const caFulfillmentLevel = isTest ? 24 : parseInt(process.env.REACT_APP_CA_FULFILLMENT_LEVEL || '0'); -const authProviders: Map = - new Map(); +const authProviders: Map< + string, + { name: string; endpoint: string; isDefault: boolean; type: AuthProviderType } +> = new Map(); for (let i = 1; ; ++i) { const id = process.env[`REACT_APP_OAUTH2_PROVIDER${i}`]; @@ -56,7 +60,19 @@ for (let i = 1; ; ++i) { const name = process.env[`REACT_APP_OAUTH2_PROVIDER${i}_NAME`] || 'Unnamed provider'; const endpoint = process.env[`REACT_APP_OAUTH2_PROVIDER${i}_ENDPOINT`] || ''; - authProviders.set(id, { name, endpoint, isDefault: i === 1 }); + authProviders.set(id, { name, endpoint, isDefault: i === 1, type: AuthProviderType.OAUTH2 }); +} + +for (let i = 1; ; ++i) { + const id = process.env[`REACT_APP_CAS_PROVIDER${i}`]; + if (!id) { + break; + } + + const name = process.env[`REACT_APP_CAS_PROVIDER${i}_NAME`] || 'Unnamed provider'; + const endpoint = process.env[`REACT_APP_CAS_PROVIDER${i}_ENDPOINT`] || ''; + + authProviders.set(id, { name, endpoint, isDefault: false, type: AuthProviderType.CAS }); } const disablePeriods: Array<{ start: Moment; end: Moment; reason?: string }> = []; diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 0f6d57192c..8733556b14 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -27,17 +27,20 @@ const providers = [...Constants.authProviders.entries()].map(([id, { name }]) => const Login: React.FunctionComponent<{}> = () => { const dispatch = useDispatch(); const location = useLocation(); - const { code, provider: providerId } = parseQuery(location.search); + const { code, ticket, provider: providerId } = parseQuery(location.search); + + // `code` parameter from OAuth2 redirect, `ticket` from CAS redirect + const authCode = code || ticket; const handleLogin = React.useCallback(providerId => dispatch(login(providerId)), [dispatch]); React.useEffect(() => { - if (code) { - dispatch(fetchAuth(code, providerId)); + if (authCode) { + dispatch(fetchAuth(authCode, providerId)); } - }, [code, providerId, dispatch]); + }, [authCode, providerId, dispatch]); - if (code) { + if (authCode) { return (
From bf215b0f89fee27cd1ebd505d665d4ef99b3c65f Mon Sep 17 00:00:00 2001 From: En Rong <53928333+chownces@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:49:02 +0800 Subject: [PATCH 2/2] fix test case --- src/commons/sagas/__tests__/BackendSaga.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 71fba2cac8..2a20d96613 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -72,7 +72,7 @@ import { } from '../../mocks/AssessmentMocks'; import { mockGradingSummary } from '../../mocks/GradingMocks'; import { mockNotifications } from '../../mocks/UserMocks'; -import { computeRedirectUri } from '../../utils/AuthHelper'; +import { AuthProviderType, computeRedirectUri } from '../../utils/AuthHelper'; import Constants from '../../utils/Constants'; import { showSuccessMessage, showWarningMessage } from '../../utils/NotificationsHelper'; import { updateHasUnsavedChanges } from '../../workspace/WorkspaceActions'; @@ -269,7 +269,8 @@ describe('Test FETCH_AUTH action', () => { Constants.authProviders.set(providerId, { name: providerId, endpoint: `https://test/?client_id=${clientId}`, - isDefault: true + isDefault: true, + type: AuthProviderType.OAUTH2 }); const redirectUrl = computeRedirectUri(providerId);