Skip to content

Commit bdb66cd

Browse files
committed
refactor(api): Return clearances based on token's access to groups
Return path exclusions and license conclusions from the groups that the API token has access to. If there are path exclusions with the same pattern in multiple groups, the path exclusion from the highest ranking group is returned. If there are multiple path exclusions with the same pattern in the same group, the path exclusion that was created most recently is returned. Similarly for the license conclusions, if multiple license conclusions have been added for the same path in the queried package, return the one from the highest-ranking group, or select the newest item within a group. Signed-off-by: Johanna Lamppu <johanna.lamppu@doubleopen.org>
1 parent d824639 commit bdb66cd

File tree

4 files changed

+456
-143
lines changed

4 files changed

+456
-143
lines changed

apps/api/src/helpers/db_operations.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -718,18 +718,53 @@ type PackageConfiguration = {
718718
};
719719

720720
export const getPackageConfiguration = async (
721-
purl: string,
721+
packageId: number,
722+
apiTokenId: string,
722723
): Promise<PackageConfiguration> => {
723-
const packageWithPathExclusions =
724-
await dbQueries.findPackageWithPathExclusionsByPurl(purl);
724+
const atcgs =
725+
await dbQueries.findClearanceGroupsForApiTokenWithPackageClearances(
726+
packageId,
727+
apiTokenId,
728+
);
729+
730+
const peMap = new Map<
731+
string,
732+
PackageConfiguration["pathExclusions"][number]
733+
>();
734+
735+
const lcMap = new Map<
736+
string,
737+
PackageConfiguration["licenseConclusions"][number]
738+
>();
739+
740+
for (const atcg of atcgs) {
741+
for (const pe of atcg.clearanceGroup.pathExclusions) {
742+
if (!peMap.has(pe.pathExclusion.pattern)) {
743+
peMap.set(pe.pathExclusion.pattern, pe.pathExclusion);
744+
}
745+
}
725746

726-
if (!packageWithPathExclusions) throw new Error("Package not found");
747+
for (const lc of atcg.clearanceGroup.licenseConclusions) {
748+
for (const ft of lc.licenseConclusion.file.filetrees) {
749+
const path = ft.path;
750+
751+
if (!lcMap.has(path)) {
752+
lcMap.set(path, {
753+
path: path,
754+
detectedLicenseExpressionSPDX:
755+
lc.licenseConclusion.detectedLicenseExpressionSPDX,
756+
concludedLicenseExpressionSPDX:
757+
lc.licenseConclusion.concludedLicenseExpressionSPDX,
758+
comment: lc.licenseConclusion.comment,
759+
});
760+
}
761+
}
762+
}
763+
}
727764

728-
const licenseConclusions =
729-
await dbQueries.findLicenseConclusionsByPackagePurl(purl);
730765
return {
731-
licenseConclusions: licenseConclusions,
732-
pathExclusions: packageWithPathExclusions.pathExclusions,
766+
licenseConclusions: [...lcMap.values()],
767+
pathExclusions: [...peMap.values()],
733768
};
734769
};
735770

apps/api/src/helpers/db_queries.ts

