Skip to content

Commit

Permalink
feat: enable multi-tenant feature toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
HuihuiWu-Microsoft committed Dec 13, 2024
1 parent c548821 commit 7f4b392
Show file tree
Hide file tree
Showing 17 changed files with 62 additions and 195 deletions.
6 changes: 1 addition & 5 deletions packages/cli/src/commands/models/accountLoginAzure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,7 @@ export const accountLoginAzureCommand: CLICommand = {
return err(new UserError(loginComponent, usageError, servicePrincipalLoginFormat));
}
} else {
if (
args.username ||
args.password ||
(args.tenant && !featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant))
) {
if (args.username || args.password) {
return err(new UserError(loginComponent, usageError, codeFlowLoginFormat));
}
}
Expand Down
18 changes: 8 additions & 10 deletions packages/cli/src/commands/models/accountLoginM365.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ import { featureFlagManager, FeatureFlags } from "@microsoft/teamsfx-core";
export const accountLoginM365Command: CLICommand = {
name: "m365",
description: commands["auth.login.m365"].description,
options: featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)
? [
{
name: "tenant",
description: commands["auth.login.m365"].options["tenant"],
type: "string",
default: "",
},
]
: undefined,
options: [
{
name: "tenant",
description: commands["auth.login.m365"].options["tenant"],
type: "string",
default: "",
},
],
telemetry: {
event: TelemetryEvent.AccountLoginM365,
},
Expand Down
18 changes: 6 additions & 12 deletions packages/cli/src/commands/models/accountShow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ class AccountUtils {
return true;
}

