diff --git a/components.py b/components.py index e1e074f51a43ce18000c64faaa8633122717a2b2..0b5a55519d4821574a966a6bcd6a5b25d9713a7e 100644 --- a/components.py +++ b/components.py @@ -26,15 +26,6 @@ class Threat: def __str__(self) -> str: return f"Threat: {self.id}, {self.description}, {self.threatSource}, {self.likelihood}, {self.vulnerability}" - - #Function link dynamics will link a threat to a dynamics component when parsing the XML file - def linkDynamic(): - # Function should link the threat to a dynamics component - pass - - def linkConsequence(): - # Function should link the threat to a consequence - pass class Consequence: """_summary_ @@ -53,14 +44,6 @@ class Consequence: def __str__(self) -> str: return f"Consequence: {self.id}, {self.description}, {self.consequence}, {self.affectedComponents}" - #Function link dynamics will link a threat to a dynamics component when parsing the XML file - def linkDynamic(): - # Function should link the threat to a dynamics component - pass - - def linkThreat(): - # Function should link the consequence to a threat - pass class Attack: """_summary_ @@ -84,19 +67,6 @@ class Attack: def __str__(self) -> str: return f"Attack: {self.id}, {self.description}, {self.attackType}" - #Function link dynamics will link a threat to a dynamics component when parsing the XML file - def linkDynamic(): - # Function should link the threat to a dynamics component - pass - - def linkConsequence(): - # Function should link the consequence to a threat - pass - - def linkThreats(): - # Function should link the attack to a threat - pass - @dataclass class Diagram(): diff --git a/dashBoard.py b/dashBoard.py index 86a44a208672677dbb767ce43cb88b24be84c030..c9644014adde456b59fe5761a4b18b0224c4b881 100644 --- a/dashBoard.py +++ b/dashBoard.py @@ -12,7 +12,8 @@ def openFile(): title= "Select lucidchart diagram file", filetypes=(("CSV Diagram file", "*.csv"),) ) - parse.parseDiagramFile(fileName) + diagram = parse.parseDiagramFile(fileName) + print(diagram.metrics) guiRoot = Tk() diff --git a/diagramParser.py b/diagramParser.py index 961cc03de5d5772a9431395fdc6007db2d6c4d8d..ba1a3323400af24c86f2ce2801efef64d55728b3 100644 --- a/diagramParser.py +++ b/diagramParser.py @@ -11,9 +11,7 @@ def parseDiagramFile(csvFile) -> component.Diagram: df = pd.read_csv(csvFile) df.drop(["Shape Library", "Page ID", "Contained By", "Group", "Comments", "property 1"], axis=1, inplace=True) #Removing unecessary data - diagram = component.Diagram() - - + diagram = component.Diagram() # Defines the diagram object # List containing all threats and their descriptions threats = diagram.threats @@ -25,18 +23,9 @@ def parseDiagramFile(csvFile) -> component.Diagram: threats = parseThreats(df, threats) consequences = parseConsequences(df, consequences) - parseDynamic(df, metrics, dynamics) + metrics, dynamics = parseDynamic(df, metrics, dynamics) - print("Number of threats: ", len(threats)) - print("Number of consequences: ", len(consequences)) - print("Number of dynamics: ", len(dynamics)) - print("Number of metrics: ", len(metricsMatrix.index)) - - print("Threats: ", threats) - print("Consequences: ", consequences) - print("Dynamics: ", dynamics) - print("Metrics: ", metricsMatrix) - + return diagram def parseThreats(df, threatDict): for i in range(len(df)): @@ -91,10 +80,10 @@ def parseDynamic(df, metricDict, dynamicsDict): df[const.Id][i], # Component ID LucidChart df[const.textArea3][i] # Name of dynamic ) - threatDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component - metricList = joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list + #threatDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component + #metricList = joinMetrcs(threatDynamic.metrics, metricList) # Adds the metrics to the global metric list - threatDynamic.associateBowtie(df, threatDynamic.type) # Associate the dynamic with the correct components + threatDynamic.associateBowtie(df, threatDynamic.type, metricDict) # Associate the dynamic with the correct components dynamicsDict[df.Id[i]] = threatDynamic elif df[const.textArea3][i] == const.ConsequenceDynamic: @@ -102,10 +91,10 @@ def parseDynamic(df, metricDict, dynamicsDict): df[const.Id][i], # Component ID LucidChart df[const.textArea3][i] # Name of dynamic ) - consequenceDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component - metricList = joinMetrcs(consequenceDynamic.metrics, metricList) # Adds the metrics to the global metric list@ + #consequenceDynamic.metrics = extractMetrics(df, i, 4) # Extracts metrics from the dynamic component + #metricList = joinMetrcs(consequenceDynamic.metrics, metricList) # Adds the metrics to the global metric list@ - consequenceDynamic.associateBowtie(df, consequenceDynamic.type) + consequenceDynamic.associateBowtie(df, consequenceDynamic.type, metricDict) dynamicsDict[df.Id[i]] = consequenceDynamic elif df[const.textArea3][i] == const.AttackDynamic: @@ -113,10 +102,10 @@ def parseDynamic(df, metricDict, dynamicsDict): df[const.Id][i], # Component ID LucidChart df[const.textArea3][i] # Name of dynamic ) - attackDynamic.metrics = extractMetrics(df, i, 4) - metricList = joinMetrcs(attackDynamic.metrics, metricList) # Adds the metrics to the global metric list + #attackDynamic.metrics = extractMetrics(df, i, 4) + #metricList = joinMetrcs(attackDynamic.metrics, metricList) # Adds the metrics to the global metric list - attackDynamic.associateBowtie(df,attackDynamic.type) # Associate the dynamic with the correct components + attackDynamic.associateBowtie(df,attackDynamic.type, metricDict) # Associate the dynamic with the correct components dynamicsDict[df.Id[i]] = attackDynamic elif df[const.textArea3][i] == const.ERDynamic: @@ -125,12 +114,12 @@ def parseDynamic(df, metricDict, dynamicsDict): df[const.textArea3][i], # Component type df[const.textArea5][i] # Description ) - erDynamic.metrics = extractMetrics(df, i, 8) - metricList = joinMetrcs(erDynamic.metrics, metricList) # Adds the metrics to the global metric list + #erDynamic.metrics = extractMetrics(df, i, 8) + #metricList = joinMetrcs(erDynamic.metrics, metricList) # Adds the metrics to the global metric list - erDynamic.associatER(df) + erDynamic.associatER(df, metricDict) dynamicsDict[df.Id[i]] = erDynamic - return metricList, dynamicsDict + return metricDict, dynamicsDict def extractMetrics(df, index, startRange): diff --git a/dynamics.py b/dynamics.py index 6e68dd451b279c946dd7146132b25f5903c22bdc..81f92e303a59022d6cf419505b22c1aca981d58b 100644 --- a/dynamics.py +++ b/dynamics.py @@ -1,8 +1,11 @@ import ERFormatConstants as const import logging as log -# Dynamics class parent class for both the bowtie dynamics and the architecture dynamics class DynamicComponent: + """_summary_ + Dynamic component, parent class of bowtie dynamic and ER dynamic + It contains a metric list which contains the ID of the metrics associated with the dynamic component + """ def __init__(self, componentID) -> None: self.componentID = componentID @@ -15,8 +18,20 @@ class DynamicComponent: #Metric class, will be used for all dynamics class Metric: + """ + The metric class is associated with all the dynamics components AND the matrix + The initial creation of metrics is done through the dynamic units, there the init function is used to create the metrics, + and fill them with the correct associated components (erID, bowtieID) + When the object is then accessed again for the matrix parse the metrics are filled with the correct values + """ def __init__(self, ID, name) -> None: - + """ + Metrics are created when parsing dynamics and are there filled with args + When the matrix is parsed the metrics are filled with the correct values + Args: + ID (str): Unique ID for the metric + name (str): Unique name for the metric + """ #Metrics found in the dynamics tables self.erID = [] # ID of the metric used to locate in dynamics matrics self.bowtieID = [] # ID of the metric used to locate in dynamics matrics @@ -32,6 +47,12 @@ class Metric: return f"Metric: {self.name}, Value: {self.value} Last update: {self.date}" class BowtieDynamic(DynamicComponent): + """ + The bowtie dynamic inherits the dynamic component and adds associated threats or consequences and attacks + Contains its own associate function and its own linkMetric function to link metrics + Args: + DynamicComponent (class): Parent class + """ def __init__(self, componentID, type) -> None: super().__init__(componentID) @@ -42,6 +63,13 @@ class BowtieDynamic(DynamicComponent): def __str__(self) -> str: + """ + This string function is used to print the dynamic component + Based on the different types of dynamics the string function will return different information + + Returns: + str: Print string + """ if self.type == const.ThreatDynamic: return super().__str__() + f"Associated threat ID: {self.associatedThreat[const.Id].item()} Associated attack ID {(self.associatedAttack[const.Id].item())}" elif self.type == const.ConsequenceDynamic: @@ -49,10 +77,8 @@ class BowtieDynamic(DynamicComponent): elif self.type == const.AttackDynamic: return super().__str__() + f"Associated attack ID: {self.associatedAttack[const.Id].item()}" - - - def associateBowtie(self, df, componentType, metricsDict: dict): - """_summary_ + def associateBowtie(self, df, componentType: str, metricsDict: dict): + """ 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. @@ -60,8 +86,6 @@ class BowtieDynamic(DynamicComponent): """ 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 @@ -80,12 +104,12 @@ class BowtieDynamic(DynamicComponent): # ! 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 + 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()] + sourceComponent = df.loc[df[const.Id]==lineTwo[const.From].item()] # Defines the source of the line connecting threats/consequences and attacks + destinationComponent = df.loc[df[const.Id]==lineTwo[const.To].item()] # Defines the destination of the line connecting threats/consequences and attacks - if componentType == const.ThreatDynamic: # If the component is a threat + 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 @@ -93,53 +117,70 @@ class BowtieDynamic(DynamicComponent): self.associatedThreat = destinationComponent self.associatedAttack = sourceComponent - elif componentType == const.ConsequenceDynamic: # If the component is a consequence + 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 - - self.linkMetric(df, metricsDict) + + # ! After parsing the different components related to the dynamics component, + # ! the metrics in the component are added to the global metric list and local metric list + self.linkMetric(df, metricsDict) def linkMetric(self, df, metricsDict: dict): - dynamicComponent = df.loc[df[const.Id] == self.componentID] # Find all dynamics components - for i in range (4, len(dynamicComponent.columns(), 2)): # Iterate through all the metrics + """ + Function will extract all the metrics from the dynamics component, add it to the local metric list of the dynamic component, + the function also adds the metric to the global metric list (if its not already there) and and links the dynamic component to the metric, + through the associatedER and associatedAttack lists in the metric class + + Args: + df (pandas Dataframe): The original dataframe being parsed from lucidchart + metricsDict (dict): The metricsdict is the global metric list, it contains all the metrics in the diagram + """ + + dynamicComponent = df.loc[df[const.Id] == self.componentID] # Finds the dynamic component we are extracting metrics from + for i in range (4, len(dynamicComponent.columns), 2): # Iterate through all the metrics + + if(dynamicComponent["Text Area "+str(i)].isnull().values.any() == True): # If there are no metrics left + break # Break the loop - if(dynamicComponent["Text Area"+str(i)] == None): # No more metrics - break - metricID = dynamicComponent.loc[dynamicComponent["Text Area"+str(i)]].item() # Find the metric ID - metricName = dynamicComponent.loc[dynamicComponent["Text Area"+str(i+1)]].item() # Find the metric ID + metricID = dynamicComponent["Text Area "+str(i)].item() # Find the metric ID in the component + metricName = dynamicComponent["Text Area "+str(i+1)].item() # Find the metric Name self.metrics.append(metricID) # ? Adds the metric to the metric list associated with the dynamic component specifically - if checkDuplicateMetrics(metricsDict, metricID) == False: # If the metric is not a duplicate + if checkDuplicateMetrics(metricsDict, metricID) == False: # If the metric is in the global list metric = Metric(metricID, metricName) # Create a new metric object else: - metric = metricsDict[metricID] + metric = metricsDict[metricID] # Find the metric - if self.type == const.ThreatDynamic: - if checkDuplicate(metric.associatedThreat, self.associatedThreat): #* If the threat component is a duplicate + if self.type == const.ThreatDynamic: # If the dynamic component is a threat + if checkDuplicate(metric.bowtieID, self.associatedThreat[const.Id].item()): #* If the threat component is associated with the metric continue # * We wont add it to the metric threatList and attack list since its already there - metric.bowtieID.append(self.associatedThreat, self.associatedAttack) + metric.bowtieID.append(self.associatedThreat[const.Id].item()) # ? Adds the associated threat to the metric - elif self.type == const.ConsequenceDynamic: - if checkDuplicate(metric.associatedConsequence, self.associatedConsequence): - continue - metric.bowtieID.append(self.associatedConsequence, self.associatedAttack) - - elif self.type == const.AttackDynamic: - if checkDuplicate(metric.associatedAttack, self.associatedAttack): + elif self.type == const.ConsequenceDynamic: # Same procedure as if above + if checkDuplicate(metric.bowtieID, self.associatedConsequence[const.Id].item()): continue - metric.bowtieID.append(self.associatedAttack) # TODO Remember to remove duplicates later + metric.bowtieID.append(self.associatedConsequence[const.Id].item()) + + if checkDuplicate(metric.bowtieID, self.associatedAttack[const.Id].item()): #* If the associated component is a duplicate + continue + metric.bowtieID.append(self.associatedAttack[const.Id].item()) metricsDict[metricID] = metric # ! Adds the metric to the global metric list - - - class ERDynamic(DynamicComponent): + """ + The ER dynamic inherits the dynamic component and adds associated ER components + Contains its own associate function and its own linkMetric function to link metrics + Has a description and a list of associated ER components + + Args: + DynamicComponent (class): parent class + """ def __init__(self, componentID, type, description) -> None: super().__init__(componentID) @@ -150,18 +191,24 @@ class ERDynamic(DynamicComponent): def __str__(self) -> str: return super().__str__() + f"ER Dynamic: {self.type}, {self.description}, Amount of associated components {len(self.associatedERComponents)}" - """_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, metricsDict: dict): + """ + Function will associate the ER dynamic with the correct components + The function will also link the metrics to the ER dynamic component + The function will also add the metrics to the global metric list + + Args: + df (pandas Dataframe): The original dataframe being parsed from lucidchart + metricsDict (dict): The metricsdict is the global metric list, it contains all the metrics in the diagram + """ 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)): - + + 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 @@ -182,20 +229,26 @@ class ERDynamic(DynamicComponent): self.associatedERComponents.append(associatedComponent) # Add the associated component to the list of associated components log.info("Associated components found: ", self.associatedERComponents) - self.linkMetric(df, metricsDict) + self.linkMetric(df, metricsDict) # Link the metrics to the ER dynamic component - - def linkMetric(self,df, metricsDict): - dynamicComponent = df.loc[df[const.Id] == self.componentID] # Find all dynamics components + """ + Links the metrics to the ER dynamic component and adds the metrics to the global metric list + + Args: + df (Dataframe): The original dataframe being parsed from lucidchart + metricsDict (dict): The metricsdict is the global metric list, it contains all the metrics in the diagram + """ + + dynamicComponent = df.loc[df[const.Id] == self.componentID] # Find the dynamic component - for i in range (6, len(dynamicComponent.columns(), 2)): # Iterate through all the metrics + for i in range (6, len(dynamicComponent.columns), 2): # Iterate through all the metrics - if(dynamicComponent["Text Area"+str(i)] == None): # No more metrics + if(dynamicComponent["Text Area "+str(i)].isnull().values.any() == True): # No more metrics break - metricID = dynamicComponent.loc[dynamicComponent["Text Area"+str(i)]] # Find the metric ID - metricName = dynamicComponent.loc[dynamicComponent["Text Area"+str(i+1)]] # Find the metric ID + metricID = dynamicComponent["Text Area "+str(i)].item() # Find the metric ID + metricName = dynamicComponent["Text Area "+str(i+1)].item() # Find the metric ID self.metrics.append(metricID) # ? Adds the metric to the metric list associated with the dynamic component specifically if checkDuplicateMetrics(metricsDict, metricID) == False: # If the metric is not a duplicate @@ -203,7 +256,7 @@ class ERDynamic(DynamicComponent): else: metric = metricsDict[metricID] - for i in range(self.associatedERComponents): + for i in range(len(self.associatedERComponents)): erID = self.associatedERComponents[i][const.Id].item() if checkDuplicate(metric.erID, erID): #* If the associated component is a duplicate @@ -219,7 +272,6 @@ def checkDuplicateMetrics(metricsDict: dict, metricID: str): with the same ID as the one which is listed in the dynamics component, if it does """ if metricID in metricsDict: - log.warning("Duplicate metric found: ", metricID) return True else: return False