roadrecon 1.6.1__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {roadrecon-1.6.1.dist-info → roadrecon-1.7.0.dist-info}/METADATA +6 -2
- {roadrecon-1.6.1.dist-info → roadrecon-1.7.0.dist-info}/RECORD +11 -11
- {roadrecon-1.6.1.dist-info → roadrecon-1.7.0.dist-info}/WHEEL +1 -1
- roadtools/roadrecon/dist_gui/index.html +3 -3
- roadtools/roadrecon/dist_gui/main.85359eb82b074d01.js +1 -0
- roadtools/roadrecon/gather.py +10 -1
- roadtools/roadrecon/main.py +6 -1
- roadtools/roadrecon/plugins/policies.py +32 -9
- roadtools/roadrecon/server.py +37 -4
- roadtools/roadrecon/dist_gui/main.007348bcb2c0fb0b.js +0 -1
- {roadrecon-1.6.1.dist-info → roadrecon-1.7.0.dist-info}/entry_points.txt +0 -0
- {roadrecon-1.6.1.dist-info → roadrecon-1.7.0.dist-info}/top_level.txt +0 -0
roadtools/roadrecon/gather.py
CHANGED
|
@@ -723,8 +723,17 @@ def main(args=None):
|
|
|
723
723
|
dburl = 'sqlite:///' + args.database
|
|
724
724
|
else:
|
|
725
725
|
dburl = args.database
|
|
726
|
+
try:
|
|
727
|
+
_, tokendata = Authentication.parse_accesstoken(token['accessToken'])
|
|
728
|
+
except KeyError:
|
|
729
|
+
print('No access token found in tokenfile')
|
|
730
|
+
return
|
|
731
|
+
if tokendata['aud'] not in ('https://graph.windows.net', 'https://graph.windows.net/', '00000002-0000-0000-c000-000000000000'):
|
|
732
|
+
print(f"Wrong token audience, got {tokendata['aud']} but expected https://graph.windows.net")
|
|
733
|
+
print("Make sure to request a token with -r https://graph.windows.net")
|
|
734
|
+
return
|
|
726
735
|
|
|
727
|
-
headers['Authorization'] =
|
|
736
|
+
headers['Authorization'] = f"Bearer {token['accessToken']}"
|
|
728
737
|
|
|
729
738
|
seconds = time.perf_counter()
|
|
730
739
|
loop = asyncio.get_event_loop()
|
roadtools/roadrecon/main.py
CHANGED
|
@@ -65,6 +65,11 @@ def main():
|
|
|
65
65
|
gui_parser.add_argument('--profile',
|
|
66
66
|
action='store_true',
|
|
67
67
|
help='Enable flask profiler')
|
|
68
|
+
gui_parser.add_argument('--host',
|
|
69
|
+
type=str,
|
|
70
|
+
action='store',
|
|
71
|
+
help='HTTP Server host to bind to (default=127.0.0.1)',
|
|
72
|
+
default='127.0.0.1')
|
|
68
73
|
gui_parser.add_argument('--port',
|
|
69
74
|
type=int,
|
|
70
75
|
action='store',
|
|
@@ -129,4 +134,4 @@ def main():
|
|
|
129
134
|
check_database_exists(args.database)
|
|
130
135
|
plugin_module.main(args)
|
|
131
136
|
if __name__ == '__main__':
|
|
132
|
-
main()
|
|
137
|
+
main()
|
|
@@ -229,22 +229,26 @@ class AccessPoliciesPlugin():
|
|
|
229
229
|
if ctype == 'Acrs':
|
|
230
230
|
ot += 'Action: '
|
|
231
231
|
ot += ', '.join([escape(action) for action in clist])
|
|
232
|
+
elif ctype == 'NetworkAccess':
|
|
233
|
+
# clist should be a dict, for example {"TrafficProfiles":"Internet"}
|
|
234
|
+
ot += 'Network access: '
|
|
235
|
+
ot += ', '.join([f"{escape(action)}: {escape(target)}" for action, target in clist.items()])
|
|
232
236
|
else:
|
|
233
237
|
if 'All' in clist:
|
|
234
|
-
ot += 'All
|
|
238
|
+
ot += 'All resources'
|
|
235
239
|
break
|
|
236
240
|
if 'None' in clist:
|
|
237
241
|
ot += 'None'
|
|
238
242
|
break
|
|
239
243
|
if 'Office365' in clist:
|
|
240
|
-
ot += 'All Office 365 applications'
|
|
244
|
+
ot += 'All Office 365 applications '
|
|
241
245
|
if 'MicrosoftAdminPortals' in clist:
|
|
242
|
-
ot += 'All Microsoft Admin Portals'
|
|
246
|
+
ot += 'All Microsoft Admin Portals '
|
|
243
247
|
objects = self._get_application(clist)
|
|
244
248
|
if objects is not None:
|
|
245
249
|
if len(objects) > 0:
|
|
246
250
|
if ctype == 'Applications':
|
|
247
|
-
ot += '
|
|
251
|
+
ot += 'Resources: '
|
|
248
252
|
ot += ', '.join([escape(uobj.displayName) for uobj in objects])
|
|
249
253
|
return ot
|
|
250
254
|
|
|
@@ -491,10 +495,26 @@ class AccessPoliciesPlugin():
|
|
|
491
495
|
def _parse_sessioncontrols(self, cond):
|
|
492
496
|
if not 'SessionControls' in cond:
|
|
493
497
|
return ''
|
|
494
|
-
ucond =
|
|
498
|
+
ucond = []
|
|
499
|
+
for condition in cond['SessionControls']:
|
|
500
|
+
if condition == 'SignInFrequency':
|
|
501
|
+
siftype = cond.get('SignInFrequencyType')
|
|
502
|
+
if not siftype:
|
|
503
|
+
ucond.append('SignInFrequency (Unknown setting)')
|
|
504
|
+
elif siftype == 30:
|
|
505
|
+
ucond.append('SignInFrequency (Every time)')
|
|
506
|
+
elif siftype == 10:
|
|
507
|
+
sifduration = cond.get('SignInFrequencyTimeSpan', '')
|
|
508
|
+
ucond.append(f'SignInFrequency (Every {sifduration})')
|
|
509
|
+
else:
|
|
510
|
+
ucond.append(f'SignInFrequency (Unknown SIF type {siftype})')
|
|
511
|
+
elif condition == 'PersistentBrowserSessionMode':
|
|
512
|
+
pbmode = cond.get('PersistentBrowserSessionMode')
|
|
513
|
+
ucond.append(f'PersistentBrowserSession: {pbmode}')
|
|
514
|
+
else:
|
|
515
|
+
ucond.append(condition)
|
|
495
516
|
return ', '.join(ucond)
|
|
496
517
|
|
|
497
|
-
|
|
498
518
|
def _parse_compressed_cidr(self,detail):
|
|
499
519
|
if not 'CompressedCidrIpRanges' in detail:
|
|
500
520
|
return ''
|
|
@@ -521,9 +541,9 @@ class AccessPoliciesPlugin():
|
|
|
521
541
|
print(policy.objectId)
|
|
522
542
|
detail = json.loads(policy.policyDetail[0])
|
|
523
543
|
if detail['State'] == 'Reporting':
|
|
524
|
-
out['name'] += ' (<
|
|
544
|
+
out['name'] += ' (<i>Report only</i>)'
|
|
525
545
|
elif detail['State'] != 'Enabled':
|
|
526
|
-
out['name'] += ' (<
|
|
546
|
+
out['name'] += ' (<i>Disabled</i>)'
|
|
527
547
|
if should_print:
|
|
528
548
|
pp.pprint(detail)
|
|
529
549
|
try:
|
|
@@ -535,6 +555,7 @@ class AccessPoliciesPlugin():
|
|
|
535
555
|
print('Invalid policy - no conditions')
|
|
536
556
|
continue
|
|
537
557
|
out['who'] = self._parse_who(conditions)
|
|
558
|
+
out['status'] = escape(detail['State'])
|
|
538
559
|
out['applications'] = self._parse_application(conditions)
|
|
539
560
|
out['authflows'] = self._parse_authflows(conditions)
|
|
540
561
|
out['platforms'] = self._parse_platform(conditions)
|
|
@@ -612,7 +633,9 @@ class AccessPoliciesPlugin():
|
|
|
612
633
|
for out in ol:
|
|
613
634
|
table = '<thead><tr><td colspan="2">{0}</td></tr></thead><tbody>'.format(out['name'])
|
|
614
635
|
table += '<tr><td>Applies to</td><td>{0}</td></tr>'.format(out['who'])
|
|
615
|
-
|
|
636
|
+
if out['status'] != 'Enabled':
|
|
637
|
+
table += '<tr><td>Policy state</td><td>{0}</td></tr>'.format(out['status'])
|
|
638
|
+
table += '<tr><td>Resources</td><td>{0}</td></tr>'.format(out['applications'])
|
|
616
639
|
if out['platforms'] != '':
|
|
617
640
|
table += '<tr><td>On platforms</td><td>{0}</td></tr>'.format(out['platforms'])
|
|
618
641
|
if out['devices'] != '':
|
roadtools/roadrecon/server.py
CHANGED
|
@@ -507,9 +507,24 @@ def get_allroles():
|
|
|
507
507
|
'scopeNames': snames,
|
|
508
508
|
'scopeIds': sids
|
|
509
509
|
}
|
|
510
|
-
|
|
510
|
+
principalType, principal = resolve_objectid(assignment.principalId)
|
|
511
511
|
aobj['principal'] = principal
|
|
512
|
+
|
|
512
513
|
roleobj['assignments'].append(aobj)
|
|
514
|
+
if principalType == 'Group':
|
|
515
|
+
group = db.session.get(Group, assignment.principalId)
|
|
516
|
+
for member in group.memberUsers:
|
|
517
|
+
mp = users_schema.dump([member])[0]
|
|
518
|
+
mp['displayName'] = f"{principal['displayName']} member: {mp['displayName']}"
|
|
519
|
+
roleobj['assignments'].append({
|
|
520
|
+
'type': 'assignment',
|
|
521
|
+
'scope': assignment.resourceScopes,
|
|
522
|
+
'scopeTypes': stypes,
|
|
523
|
+
'scopeNames': snames,
|
|
524
|
+
'scopeIds': sids,
|
|
525
|
+
'principal': mp
|
|
526
|
+
})
|
|
527
|
+
|
|
513
528
|
for assignment in role.eligibleAssignments:
|
|
514
529
|
stypes, snames, sids = translate_rolescopes(assignment.resourceScopes)
|
|
515
530
|
aobj = {
|
|
@@ -519,9 +534,22 @@ def get_allroles():
|
|
|
519
534
|
'scopeNames': snames,
|
|
520
535
|
'scopeIds': sids
|
|
521
536
|
}
|
|
522
|
-
|
|
537
|
+
principalType, principal = resolve_objectid(assignment.principalId)
|
|
523
538
|
aobj['principal'] = principal
|
|
524
539
|
roleobj['assignments'].append(aobj)
|
|
540
|
+
if principalType == 'Group':
|
|
541
|
+
group = db.session.get(Group, assignment.principalId)
|
|
542
|
+
for member in group.memberUsers:
|
|
543
|
+
mp = users_schema.dump([member])[0]
|
|
544
|
+
mp['displayName'] = f"{principal['displayName']} member: {mp['displayName']}"
|
|
545
|
+
roleobj['assignments'].append({
|
|
546
|
+
'type': 'eligible',
|
|
547
|
+
'scope': assignment.resourceScopes,
|
|
548
|
+
'scopeTypes': stypes,
|
|
549
|
+
'scopeNames': snames,
|
|
550
|
+
'scopeIds': sids,
|
|
551
|
+
'principal': mp
|
|
552
|
+
})
|
|
525
553
|
allroles.append(roleobj)
|
|
526
554
|
return jsonify(allroles)
|
|
527
555
|
|
|
@@ -583,6 +611,11 @@ def main(args=None):
|
|
|
583
611
|
parser.add_argument('--profile',
|
|
584
612
|
action='store_true',
|
|
585
613
|
help='Enable flask profiler')
|
|
614
|
+
parser.add_argument('--host',
|
|
615
|
+
type=str,
|
|
616
|
+
action='store',
|
|
617
|
+
help='Host IP to bind to (default=127.0.0.1)',
|
|
618
|
+
default='127.0.0.1')
|
|
586
619
|
parser.add_argument('--port',
|
|
587
620
|
type=int,
|
|
588
621
|
action='store',
|
|
@@ -600,7 +633,7 @@ def main(args=None):
|
|
|
600
633
|
if args.profile:
|
|
601
634
|
from werkzeug.middleware.profiler import ProfilerMiddleware
|
|
602
635
|
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[5])
|
|
603
|
-
app.run(
|
|
636
|
+
app.run(host=args.host, port=args.port, debug=args.debug)
|
|
604
637
|
|
|
605
638
|
if __name__ == '__main__':
|
|
606
|
-
main()
|
|
639
|
+
main()
|