From 8af83aca7adb3dbfe51208d51a62a72685a1ec1c Mon Sep 17 00:00:00 2001 From: Gero Posmyk-Leinemann Date: Tue, 19 Sep 2023 09:10:09 +0000 Subject: [PATCH] [server] EntitlementService.maySetTimeout: Make organizationId mandatory --- .../gitpod-protocol/src/gitpod-service.ts | 2 +- components/server/src/billing/billing-mode.ts | 16 ----- .../src/billing/entitlement-service-ubp.ts | 58 ++++--------------- .../server/src/billing/entitlement-service.ts | 7 +-- .../src/workspace/gitpod-server-impl.ts | 14 ++--- 5 files changed, 20 insertions(+), 77 deletions(-) diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 6ea0683e390f75..0e215ead17a014 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -259,7 +259,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, reportErrorBoundary(url: string, message: string): Promise; getSupportedWorkspaceClasses(): Promise; - maySetTimeout(opts?: { organizationId?: string }): Promise; + maySetTimeout(opts: { organizationId: string }): Promise; updateWorkspaceTimeoutSetting(setting: Partial): Promise; /** diff --git a/components/server/src/billing/billing-mode.ts b/components/server/src/billing/billing-mode.ts index 75d9177ef2dfb9..1f7a73115742af 100644 --- a/components/server/src/billing/billing-mode.ts +++ b/components/server/src/billing/billing-mode.ts @@ -31,20 +31,4 @@ export class BillingModes { const paid = billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; return { mode: "usage-based", paid }; } - - /** - * @deprecated use getBillingMode(userId, organizationId) instead - * @returns - */ - async getBillingModeForUser(): Promise { - if (!this.config.enablePayment) { - // Payment is not enabled. E.g. Self-Hosted. - return { mode: "none" }; - } - - // "paid" is not set here, just as before. Also, it's we should remove this whole method once the org-migration is done, and center all capabilities around Organizations - return { - mode: "usage-based", - }; - } } diff --git a/components/server/src/billing/entitlement-service-ubp.ts b/components/server/src/billing/entitlement-service-ubp.ts index 4eb5869e45ad50..f7105072e016e5 100644 --- a/components/server/src/billing/entitlement-service-ubp.ts +++ b/components/server/src/billing/entitlement-service-ubp.ts @@ -4,7 +4,6 @@ * See License.AGPL.txt in the project root for license information. */ -import { TeamDB } from "@gitpod/gitpod-db/lib"; import { WorkspaceInstance, WorkspaceTimeoutDuration, @@ -14,7 +13,6 @@ import { WORKSPACE_LIFETIME_SHORT, User, BillingTier, - Team, } from "@gitpod/gitpod-protocol"; import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution"; import { inject, injectable } from "inversify"; @@ -31,10 +29,7 @@ const MAX_PARALLEL_WORKSPACES_PAID = 16; */ @injectable() export class EntitlementServiceUBP implements EntitlementService { - constructor( - @inject(UsageService) private readonly usageService: UsageService, - @inject(TeamDB) private readonly teamDB: TeamDB, - ) {} + constructor(@inject(UsageService) private readonly usageService: UsageService) {} async mayStartWorkspace( user: User, @@ -79,7 +74,7 @@ export class EntitlementServiceUBP implements EntitlementService { } } - async maySetTimeout(userId: string, organizationId?: string): Promise { + async maySetTimeout(userId: string, organizationId: string): Promise { return this.hasPaidSubscription(userId, organizationId); } @@ -109,48 +104,15 @@ export class EntitlementServiceUBP implements EntitlementService { return true; } - private async hasPaidSubscription(userId: string, organizationId?: string): Promise { - if (organizationId) { - try { - // This is the "stricter", more correct version: We only allow privileges on the Organization that is paying for it - const { billingStrategy } = await this.usageService.getCostCenter(userId, organizationId); - return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; - } catch (err) { - log.warn({ userId, organizationId }, "Error checking if user is subscribed to organization", err); - return false; - } - } - - // TODO(gpl) Remove everything below once organizationId is always passed - // This is the old behavior, stemming from our transition to PAYF, where our API did-/doesn't pass organizationId, yet - // Member of paid team? - const teams = await this.teamDB.findTeamsByUser(userId); - const isTeamSubscribedPromises = teams.map(async (team: Team) => { - const { billingStrategy } = await this.usageService.getCostCenter(userId, team.id); + private async hasPaidSubscription(userId: string, organizationId: string): Promise { + try { + // This is the "stricter", more correct version: We only allow privileges on the Organization that is paying for it + const { billingStrategy } = await this.usageService.getCostCenter(userId, organizationId); return billingStrategy === CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; - }); - // Return the first truthy promise, or false if all the promises were falsy. - // Source: https://gist.github.com/jbreckmckye/66364021ebaa0785e426deec0410a235 - return new Promise((resolve, reject) => { - // If any promise returns true, immediately resolve with true - isTeamSubscribedPromises.forEach(async (isTeamSubscribedPromise: Promise) => { - try { - const isTeamSubscribed = await isTeamSubscribedPromise; - if (isTeamSubscribed) resolve(true); - } catch (err) { - log.warn({ userId, organizationId }, "Error checking if user is subscribed to organization", err); - resolve(false); - } - }); - - // If neither of the above fires, resolve with false - // Check truthiness just in case callbacks fire out-of-band - Promise.all(isTeamSubscribedPromises) - .then((areTeamsSubscribed) => { - resolve(!!areTeamsSubscribed.find((isTeamSubscribed: boolean) => !!isTeamSubscribed)); - }) - .catch(reject); - }); + } catch (err) { + log.warn({ userId, organizationId }, "Error checking if user is subscribed to organization", err); + return false; + } } async getBillingTier(userId: string, organizationId: string): Promise { diff --git a/components/server/src/billing/entitlement-service.ts b/components/server/src/billing/entitlement-service.ts index 6bc49f91ca6374..32f4f792556027 100644 --- a/components/server/src/billing/entitlement-service.ts +++ b/components/server/src/billing/entitlement-service.ts @@ -54,7 +54,7 @@ export interface EntitlementService { * @param userId * @param organizationId */ - maySetTimeout(userId: string, organizationId?: string): Promise; + maySetTimeout(userId: string, organizationId: string): Promise; /** * Returns the default workspace timeout for the given user at a given point in time @@ -127,10 +127,9 @@ export class EntitlementServiceImpl implements EntitlementService { } } - async maySetTimeout(userId: string, organizationId?: string): Promise { + async maySetTimeout(userId: string, organizationId: string): Promise { try { - // TODO(gpl): We need to replace this with ".getBillingMode(user.id, organizationId);" once all callers forward organizationId - const billingMode = await this.billingModes.getBillingModeForUser(); + const billingMode = await this.billingModes.getBillingMode(userId, organizationId); switch (billingMode.mode) { case "none": // when payment is disabled users can do everything diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index aff6f546f1d584..a18d73bf71c691 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -650,16 +650,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { return updatedUser; } - public async maySetTimeout(ctx: TraceContext, opts?: { organizationId?: string }): Promise { + public async maySetTimeout(ctx: TraceContext, opts: { organizationId: string }): Promise { const user = await this.checkUser("maySetTimeout", opts); - await this.guardAccess({ kind: "user", subject: user }, "get"); - // TODO(gpl) Remove once organizationId is mandatory - await this.auth.checkPermissionOnUser(user.id, "read_info", user.id); - if (opts?.organizationId) { - await this.auth.checkPermissionOnOrganization(user.id, "read_info", opts.organizationId); - } + const org = await this.organizationService.getOrganization(user.id, opts.organizationId); + const members = await this.organizationService.listMembers(user.id, opts.organizationId); + await this.guardAccess({ kind: "team", subject: org, members }, "get"); - return await this.entitlementService.maySetTimeout(user.id, opts?.organizationId); + await this.auth.checkPermissionOnOrganization(user.id, "read_info", opts.organizationId); + return await this.entitlementService.maySetTimeout(user.id, opts.organizationId); } public async updateWorkspaceTimeoutSetting(