Skip to content

Commit

Permalink
[server] EntitlementService.maySetTimeout: Make organizationId mandatory
Browse files Browse the repository at this point in the history
  • Loading branch information
geropl committed Sep 19, 2023
1 parent 3b6cef2 commit 8af83ac
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 77 deletions.
2 changes: 1 addition & 1 deletion components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
reportErrorBoundary(url: string, message: string): Promise<void>;

getSupportedWorkspaceClasses(): Promise<SupportedWorkspaceClass[]>;
maySetTimeout(opts?: { organizationId?: string }): Promise<boolean>;
maySetTimeout(opts: { organizationId: string }): Promise<boolean>;
updateWorkspaceTimeoutSetting(setting: Partial<WorkspaceTimeoutSetting>): Promise<void>;

/**
Expand Down
16 changes: 0 additions & 16 deletions components/server/src/billing/billing-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BillingMode> {
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",
};
}
}
58 changes: 10 additions & 48 deletions components/server/src/billing/entitlement-service-ubp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand All @@ -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,
Expand Down Expand Up @@ -79,7 +74,7 @@ export class EntitlementServiceUBP implements EntitlementService {
}
}

async maySetTimeout(userId: string, organizationId?: string): Promise<boolean> {
async maySetTimeout(userId: string, organizationId: string): Promise<boolean> {
return this.hasPaidSubscription(userId, organizationId);
}

Expand Down Expand Up @@ -109,48 +104,15 @@ export class EntitlementServiceUBP implements EntitlementService {
return true;
}

private async hasPaidSubscription(userId: string, organizationId?: string): Promise<boolean> {
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<boolean> {
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<boolean>) => {
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<BillingTier> {
Expand Down
7 changes: 3 additions & 4 deletions components/server/src/billing/entitlement-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface EntitlementService {
* @param userId
* @param organizationId
*/
maySetTimeout(userId: string, organizationId?: string): Promise<boolean>;
maySetTimeout(userId: string, organizationId: string): Promise<boolean>;

/**
* Returns the default workspace timeout for the given user at a given point in time
Expand Down Expand Up @@ -127,10 +127,9 @@ export class EntitlementServiceImpl implements EntitlementService {
}
}

async maySetTimeout(userId: string, organizationId?: string): Promise<boolean> {
async maySetTimeout(userId: string, organizationId: string): Promise<boolean> {
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
Expand Down
14 changes: 6 additions & 8 deletions components/server/src/workspace/gitpod-server-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,16 +650,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
return updatedUser;
}

public async maySetTimeout(ctx: TraceContext, opts?: { organizationId?: string }): Promise<boolean> {
public async maySetTimeout(ctx: TraceContext, opts: { organizationId: string }): Promise<boolean> {
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(
Expand Down

0 comments on commit 8af83ac

Please sign in to comment.