Skip to content

Commit

Permalink
fix: simplification des methode canAccessX
Browse files Browse the repository at this point in the history
  • Loading branch information
remy-auricoste committed Dec 6, 2023
1 parent d3e8b9f commit 14007f8
Showing 1 changed file with 25 additions and 62 deletions.
87 changes: 25 additions & 62 deletions server/src/security/authorisationService.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import Boom from "boom"
import { FastifyRequest } from "fastify"
import { IApplication, IJob, IRecruiter, IUserRecruteur } from "shared/models"
import { IApplication, ICredential, IJob, IRecruiter, IUserRecruteur } from "shared/models"
import { IRouteSchema, WithSecurityScheme } from "shared/routes/common.routes"
import { AccessPermission, AccessResourcePath, AdminRole, CfaRole, OpcoRole, RecruiterRole, Role } from "shared/security/permissions"
import { AccessPermission, AccessResourcePath, AdminRole, CfaRole, OpcoRole, RecruiterRole, Role, UserWithType } from "shared/security/permissions"
import { assertUnreachable } from "shared/utils"
import { Primitive } from "type-fest"

import { Application, Recruiter, UserRecruteur } from "@/common/model"

import { getAccessTokenScope } from "./accessTokenService"
import { IUserWithType, getUserFromRequest } from "./authenticationService"
import { getUserFromRequest } from "./authenticationService"

