diff --git a/ERFormatConstants.py b/ERFormatConstants.py index a53c47ac44c34440b28ba3897f2d794d2e851fc4..1d214bebb3943b68416cee7cdcdd516e5d7c7a43 100644 --- a/ERFormatConstants.py +++ b/ERFormatConstants.py @@ -2,6 +2,7 @@ #Generic fields for all rows in the CSV file Id = "Id" # The id of the entity +Name = "Name" # ! Identifier, can be used to distinguish diagram components textArea1 = "Text Area 1" textArea2 = "Text Area 2" @@ -76,3 +77,12 @@ ConsequenceDynamic = "Consequence" # The consequence dynamics AttackDynamic = "Attack" # The attack dynamics ERDynamic = "ER" # The ER dynamics BowtieDynamicType = textArea3 # The bowtie dynamics + +# ! Note all metric fields use ID and name as the fields for the metric +# ! The ID will be used to link them in the dynamic matrix + + +# Lines +From = "Line Source" +To = "Line Destination" + diff --git a/diagramParser.py b/diagramParser.py index ca12b16c570683604d74cd1d26f3dcbd9c19d6b6..4b853302b89d322e5db0f6bfe838220fbac84d9d 100644 --- a/diagramParser.py +++ b/diagramParser.py @@ -17,12 +17,15 @@ def parseDiagramFile(csvFile): consequences = {} #consequences = parseConsequences(df, consequences) + # ER components + erComponents = {} + + #List containing all metrics metrics = [] #List containing all attacks attacks = [] - parseDynamic(df, metrics) @@ -67,7 +70,7 @@ def parseAttacks(df, attackDict): ) attackDict[df.Id[i]] = attack - return attackDict + return attackDict #Parses metrics components and adds it to list def parseDynamic(df, metricList): @@ -81,8 +84,8 @@ def parseDynamic(df, metricList): ) threatDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list - - + + threatDynamic.associateBowtie(df, threatDynamic.type) # Associate the dynamic with the correct components elif df[const.textArea3][i] == const.ConsequenceDynamic: consequenceDynamic = dynamic.BowtieDynamic( @@ -91,6 +94,7 @@ def parseDynamic(df, metricList): ) consequenceDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list + consequenceDynamic.associateBowtie(df, consequenceDynamic.type) # Associate the dynamic with the correct components elif df[const.textArea3][i] == const.AttackDynamic: @@ -100,15 +104,18 @@ def parseDynamic(df, metricList): ) attackDynamic.metrics = extractMetrics(df, i, 4) joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list + attackDynamic.associateBowtie(df,attackDynamic.type) # Associate the dynamic with the correct components elif df[const.textArea3][i] == const.ERDynamic: erDynamic = dynamic.ERDynamic( - df[const.id][i], # Component ID LucidChart - df.textArea3[i], # Name of dynamic + df[const.Id][i], # Component ID LucidChart + df[const.textArea3][i], # Component type + df[const.textArea5][i] # Description ) erDynamic.metrics = extractMetrics(df, i, 8) joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list + #erDynamic.associatedERComponents(df) def extractMetrics(df, index, startRange): @@ -117,8 +124,8 @@ def extractMetrics(df, index, startRange): metricID = "Text Area "+str(j) metricName = "Text Area "+str(j+1) - if pd.isnull(df[metric][index]) == False: # If the text area is not empty - print("Metric: ID", df[metricID][index], "Name: ", df[metricName][index]) + if pd.isnull(df[metricID][index]) == False: # If the text area is not empty + log.info("Metric: ID", df[metricID][index], "Name: ", df[metricName][index]) metric = dynamic.Metric(df[metricID][index], df[metricName][index]) listOfMetrics.append(metric) else: @@ -133,8 +140,8 @@ def extractMetrics(df, index, startRange): """ def joinMetrcs(localMetrics, globalMetrics): duplicateMetrics = 0 # Counter for duplicate metrics per function run - for i in range(globalMetrics): - for j in range(localMetrics): + for i in range(len(globalMetrics)): + for j in range(len(localMetrics)): if globalMetrics[i].name == localMetrics[j].name: # Local metric already exists in the global metric list duplicateMetrics += 1 break # Check next entry diff --git a/documentation/requirements.txt b/documentation/requirements.txt index 2e7582422e7b85c961cfd87ef17665225848c56c..1c782dcbaa11eb9a14613c59c1a8586c5a753ccb 100644 --- a/documentation/requirements.txt +++ b/documentation/requirements.txt @@ -1,7 +1,7 @@ Har komponenter Lagt til lokale metrics i hver komponent -1. Må nå lage en global metric liste -2. Må lage ER dynamikk liste +1. Må nå lage en global metric liste FERDIG +2. Må lage ER dynamikk liste 3. Må koble dynamic komponenter til trusselkomponent diff --git a/dynamics.py b/dynamics.py index dcaf9313c96fc20ea9a952d7927d41d2b3d61e2f..b77cc346ad4dd0ec115c87f7fff653d3afbbac12 100644 --- a/dynamics.py +++ b/dynamics.py @@ -1,3 +1,4 @@ +import ERFormatConstants as const import logging as log # Dynamics class parent class for both the bowtie dynamics and the architecture dynamics @@ -13,30 +14,6 @@ class DynamicComponent: def __str__(self) -> str: return f"Dynamic Component: {self.componentID}, {len(self.metrics)}" - #Simple contains function to check if metric already exists - def containsMetric(metricList, metric): - if len(metricList)==0: - return False - for i in metricList: - if i.name == metric: - return True - return False - - # Function used to add a metric to the dynamics list - # The function will check if the metric already exists in the global list, if it does it will not be added to the global list - def addMetrics(self, globalMetricList , newMetric, description, metricType ): - metric = Metric(newMetric, description, metricType) - - #Check if metric is already in the global metrics list if it is the metric will not be added to the global list - if DynamicComponent.containsMetric(globalMetricList, newMetric) == False: - globalMetricList = globalMetricList.append(metric) - log.info(f"Added metric {newMetric} to GLOBAL dynamics list with ID: {self.id}") - else: - log.info(f"Metric {newMetric} already exists in GLOBAL dynamics list with ID: {self.id}") - self.metrics.append(metric) - log.info(f"Added metric {newMetric} to LOCAL dynamics list with ID: {self.id}") - - #Metric class, will be used for all dynamics class Metric: def __init__(self, ID, name) -> None: @@ -45,7 +22,7 @@ class Metric: self.ID = ID # ID of the metric used to locate in dynamics matrics self.name = name # Name of the metric - + # Metrics found in the metric table @@ -57,17 +34,112 @@ class BowtieDynamic(DynamicComponent): def __init__(self, componentID, type) -> None: super().__init__(componentID) - self.type = type # Type of dynamicM - self.associatedThreat = None # List of threats associated with the dynamic - self.associatedAttack = None # List of attacks associated with the dynamic + self.type = type # Type of dynamicM + self.associatedThreat = None # Threat associated with the dynamic + self.associatedAttack = None # Attack associated with the dynamic + self.associatedConsequence = None # Consequence associated with the dynamic + + """_summary_ + Handles the associated dynamics for the bowtie model as they are different from the ER model + ER model has one type of dynamic component with metrics however, the bowtie model has three dynamic types and needs more parsing + than the ER model du to the modeling annotation. + Abstracting this saves time + """ + def associateBowtie(self, df, componentType ): + componentId = self.componentID # Define the ID of the component + #Find the lines associated with the component + lines = df.loc[df['Name']=='Line'] # Find all lines in the diagram + + lineSource = lines.loc[lines[const.From] == componentId] # Find the line where the component is the source + lineDestination = lines.loc[lines[const.To] == componentId] # ! The component SHOULD be source however, to error handle we need to check for destination as well + + if lineSource.isnull().values.any() == True: + lineOne = lineSource + else: + lineOne = lineDestination + + if lineOne[const.From].item() == componentId: # If the component is the source + lineTwo = df.loc[df[const.Id] == lineOne[const.To].item()] # Find the component associated with the line + else: + lineTwo = df.loc[df[const.Id] == lineOne[const.From].item()] # Find the component associated with the line + + # ! Edge case, if the second line is in fact not a second line it is an attack meaning that the dynamic component is an attack + if componentType == const.Attack: # If the associated component is an attack + self.associatedAttack = lineTwo # ! The "line" is an attack, not a line, we add it to the associated attack field and move on + return # The attack is the only component associated with the dynamic + + + sourceComponent = df.loc[df[const.Id]==lineTwo[const.From].item()] + destinationComponent = df.loc[df[const.Id]==lineTwo[const.To].item()] + + + + if componentType == const.ThreatDynamic: # If the component is a threat + if sourceComponent[const.textArea1].item() == const.Threat: # Checks if source or destination is the threat + self.associatedThreat = sourceComponent + self.associatedAttack = destinationComponent + else: + self.associatedThreat = destinationComponent + self.associatedAttack = sourceComponent + + elif componentType == const.ConsequenceDynamic: # If the component is a consequence + if sourceComponent[const.textArea1].item() == const.Consequence: + self.associatedConsequence = sourceComponent + self.associatedAttack = destinationComponent + else: + self.associatedConsequence = destinationComponent + self.associatedAttack = sourceComponent + + + + + + + + + + class ERDynamic(DynamicComponent): def __init__(self, componentID, type, description) -> None: - super().__init__(componentID, type) + super().__init__(componentID) self.description = description # Description of the dynamic - self.associatedComponent = None # Associated ER component + self.associatedERComponents = [] # Associated ER component + self.type = type - \ No newline at end of file + """_summary_ + Function will use the dynamic component ID to find linked threats, ER components, attacks and consequences + This will be needed to associate the dynamics with the correct components and analyze relationships + """ + def associatER(self, df): + componentId = self.componentID # Define the ID of the component + + #Find the lines associated with the component + lines = df.loc[df['Name']=='Line'] # Find all lines in the diagram + relevantLines = [] # List of lines relevant to the component + for i in range(len(lines)): + + line = lines.iloc[i] + + if line[const.From] == componentId or line[const.To] == componentId: # If the line is associated with the component + relevantLines.append(lines.iloc[i]) # Add the line to the relevant lines list + log.info("Amount of relevant lines found:", len(relevantLines)) + + for i in range(len(relevantLines)): + line = relevantLines[i] + lineSource = line[const.From] # The source of the line + lineDestination = line[const.To] + + if lineSource == componentId: # If the line is associated with the component + associatedComponentID = lineDestination + else: + associatedComponentID = lineSource + associatedComponent = df.iloc[df.index[df[const.Id] == associatedComponentID]] # Find the component associated with the line + + self.associatedERComponents.append(associatedComponent) # Add the associated component to the list of associated components + log.info("Associated components found: ", self.associatedERComponents) + return + \ No newline at end of file