Skip to content

Commit b2324bd

Browse files
authored
[fga] allow toggling centralizedPermissions on/off (#18444)
1 parent 56adfff commit b2324bd

File tree

6 files changed

+69
-36
lines changed

6 files changed

+69
-36
lines changed

.github/workflows/build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,16 @@ jobs:
151151
MYSQL_TCP_PORT: 23306
152152
ports:
153153
- 23306:23306
154+
redis:
155+
image: redis
156+
ports:
157+
- 6379:6379
154158
container:
155159
image: eu.gcr.io/gitpod-core-dev/dev/dev-environment:aledbf-oci-tool-gha.14121
156160
env:
157161
DB_HOST: "mysql"
158162
DB_PORT: "23306"
163+
REDIS_HOST: "redis"
159164
steps:
160165
- uses: actions/checkout@v3
161166
- uses: ./.github/actions/setup-environment

components/dashboard/src/data/featureflag-query.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { useCurrentUser } from "../user-context";
1212
import { useCurrentOrg } from "./organizations/orgs-query";
1313

1414
const featureFlags = {
15-
start_with_options: false,
1615
showUseLastSuccessfulPrebuild: false,
1716
publicApiExperimentalWorkspaceService: false,
1817
personalAccessTokensEnabled: false,

components/server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"clean:node": "rimraf node_modules",
1818
"purge": "yarn clean && yarn clean:node && yarn run rimraf yarn.lock",
1919
"test:leeway": "yarn build && yarn test",
20-
"test": "cleanup() { echo 'Cleanup started'; yarn stop-services; }; trap cleanup EXIT; yarn test:unit && yarn test:db",
20+
"test": "yarn test:unit && yarn test:db",
2121
"test:unit": "mocha --opts mocha.opts './**/*.spec.js' --exclude './node_modules/**'",
22-
"test:db": ". $(leeway run components/gitpod-db:db-test-env) && yarn start-services && mocha --opts mocha.opts './**/*.spec.db.js' --exclude './node_modules/**'",
22+
"test:db": "cleanup() { echo 'Cleanup started'; yarn stop-spicedb; }; trap cleanup EXIT; . $(leeway run components/gitpod-db:db-test-env) && yarn start-spicedb && mocha --opts mocha.opts './**/*.spec.db.js' --exclude './node_modules/**'",
2323
"start-services": "yarn start-testdb && yarn start-redis && yarn start-spicedb",
2424
"stop-services": "yarn stop-redis && yarn stop-spicedb",
2525
"start-testdb": "if netstat -tuln | grep ':23306 '; then echo 'Mysql is already running.'; else leeway run components/gitpod-db:init-testdb; fi",

components/server/src/authorization/relationship-updater.spec.db.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ describe("RelationshipUpdater", async () => {
5959
await expected(rel.installation.member.user(user.id));
6060
});
6161

62+
it("should update a simple user once it was feature-flag disabled", async () => {
63+
let user = await userDB.newUser();
64+
65+
user = await migrator.migrate(user);
66+
expect(user.additionalData?.fgaRelationshipsVersion).to.not.be.undefined;
67+
68+
Experiments.configureTestingClient({
69+
centralizedPermissions: false,
70+
});
71+
72+
user = await migrator.migrate(user);
73+
expect(user.additionalData?.fgaRelationshipsVersion).to.be.undefined;
74+
75+
Experiments.configureTestingClient({
76+
centralizedPermissions: true,
77+
});
78+
79+
user = await migrator.migrate(user);
80+
expect(user.additionalData?.fgaRelationshipsVersion).to.not.be.undefined;
81+
});
82+
6283
it("should correctly update a simple user after it moves between org and installation level", async () => {
6384
let user = await userDB.newUser();
6485
user = await migrate(user);

components/server/src/authorization/relationship-updater.ts

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messag
1313
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
1414
import { v1 } from "@authzed/authzed-node";
1515
import { fgaRelationsUpdateClientLatency } from "../prometheus-metrics";
16+
import { RedisMutex } from "../redis/mutex";
1617

1718
@injectable()
1819
export class RelationshipUpdater {
@@ -23,6 +24,7 @@ export class RelationshipUpdater {
2324
@inject(TeamDB) private readonly orgDB: TeamDB,
2425
@inject(ProjectDB) private readonly projectDB: ProjectDB,
2526
@inject(Authorizer) private readonly authorizer: Authorizer,
27+
@inject(RedisMutex) private readonly mutex: RedisMutex,
2628
) {}
2729

2830
/**
@@ -42,6 +44,12 @@ export class RelationshipUpdater {
4244
},
4345
});
4446
if (!fgaEnabled) {
47+
if (user.additionalData?.fgaRelationshipsVersion !== undefined) {
48+
log.info({ userId: user.id }, `User has been removed from FGA.`);
49+
// reset the fgaRelationshipsVersion to undefined, so the migration is triggered again when the feature is enabled
50+
AdditionalUserData.set(user, { fgaRelationshipsVersion: undefined });
51+
return await this.userDB.storeUser(user);
52+
}
4553
return user;
4654
}
4755
if (user.additionalData?.fgaRelationshipsVersion === this.version) {
@@ -53,41 +61,40 @@ export class RelationshipUpdater {
5361
fromVersion: user?.additionalData?.fgaRelationshipsVersion,
5462
toVersion: this.version,
5563
});
56-
// TODO run in distributed lock
57-
// return this.mutex.using([`fga-migration-${user.id}`], 2000, async () => {
58-
const before = new Date().getTime();
64+
return this.mutex.using([`fga-migration-${user.id}`], 2000, async () => {
65+
const before = new Date().getTime();
5966

60-
const updatedUser = await this.userDB.findUserById(user.id);
61-
if (!updatedUser) {
62-
throw new ApplicationError(ErrorCodes.NOT_FOUND, "User not found");
63-
}
64-
user = updatedUser;
65-
if (user.additionalData?.fgaRelationshipsVersion === this.version) {
66-
return user;
67-
}
68-
log.info({ userId: user.id }, `Updating FGA relationships for user.`, {
69-
fromVersion: user?.additionalData?.fgaRelationshipsVersion,
70-
toVersion: this.version,
71-
});
72-
const orgs = await this.findAffectedOrganizations(user.id);
67+
const updatedUser = await this.userDB.findUserById(user.id);
68+
if (!updatedUser) {
69+
throw new ApplicationError(ErrorCodes.NOT_FOUND, "User not found");
70+
}
71+
user = updatedUser;
72+
if (user.additionalData?.fgaRelationshipsVersion === this.version) {
73+
return user;
74+
}
75+
log.info({ userId: user.id }, `Updating FGA relationships for user.`, {
76+
fromVersion: user?.additionalData?.fgaRelationshipsVersion,
77+
toVersion: this.version,
78+
});
79+
const orgs = await this.findAffectedOrganizations(user.id);
7380

74-
// Add relationships
75-
await this.updateUser(user);
76-
for (const org of orgs) {
77-
await this.updateOrganization(user.id, org);
78-
}
79-
AdditionalUserData.set(user, {
80-
fgaRelationshipsVersion: this.version,
81-
});
82-
await this.userDB.updateUserPartial({
83-
id: user.id,
84-
additionalData: user.additionalData,
85-
});
86-
log.info({ userId: user.id }, `Finished updating relationships.`, {
87-
duration: new Date().getTime() - before,
81+
// Add relationships
82+
await this.updateUser(user);
83+
for (const org of orgs) {
84+
await this.updateOrganization(user.id, org);
85+
}
86+
AdditionalUserData.set(user, {
87+
fgaRelationshipsVersion: this.version,
88+
});
89+
await this.userDB.updateUserPartial({
90+
id: user.id,
91+
additionalData: user.additionalData,
92+
});
93+
log.info({ userId: user.id }, `Finished updating relationships.`, {
94+
duration: new Date().getTime() - before,
95+
});
96+
return user;
8897
});
89-
return user;
90-
// });
9198
} catch (error) {
9299
log.error({ userId: user.id }, `Error updating relationships.`, error);
93100
return user;

components/server/src/test/service-testing-container-module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { testContainer } from "@gitpod/gitpod-db/lib";
2121
import { productionContainerModule } from "../container-module";
2222
import { createMock } from "./mocks/mock";
2323
import { UsageServiceClientMock } from "./mocks/usage-service-client-mock";
24+
import { env } from "process";
2425

2526
/**
2627
* Expects a fully configured production container and
@@ -40,7 +41,7 @@ const mockApplyingContainerModule = new ContainerModule((bind, unbound, isbound,
4041
passlist: [],
4142
},
4243
redis: {
43-
address: "localhost:6379",
44+
address: (env.REDIS_HOST || "127.0.0.1") + ":" + (env.REDIS_PORT || "6379"),
4445
},
4546
});
4647
rebind(IAnalyticsWriter).toConstantValue(NullAnalyticsWriter);

0 commit comments

Comments
 (0)