Skip to content

Commit

Permalink
Diverge tenant config defaults (#3822)
Browse files Browse the repository at this point in the history
Diverge in tenant config defaults. Tenant configs default to enabled for implicitly created tenants and default to disabled for explicitly created tenants.

Corresponding internal bug: b/192387245
  • Loading branch information
lisajian committed Oct 15, 2021
1 parent 231a26c commit cff2821
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 28 deletions.
20 changes: 15 additions & 5 deletions src/emulator/auth/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
UsageMode,
AgentProjectState,
TenantProjectState,
MfaConfig,
} from "./state";
import { MfaEnrollments, Schemas } from "./types";

Expand Down Expand Up @@ -2692,13 +2693,22 @@ function createTenant(
throw new InternalError("INTERNAL_ERROR: Can only create tenant in agent project", "INTERNAL");
}

const mfaConfig = reqBody.mfaConfig ?? {};
if (!("state" in mfaConfig)) {
mfaConfig.state = "DISABLED";
}
if (!("enabledProviders" in mfaConfig)) {
mfaConfig.enabledProviders = [];
}

// Default to production settings if unset
const tenant = {
displayName: reqBody.displayName,
allowPasswordSignup: reqBody.allowPasswordSignup,
enableEmailLinkSignin: reqBody.enableEmailLinkSignin,
enableAnonymousUser: reqBody.enableAnonymousUser,
disableAuth: reqBody.disableAuth,
mfaConfig: reqBody.mfaConfig,
allowPasswordSignup: reqBody.allowPasswordSignup ?? false,
enableEmailLinkSignin: reqBody.enableEmailLinkSignin ?? false,
enableAnonymousUser: reqBody.enableAnonymousUser ?? false,
disableAuth: reqBody.disableAuth ?? false,
mfaConfig: mfaConfig as MfaConfig,
tenantId: "", // Placeholder until one is generated
};