type Ressources = {
recruiters: Array<IRecruiter>
Expand All @@ -21,6 +20,8 @@ type Ressources = {
// Specify what we need to simplify mocking in tests
type IRequest = Pick<FastifyRequest, "user" | "params" | "query">

type NonTokenUserWithType = UserWithType<"IUserRecruteur", IUserRecruteur> | UserWithType<"ICredential", ICredential>

// TODO: Unit test access control
// TODO: job.delegations
// TODO: Unit schema access path properly defined (exists in Zod schema)
Expand Down Expand Up @@ -168,15 +169,11 @@ export async function getResources<S extends WithSecurityScheme>(schema: S, req:
}
}

export function getUserRole(userWithType: IUserWithType): Role | null {
export function getUserRole(userWithType: NonTokenUserWithType): Role | null {
if (userWithType.type === "ICredential") {
return OpcoRole
}

if (userWithType.type === "IAccessToken") {
return null
}

switch (userWithType.value.type) {
case "ADMIN":
return AdminRole
Expand All @@ -191,11 +188,7 @@ export function getUserRole(userWithType: IUserWithType): Role | null {
}
}

function canAccessRecruiter<S extends Pick<IRouteSchema, "method" | "path"> & WithSecurityScheme>(
userWithType: IUserWithType,
resource: Ressources["recruiters"][number],
schema: S
): boolean {
function canAccessRecruiter(userWithType: NonTokenUserWithType, resource: Ressources["recruiters"][number]): boolean {
if (resource === null) {
return true
}
Expand All @@ -204,11 +197,6 @@ function canAccessRecruiter<S extends Pick<IRouteSchema, "method" | "path"> & Wi
return resource.opco === userWithType.value.organisation
}

if (userWithType.type === "IAccessToken") {
const scope = getAccessTokenScope(userWithType.value, schema)?.resources.recruiter?.find((id) => id === resource._id.toString()) ?? null
return scope !== null
}

const user = userWithType.value
switch (user.type) {
case "ADMIN":
Expand All @@ -224,7 +212,7 @@ function canAccessRecruiter<S extends Pick<IRouteSchema, "method" | "path"> & Wi
}
}

function canAccessJob<S extends Pick<IRouteSchema, "method" | "path"> & WithSecurityScheme>(userWithType: IUserWithType, resource: Ressources["jobs"][number], schema: S): boolean {
function canAccessJob(userWithType: NonTokenUserWithType, resource: Ressources["jobs"][number]): boolean {
if (resource === null) {
return true
}
Expand All @@ -233,11 +221,6 @@ function canAccessJob<S extends Pick<IRouteSchema, "method" | "path"> & WithSecu
return resource.recruiter.opco === userWithType.value.organisation
}

if (userWithType.type === "IAccessToken") {
const scope = getAccessTokenScope(userWithType.value, schema)?.resources.job?.find((id) => id === resource.job._id.toString()) ?? null
return scope !== null
}

const user = userWithType.value
switch (user.type) {
case "ADMIN":
Expand All @@ -253,11 +236,7 @@ function canAccessJob<S extends Pick<IRouteSchema, "method" | "path"> & WithSecu
}
}

function canAccessUser<S extends Pick<IRouteSchema, "method" | "path"> & WithSecurityScheme>(
userWithType: IUserWithType,
resource: Ressources["users"][number],
schema: S
): boolean {
function canAccessUser(userWithType: NonTokenUserWithType, resource: Ressources["users"][number]): boolean {
if (resource === null) {
return true
}
Expand All @@ -266,11 +245,6 @@ function canAccessUser<S extends Pick<IRouteSchema, "method" | "path"> & WithSec
return resource.type === "OPCO" && resource.scope === userWithType.value.organisation
}

if (userWithType.type === "IAccessToken") {
const scope = getAccessTokenScope(userWithType.value, schema)?.resources.user?.find((id) => id === resource._id.toString()) ?? null
return scope !== null
}

if (resource._id.toString() === userWithType.value._id.toString()) {
return true
}
Expand All @@ -290,11 +264,7 @@ function canAccessUser<S extends Pick<IRouteSchema, "method" | "path"> & WithSec
}
}

function canAccessApplication<S extends Pick<IRouteSchema, "method" | "path"> & WithSecurityScheme>(
userWithType: IUserWithType,
resource: Ressources["applications"][number],
schema: S
): boolean {
function canAccessApplication(userWithType: NonTokenUserWithType, resource: Ressources["applications"][number]): boolean {
if (resource === null) {
return true
}
Expand All @@ -303,11 +273,6 @@ function canAccessApplication<S extends Pick<IRouteSchema, "method" | "path"> &
return false
}

if (userWithType.type === "IAccessToken") {
const scope = getAccessTokenScope(userWithType.value, schema)?.resources.application?.find((id) => id === resource.application._id.toString()) ?? null
return scope !== null
}

const user = userWithType.value
switch (user.type) {
case "ADMIN":
Expand All @@ -328,20 +293,14 @@ function canAccessApplication<S extends Pick<IRouteSchema, "method" | "path"> &
}
}

export function isAuthorized<S extends Pick<IRouteSchema, "method" | "path"> & WithSecurityScheme>(
access: AccessPermission,
userWithType: IUserWithType,
role: Role | null,
resources: Ressources,
schema: S
): boolean {
export function isAuthorized(access: AccessPermission, userWithType: NonTokenUserWithType, role: Role | null, resources: Ressources): boolean {
if (typeof access === "object") {
if ("some" in access) {
return access.some.some((a) => isAuthorized(a, userWithType, role, resources, schema))
return access.some.some((a) => isAuthorized(a, userWithType, role, resources))
}

if ("every" in access) {
return access.every.every((a) => isAuthorized(a, userWithType, role, resources, schema))
return access.every.every((a) => isAuthorized(a, userWithType, role, resources))
}

assertUnreachable(access)
Expand All @@ -356,18 +315,18 @@ export function isAuthorized<S extends Pick<IRouteSchema, "method" | "path"> & W
case "recruiter:manage":
case "recruiter:validate":
case "recruiter:add_job":
return resources.recruiters.every((r) => canAccessRecruiter(userWithType, r, schema))
return resources.recruiters.every((recruiter) => canAccessRecruiter(userWithType, recruiter))

case "job:manage":
return resources.jobs.every((r) => canAccessJob(userWithType, r, schema))
return resources.jobs.every((job) => canAccessJob(userWithType, job))

case "school:manage":
// School is actually the UserRecruteur
return resources.users.every((r) => canAccessUser(userWithType, r, schema))
return resources.users.every((user) => canAccessUser(userWithType, user))
case "application:manage":
return resources.applications.every((r) => canAccessApplication(userWithType, r, schema))
return resources.applications.every((application) => canAccessApplication(userWithType, application))
case "user:manage":
return resources.users.every((r) => canAccessUser(userWithType, r, schema))
return resources.users.every((user) => canAccessUser(userWithType, user))
case "admin":
// Admin should already have been approved, otherwise you cannot access to admin
return false
Expand All @@ -381,20 +340,24 @@ export async function authorizationMiddleware<S extends Pick<IRouteSchema, "meth
throw Boom.internal(`authorizationMiddleware: route doesn't have security scheme`, { method: schema.method, path: schema.path })
}

if (schema.securityScheme.access === null) {
return
}

const userWithType = getUserFromRequest(req, schema)

if (userWithType.type === "IUserRecruteur" && userWithType.value.type === "ADMIN") {
return
}

if (schema.securityScheme.access === null) {
if (userWithType.type === "IAccessToken") {
// authorization check has already been done in authentication
return
}

const resources = await getResources(schema, req)
const role = getUserRole(userWithType)

if (!isAuthorized(schema.securityScheme.access, userWithType, role, resources, schema)) {
if (!isAuthorized(schema.securityScheme.access, userWithType, role, resources)) {
throw Boom.forbidden()
}
}

0 comments on commit 14007f8

Please sign in to comment.