async outputM365Info(commandType: "login" | "show", tenantId?: string): Promise<boolean> {
const tid = featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) ? tenantId : undefined;
async outputM365Info(commandType: "login" | "show", tid?: string): Promise<boolean> {
const appStudioTokenJsonRes = await M365TokenProvider.getJsonObject(
{
scopes: AppStudioScopes,
Expand All @@ -46,9 +45,7 @@ class AccountUtils {
logger.outputSuccess(strings["account.login.m365"]);
}

const cachedTenantId = featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)
? await M365TokenProvider.getTenant()
: undefined;
const cachedTenantId = await M365TokenProvider.getTenant();
if (cachedTenantId) {
const listTenantToken = await M365TokenProvider.getAccessToken({ scopes: AzureScopes });
if (listTenantToken.isOk()) {
Expand Down Expand Up @@ -84,21 +81,18 @@ class AccountUtils {
await AzureTokenCIProvider.init(userName, password, tenantId);
azureProvider = AzureTokenCIProvider;
}
const tid = featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) ? tenantId : undefined;
const result = await azureProvider.getJsonObject(true, tid);
const result = await azureProvider.getJsonObject(true, tenantId);
if (result) {
if (tid) {
await azureProvider.switchTenant(tid);
if (tenantId) {
await azureProvider.switchTenant(tenantId);
}
const subscriptions = await azureProvider.listSubscriptions();
const username = (result as any).upn ?? (result as any).unique_name;
if (commandType === "login") {
logger.outputSuccess(strings["account.login.azure"]);
}

const cachedTenantId = featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)
? await azureProvider.getTenant()
: undefined;
const cachedTenantId = await azureProvider.getTenant();
if (cachedTenantId) {
const identityCredential = await azureProvider.getIdentityCredentialAsync(false);
const listTenantToken = identityCredential
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/commonlib/azureLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,10 @@ export class AzureAccountManager extends login implements AzureAccountProvider {
AzureAccountManager.codeFlowInstance
);
const cachedTenantId = await loadTenantId(accountName);
const multiTenantEnabled = featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant);
for await (const page of tenantClient.tenants.list().byPage({ maxPageSize: 100 })) {
for (const tenant of page) {
if (
multiTenantEnabled && cachedTenantId
cachedTenantId
? tenant.tenantId && tenant.tenantId == cachedTenantId
: tenant.tenantId
) {
Expand Down
17 changes: 6 additions & 11 deletions packages/cli/src/commonlib/codeFlowLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,10 @@ export class CodeFlowLogin {
this.account = dataCache;
}

if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
const tenantCache = await loadTenantId(this.accountName);
if (tenantCache) {
const allAccounts = await this.msalTokenCache.getAllAccounts();
this.account = allAccounts.find((account) => account.tenantId == tenantCache);
}
const tenantCache = await loadTenantId(this.accountName);
if (tenantCache) {
const allAccounts = await this.msalTokenCache.getAllAccounts();
this.account = allAccounts.find((account) => account.tenantId == tenantCache);
}
} else {
this.account = undefined;
Expand Down Expand Up @@ -137,10 +135,7 @@ export class CodeFlowLogin {
this.destroySockets();
});

const authority =
featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && tenantId
? env.activeDirectoryEndpointUrl + tenantId
: undefined;
const authority = tenantId ? env.activeDirectoryEndpointUrl + tenantId : undefined;
const authCodeUrlParameters = {
scopes: scopes,
codeChallenge: codeChallenge,
Expand Down Expand Up @@ -278,7 +273,7 @@ export class CodeFlowLogin {
await this.reloadCache();
}

if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && !tenantId) {
if (!tenantId) {
tenantId = await loadTenantId(this.accountName);
}
if (!this.account) {
Expand Down
5 changes: 0 additions & 5 deletions packages/fx-core/src/common/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export class FeatureFlagName {
static readonly KiotaIntegration = "TEAMSFX_KIOTA_INTEGRATION";
static readonly ApiPluginAAD = "TEAMSFX_API_PLUGIN_AAD";
static readonly CEAEnabled = "TEAMSFX_CEA_ENABLED";
static readonly MultiTenant = "TEAMSFX_MULTI_TENANT";
}

export interface FeatureFlag {
Expand Down Expand Up @@ -95,10 +94,6 @@ export class FeatureFlags {
name: FeatureFlagName.CEAEnabled,
defaultValue: "false",
};
static readonly MultiTenant = {
name: FeatureFlagName.MultiTenant,
defaultValue: "false",
};
}

export class FeatureFlagManager {
Expand Down
4 changes: 1 addition & 3 deletions packages/fx-core/src/component/m365/launchHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ export class LaunchHelper {
url = new URL(baseUrl);
const tid = await this.getTidFromToken();
if (tid) {
if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
url.searchParams.append("tenantId", tid);
}
url.searchParams.append("tenantId", tid);
url.searchParams.append("appTenantId", tid);
}
break;
Expand Down
38 changes: 0 additions & 38 deletions packages/fx-core/tests/component/m365/launchHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,9 @@ import { FeatureFlagName } from "../../../src";
describe("LaunchHelper", () => {
const m365TokenProvider = new MockedM365Provider();
const launchHelper = new LaunchHelper(m365TokenProvider);
let mockedEnvRestore: RestoreFn = () => {};

afterEach(() => {
sinon.restore();
mockedEnvRestore();
});

beforeEach(() => {
mockedEnvRestore = mockedEnv({
[FeatureFlagName.MultiTenant]: "true",
});
});

describe("getLaunchUrl", () => {
Expand Down Expand Up @@ -58,36 +50,6 @@ describe("LaunchHelper", () => {
);
});

it("getLaunchUrl: Teams, signed in - multi tenant off", async () => {
mockedEnvRestore = mockedEnv({
[FeatureFlagName.MultiTenant]: "false",
});
sinon.stub(m365TokenProvider, "getStatus").resolves(
ok({
status: "",
accountInfo: {
tid: "test-tid",
upn: "test-upn",
},
})
);
const properties: ManifestProperties = {
capabilities: ["staticTab"],
id: "test-id",
version: "1.0.0",
manifestVersion: "1.16",
isApiME: false,
isSPFx: false,
isApiMeAAD: false,
};
const result = await launchHelper.getLaunchUrl(HubTypes.teams, "test-id", properties);
chai.assert(result.isOk());
chai.assert.equal(
(result as any).value,
"https://teams.microsoft.com/l/app/test-id?installAppPackage=true&webjoin=true&appTenantId=test-tid&login_hint=test-upn"
);
});

it("getLaunchUrl: Teams, signed in, copilot plugin", async () => {
sinon.stub(m365TokenProvider, "getStatus").resolves(
ok({
Expand Down
4 changes: 2 additions & 2 deletions packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,12 @@
"view/item/context": [
{
"command": "fx-extension.m365SwitchTenant",
"when": "fx-extension.isMultiTenantEnabled && view == teamsfx-accounts && viewItem == signedinM365",
"when": "view == teamsfx-accounts && viewItem == signedinM365",
"group": "inline@1"
},
{
"command": "fx-extension.azureSwitchTenant",
"when": "fx-extension.isMultiTenantEnabled && view == teamsfx-accounts && viewItem == signedinAzure",
"when": "view == teamsfx-accounts && viewItem == signedinAzure",
"group": "inline@1"
},
{
Expand Down
29 changes: 10 additions & 19 deletions packages/vscode-extension/src/commonlib/azureLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,29 +89,21 @@ export class AzureAccountManager extends login implements AzureAccountProvider {
* Async get identity [crendential](https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/core/core-auth/src/tokenCredential.ts)
*/
async getIdentityCredentialAsync(showDialog = true): Promise<TokenCredential | undefined> {
if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
const tenantId = await loadTenantId(azureCacheName);
if (await this.isUserLogin(tenantId)) {
const res = await this.getIdentityCredentialSilently(tenantId);
if (res.isOk()) {
return res.value;
} else {
return undefined;
}
const tenantId = await loadTenantId(azureCacheName);
if (await this.isUserLogin(tenantId)) {
const res = await this.getIdentityCredentialSilently(tenantId);
if (res.isOk()) {
return res.value;
} else {
return await this.login(showDialog, tenantId);
return undefined;
}
} else {
if (await this.isUserLogin()) {
return this.doGetIdentityCredentialAsync();
}
await this.login(showDialog);
return this.doGetIdentityCredentialAsync();
return await this.login(showDialog, tenantId);
}
}

private async isUserLogin(tenantId?: string): Promise<boolean> {
if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && !tenantId) {
if (!tenantId) {
tenantId = await loadTenantId(azureCacheName);
}
const session = await getSessionFromVSCode(AzureScopes, tenantId, {
Expand Down Expand Up @@ -162,9 +154,8 @@ export class AzureAccountManager extends login implements AzureAccountProvider {
}
});
}
if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
await saveTenantId(azureCacheName, session.id.split("/")[0]);
}
await saveTenantId(azureCacheName, session.id.split("/")[0]);

const credential: TokenCredential = {
// eslint-disable-next-line @typescript-eslint/require-await
getToken: async () => {
Expand Down
65 changes: 9 additions & 56 deletions packages/vscode-extension/src/commonlib/codeFlowLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,10 @@ export class CodeFlowLogin {
this.status = loggedIn;
}

if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
const tenantCache = await loadTenantId(this.accountName);
if (tenantCache) {
const allAccounts = await this.msalTokenCache.getAllAccounts();
this.account = allAccounts.find((account) => account.tenantId == tenantCache);
}
const tenantCache = await loadTenantId(this.accountName);
if (tenantCache) {
const allAccounts = await this.msalTokenCache.getAllAccounts();
this.account = allAccounts.find((account) => account.tenantId == tenantCache);
}
} else if (this.status !== loggingIn) {
this.account = undefined;
Expand All @@ -125,10 +123,7 @@ export class CodeFlowLogin {
const app = express();
const server = app.listen(serverPort);
serverPort = (server.address() as AddressInfo).port;
const authority =
featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && tenantId
? BASE_AUTHORITY + tenantId
: undefined;
const authority = tenantId ? BASE_AUTHORITY + tenantId : undefined;

const authCodeUrlParameters: AuthorizationUrlRequest = {
scopes: scopes,
Expand Down Expand Up @@ -272,10 +267,7 @@ export class CodeFlowLogin {
const codeChallenge = CodeFlowLogin.toBase64UrlEncoding(
await CodeFlowLogin.sha256(codeVerifier)
);
const authority =
featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && tenantId
? BASE_AUTHORITY + tenantId
: undefined;
const authority = tenantId ? BASE_AUTHORITY + tenantId : undefined;
const authCodeUrlParameters: AuthorizationUrlRequest = {
scopes: scopes,
codeChallenge: codeChallenge,
Expand Down Expand Up @@ -346,51 +338,12 @@ export class CodeFlowLogin {
loginHint?: string,
tenantId?: string
): Promise<Result<string, FxError>> {
if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant)) {
if (!tenantId) {
tenantId = await loadTenantId(this.accountName);
}
return await this.getToken(scopes, refresh, tenantId, loginHint);
}

if (!this.account) {
const accessToken = await this.login(scopes, loginHint);
return ok(accessToken);
} else {
try {
const res = await this.pca.acquireTokenSilent({
account: this.account,
scopes: scopes,
forceRefresh: false,
});
if (res) {
return ok(res.accessToken);
} else {
return err(LoginCodeFlowError(new Error("No token response.")));
}
} catch (error) {
VsCodeLogInstance.debug(
"[Login] " +
stringUtil.format(
localize("teamstoolkit.codeFlowLogin.silentAcquireToken"),
path.join(os.homedir(), ".fx", "account"),
error.message
)
);
if (!(await checkIsOnline())) {
return err(CheckOnlineError());
}
await this.logout();
if (refresh) {
const accessToken = await this.login(scopes, loginHint);
return ok(accessToken);
}
return err(LoginCodeFlowError(error));
}
if (!tenantId) {
tenantId = await loadTenantId(this.accountName);
}
return await this.getToken(scopes, refresh, tenantId, loginHint);
}

// For multi-tenant support, the legacy function wil be removed later
async getToken(
scopes: Array<string>,
refresh = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class VSCodeAzureSubscriptionProvider {
public async getSubscriptions(tenantId?: string): Promise<AzureSubscription[]> {
const results: AzureSubscription[] = [];

if (featureFlagManager.getBooleanValue(FeatureFlags.MultiTenant) && tenantId) {
if (tenantId) {
results.push(...(await this.getSubscriptionsForTenant(tenantId)));
} else {
for (const tenant of await this.getTenants()) {
Expand Down
Loading

0 comments on commit 7f4b392

Please sign in to comment.