Skip to content

Commit

Permalink
OEQ-2229 - feat: add platform OKTA
Browse files Browse the repository at this point in the history
  • Loading branch information
edalex-yinzi authored and PenghaiZhang committed Nov 28, 2024
1 parent fb9909e commit 072a608
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ describe("General details section", () => {
describe("Platform details section", () => {
const entraIdFields = [apiClientIdLabel, apiClientSecretLabel];
const auth0Fields = [apiUrlLabel, apiClientIdLabel, apiClientSecretLabel];
const oktaFields = [apiUrlLabel, apiClientIdLabel];

it.each<[OEQ.Oidc.IdentityProviderPlatform, string[]]>([
["ENTRA_ID", entraIdFields],
["AUTH0", auth0Fields],
["OKTA", oktaFields],
])("should render fields for platform '%s'", async (platform, fields) => {
const { container } = await renderOidcSettings();

Expand Down
12 changes: 7 additions & 5 deletions react-front-end/tsrc/modules/Lti13PlatformsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@ const { ltiRoles: ltiRolesStrings } =

const baseUrl = getBaseUrl();

interface ProviderDetail {
export interface ProviderDetail {
readonly name: string;
readonly value: string;
}

export const keysetUrlDetails = {
name: keysetUrlinitiaLabel,
value: `${baseUrl}.well-known/jwks.json`,
};

/**
* LTI 1.3 provider details which can be used to configure LTI platform.
*/
Expand All @@ -54,10 +59,7 @@ export const providerDetails: { [key: string]: ProviderDetail } = {
name: toolUrlLabel,
value: baseUrl,
},
keysetUrl: {
name: keysetUrlinitiaLabel,
value: `${baseUrl}.well-known/jwks.json`,
},
keysetUrl: keysetUrlDetails,
initialLoginUrl: {
name: initialLoginUrlLabel,
value: `${baseUrl}lti13/launch`,
Expand Down
1 change: 1 addition & 0 deletions react-front-end/tsrc/modules/OidcModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
import * as OEQ from "@openequella/rest-api-client";
import { API_BASE_URL } from "../AppConfig";

/**
* Get the OIDC settings from the server.
*/
Expand Down
50 changes: 18 additions & 32 deletions react-front-end/tsrc/settings/Integrations/oidc/OidcSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Card,
CardContent,
Divider,
Grid,
ListItem,
ListItemText,
} from "@mui/material";
import { Card, CardContent, Divider, Grid } from "@mui/material";
import { RoleDetails } from "@openequella/rest-api-client/dist/UserQuery";
import { constVoid, flow, identity, pipe } from "fp-ts/function";
import * as T from "fp-ts/Task";
import { isEqual } from "lodash";
import { useContext, useEffect, useState } from "react";
import * as React from "react";
import { getBaseUrl } from "../../../AppConfig";
import {
CustomRolesMapping,
transformCustomRoleMapping,
Expand All @@ -42,7 +34,6 @@ import GeneralDetailsSection, {
import SettingPageTemplate from "../../../components/SettingPageTemplate";
import SettingsList from "../../../components/SettingsList";
import SettingsListControl from "../../../components/SettingsListControl";
import SettingsListConfiguration from "../../../components/SettingsListConfiguration";
import SettingsListAlert from "../../../components/SettingsListAlert";
import { AppContext } from "../../../mainui/App";
import { routes } from "../../../mainui/routes";
Expand Down Expand Up @@ -74,6 +65,7 @@ import {
generateApiDetails,
ApiDetails,
generatePlatform,
oeqDetailsList,
} from "./OidcSettingsHelper";
import * as O from "fp-ts/Option";
import * as R from "fp-ts/Record";
Expand All @@ -91,17 +83,10 @@ const {
roleClaimDesc,
customRoleDialog: customRoleDialogStrings,
},
oeqDetails: {
title: oeqDetailsTitle,
desc: oeqDetailsDesc,
redirect: redirectTitle,
},
} = languageStrings.settings.integration.oidc;
const { edit: editLabel } = languageStrings.common.action;
const { checkForm } = languageStrings.common.result;

const redirectUrl = getBaseUrl() + "oidc/callback";

// Compare the initial and current details to see if the configuration has changed.
// In order to handle the secret field,
// It will consider the configuration is not changed if the current value is an empty string and the initial value is missing.
Expand Down Expand Up @@ -278,6 +263,12 @@ const OidcSettings = ({
apiClientId: idp.apiClientId,
apiClientSecret: idp.apiClientSecret,
});
} else if (OEQ.Codec.Oidc.OktaCodec.is(idp)) {
setApiDetails({
platform: idp.platform,
apiUrl: idp.apiUrl,
apiClientId: idp.apiClientId,
});
}

// process role mappings value to display existing settings
Expand Down Expand Up @@ -349,15 +340,21 @@ const OidcSettings = ({
const validateStructure = (): TE.TaskEither<
string,
OEQ.Oidc.IdentityProvider
> =>
pipe(
> => {
const codec =
currentOidcValue.platform === "OKTA"
? OEQ.Codec.Oidc.OktaCodec
: OEQ.Codec.Oidc.GenericIdentityProviderCodec;

return pipe(
currentOidcValue,
E.fromPredicate(
OEQ.Codec.Oidc.GenericIdentityProviderCodec.is,
codec.is,
() => `Validation for the structure of OIDC configuration failed.`,
),
TE.fromEither,
);
};

const submit = (
oidcValue: OEQ.Oidc.IdentityProvider,
Expand Down Expand Up @@ -471,18 +468,7 @@ const OidcSettings = ({
</Card>

<Card>
<CardContent>
<SettingsList subHeading={oeqDetailsTitle}>
<ListItem>
<ListItemText>{oeqDetailsDesc}</ListItemText>
</ListItem>

<SettingsListConfiguration
title={redirectTitle}
value={redirectUrl}
/>
</SettingsList>
</CardContent>
<CardContent>{oeqDetailsList}</CardContent>
</Card>
</SettingPageTemplate>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,32 @@
* limitations under the License.
*/
import Switch from "@mui/material/Switch";
import { getBaseUrl } from "../../../AppConfig";
import {
FieldRenderOptions,
passwordMask,
passwordTextFiled,
plainTextFiled,
} from "../../../components/GeneralDetailsSection";
import { FormControl, MenuItem, Select } from "@mui/material";
import { constTrue, pipe } from "fp-ts/function";
import {
FormControl,
ListItem,
ListItemText,
MenuItem,
Select,
} from "@mui/material";
import { absurd, constTrue, pipe } from "fp-ts/function";
import * as React from "react";
import SettingsList from "../../../components/SettingsList";
import SettingsListConfiguration from "../../../components/SettingsListConfiguration";
import { keysetUrlDetails } from "../../../modules/Lti13PlatformsModule";
import { languageStrings } from "../../../util/langstrings";
import { isNonEmptyString, isValidURL } from "../../../util/validation";
import * as OEQ from "@openequella/rest-api-client";
import * as A from "fp-ts/Array";
import * as M from "fp-ts/Map";
import * as S from "fp-ts/string";
import * as R from "fp-ts/Record";

const {
enable: enableLabel,
Expand Down Expand Up @@ -60,10 +71,18 @@ const {
} = languageStrings.settings.integration.oidc.apiDetails;
const { select: selectLabel } = languageStrings.common.action;
const { missingValue, invalidUrl } = languageStrings.error;
const {
title: oeqDetailsTitle,
desc: oeqDetailsDesc,
redirect: redirectTitle,
} = languageStrings.settings.integration.oidc.oeqDetails;

const baseUrl = getBaseUrl();

export const platforms = new Map<OEQ.Oidc.IdentityProviderPlatform, string>([
["ENTRA_ID", "Entra ID"],
["AUTH0", "Auth0"],
["OKTA", "Okta"],
]);

// Use 'platform' as the discriminator.
Expand All @@ -73,8 +92,10 @@ export interface GenericApiDetails
"platform" | "apiUrl" | "apiClientId" | "apiClientSecret"
> {}

//TODO: Add more platform API details: Okta.
export type ApiDetails = GenericApiDetails;
export interface OkatApiDetails
extends Pick<OEQ.Oidc.Okta, "platform" | "apiUrl" | "apiClientId"> {}

export type ApiDetails = GenericApiDetails | OkatApiDetails;

export const defaultGeneralDetails: OEQ.Oidc.IdentityProvider = {
enabled: false,
Expand All @@ -100,14 +121,19 @@ export const defaultEntraIdApiDetails: GenericApiDetails = {
platform: "ENTRA_ID",
};

export const defaultOktaApiDetails: OkatApiDetails = {
platform: "OKTA",
apiUrl: "",
apiClientId: "",
};

export const defaultApiDetailsMap: Record<
OEQ.Oidc.IdentityProviderPlatform,
ApiDetails
> = {
ENTRA_ID: defaultEntraIdApiDetails,
AUTH0: defaultAuth0ApiDetails,
//TODO: Update default values for OKTA.
OKTA: defaultEntraIdApiDetails,
OKTA: defaultOktaApiDetails,
};

const platformSelector = (
Expand Down Expand Up @@ -282,10 +308,12 @@ export const generateGeneralDetails = (
const commonApiDetails = (
onChange: (key: string, value: unknown) => void,
showValidationErrors: boolean,
apiDetails: GenericApiDetails,
apiDetails: ApiDetails,
isConfigured: boolean,
): Record<string, FieldRenderOptions> => {
const { apiUrl, apiClientId, apiClientSecret } = apiDetails;
const { platform, apiUrl, apiClientId } = apiDetails;
// apiClientSecret is not exist in OktaApiDetails.
const apiClientSecret = platform === "OKTA" ? "" : apiDetails.apiClientSecret;

return {
apiUrl: {
Expand Down Expand Up @@ -381,13 +409,40 @@ export const generateApiDetails = (
apiDetails,
isConfigured,
);
const { apiClientId, apiClientSecret } = apiCommonFields;
const { apiUrl, apiClientId, apiClientSecret } = apiCommonFields;

switch (platform) {
case "AUTH0":
return apiCommonFields;
case "ENTRA_ID":
return { apiClientId, apiClientSecret };
case "OKTA":
return { apiUrl, apiClientId };
default:
throw new Error(`Unsupported platform: ${platform}`);
return absurd(platform);
}
};

const oeqDetails = {
redirect: {
name: redirectTitle,
value: `${baseUrl}oidc/callback`,
},
keysetUrl: keysetUrlDetails,
};

/*** A list of OEQ details for the OIDC settings. **/
export const oeqDetailsList = (
<SettingsList subHeading={oeqDetailsTitle}>
<ListItem>
<ListItemText>{oeqDetailsDesc}</ListItemText>
</ListItem>
{pipe(
oeqDetails,
R.toEntries,
A.map(([key, { name, value }]) => (
<SettingsListConfiguration key={key} title={name} value={value} />
)),
)}
</SettingsList>
);

0 comments on commit 072a608

Please sign in to comment.