Skip to content

Commit

Permalink
Merge pull request wso2#5052 from brionmario/feat-org-sso-login
Browse files Browse the repository at this point in the history
Enable Sub Org admins to login to Console
  • Loading branch information
brionmario authored Dec 22, 2023
2 parents 7c22379 + b3d51c1 commit c8bfc54
Show file tree
Hide file tree
Showing 21 changed files with 566 additions and 190 deletions.
15 changes: 15 additions & 0 deletions .changeset/light-guests-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@wso2is/react-components": patch
"@wso2is/access-control": patch
"@wso2is/dynamic-forms": patch
"@wso2is/identity-apps-core": patch
"@wso2is/myaccount": patch
"@wso2is/common": patch
"@wso2is/theme": patch
"@wso2is/console": patch
"@wso2is/core": patch
"@wso2is/form": patch
"@wso2is/i18n": patch
---

Enable sub org logins for Console
2 changes: 1 addition & 1 deletion apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"typecheck": "pnpm run compile"
},
"dependencies": {
"@asgardeo/auth-react": "^3.1.1",
"@asgardeo/auth-react": "^4.0.0",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@microsoft/applicationinsights-core-js": "^3.0.0",
Expand Down
55 changes: 53 additions & 2 deletions apps/console/src/features/authentication/hooks/use-sign-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ const useSignIn = (): UseSignInInterface => {

const { legacyAuthzRuntime } = useAuthorization();

const { transformTenantDomain } = useOrganizations();
const {
transformTenantDomain,
setUserOrgInLocalStorage,
setOrgIdInLocalStorage,
getUserOrgInLocalStorage,
removeOrgIdInLocalStorage,
removeUserOrgInLocalStorage
} = useOrganizations();

const setCustomServerHost = (orgType: string, wellKnownEndpoint: string) => {
const disabledFeatures: string[] = window["AppUtils"]?.getConfig()?.ui?.features?.branding?.disabledFeatures;
Expand Down Expand Up @@ -427,7 +434,11 @@ const useSignIn = (): UseSignInInterface => {
endSessionEndpoint: logoutUrl.split("?")[0],
tokenEndpoint: tokenEndpoint
},
signOutRedirectURL: logoutRedirectUrl
signOutRedirectURL: deriveLogoutRedirectForSubOrgLogins(
logoutRedirectUrl,
userOrganizationId,
orgIdIdToken
)
});
})
.catch((error: any) => {
Expand All @@ -449,6 +460,46 @@ const useSignIn = (): UseSignInInterface => {
setCustomServerHost(orgType, wellKnownEndpoint);
};

/**
* Derives the logout redirect URL for sub-org logins.
*
* @remarks This only applies to the new authz runtime.
*
* @param currentLogoutRedirect - Current logout redirect URL.
* @param userOrg - User's org.
* @param orgId - User's org ID.
* @returns Derived logout redirect URL.
*/
const deriveLogoutRedirectForSubOrgLogins = (currentLogoutRedirect: string, userOrg: string, orgId: string) => {
if (legacyAuthzRuntime) {
return currentLogoutRedirect;
}

let logoutRedirectUrl: string = currentLogoutRedirect;

// When a first level tenant login happens, `user_org` is undefined.
// We need to save that in the local storage to handle the login/logout if they switch.
if (userOrg === undefined && orgId) {
removeUserOrgInLocalStorage();
removeOrgIdInLocalStorage();

setUserOrgInLocalStorage(userOrg);
setOrgIdInLocalStorage(orgId);
}

if (userOrg) {
const isSwitchedFromRootOrg: boolean = getUserOrgInLocalStorage() === "undefined";

if (!isSwitchedFromRootOrg) {
logoutRedirectUrl = window["AppUtils"]?.getConfig()?.clientOriginWithTenant?.replace(
orgId, userOrg
);
}
}

return logoutRedirectUrl;
};

return {
onSignIn
};
Expand Down
24 changes: 18 additions & 6 deletions apps/console/src/features/authentication/pages/sign-out.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -22,17 +22,25 @@ import { setSignOut } from "@wso2is/core/store";
import { AuthenticateUtils } from "@wso2is/core/utils";
import React, { FunctionComponent, ReactElement, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Dispatch } from "redux";
import { AppState, PreLoader, history } from "../../core";
import useOrganizations from "../../organizations/hooks/use-organizations";

/**
* Virtual component used to handle Sign in action.
*
* @return {React.ReactElement}
* @returns Sign Out component.
*/
const SignOut: FunctionComponent<Record<string, unknown>> = (): ReactElement => {
const dispatch = useDispatch();
const dispatch: Dispatch = useDispatch();

const { signOut, on } = useAuthContext();

const {
removeOrgIdInLocalStorage,
removeUserOrgInLocalStorage
} = useOrganizations();

const logoutInit: boolean = useSelector((state: AppState) => state.auth.logoutInit);

useEffect(() => {
Expand All @@ -44,9 +52,13 @@ const SignOut: FunctionComponent<Record<string, unknown>> = (): ReactElement =>

useEffect(() => {
if (!logoutInit) {
signOut().catch(() => {
history.push(window[ "AppUtils" ].getConfig().routes.home);
});
removeOrgIdInLocalStorage();
removeUserOrgInLocalStorage();

signOut()
.catch(() => {
history.push(window[ "AppUtils" ].getConfig().routes.home);
});
}
}, [ logoutInit ]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import React, { ReactElement, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { Dispatch } from "redux";
import { DropdownProps, Icon, PaginationProps } from "semantic-ui-react";
import { Dropdown, DropdownItemProps, DropdownProps, Icon, PaginationProps } from "semantic-ui-react";
import AdministratorsTable from "./administrators-table";
import { AccessControlConstants } from "../../../../access-control/constants/access-control";
import {
Expand Down Expand Up @@ -72,6 +72,20 @@ interface AdministratorsListProps extends IdentifiableComponentInterface {
readOnlyUserStores?: string[];
}

/**
* Enum for add administrator modes.
*/
enum AddAdministratorModes {
/**
* To add an existing user as an administrator.
*/
AddExisting = "addExistingUser",
/**
* To invite a new user as an administrator.
*/
InviteNew = "inviteNewUser"
}

/**
* Component to list and manage administrators.
*
Expand Down Expand Up @@ -179,14 +193,58 @@ const AdministratorsList: React.FunctionComponent<AdministratorsListProps> = (

const renderAdministratorAddOptions = (): ReactElement => {
if (isSubOrganization()) {
const getAddUserOptions = () => {
const options: DropdownItemProps[] = [
{
"data-componentid": `${ componentId }-add-existing-user-dropdown-item`,
key: 1,
text: t("console:consoleSettings.administrators.add.options.addExistingUser"),
value: AddAdministratorModes.AddExisting
},
{
"data-componentid": `${ componentId }-invite-new-user-dropdown-item`,
key: 2,
text: t("console:consoleSettings.administrators.add.options.inviteNewUser"),
value: AddAdministratorModes.InviteNew
}
];

return options;
};

return (
<PrimaryButton
data-componentid={ `${ componentId }-add-button` }
onClick={ () => setShowInviteNewAdministratorModal(true) }
<Dropdown
data-componentid={ `${ componentId }-add-administrator-dropdown` }
direction="left"
floating
icon={ null }
trigger={ (
<PrimaryButton
data-componentid={ `${ componentId }-add-button` }
className="add-administrator-dropdown-trigger"
>
<Icon data-componentid={ `${componentId}-add-button-icon` } name="add" />
{ t("console:consoleSettings.administrators.add.action") }
<Icon name="dropdown" className="add-administrator-dropdown-chevron"/>
</PrimaryButton>
) }
>
<Icon data-componentid={ `${componentId}-add-button-icon` } name="add" />
Invite New User
</PrimaryButton>
<Dropdown.Menu >
{ getAddUserOptions().map((option: DropdownItemProps) => (
<Dropdown.Item
key={ option.value as string }
onClick={ () => {
if (option.value === AddAdministratorModes.AddExisting) {
setShowAddExistingUserWizard(true);
} else {
setShowInviteNewAdministratorModal(true);
}
} }
{ ...option }
/>
)) }
</Dropdown.Menu>
</Dropdown>
);
}

Expand All @@ -196,7 +254,7 @@ const AdministratorsList: React.FunctionComponent<AdministratorsListProps> = (
onClick={ () => setShowAddExistingUserWizard(true) }
>
<Icon data-componentid={ `${componentId}-add-button-icon` } name="add" />
Add Administrator
{ t("console:consoleSettings.administrators.add.action") }
</PrimaryButton>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@
align-items: center;
}
}

.add-administrator-dropdown-trigger {
&.ui.button>.icon:not(.button).add-administrator-dropdown-chevron {
margin-left: 1rem;
margin-right: 0;
}
}
55 changes: 55 additions & 0 deletions apps/console/src/features/core/context/app-settings-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { Context, createContext } from "react";

/**
* Props interface of {@link AppSettingsContext}
*/
export interface AppSettingsContextProps {
/**
* Get the local storage setting value.
* @param key - Key of the setting.
* @returns Value of the setting.
*/
getLocalStorageSetting: (key: string) => string;
/**
* Remove a value from the local storage.
* @param key - Key of the setting.
*/
removeLocalStorageSetting: (key: string) => void;
/**
* Get the local storage setting value.
* @param key - Key of the setting.
* @param value - Value of the setting.
*/
setLocalStorageSetting: (key: string, value: string) => void;
}

/**
* Context object for managing the Application local settings.
*/
const AppSettingsContext: Context<AppSettingsContextProps> =
createContext<null | AppSettingsContextProps>(null);

/**
* Display name for the AppSettingsContext.
*/
AppSettingsContext.displayName = "AppSettingsContext";

export default AppSettingsContext;
41 changes: 41 additions & 0 deletions apps/console/src/features/core/hooks/use-app-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { useContext } from "react";
import AppSettingsContext, { AppSettingsContextProps } from "../context/app-settings-context";

/**
* Props interface of {@link UseAppSettings}
*/
export type UseAppSettingsInterface = AppSettingsContextProps;

/**
* Hook that provides access to the local App settings context.
* @returns An object containing the context values of {@link AppSettingsContext}.
*/
const useAppSettings = (): UseAppSettingsInterface => {
const context: AppSettingsContextProps = useContext(AppSettingsContext);

if (context === undefined) {
throw new Error("useAppSettings must be used within a AppSettingsProvider");
}

return context;
};

export default useAppSettings;
Loading

0 comments on commit c8bfc54

Please sign in to comment.