diff --git a/backend/src/graphql/resolvers/index.ts b/backend/src/graphql/resolvers/index.ts index 5443653..5bdee49 100644 --- a/backend/src/graphql/resolvers/index.ts +++ b/backend/src/graphql/resolvers/index.ts @@ -14,12 +14,13 @@ import { threatActorResolvers } from "./threatActorResolver.js"; import { techniqueResolvers } from "./techniqueResolver.js"; - +import { softwareResolvers } from "./softwareResolver.js"; export const resolvers = { Query: { ...threatActorResolvers.Query, ...techniqueResolvers.Query, + ...softwareResolvers.Query, }, }; \ No newline at end of file diff --git a/backend/src/graphql/resolvers/softwareResolver.ts b/backend/src/graphql/resolvers/softwareResolver.ts index c0d3458..cc3ebcb 100644 --- a/backend/src/graphql/resolvers/softwareResolver.ts +++ b/backend/src/graphql/resolvers/softwareResolver.ts @@ -1,12 +1,12 @@ -/** - * Resolver-laget håndterer GraphQL-spørringer fra klienten. - * - * Resolverne kobler GraphQL-feltene til riktig funksjon i - * repository-laget og sender resultatet tilbake til klienten. - * - * Database-logikk skal ikke ligge direkte i resolverne. - * Den håndteres av repositories. - * - * - * GraphQL -> Resolver -> Repository -> Drizzle -> MySQL - */ \ No newline at end of file +import { + getAllSoftware, + getSoftwareByMitreId, +} from "../../repositories/softwareRepository.js"; + +export const softwareResolvers = { + Query: { + software: async () => getAllSoftware(), + softwareByMitreId: async (_parent: unknown, args: { mitreId: string }) => + getSoftwareByMitreId(args.mitreId), + }, +}; \ No newline at end of file diff --git a/backend/src/graphql/typeDefs.ts b/backend/src/graphql/typeDefs.ts index 15416a8..000a6f8 100644 --- a/backend/src/graphql/typeDefs.ts +++ b/backend/src/graphql/typeDefs.ts @@ -1,15 +1,7 @@ /** - * typeDefs beskriver hvilke operasjoner frontend - * har lov til å spørre GraphQL-API-et om. - * - * Det kan sammenlignes med et interface/kontrakt: - * vi definerer hvilke typer og operasjoner som finnes, - * mens selve implementasjonen ligger i resolvers/. + * GraphQL schema: defines the types and operations available to the frontend. */ - export const typeDefs = `#graphql - - # Beskriver hvordan et ThreatActor-objekt ser ut type ThreatActorGrid { name: String! mitreId: String! @@ -23,10 +15,10 @@ export const typeDefs = `#graphql items: [ThreatActorGrid!]! totalCount: Int! } - + type AssociatedItem { - mitreId: String! - name: String! + mitreId: String! + name: String! } type AssociatedItemsPage { @@ -48,7 +40,6 @@ export const typeDefs = `#graphql status: String! dangerLevel: String! xMitreDeprecated: Boolean! - aliases: [String!]! sectors: [String!]! } @@ -59,35 +50,32 @@ export const typeDefs = `#graphql description: String! } - type Query { - ######################################## ThreatActors vvvv + type RelatedSoftwareEntity { + id: String! + mitreId: String! + name: String! + } - # Henter alle threat actors som trengs i grid - allThreatActorsGrid(limit: Int!, offset: Int!): ThreatActorGridInfo! + type Software { + id: String! + mitreId: String! + name: String! + description: String! + type: String! + aliases: [String!]! + platforms: [String!]! + associatedTechniques: [RelatedSoftwareEntity!]! + usedByThreatActors: [RelatedSoftwareEntity!]! + } - # Henter alt nødvending info for ThreatActorDetailed i frontend + type Query { + allThreatActorsGrid(limit: Int!, offset: Int!): ThreatActorGridInfo! getThreatActorByMitreId(mitreId: String!): ThreatActorDetailed - getThreatActorTechniques(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! - getThreatActorSoftwares(mitreId: String!, limit: Int!, offset: Int!): AssociatedItemsPage! - - ######################################## Techinques vvvv - - # Henter alle Techniques allTechniques: [Technique!]! - - # Henter én Technique basert på MITRE-ID techniqueByMitreId(mitreId: String!): Technique - - - ######################################## Software vvvv - - - - ######################################## Tactics vvvv - - + software: [Software!]! + softwareByMitreId(mitreId: String!): Software } - -`; \ No newline at end of file +`; diff --git a/backend/src/repositories/softwareRepository.ts b/backend/src/repositories/softwareRepository.ts index b6f2fc5..7fa4347 100644 --- a/backend/src/repositories/softwareRepository.ts +++ b/backend/src/repositories/softwareRepository.ts @@ -1,16 +1,134 @@ -/** - * Repository-laget samler databaseoperasjoner relatert til Software. - * - * Hensikten er å skille database-logikken fra resten av backenden: - * resolvers skal ikke skrive Drizzle-spørringer direkte, men heller - * kalle funksjoner fra dette repositoryet. - * - * Dermed får vi en tydelig ansvarsfordeling: - * - * GraphQL resolver -> Repository -> Drizzle -> MySQL - * - * Dette gjør koden lettere å lese, teste og endre senere. - * - * - * !!!ALTSÅ HER SKRIVER VI KODEN SOM FAKTISK HENTER UT DATA FRA DB VIA DRIZZLE!!!! - */ \ No newline at end of file +import { eq, inArray } from "drizzle-orm"; + +import { db } from "../db/connection.js"; +import { + software, + softwareAlias, + softwarePlatform, + technique, + techniqueSoftware, + threatActor, + threatActorSoftware, +} from "../db/schema.js"; + +async function getSoftwareMetadata(softwareIds: string[]) { + if (softwareIds.length === 0) { + return { + aliases: [], + platforms: [], + associatedTechniques: [], + usedByThreatActors: [], + }; + } + + const [aliases, platforms, associatedTechniques, usedByThreatActors] = + await Promise.all([ + db + .select({ + softwareId: softwareAlias.software_id, + alias: softwareAlias.alias, + }) + .from(softwareAlias) + .where(inArray(softwareAlias.software_id, softwareIds)), + db + .select({ + softwareId: softwarePlatform.software_id, + platform: softwarePlatform.platform, + }) + .from(softwarePlatform) + .where(inArray(softwarePlatform.software_id, softwareIds)), + db + .select({ + softwareId: techniqueSoftware.software_id, + id: technique.technique_id, + mitreId: technique.mitre_id, + name: technique.name, + }) + .from(techniqueSoftware) + .innerJoin( + technique, + eq(techniqueSoftware.technique_id, technique.technique_id), + ) + .where(inArray(techniqueSoftware.software_id, softwareIds)), + db + .select({ + softwareId: threatActorSoftware.software_id, + id: threatActor.threat_id, + mitreId: threatActor.mitre_id, + name: threatActor.name, + }) + .from(threatActorSoftware) + .innerJoin( + threatActor, + eq(threatActorSoftware.threat_id, threatActor.threat_id), + ) + .where(inArray(threatActorSoftware.software_id, softwareIds)), + ]); + + return { aliases, platforms, associatedTechniques, usedByThreatActors }; +} + +export async function getAllSoftware() { + const entries = await db + .select({ + id: software.software_id, + mitreId: software.mitre_id, + name: software.name, + description: software.description, + type: software.software_type, + }) + .from(software) + .orderBy(software.name); + + const metadata = await getSoftwareMetadata(entries.map((entry) => entry.id)); + + return entries.map((entry) => ({ + ...entry, + aliases: metadata.aliases + .filter((alias) => alias.softwareId === entry.id) + .map((alias) => alias.alias), + platforms: metadata.platforms + .filter((platform) => platform.softwareId === entry.id) + .map((platform) => platform.platform), + })); +} + +export async function getSoftwareByMitreId(mitreId: string) { + const [entry] = await db + .select({ + id: software.software_id, + mitreId: software.mitre_id, + name: software.name, + description: software.description, + type: software.software_type, + }) + .from(software) + .where(eq(software.mitre_id, mitreId)) + .limit(1); + + if (!entry) { + return null; + } + + const metadata = await getSoftwareMetadata([entry.id]); + + return { + ...entry, + aliases: metadata.aliases.map((alias) => alias.alias), + platforms: metadata.platforms.map((platform) => platform.platform), + associatedTechniques: metadata.associatedTechniques.map( + ({ id, mitreId: relatedMitreId, name }) => ({ + id, + mitreId: relatedMitreId, + name, + }), + ), + usedByThreatActors: metadata.usedByThreatActors.map( + ({ id, mitreId: relatedMitreId, name }) => ({ + id, + mitreId: relatedMitreId, + name, + }), + ), + }; +} \ No newline at end of file