diff --git a/apps/console/src/app.tsx b/apps/console/src/app.tsx index 28a26ed17eb..afe5934f536 100755 --- a/apps/console/src/app.tsx +++ b/apps/console/src/app.tsx @@ -94,14 +94,14 @@ export const App: FunctionComponent> = (): ReactElement => const [ baseRoutes, setBaseRoutes ] = useState(getBaseRoutes()); const [ sessionTimedOut, setSessionTimedOut ] = useState(false); const [ orgId, setOrgId ] = useState(); - const [ featureGateConfigData, setFeatureGateConfigData ] = + const [ featureGateConfigData, setFeatureGateConfigData ] = useState(featureGateConfigUpdated); - const { - data: allFeatures, - error: featureGateAPIException - } = useGetAllFeatures(orgId, state.isAuthenticated); - + // const { + // data: allFeatures, + // error: featureGateAPIException + // } = useGetAllFeatures(orgId, state.isAuthenticated); + const allFeatures = [] useEffect(() => { if(state.isAuthenticated) { if (OrganizationUtils.isSuperOrganization(store.getState().organization.organization) @@ -121,7 +121,7 @@ export const App: FunctionComponent> = (): ReactElement => }, [ state ]); useEffect(() => { - if (allFeatures instanceof IdentityAppsApiException || featureGateAPIException) { + if (allFeatures instanceof IdentityAppsApiException) { return; } @@ -135,13 +135,13 @@ export const App: FunctionComponent> = (): ReactElement => const path: string = feature.featureIdentifier.replace(/-/g, "."); // Obtain the status and set it to the feature gate config. const featureStatusPath: string = `${ path }.status`; - + set(featureGateConfigUpdated,featureStatusPath, feature.featureStatus); - + const featureTagPath: string = `${ path }.tags`; - + set(featureGateConfigUpdated,featureTagPath, feature.featureTags); - + setFeatureGateConfigData(featureGateConfigUpdated); }); } diff --git a/apps/console/src/auth.html b/apps/console/src/auth.html index d5cf54c303d..6e3695a5ad5 100644 --- a/apps/console/src/auth.html +++ b/apps/console/src/auth.html @@ -161,9 +161,22 @@ ? userAccessedPath.split("utype=")[1] : null; - const urlParams = new URLSearchParams(window.location.search); + if (userTenant) { + sessionStorage.setItem("user_tenant", userTenant); + } + const urlParams = new URLSearchParams(window.location.search); + var isPrivilegedUser = false; + var tenantReplacement = "a"; var serverOrigin = startupConfig.serverUrl; + if (userTenant) { + isPrivilegedUser = true; + serverOrigin = startupConfig.serverUrl; + tenantReplacement = userTenant; + } else { + isPrivilegedUser = false; + serverOrigin = "https://api.eu.asg.io"; + } var authorizationCode = urlParams.get("code"); var authSessionState = urlParams.get("session_state"); @@ -213,7 +226,7 @@ var basePath = applicationDomain.replace(/\/+$/, '') + getOrganizationPath(); var baseRedirectURL = "<%= htmlWebpackPlugin.options.basename%>" ? basePath + '/' + "<%= htmlWebpackPlugin.options.basename%>" : basePath; - + var loginTenant = userTenant.replace(/\/+$/, ""); var authConfig = { signInRedirectURL: baseRedirectURL, signOutRedirectURL: baseRedirectURL, @@ -227,20 +240,24 @@ endpoints: { authorizationEndpoint: getApiPath( userTenant - ? "/" + startupConfig.tenantPrefix + "/" + startupConfig.superTenantProxy + startupConfig.pathExtension + "/oauth2/authorize" + "?ut=" + + ? "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oauth2/authorize" + "?ut=" + userTenant.replace(/\/+$/, "") + (utype ? "&utype=" + utype : "") - : "/" + startupConfig.tenantPrefix + "/" + startupConfig.superTenantProxy + startupConfig.pathExtension + "/oauth2/authorize" + : "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oauth2/authorize" ), clockTolerance: 300, - jwksEndpointURL: undefined, + jwksEndpointURL: getApiPath( + "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oauth2/jwks" + ), logoutEndpointURL: getApiPath( - "/" + startupConfig.tenantPrefix + "/" + startupConfig.superTenantProxy + startupConfig.pathExtension + "/oidc/logout" + "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oidc/logout" ), oidcSessionIFrameEndpointURL: getApiPath( - "/" + startupConfig.tenantPrefix + "/" + startupConfig.superTenantProxy + startupConfig.pathExtension + "/oidc/checksession" + "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oidc/checksession" + ), + tokenEndpointURL: getApiPath( + "/" + startupConfig.tenantPrefix + "/" + loginTenant + startupConfig.pathExtension + "/oauth2/token" ), - tokenEndpointURL: undefined, tokenRevocationEndpointURL: undefined }, enablePKCE: true diff --git a/apps/console/src/extensions/components/tenants/api/tenants.ts b/apps/console/src/extensions/components/tenants/api/tenants.ts index 479348dd29c..5d9c46556e8 100644 --- a/apps/console/src/extensions/components/tenants/api/tenants.ts +++ b/apps/console/src/extensions/components/tenants/api/tenants.ts @@ -23,6 +23,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios"; import { store } from "../../../../features/core/store"; import { getTenantResourceEndpoints } from "../configs"; import { TenantRequestResponse } from "../models"; +import { EnvironmentRequestResponse } from "../models"; export const getDomainQueryParam = (): string => { const tenantDomain: string = store.getState().auth.tenantDomain; @@ -30,6 +31,19 @@ export const getDomainQueryParam = (): string => { return `?domain=${ tenantDomain }`; }; +const getUsernameQueryParam = (): string => { + // const username: string = store.getState()?.auth?.username; + const userId: string = store.getState()?.environment?.loggedInUserId; + + return `&userUUID=${ userId }`; +}; + +const getUserIdQueryParam = (): string => { + const userId: string = store.getState()?.environment?.loggedInUserId; + + return `&userId=${ userId }`; +}; + const isPrivilegedUser = (): boolean => { const isPrivileged: boolean = store.getState()?.auth?.isPrivilegedUser ?? false; @@ -48,17 +62,21 @@ const httpClient: HttpClientInstance = AsgardeoSPAClient.getInstance() * * @param tenantName - new tenant name */ -export const addNewTenant = (tenantName: string): Promise => { +export const addNewTenant = (tenantName: string, deploymentUUID: string): Promise => { const requestConfig: AxiosRequestConfig = { data: { - domain: tenantName - }, + "orgName": tenantName, + "defaultEnvName": "prod", + "deploymentUUIDForDefaultEnv": deploymentUUID, + "isDefault": false + }, + headers: { "Access-Control-Allow-Origin": store.getState().config.deployment.clientOrigin, "Content-Type": "application/json" }, method: HttpMethods.POST, - url: getTenantResourceEndpoints().tenantManagementApi + getDomainQueryParam() + url: getTenantResourceEndpoints().tenantManagementApi + "/organization" + getDomainQueryParam() }; return httpClient(requestConfig) @@ -104,7 +122,8 @@ export const checkDuplicateTenants = (tenantName: string): Promise => { return Promise.reject(error?.response?.data); }); }; + +/** + * Get the deployments associated with the current user. + */ +export const getDeployments = (): Promise => { + + const requestConfig: AxiosRequestConfig = { + url: getTenantResourceEndpoints().tenantManagementApi + "/deployment" + getDomainQueryParam() + }; + + return httpClient(requestConfig) + .then((response: AxiosResponse) => { + return Promise.resolve(response); + }) + .catch((error: AxiosError) => { + return Promise.reject(error?.response?.data); + }); +}; diff --git a/apps/console/src/extensions/components/tenants/components/add-modal/add-tenant.tsx b/apps/console/src/extensions/components/tenants/components/add-modal/add-tenant.tsx index 3184d930df5..baa4aedf110 100644 --- a/apps/console/src/extensions/components/tenants/components/add-modal/add-tenant.tsx +++ b/apps/console/src/extensions/components/tenants/components/add-modal/add-tenant.tsx @@ -16,6 +16,7 @@ * under the License. */ +import { useAuthContext } from "@asgardeo/auth-react"; import { AlertInterface, AlertLevels, TestableComponentInterface } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; import { useTrigger } from "@wso2is/forms"; @@ -78,6 +79,8 @@ export const AddTenantWizard: FunctionComponent = } }, [ submissionValue, finishSubmit ]); + const { updateConfig } = useAuthContext(); + const handleFormSubmit = (): void => { setIsNewTenantLoading(true); setTenantLoaderText( @@ -99,7 +102,7 @@ export const AddTenantWizard: FunctionComponent = .catch((error: AxiosError) => { if (error.response.status == 404) { // Proceed to tenant creation if tenant does not exist. - addTenant(submissionValue.tenantName); + addTenant(submissionValue.tenantName, submissionValue.deploymentUUID); } else { setIsNewTenantLoading(false); setAlert({ @@ -140,10 +143,10 @@ export const AddTenantWizard: FunctionComponent = title: t("extensions:manage.features.tenant.wizards.addTenant.heading") } ]; - const addTenant = (tenantName: string): void => { + const addTenant = (tenantName: string, deploymentUUID: string): void => { setIsNewTenantLoading(true); setTenantLoaderText(t("extensions:manage.features.tenant.wizards.addTenant.forms.loaderMessages.tenantCreate")); - addNewTenant(tenantName) + addNewTenant(tenantName, deploymentUUID) .then((response: AxiosResponse) => { if (response.status === 201) { eventPublisher.publish("create-new-organization"); diff --git a/apps/console/src/extensions/components/tenants/components/dropdown/tenant-dropdown.tsx b/apps/console/src/extensions/components/tenants/components/dropdown/tenant-dropdown.tsx index 73edf384c6c..e13cdc57573 100644 --- a/apps/console/src/extensions/components/tenants/components/dropdown/tenant-dropdown.tsx +++ b/apps/console/src/extensions/components/tenants/components/dropdown/tenant-dropdown.tsx @@ -55,7 +55,7 @@ import { OrganizationType } from "../../../../../features/organizations/constant import { useGetCurrentOrganizationType } from "../../../../../features/organizations/hooks/use-get-organization-type"; import { FeatureGateConstants } from "../../../feature-gate/constants/feature-gate"; import { getAssociatedTenants, makeTenantDefault } from "../../api"; -import { TenantInfo, TenantRequestResponse, TriggerPropTypesInterface } from "../../models"; +import { TenantInfo, TenantRequestResponse, TriggerPropTypesInterface, EnvironmentInfo, EnvironmentRequestResponse } from "../../models"; import { handleTenantSwitch } from "../../utils"; import { AddTenantWizard } from "../add-modal"; @@ -124,19 +124,27 @@ const TenantDropdown: FunctionComponent = (props: Tenan useEffect(() => { if (!isPrivilegedUser && saasFeatureStatus !== FeatureStatus.DISABLED) { getAssociatedTenants() - .then((response: TenantRequestResponse) => { + .then((response: EnvironmentRequestResponse []) => { let defaultDomain: string = ""; const tenants: string[] = []; + const associatedTenants: TenantInfo[] = []; - response.associatedTenants.forEach((tenant: TenantInfo) => { - if (tenant.default) { - defaultDomain = tenant.domain; + response.forEach((environmentRequestResponse: EnvironmentRequestResponse) => { + environmentRequestResponse.environments.forEach((environment: EnvironmentInfo) => { + if (environment.isDefault) { + defaultDomain = environmentRequestResponse.orgName; } - - tenants.push(tenant.domain); + }); + tenants.push(environmentRequestResponse.orgName); + associatedTenants.push({ + id: environmentRequestResponse.orgUUID, + domain: environmentRequestResponse.orgName, + default: defaultDomain == environmentRequestResponse.orgName, + associationType: "member" + }); }); - dispatch(setTenants(response.associatedTenants)); + dispatch(setTenants(associatedTenants)); setAssociatedTenants(tenants); setDefaultTenant(defaultDomain); }) @@ -383,7 +391,7 @@ const TenantDropdown: FunctionComponent = (props: Tenan { @@ -434,7 +442,7 @@ const TenantDropdown: FunctionComponent = (props: Tenan data-inverted data-tooltip={ isCopying ? t("extensions:manage.features.tenant." + - "header.copied") + "header.copied") : t("extensions:manage.features.tenant." + "header.copyOrganizationId") } @@ -474,7 +482,7 @@ const TenantDropdown: FunctionComponent = (props: Tenan disabled={ true } > - { + { t("extensions:manage.features.tenant." + "header.makeDefaultOrganization") } @@ -490,7 +498,7 @@ const TenantDropdown: FunctionComponent = (props: Tenan disabled={ isSetDefaultTenantInProgress } > - { + { t("extensions:manage.features.tenant." + "header.makeDefaultOrganization") } diff --git a/apps/console/src/extensions/components/tenants/components/dropdown/tenant-switch-dropdown.tsx b/apps/console/src/extensions/components/tenants/components/dropdown/tenant-switch-dropdown.tsx index ba69ac35242..82d8b82484a 100644 --- a/apps/console/src/extensions/components/tenants/components/dropdown/tenant-switch-dropdown.tsx +++ b/apps/console/src/extensions/components/tenants/components/dropdown/tenant-switch-dropdown.tsx @@ -34,7 +34,7 @@ import { OrganizationManagementConstants } from "../../../../../features/organiz import { OrganizationInterface } from "../../../../../features/organizations/models"; import { ReactComponent as CrossIcon } from "../../../../../themes/default/assets/images/icons/cross-icon.svg"; import { getAssociatedTenants } from "../../api"; -import { TenantInfo, TenantRequestResponse } from "../../models"; +import { TenantInfo, TenantRequestResponse, EnvironmentInfo, EnvironmentRequestResponse } from "../../models"; import { AddTenantWizard } from "../add-modal"; /** @@ -74,23 +74,33 @@ const TenantSwitchDropdown: FunctionComponent = ( const getOrganizationList: () => Promise = useCallback(async () => { setIsOrganizationsLoading(true); getAssociatedTenants() - .then((response: TenantRequestResponse) => { + .then((response: EnvironmentRequestResponse []) => { const tenants: OrganizationInterface[] = []; + let defaultDomain: string = ""; + const associatedTenants: TenantInfo[] = []; - response.associatedTenants.forEach((tenant: TenantInfo) => { - if (window[ "AppUtils" ].getConfig().tenant === tenant.domain) { - return; - } + response.forEach((environmentRequestResponse: EnvironmentRequestResponse) => { + environmentRequestResponse.environments.forEach((environment: EnvironmentInfo) => { + if (environment.isDefault) { + defaultDomain = environmentRequestResponse.orgName; + } + }); tenants.push({ - id: tenant.id, - name: tenant.domain, - ref: tenant.id, - status: "ACTIVE" + id: environmentRequestResponse.orgUUID, + name: environmentRequestResponse.orgName, + ref: environmentRequestResponse.orgUUID, + status: "ACTIVE" + }); + associatedTenants.push({ + id: environmentRequestResponse.orgUUID, + domain: environmentRequestResponse.orgName, + default: defaultDomain == environmentRequestResponse.orgName, + associationType: "Member" }); }); - dispatch(setTenants(response.associatedTenants)); + dispatch(setTenants(associatedTenants)); setAssociatedOrganizations(tenants); setFilteredOrganizations(tenants); }) diff --git a/apps/console/src/extensions/components/tenants/components/forms/add-tenant-wizard-form.tsx b/apps/console/src/extensions/components/tenants/components/forms/add-tenant-wizard-form.tsx index a9c5ece0fd6..50d2fb2f7ed 100644 --- a/apps/console/src/extensions/components/tenants/components/forms/add-tenant-wizard-form.tsx +++ b/apps/console/src/extensions/components/tenants/components/forms/add-tenant-wizard-form.tsx @@ -29,14 +29,22 @@ import React, { ReactElement, SetStateAction, useCallback, + useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import { Divider, Segment } from "semantic-ui-react"; import { AppState } from "../../../../../features/core/store"; -import { checkDuplicateTenants } from "../../api"; +import { addNewTenant, checkDuplicateTenants, getDeployments } from "../../api"; import { TenantManagementConstants } from "../../constants"; +import { + ORGANIZATION_DESCRIPTION_MAX_LENGTH, + ORGANIZATION_DESCRIPTION_MIN_LENGTH +} from "../../../../../features/organizations/constants"; +import delay from "lodash-es/delay"; +import {handleTenantSwitch} from "../../utils"; +import {DropdownOptionsInterface} from "../../../../../features/applications/components/settings"; /** * Interface to capture add tenant wizard form props. @@ -62,6 +70,7 @@ export interface AddTenantWizardFormErrorValidationsInterface { */ export interface AddTenantWizardFormValuesInterface { tenantName: string; + deploymentUUID: string; } const FORM_ID: string = "add-tenant-wizard-form"; @@ -98,6 +107,10 @@ export const AddTenantWizardForm: FunctionComponent(); + const [ deployments, setDeployments ] = useState(); + const [ deploymentsLoading, setDeploymentsLoading ] = useState(false); + const { t } = useTranslation(); const checkTenantValidity = (tenantName: string): boolean => { @@ -153,6 +166,30 @@ export const AddTenantWizardForm: FunctionComponent { + const deployments: DropdownOptionsInterface[] = []; + getDeployments().then((response: AxiosResponse) => { + if (response.status == 200) { + response.data.map((dep) => { + if (dep.isPublic) { + deployments.push({ + key: dep.deploymentName, + text: dep.displayName, + value: dep.deploymentUUID + }); + } + }); + } + }); + setDeployments(deployments); + setDeploymentsLoading(false); + setSelectedRegionValue(deployments.length && deployments[0].value); + }; + + useEffect(() => { + getDeploymentNames(); + }, []); + /** * Validate input data. * @@ -349,6 +386,21 @@ export const AddTenantWizardForm: FunctionComponent + {!deploymentsLoading && ( + setSelectedRegionValue(value)} + width={16} + /> + )}