Files
raven/devdocs/modernization/security/routemap.py

52 lines
2.4 KiB
Python

# Map every controller action: HTTP verb + route, anonymous or not, and which checks its body makes.
# Usage: python -I routemap.py <Controllers folder> <out.tsv>
import os, re, sys
src, out = sys.argv[1], sys.argv[2]
verb_re = re.compile(r'\[Http(Get|Post|Put|Delete|Patch)(?:\("([^"]*)"\))?\]')
method_re = re.compile(r'public\s+(?:async\s+)?[\w<>\[\], ]+\s+(\w+)\s*\(')
checks = {
'role': re.compile(r'Authorized\.Has\w+Role|HasAnyRole|HasReadFullRole|HasModifyRole|HasCreateRole|HasDeleteRole|HasSelectRole'),
'usertype': re.compile(r'UserTypeFromContext|UserType\.Customer|UserType\.HeadOffice|IsCustomerUser|CustomerUser'),
'biz': re.compile(r'Biz\.GetBiz|new \w+Biz\(|\w+Biz\.'),
'dltoken': re.compile(r'ValidateDownloadToken'),
}
rows = []
for fn in sorted(os.listdir(src)):
if not fn.endswith('.cs'):
continue
text = open(os.path.join(src, fn), encoding='utf-8-sig').read()
# drop // comments so commented-out actions are ignored
text = '\n'.join(l for l in text.splitlines() if not l.lstrip().startswith('//'))
m = re.search(r'\[Route\("([^"]*)"\)\]', text)
base = m.group(1).replace('api/v{version:apiVersion}/', '') if m else ''
cls_auth = bool(re.search(r'\[Authorize\][^{]*class\s+\w+Controller', text, re.S))
for vm in verb_re.finditer(text):
after = text[vm.end():]
mm = method_re.search(after)
if not mm:
continue
attrs = text[max(0, vm.start() - 300):vm.end()] + after[:mm.start()]
anon = '[AllowAnonymous]' in attrs
# body: from the method's opening brace to the matching closing brace
start = vm.end() + mm.end()
brace = text.find('{', start)
depth, i = 0, brace
while i < len(text):
if text[i] == '{': depth += 1
elif text[i] == '}':
depth -= 1
if depth == 0: break
i += 1
body = text[brace:i]
found = [k for k, r in checks.items() if r.search(body)]
rows.append((fn.replace('Controller.cs', ''), vm.group(1).upper(), (base + '/' + (vm.group(2) or '')).rstrip('/'),
mm.group(1), 'ANON' if anon else ('auth' if cls_auth or '[Authorize]' in attrs else 'NO-ATTR'), ','.join(found) or '-'))
with open(out, 'w', encoding='utf-8') as f:
f.write('controller\tverb\troute\taction\taccess\tchecks\n')
for r in rows:
f.write('\t'.join(r) + '\n')
print(len(rows), 'actions')