Skip to content

Commit e6a8019

Browse files
committed
C#: Add python script for generating YAML files containing data extensions.
1 parent 4972839 commit e6a8019

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Tool to generate data extensions files based on the existing models.
2+
# Usage:
3+
# python3 ConvertExtensions.py
4+
# (1) A folder named `csharp/ql/lib/ext` will be created, if it doesn't already exist.
5+
# (2) The converted models will be written to `csharp/ql/lib/ext`. One file for each namespace.
6+
7+
import os
8+
import subprocess
9+
import sys
10+
11+
# Add Models as Data script directory to sys.path.
12+
gitroot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
13+
madpath = os.path.join(gitroot, "misc/scripts/models-as-data/")
14+
sys.path.append(madpath)
15+
16+
import helpers
17+
import convert_extensions as extensions
18+
19+
print('Running script to generate data extensions files from the existing MaD models.')
20+
print('Making a dummy database.')
21+
22+
# Configuration
23+
language = "csharp"
24+
dbDir = "db"
25+
26+
helpers.run_cmd(['codeql', 'database', 'create', f'--language={language}', '-c', 'dotnet clean project/', '-c', 'dotnet build project/', dbDir])
27+
28+
print('Converting data extensions for C#.')
29+
extensions.Converter(language, dbDir).run()
30+
31+
print('Cleanup.')
32+
# Cleanup - delete database.
33+
helpers.remove_dir(dbDir)
34+
print('Done.')
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Helper functionality for MaD models extensions conversion.
2+
3+
import helpers
4+
import os
5+
import shutil
6+
import subprocess
7+
import sys
8+
import tempfile
9+
10+
11+
def quote_if_needed(v):
12+
# string columns
13+
if type(v) is str:
14+
return "\"" + v + "\""
15+
# bool column
16+
return str(v)
17+
18+
def insert_update(rows, key, value):
19+
if key in rows:
20+
rows[key] += value
21+
else:
22+
rows[key] = value
23+
24+
def merge(*dicts):
25+
merged = {}
26+
for d in dicts:
27+
for entry in d:
28+
insert_update(merged, entry, d[entry])
29+
return merged
30+
31+
def parseData(data):
32+
rows = { }
33+
for row in data:
34+
d = map(quote_if_needed, row)
35+
insert_update(rows, row[0], " - [" + ', '.join(d) + ']\n')
36+
37+
return rows
38+
39+
class Converter:
40+
def __init__(self, language, dbDir):
41+
self.language = language
42+
self.dbDir = dbDir
43+
self.codeQlRoot = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip()
44+
self.extDir = os.path.join(self.codeQlRoot, f"{self.language}/ql/lib/ext/")
45+
self.dirname = "modelconverter"
46+
self.modelFileExtension = ".model.yml"
47+
self.workDir = tempfile.mkdtemp()
48+
49+
50+
def runQuery(self, query):
51+
print('########## Querying: ', query)
52+
queryFile = os.path.join(self.codeQlRoot, f"{self.language}/ql/src/utils/{self.dirname}", query)
53+
resultBqrs = os.path.join(self.workDir, "out.bqrs")
54+
55+
helpers.run_cmd(['codeql', 'query', 'run', queryFile, '--database', self.dbDir, '--output', resultBqrs], "Failed to generate " + query)
56+
return helpers.readData(self.workDir, resultBqrs)
57+
58+
59+
def asAddsTo(self, rows, predicate):
60+
extensions = { }
61+
for key in rows:
62+
extensions[key] = helpers.addsToTemplate.format(f"codeql/{self.language}-all", predicate, rows[key])
63+
64+
return extensions
65+
66+
67+
def getAddsTo(self, query, predicate):
68+
data = self.runQuery(query)
69+
rows = parseData(data)
70+
return self.asAddsTo(rows, predicate)
71+
72+
73+
def makeContent(self):
74+
summaries = self.getAddsTo("ExtractSummaries.ql", helpers.summaryModelPredicate)
75+
sources = self.getAddsTo("ExtractSources.ql", helpers.sourceModelPredicate)
76+
sinks = self.getAddsTo("ExtractSinks.ql", helpers.sinkModelPredicate)
77+
return merge(sources, sinks, summaries)
78+
79+
80+
def save(self, extensions):
81+
# Create directory if it doesn't exist
82+
os.makedirs(self.extDir, exist_ok=True)
83+
84+
# Create a file for each namespace and save models.
85+
extensionTemplate = """
86+
extensions:
87+
{0}
88+
"""
89+
for entry in extensions:
90+
with open(self.extDir + "/" + entry + self.modelFileExtension, "w") as f:
91+
f.write(extensionTemplate.format(extensions[entry]))
92+
93+
def run(self):
94+
extensions = self.makeContent()
95+
self.save(extensions)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import json
2+
import os
3+
import shutil
4+
import subprocess
5+
6+
# Shared strings.
7+
summaryModelPredicate = "extSummaryModel"
8+
sinkModelPredicate = "extSinkModel"
9+
sourceModelPredicate = "extSourceModel"
10+
addsToTemplate = """
11+
- addsTo:
12+
pack: {0}
13+
extensible: {1}
14+
data:
15+
{2}
16+
"""
17+
18+
def remove_dir(dirName):
19+
if os.path.isdir(dirName):
20+
shutil.rmtree(dirName)
21+
print("Removed directory:", dirName)
22+
23+
def run_cmd(cmd, msg="Failed to run command"):
24+
print('Running ' + ' '.join(cmd))
25+
if subprocess.check_call(cmd):
26+
print(msg)
27+
exit(1)
28+
29+
def readData(workDir, bqrsFile):
30+
generatedJson = os.path.join(workDir, "out.json")
31+
print('Decoding BQRS to JSON.')
32+
run_cmd(['codeql', 'bqrs', 'decode', bqrsFile, '--output', generatedJson, '--format=json'], "Failed to decode BQRS.")
33+
34+
with open(generatedJson) as f:
35+
results = json.load(f)
36+
37+
try:
38+
return results['#select']['tuples']
39+
except KeyError:
40+
print('Unexpected JSON output - no tuples found')
41+
exit(1)

0 commit comments

Comments
 (0)