Lines changed: 81 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,7 @@ export const findPackagesByPurls = async (
21782178

21792179
export const findPackageIdByPurl = async (
21802180
purl: string,
2181+
scanStatus?: string,
21812182
): Promise<number | null> => {
21822183
let retries = initialRetryCount;
21832184
let packageId: number | null = null;
@@ -2189,6 +2190,7 @@ export const findPackageIdByPurl = async (
21892190
.findUnique({
21902191
where: {
21912192
purl: purl,
2193+
scanStatus: scanStatus,
21922194
},
21932195
select: {
21942196
id: true,
@@ -2210,56 +2212,89 @@ export const findPackageIdByPurl = async (
22102212
return packageId;
22112213
};
22122214

2213-
type PackageWithRelations = Prisma.PackageGetPayload<{
2214-
select: {
2215-
id: true;
2216-
pathExclusions: {
2215+
export const findClearanceGroupsForApiTokenWithPackageClearances = async (
2216+
packageId: number,
2217+
apiTokenId: string,
2218+
) => {
2219+
return await retry(async () => {
2220+
return prisma.apiToken_ClearanceGroup.findMany({
2221+
where: {
2222+
apiTokenId: apiTokenId,
2223+
},
2224+
orderBy: {
2225+
rank: "asc",
2226+
},
22172227
select: {
2218-
pattern: true;
2219-
reason: true;
2220-
comment: true;
2221-
};
2222-
};
2223-
};
2224-
}>;
2225-
2226-
export const findPackageWithPathExclusionsByPurl = async (
2227-
purl: string,
2228-
): Promise<PackageWithRelations> => {
2229-
let retries = initialRetryCount;
2230-
let querySuccess = false;
2231-
let packageObj: PackageWithRelations | null = null;
2232-
2233-
while (!querySuccess && retries > 0) {
2234-
try {
2235-
packageObj = await prisma.package.findUnique({
2236-
where: {
2237-
purl: purl,
2238-
},
2239-
select: {
2240-
id: true,
2241-
pathExclusions: {
2242-
select: {
2243-
pattern: true,
2244-
reason: true,
2245-
comment: true,
2228+
clearanceGroup: {
2229+
select: {
2230+
id: true,
2231+
pathExclusions: {
2232+
select: {
2233+
pathExclusion: {
2234+
select: {
2235+
pattern: true,
2236+
reason: true,
2237+
comment: true,
2238+
},
2239+
},
2240+
},
2241+
where: {
2242+
pathExclusion: {
2243+
packageId: packageId,
2244+
},
2245+
},
2246+
orderBy: {
2247+
pathExclusion: {
2248+
createdAt: "desc",
2249+
},
2250+
},
2251+
},
2252+
licenseConclusions: {
2253+
select: {
2254+
licenseConclusion: {
2255+
select: {
2256+
detectedLicenseExpressionSPDX: true,
2257+
concludedLicenseExpressionSPDX: true,
2258+
comment: true,
2259+
fileSha256: true,
2260+
file: {
2261+
select: {
2262+
filetrees: {
2263+
select: {
2264+
path: true,
2265+
},
2266+
where: {
2267+
packageId: packageId,
2268+
},
2269+
},
2270+
},
2271+
},
2272+
},
2273+
},
2274+
},
2275+
where: {
2276+
licenseConclusion: {
2277+
file: {
2278+
filetrees: {
2279+
some: {
2280+
packageId: packageId,
2281+
},
2282+
},
2283+
},
2284+
},
2285+
},
2286+
orderBy: {
2287+
licenseConclusion: {
2288+
createdAt: "desc",
2289+
},
2290+
},
22462291
},
22472292
},
22482293
},
2249-
});
2250-
querySuccess = true;
2251-
} catch (error) {
2252-
console.log("Error with trying to find Package: " + error);
2253-
handleError(error);
2254-
retries--;
2255-
if (retries > 0) await waitToRetry();
2256-
else throw error;
2257-
}
2258-
}
2259-
2260-
if (!packageObj) throw new Error("Error: Unable to find Package");
2261-
2262-
return packageObj;
2294+
rank: true,
2295+
},
2296+
});
2297+
});
22632298
};
22642299

22652300
export const getPathExclusionsByPackagePurl = async (
@@ -3896,82 +3931,6 @@ export const findPathExclusionsByPackageId = async (
38963931
return pathExclusions;
38973932
};
38983933

3899-
export const findLicenseConclusionsByPackagePurl = async (
3900-
purl: string,
3901-
): Promise<
3902-
{
3903-
path: string;
3904-
detectedLicenseExpressionSPDX: string | null;
3905-
concludedLicenseExpressionSPDX: string;
3906-
comment: string | null;
3907-
}[]
3908-
> => {
3909-
let retries = initialRetryCount;
3910-
let querySuccess = false;
3911-
let filetrees = null;
3912-
3913-
while (!querySuccess && retries > 0) {
3914-
try {
3915-
filetrees = await prisma.fileTree.findMany({
3916-
where: {
3917-
package: {
3918-
purl: purl,
3919-
},
3920-
},
3921-
select: {
3922-
path: true,
3923-
file: {
3924-
select: {
3925-
licenseConclusions: {
3926-
select: {
3927-
detectedLicenseExpressionSPDX: true,
3928-
concludedLicenseExpressionSPDX: true,
3929-
comment: true,
3930-
local: true,
3931-
contextPurl: true,
3932-
},
3933-
},
3934-
},
3935-
},
3936-
},
3937-
});
3938-
querySuccess = true;
3939-
} catch (error) {
3940-
console.log(
3941-
"Error with trying to find LicenseConclusions: " + error,
3942-
);
3943-
handleError(error);
3944-
retries--;
3945-
if (retries > 0) await waitToRetry();
3946-
else throw error;
3947-
}
3948-
}
3949-
3950-
if (!filetrees) throw new Error("Error: Unable to find LicenseConclusions");
3951-
3952-
const licenseConclusions = [];
3953-
3954-
for (const filetree of filetrees) {
3955-
for (const licenseConclusion of filetree.file.licenseConclusions) {
3956-
if (
3957-
!licenseConclusion.local ||
3958-
(licenseConclusion.local &&
3959-
licenseConclusion.contextPurl === purl)
3960-
)
3961-
licenseConclusions.push({
3962-
path: filetree.path,
3963-
detectedLicenseExpressionSPDX:
3964-
licenseConclusion.detectedLicenseExpressionSPDX,
3965-
concludedLicenseExpressionSPDX:
3966-
licenseConclusion.concludedLicenseExpressionSPDX,
3967-
comment: licenseConclusion.comment,
3968-
});
3969-
}
3970-
}
3971-
3972-
return licenseConclusions;
3973-
};
3974-
39753934
export const findLicenseConclusionsByContextPurl = async (
39763935
contextPurl: string,
39773936
): Promise<LicenseConclusion[]> => {

apps/api/src/routes/scanner_router.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { zodiosRouter } from "@zodios/express";
66
import { JobStatus } from "bull";
7-
import { ApiScope, Package, Prisma, ScannerJob } from "database";
7+
import { ApiScope, Package, ScannerJob } from "database";
88
import log from "loglevel";
99
import { deleteFile, getPresignedPutUrl, objectExistsCheck } from "s3-helpers";
1010
import { scannerAPI } from "validation-helpers";
@@ -459,29 +459,37 @@ scannerRouter.post(
459459
requireApiScope(ApiScope.CLEARANCE_DATA),
460460
async (req, res) => {
461461
try {
462-
// TODO: Return results based on user access rights and choices
462+
// The middlewares have already verified that req.apiTokenAuth exists, so the non-null assertion operator can be safely used here.
463+
const auth = req.apiTokenAuth!;
463464

464465
console.log(
465466
"Searching for configuration for package with purl: " +
466467
req.body.purl,
467468
);
468469

470+
const packageId = await dbQueries.findPackageIdByPurl(
471+
req.body.purl,
472+
"scanned",
473+
);
474+
475+
if (!packageId) throw new CustomError("Package not found", 404);
476+
469477
const packageConfiguration =
470-
await dbOperations.getPackageConfiguration(req.body.purl);
478+
await dbOperations.getPackageConfiguration(
479+
packageId,
480+
auth.apiTokenId,
481+
);
471482

472483
res.status(200).json(packageConfiguration);
473484
} catch (error) {
474485
console.log("Error: ", error);
475-
if (
476-
error instanceof Prisma.PrismaClientKnownRequestError &&
477-
error.code === "P2025"
478-
) {
479-
return res.status(404).json({
480-
message: "Unable to find results for the requested package",
481-
});
482-
} else if (error instanceof Error)
483-
res.status(404).json({ message: error.message });
484-
else res.status(500).json({ message: "Internal server error" });
486+
487+
if (error instanceof CustomError) {
488+
res.status(error.statusCode).json({ message: error.message });
489+
} else {
490+
const err = await getErrorCodeAndMessage(error);
491+
res.status(err.statusCode).json({ message: err.message });
492+
}
485493
}
486494
},
487495
);

0 commit comments

Comments
 (0)