Expand Down
73 changes: 54 additions & 19 deletions src/emulator/auth/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,22 @@ export class AgentProjectState extends ProjectState {

getTenantProject(tenantId: string): TenantProjectState {
if (!this.tenantProjectForTenantId.has(tenantId)) {
this.createTenantWithTenantId(tenantId, { tenantId });
// Implicitly creates tenant if it does not already exist and sets all
// configurations to enabled. This is for convenience and differs from
// production in which configurations, are default disabled. Tests that
// need to reflect production defaults should first explicitly call
// `createTenant()` with a `Tenant` object.
this.createTenantWithTenantId(tenantId, {
tenantId,
allowPasswordSignup: true,
disableAuth: false,
mfaConfig: {
state: "ENABLED",
enabledProviders: ["PHONE_SMS"],
},
enableAnonymousUser: true,
enableEmailLinkSignin: true,
});
}
return this.tenantProjectForTenantId.get(tenantId)!;
}
Expand Down Expand Up @@ -714,42 +729,55 @@ export class TenantProjectState extends ProjectState {
return this._tenantConfig;
}

// TODO(lisajian): Handle divergence in tenant config settings between what is
// needed for admin SDK (default disabled, parallels production) vs emulator
// tests (default enabled, for convenience)
get allowPasswordSignup() {
return this._tenantConfig.allowPasswordSignup ?? true;
return this._tenantConfig.allowPasswordSignup;
}

get disableAuth() {
return this._tenantConfig.disableAuth ?? false;
return this._tenantConfig.disableAuth;
}

get mfaConfig() {
return (
this._tenantConfig.mfaConfig ?? {
state: "ENABLED" as const,
enabledProviders: ["PHONE_SMS" as const],
}
);
return this._tenantConfig.mfaConfig;
}

get enableAnonymousUser() {
return this._tenantConfig.enableAnonymousUser ?? true;
return this._tenantConfig.enableAnonymousUser;
}

get enableEmailLinkSignin() {
return this._tenantConfig.enableEmailLinkSignin ?? true;
return this._tenantConfig.enableEmailLinkSignin;
}

delete(): void {
this.parentProject.deleteTenant(this.tenantId);
}

updateTenant(update: Partial<Tenant>, updateMask: string | undefined): Tenant {
updateTenant(
update: Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"],
updateMask: string | undefined
): Tenant {
// Empty masks indicate a full update
if (!updateMask) {
this._tenantConfig = { ...update, tenantId: this.tenantId, name: this.tenantConfig.name };
const mfaConfig = update.mfaConfig ?? {};
if (!("state" in mfaConfig)) {
mfaConfig.state = "DISABLED";
}
if (!("enabledProviders" in mfaConfig)) {
mfaConfig.enabledProviders = [];
}

// Default to production defaults if unset
this._tenantConfig = {
tenantId: this.tenantId,
name: this.tenantConfig.name,
allowPasswordSignup: update.allowPasswordSignup ?? false,
disableAuth: update.disableAuth ?? false,
mfaConfig: mfaConfig as MfaConfig,
enableAnonymousUser: update.enableAnonymousUser ?? false,
enableEmailLinkSignin: update.enableEmailLinkSignin ?? false,
displayName: update.displayName,
};
return this.tenantConfig;
}

Expand Down Expand Up @@ -813,10 +841,17 @@ export type UserInfo = Omit<
localId: string;
providerUserInfo?: ProviderUserInfo[];
};
export type MfaConfig = MakeRequired<
Schemas["GoogleCloudIdentitytoolkitAdminV2MultiFactorAuthConfig"],
"enabledProviders" | "state"
>;
export type Tenant = Omit<
Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"],
"testPhoneNumbers"
> & { tenantId: string };
MakeRequired<
Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"],
"allowPasswordSignup" | "disableAuth" | "enableAnonymousUser" | "enableEmailLinkSignin"
>,
"testPhoneNumbers" | "mfaConfig"
> & { tenantId: string; mfaConfig: MfaConfig };

interface RefreshTokenRecord {
localId: string;
Expand Down
4 changes: 2 additions & 2 deletions src/test/emulators/auth/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { expect, AssertionError } from "chai";
import { IdpJwtPayload } from "../../../emulator/auth/operations";
import { OobRecord, PhoneVerificationRecord, Tenant, UserInfo } from "../../../emulator/auth/state";
import { TestAgent, PROJECT_ID } from "./setup";
import { MfaEnrollments } from "../../../emulator/auth/types";
import { MfaEnrollments, Schemas } from "../../../emulator/auth/types";

export { PROJECT_ID };
export const TEST_PHONE_NUMBER = "+15555550100";
Expand Down Expand Up @@ -407,7 +407,7 @@ export function deleteAccount(testAgent: TestAgent, reqBody: {}): Promise<string
export function registerTenant(
testAgent: TestAgent,
projectId: string,
tenant?: Partial<Tenant>
tenant?: Schemas["GoogleCloudIdentitytoolkitAdminV2Tenant"]
): Promise<Tenant> {
return testAgent
.post(`/identitytoolkit.googleapis.com/v2/projects/${projectId}/tenants`)
Expand Down
53 changes: 51 additions & 2 deletions src/test/emulators/auth/tenant.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from "chai";
import { Tenant } from "../../../emulator/auth/state";
import { expectStatusCode, registerTenant } from "./helpers";
import { describeAuthEmulator } from "./setup";
import { describeAuthEmulator, PROJECT_ID } from "./setup";

describeAuthEmulator("tenant management", ({ authApi }) => {
describe("createTenant", () => {
Expand Down Expand Up @@ -39,6 +39,24 @@ describeAuthEmulator("tenant management", ({ authApi }) => {
expect(res.body.name).to.eql(`projects/project-id/tenants/${res.body.tenantId}`);
});
});

it("should create a tenant with default disabled settings", async () => {
await authApi()
.post("/identitytoolkit.googleapis.com/v2/projects/project-id/tenants")
.set("Authorization", "Bearer owner")
.send({})
.then((res) => {
expectStatusCode(200, res);
expect(res.body.allowPasswordSignup).to.be.false;
expect(res.body.disableAuth).to.be.false;
expect(res.body.enableAnonymousUser).to.be.false;
expect(res.body.enableEmailLinkSignin).to.be.false;
expect(res.body.mfaConfig).to.eql({
state: "DISABLED",
enabledProviders: [],
});
});
});
});

describe("getTenants", () => {
Expand All @@ -55,7 +73,7 @@ describeAuthEmulator("tenant management", ({ authApi }) => {
});
});

it("should create tenants if they do not exist", async () => {
it("should create tenants with default enabled settings if they do not exist", async () => {
// No projects exist initially
const projectId = "project-id";
await authApi()
Expand All @@ -71,6 +89,14 @@ describeAuthEmulator("tenant management", ({ authApi }) => {
const createdTenant: Tenant = {
tenantId,
name: `projects/${projectId}/tenants/${tenantId}`,
allowPasswordSignup: true,
disableAuth: false,
enableAnonymousUser: true,
enableEmailLinkSignin: true,
mfaConfig: {
enabledProviders: ["PHONE_SMS"],
state: "ENABLED",
},
};
await authApi()
.get(`/identitytoolkit.googleapis.com/v2/projects/${projectId}/tenants/${tenantId}`)
Expand Down Expand Up @@ -325,5 +351,28 @@ describeAuthEmulator("tenant management", ({ authApi }) => {
});
});
});

it("performs a full update with production defaults if the update mask is empty", async () => {
const projectId = "project-id";
const tenant = await registerTenant(authApi(), projectId, {});

await authApi()
.patch(
`/identitytoolkit.googleapis.com/v2/projects/${projectId}/tenants/${tenant.tenantId}`
)
.set("Authorization", "Bearer owner")
.send({})
.then((res) => {
expectStatusCode(200, res);
expect(res.body.allowPasswordSignup).to.be.false;
expect(res.body.disableAuth).to.be.false;
expect(res.body.enableAnonymousUser).to.be.false;
expect(res.body.enableEmailLinkSignin).to.be.false;
expect(res.body.mfaConfig).to.eql({
enabledProviders: [],
state: "DISABLED",
});
});
});
});
});

0 comments on commit cff2821

Please sign in to comment.