# Map every controller action: HTTP verb + route, anonymous or not, and which checks its body makes. # Usage: python -I routemap.py 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')