Skip to content

Commit 665b7ab

Browse files
Merge branch 'main' into bradnew-project-repo-search
2 parents c005d3b + 35f98d9 commit 665b7ab

35 files changed

+894
-972
lines changed

components/dashboard/src/Login.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
155155
{providerFromContext ? (
156156
<button
157157
key={"button" + providerFromContext.host}
158-
className="btn-login flex-none w-56 h-10 p-0 inline-flex"
158+
className="btn-login flex-none w-56 h-10 p-0 inline-flex rounded-xl"
159159
onClick={() => openLogin(providerFromContext!.host)}
160160
>
161161
{iconForAuthProvider(providerFromContext.authProviderType)}
@@ -167,7 +167,7 @@ export const Login: FC<LoginProps> = ({ onLoggedIn }) => {
167167
authProviders.data?.map((ap) => (
168168
<button
169169
key={"button" + ap.host}
170-
className="btn-login flex-none w-56 h-10 p-0 inline-flex"
170+
className="btn-login flex-none w-56 h-10 p-0 inline-flex rounded-xl"
171171
onClick={() => openLogin(ap.host)}
172172
>
173173
{iconForAuthProvider(ap.authProviderType)}

components/dashboard/src/components/Button.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
4949
className={classNames(
5050
"cursor-pointer my-auto",
5151
"text-sm font-medium whitespace-nowrap",
52-
"rounded-md focus:outline-none focus:ring transition ease-in-out",
52+
"rounded-xl focus:outline-none focus:ring transition ease-in-out",
5353
spacing === "compact" ? ["px-1 py-1"] : null,
5454
spacing === "default" ? ["px-4 py-2"] : null,
5555
type === "primary"
5656
? [
57-
"bg-green-600 dark:bg-green-700 hover:bg-green-700 dark:hover:bg-green-600",
58-
"text-gray-100 dark:text-green-100",
57+
"bg-gray-900 hover:bg-gray-800 dark:bg-kumquat-base dark:hover:bg-kumquat-ripe",
58+
"text-gray-50 dark:text-gray-900",
5959
]
6060
: null,
6161
type === "secondary"

components/dashboard/src/components/EmptyMessage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const EmptyMessage: FC<Props> = ({ title, subtitle, buttonText, onClick,
2323
return (
2424
<div
2525
className={classNames(
26-
"w-full flex justify-center mt-2 rounded-xl bg-gray-100 dark:bg-gray-900 px-4 py-14",
26+
"w-full flex justify-center mt-2 rounded-xl bg-gray-100 dark:bg-gray-800 px-4 py-14",
2727
className,
2828
)}
2929
>

components/dashboard/src/components/SelectableCardSolid.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ function SelectableCardSolid(props: SelectableCardSolidProps) {
2727

2828
return (
2929
<div
30-
className={`rounded-xl px-3 py-3 flex flex-col cursor-pointer group transition ease-in-out ${
30+
className={`rounded-xl px-2 py-2 flex flex-col cursor-pointer group transition ease-in-out ${
3131
isFocused ? "ring-2 ring-blue-500" : ""
3232
} ${
3333
props.selected
34-
? "bg-gray-800 dark:bg-gray-100"
35-
: "bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700"
34+
? "border-4 border-gray-500 dark:border-gray-50"
35+
: "hover:bg-gray-100 dark:hover:bg-gray-800 border-4 border-gray-100 dark:border-gray-800"
3636
} ${props.className || ""}`}
3737
onClick={props.onClick}
3838
>
3939
<div className="flex items-center">
4040
<p
4141
className={`w-full pl-1 text-base font-semibold truncate ${
42-
props.selected ? "text-gray-100 dark:text-gray-600" : "text-gray-600 dark:text-gray-500"
42+
props.selected ? "text-gray-600 dark:text-gray-400" : "text-gray-600 dark:text-gray-400"
4343
}`}
4444
title={props.title}
4545
>

components/dashboard/src/index.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
@layer components {
5555
/* TODO: deprecate button styles in favor of using <Button> component and handling there */
5656
button {
57-
@apply cursor-pointer px-4 py-2 my-auto bg-green-600 dark:bg-green-700 hover:bg-green-700 dark:hover:bg-green-600 text-gray-100 dark:text-green-100 text-sm font-medium rounded-md focus:outline-none focus:ring transition ease-in-out;
57+
@apply cursor-pointer px-4 py-2 my-auto bg-gray-900 hover:bg-gray-800 dark:bg-kumquat-base dark:hover:bg-kumquat-ripe text-gray-50 dark:text-gray-900 text-sm font-medium rounded-xl focus:outline-none focus:ring transition ease-in-out;
5858
}
5959
button.secondary {
6060
@apply bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-500 dark:text-gray-100 hover:text-gray-600;
@@ -78,7 +78,7 @@
7878
}
7979

8080
code {
81-
@apply bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
81+
@apply bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded-md text-sm font-mono font-medium;
8282
}
8383

8484
textarea,

components/dashboard/src/teams/Members.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import searchIcon from "../icons/search.svg";
1919
import { teamsService } from "../service/public-api";
2020
import { useCurrentUser } from "../user-context";
2121
import { SpinnerLoader } from "../components/Loader";
22+
import { Delayed } from "../components/Delayed";
2223
import { InputField } from "../components/forms/InputField";
2324
import { InputWithCopy } from "../components/InputWithCopy";
2425

@@ -74,6 +75,15 @@ export default function MembersPage() {
7475
return !!owners?.some((o) => o.userId === user?.id);
7576
}, [org.data?.members, user?.id]);
7677

78+
// Note: We would hardly get here, but just in case. We should show a loader instead of blank section.
79+
if (org.isLoading) {
80+
return (
81+
<Delayed>
82+
<SpinnerLoader />
83+
</Delayed>
84+
);
85+
}
86+
7787
const filteredMembers =
7888
org.data?.members.filter((m) => {
7989
if (!!roleFilter && m.role !== roleFilter) {
@@ -105,7 +115,7 @@ export default function MembersPage() {
105115
onChange={(e) => setSearchText(e.target.value)}
106116
/>
107117
</div>
108-
<div className="py-2 pl-3 pr-1 border border-gray-100 ml-2 rounded-md">
118+
<div className="py-2 pl-3 pr-1 border border-gray-100 dark:border-gray-800 ml-2 rounded-md">
109119
<DropDown
110120
customClasses="w-32"
111121
activeEntry={
@@ -163,7 +173,7 @@ export default function MembersPage() {
163173
</ItemField>
164174
</Item>
165175
{filteredMembers.length === 0 ? (
166-
<SpinnerLoader />
176+
<p className="pt-16 text-center">No members found</p>
167177
) : (
168178
filteredMembers.map((m) => (
169179
<Item className="grid grid-cols-3" key={m.userId}>

components/dashboard/src/user-settings/Integrations.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ function GitIntegrations() {
465465
</div>
466466

467467
{providers && providers.length === 0 && (
468-
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-900">
468+
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-800">
469469
<div className="m-auto text-center">
470470
<Heading2 color="light" className="self-center mb-4">
471471
No Git Integrations

components/dashboard/tailwind.config.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,29 @@ module.exports = {
1515
theme: {
1616
extend: {
1717
colors: {
18-
gray: colors.warmGray,
1918
green: colors.lime,
2019
orange: colors.amber,
21-
blue: {
22-
light: "#75A9EC",
23-
DEFAULT: "#5C8DD6",
24-
dark: "#265583",
25-
},
2620
// TODO: figure out if we want to just pull in the specific gitpod-* colors
2721
teal: colors.teal,
2822
sky: colors.sky,
2923
rose: colors.rose,
3024
"gitpod-black": "#161616",
31-
"gitpod-gray": "#8E8787",
3225
"gitpod-red": "#CE4A3E",
33-
"gitpod-kumquat-light": "#FFE4BC",
34-
"gitpod-kumquat": "#FFB45B",
35-
"gitpod-kumquat-dark": "#FF8A00",
36-
"gitpod-kumquat-darker": "#f28300",
37-
"gitpod-kumquat-gradient": "linear-gradient(137.41deg, #FFAD33 14.37%, #FF8A00 91.32%)",
26+
"kumquat-dark": "#FF8A00",
27+
"kumquat-base": "#FFAE33",
28+
"kumquat-ripe": "#FFB45B",
29+
"kumquat-light": "#FFE4BC",
30+
"kumquat-gradient": "linear-gradient(137.41deg, #FFAD33 14.37%, #FF8A00 91.32%)",
31+
"gray-900": "#12100C",
32+
"gray-800": "#23211E",
33+
"gray-700": "#514F4D",
34+
"gray-600": "#565451",
35+
"gray-500": "#666564",
36+
"gray-400": "#999795",
37+
"gray-300": "#DADADA",
38+
"gray-200": "#ECE7E5",
39+
"gray-100": "#F5F4F4",
40+
"gray-50": "#F9F9F9",
3841
},
3942
container: {
4043
center: true,

components/gitpod-db/src/team-db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface TeamDB extends TransactionalDB<TeamDB> {
2121
limit: number,
2222
orderBy: keyof Team,
2323
orderDir: "ASC" | "DESC",
24-
searchTerm: string,
24+
searchTerm?: string,
2525
): Promise<{ total: number; rows: Team[] }>;
2626
findTeamById(teamId: string): Promise<Team | undefined>;
2727
findTeamByMembershipId(membershipId: string): Promise<Team | undefined>;

components/gitpod-db/src/typeorm/team-db-impl.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@ export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {
6565
searchTerm?: string,
6666
): Promise<{ total: number; rows: Team[] }> {
6767
const teamRepo = await this.getTeamRepo();
68-
const queryBuilder = teamRepo
69-
.createQueryBuilder("team")
70-
.where("LOWER(team.name) LIKE LOWER(:searchTerm)", { searchTerm: `%${searchTerm}%` })
68+
let queryBuilder = teamRepo.createQueryBuilder("team");
69+
if (searchTerm) {
70+
queryBuilder = queryBuilder.where("LOWER(team.name) LIKE LOWER(:searchTerm)", {
71+
searchTerm: `%${searchTerm}%`,
72+
});
73+
}
74+
queryBuilder = queryBuilder
7175
.andWhere("deleted = 0")
7276
.andWhere("markedDeleted = 0")
7377
.skip(offset)

components/server/src/authorization/authorizer.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { Project, TeamMemberRole } from "@gitpod/gitpod-protocol";
1111
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
1212
import {
1313
AllResourceTypes,
14+
InstallationID,
15+
InstallationPermission,
1416
OrganizationPermission,
1517
Permission,
1618
ProjectPermission,
@@ -54,6 +56,31 @@ export const SYSTEM_USER = "SYSTEM_USER";
5456
export class Authorizer {
5557
constructor(private authorizer: SpiceDBAuthorizer) {}
5658

59+
async hasPermissionOnInstallation(userId: string, permission: InstallationPermission): Promise<boolean> {
60+
if (userId === SYSTEM_USER) {
61+
return true;
62+
}
63+
64+
const req = v1.CheckPermissionRequest.create({
65+
subject: subject("user", userId),
66+
permission,
67+
resource: object("installation", InstallationID),
68+
consistency,
69+
});
70+
71+
return this.authorizer.check(req, { userId });
72+
}
73+
74+
async checkPermissionOnInstallation(userId: string, permission: InstallationPermission): Promise<void> {
75+
if (await this.hasPermissionOnInstallation(userId, permission)) {
76+
return;
77+
}
78+
throw new ApplicationError(
79+
ErrorCodes.PERMISSION_DENIED,
80+
`User ${userId} does not have permission '${permission}' on the installation.`,
81+
);
82+
}
83+
5784
async hasPermissionOnOrganization(
5885
userId: string,
5986
permission: OrganizationPermission,
@@ -386,14 +413,19 @@ export class Authorizer {
386413
);
387414
}
388415

389-
async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
416+
async addWorkspaceToOrg(orgID: string, userID: string, workspaceID: string, shared: boolean): Promise<void> {
390417
if (await this.isDisabled(userID)) {
391418
return;
392419
}
393-
await this.authorizer.writeRelationships(
420+
const rels = [
394421
set(rel.workspace(workspaceID).org.organization(orgID)),
395422
set(rel.workspace(workspaceID).owner.user(userID)),
396-
);
423+
];
424+
if (shared) {
425+
rels.push(set(rel.workspace(workspaceID).shared.anyUser));
426+
}
427+
428+
await this.authorizer.writeRelationships(...rels);
397429
}
398430

399431
async removeWorkspaceFromOrg(orgID: string, userID: string, workspaceID: string): Promise<void> {
@@ -403,9 +435,18 @@ export class Authorizer {
403435
await this.authorizer.writeRelationships(
404436
remove(rel.workspace(workspaceID).org.organization(orgID)),
405437
remove(rel.workspace(workspaceID).owner.user(userID)),
438+
remove(rel.workspace(workspaceID).shared.anyUser),
406439
);
407440
}
408441

442+
async setWorkspaceIsShared(userID: string, workspaceID: string, shared: boolean): Promise<void> {
443+
if (await this.isDisabled(userID)) {
444+
return;
445+
}
446+
const op = shared ? set : remove;
447+
await this.authorizer.writeRelationships(op(rel.workspace(workspaceID).shared.anyUser));
448+
}
449+
409450
async bulkCreateWorkspaceInOrg(ids: { orgID: string; userID: string; workspaceID: string }[]): Promise<void> {
410451
const rels = ids
411452
.map(({ orgID, userID, workspaceID }) => [

components/server/src/authorization/definitions.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { v1 } from "@authzed/authzed-node";
1010

11-
const InstallationID = "1";
11+
export const InstallationID = "1";
1212

1313
export type ResourceType =
1414
| UserResourceType
@@ -37,6 +37,7 @@ export type UserPermission =
3737
| "write_info"
3838
| "delete"
3939
| "make_admin"
40+
| "admin_control"
4041
| "read_ssh"
4142
| "write_ssh"
4243
| "read_tokens"
@@ -48,7 +49,7 @@ export type InstallationResourceType = "installation";
4849

4950
export type InstallationRelation = "member" | "admin";
5051

51-
export type InstallationPermission = "create_organization";
52+
export type InstallationPermission = "create_organization" | "configure";
5253

5354
export type OrganizationResourceType = "organization";
5455

@@ -89,7 +90,7 @@ export type ProjectPermission =
8990

9091
export type WorkspaceResourceType = "workspace";
9192

92-
export type WorkspaceRelation = "org" | "owner";
93+
export type WorkspaceRelation = "org" | "owner" | "shared";
9394

9495
export type WorkspacePermission = "access" | "start" | "stop" | "delete" | "read_info";
9596

@@ -424,6 +425,26 @@ export const rel = {
424425
},
425426
};
426427
},
428+
429+
get shared() {
430+
const result2 = {
431+
...result,
432+
relation: "shared",
433+
};
434+
return {
435+
get anyUser() {
436+
return {
437+
...result2,
438+
subject: {
439+
object: {
440+
objectType: "user",
441+
objectId: "*",
442+
},
443+
},
444+
} as v1.Relationship;
445+
},
446+
};
447+
},
427448
};
428449
},
429450
};

components/server/src/authorization/spicedb-authorizer.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import { v1 } from "@authzed/authzed-node";
88
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
9+
import { TrustedValue } from "@gitpod/gitpod-protocol/lib/util/scrubbing";
910

1011
import { getExperimentsClientForBackend } from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
1112
import { inject, injectable } from "inversify";
@@ -47,7 +48,9 @@ export class SpiceDBAuthorizer {
4748
return permitted;
4849
} catch (err) {
4950
error = err;
50-
log.error("[spicedb] Failed to perform authorization check.", err, { req });
51+
log.error("[spicedb] Failed to perform authorization check.", err, {
52+
request: new TrustedValue(req),
53+
});
5154
return false;
5255
} finally {
5356
observeSpicedbClientLatency("check", error, timer());
@@ -72,7 +75,7 @@ export class SpiceDBAuthorizer {
7275
return response;
7376
} catch (err) {
7477
error = err;
75-
log.error("[spicedb] Failed to write relationships.", err, { updates });
78+
log.error("[spicedb] Failed to write relationships.", err, { updates: new TrustedValue(updates) });
7679
} finally {
7780
observeSpicedbClientLatency("write", error, timer());
7881
}
@@ -104,7 +107,7 @@ export class SpiceDBAuthorizer {
104107
error = err;
105108
// While in we're running two authorization systems in parallel, we do not hard fail on writes.
106109
//TODO throw new ApplicationError(ErrorCodes.INTERNAL_SERVER_ERROR, "Failed to delete relationships.");
107-
log.error("[spicedb] Failed to delete relationships.", err, { req });
110+
log.error("[spicedb] Failed to delete relationships.", err, { request: new TrustedValue(req) });
108111
return [];
109112
} finally {
110113
observeSpicedbClientLatency("delete", error, timer());

0 commit comments

Comments
 (0)