-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[server] Perform authorization checks for Orgs against spicedb #16207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/** | ||
* Copyright (c) 2023 Gitpod GmbH. All rights reserved. | ||
* Licensed under the GNU Affero General Public License (AGPL). | ||
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { v1 } from "@authzed/authzed-node"; | ||
import { ResourceType, SubjectType } from "./perms"; | ||
|
||
const FULLY_CONSISTENT = v1.Consistency.create({ | ||
requirement: { | ||
oneofKind: "fullyConsistent", | ||
fullyConsistent: true, | ||
}, | ||
}); | ||
|
||
type SubjectResourceCheckFn = (subjectID: string, resourceID: string) => v1.CheckPermissionRequest; | ||
|
||
function check(subjectT: SubjectType, op: string, resourceT: ResourceType): SubjectResourceCheckFn { | ||
return (subjectID, resourceID) => | ||
v1.CheckPermissionRequest.create({ | ||
subject: v1.SubjectReference.create({ | ||
object: v1.ObjectReference.create({ | ||
objectId: subjectID, | ||
objectType: subjectT, | ||
}), | ||
}), | ||
permission: op, | ||
resource: v1.ObjectReference.create({ | ||
objectId: resourceID, | ||
objectType: resourceT, | ||
}), | ||
consistency: FULLY_CONSISTENT, | ||
}); | ||
} | ||
|
||
export const ReadOrganizationMetadata = check("user", "organization_metadata_read", "organization"); | ||
export const WriteOrganizationMetadata = check("user", "organization_metadata_write", "organization"); | ||
|
||
export const ReadOrganizationMembers = check("user", "organization_members_read", "organization"); | ||
export const WriteOrganizationMembers = check("user", "organization_members_write", "organization"); | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,17 @@ | |
* See License.AGPL.txt in the project root for license information. | ||
*/ | ||
|
||
import { v1 } from "@authzed/authzed-node"; | ||
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; | ||
import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; | ||
import { inject, injectable } from "inversify"; | ||
import { ResponseError } from "vscode-ws-jsonrpc"; | ||
import { | ||
observespicedbClientLatency as observeSpicedbClientLatency, | ||
spicedbClientLatency, | ||
} from "../prometheus-metrics"; | ||
import { SpiceDBClient } from "./spicedb"; | ||
|
||
export type OrganizationOperation = | ||
// A not yet implemented operation at this time. This exists such that we can be explicit about what | ||
// we have not yet migrated to fine-grained-permissions. | ||
|
@@ -31,3 +42,66 @@ export type OrganizationOperation = | |
| "org_authprovider_write" | ||
// Ability to read Organization Auth Providers. | ||
| "org_authprovider_read"; | ||
|
||
export type ResourceType = "organization"; | ||
|
||
export type SubjectType = "user"; | ||
|
||
export type CheckResult = { | ||
permitted: boolean; | ||
err?: Error; | ||
response?: v1.CheckPermissionResponse; | ||
}; | ||
|
||
export const NotPermitted = { permitted: false }; | ||
|
||
export const PermissionChecker = Symbol("PermissionChecker"); | ||
|
||
export interface PermissionChecker { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of introducing an interface for this? Also the naming pattern is a little unusal, as we'd uzse the same name for bot interface and implmentations but just add a word about the implementation to it. E.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the The type does in fact rely on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you, makes sense 👍 |
||
check(req: v1.CheckPermissionRequest): Promise<CheckResult>; | ||
} | ||
|
||
@injectable() | ||
export class Authorizer implements PermissionChecker { | ||
@inject(SpiceDBClient) | ||
private client: SpiceDBClient; | ||
|
||
async check(req: v1.CheckPermissionRequest): Promise<CheckResult> { | ||
if (!this.client) { | ||
return { | ||
permitted: false, | ||
err: new Error("Authorization client not available."), | ||
response: v1.CheckPermissionResponse.create({}), | ||
}; | ||
} | ||
|
||
const timer = spicedbClientLatency.startTimer(); | ||
try { | ||
const response = await this.client.checkPermission(req); | ||
const permitted = response.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION; | ||
const err = !permitted ? newUnathorizedError(req.resource!, req.permission, req.subject!) : undefined; | ||
|
||
observeSpicedbClientLatency("check", req.permission, undefined, timer()); | ||
|
||
return { permitted, response, err }; | ||
} catch (err) { | ||
log.error("[spicedb] Failed to perform authorization check.", err, { req }); | ||
observeSpicedbClientLatency("check", req.permission, err, timer()); | ||
|
||
throw err; | ||
} | ||
} | ||
} | ||
|
||
function newUnathorizedError(resource: v1.ObjectReference, relation: string, subject: v1.SubjectReference) { | ||
return new ResponseError( | ||
ErrorCodes.PERMISSION_DENIED, | ||
`Subject (${objString(subject.object)}) is not permitted to perform ${relation} on resource ${objString( | ||
resource, | ||
)}.`, | ||
); | ||
} | ||
|
||
function objString(obj?: v1.ObjectReference): string { | ||
return `${obj?.objectType}:${obj?.objectId}`; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are declarative permission checks. The idea is to be able to collate all of these in a single place, and be able to trace their usage.