roadrecon 1.3.0__py3-none-any.whl → 1.5.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.
@@ -615,6 +615,7 @@ async def run(args):
615
615
 
616
616
  tasks = []
617
617
  dumper.session = dbsession
618
+ # pylint: disable=not-callable
618
619
  totalgroups = dbsession.query(func.count(Group.objectId)).scalar()
619
620
  totaldevices = dbsession.query(func.count(Device.objectId)).scalar()
620
621
  if totalgroups > MAX_GROUPS:
@@ -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('--port',
69
+ type=int,
70
+ action='store',
71
+ help='HTTP Server port (default=5000)',
72
+ default=5000)
68
73
 
69
74
  # Construct plugins module options
70
75
  plugin_parser = subparsers.add_parser('plugin', help='Run a ROADrecon plugin')
@@ -403,6 +403,16 @@ class AccessPoliciesPlugin():
403
403
  ot += self._parse_appcrit(icrit)
404
404
  return ot
405
405
 
406
+ def _parse_authflows(self, cond):
407
+ if not 'AuthFlows' in cond:
408
+ return ''
409
+ ucond = cond['AuthFlows']
410
+ ot = '<strong>Flows included</strong>: '
411
+
412
+ for icrit in ucond['Include']:
413
+ for _, clist in icrit.items():
414
+ ot += escape(', '.join(clist))
415
+ return ot
406
416
 
407
417
  def _parse_associated_polcies(self,location_object,is_trusted_location,condition_policy_list):
408
418
  found_pols = []
@@ -454,15 +464,19 @@ class AccessPoliciesPlugin():
454
464
  ot = '<strong>Including</strong>: '
455
465
 
456
466
  for icrit in ucond['Include']:
457
- ot += ', '.join([escape(crit) for crit in icrit['ClientTypes']])
467
+ ot += ', '.join(list({escape(self._translate_clienttype(crit)) for crit in icrit['ClientTypes']}))
458
468
 
459
- if 'Exclude' in ucond:
460
- ot += '\n<br /><strong>Excluding</strong>: '
461
-
462
- for icrit in ucond['Exclude']:
463
- ot += ', '.join([escape(crit) for crit in icrit['ClientTypes']])
464
469
  return ot
465
470
 
471
+ def _translate_clienttype(self, client):
472
+ if client in ['EasSupported', 'EasUnsupported']:
473
+ return 'Exchange ActiveSync'
474
+ if client in ['OtherLegacy', 'LegacySmtp', 'LegacyPop', 'LegacyImap', 'LegacyMapi', 'LegacyOffice']:
475
+ return 'Legacy Clients'
476
+ if client == 'Native':
477
+ return 'Mobile and Desktop clients'
478
+ return client
479
+
466
480
  def _parse_sessioncontrols(self, cond):
467
481
  if not 'SessionControls' in cond:
468
482
  return ''
@@ -511,6 +525,7 @@ class AccessPoliciesPlugin():
511
525
  continue
512
526
  out['who'] = self._parse_who(conditions)
513
527
  out['applications'] = self._parse_application(conditions)
528
+ out['authflows'] = self._parse_authflows(conditions)
514
529
  out['platforms'] = self._parse_platform(conditions)
515
530
  out['locations'] = self._parse_locations(conditions)
516
531
  out['clients'] = self._parse_clients(conditions)
@@ -572,7 +587,7 @@ class AccessPoliciesPlugin():
572
587
  loc['name'] = escape(detail.get("NetworkName"))
573
588
  loc['trusted'] = ("trusted" in detail.get("Categories","") if detail.get("Categories") else False)
574
589
  loc['appliestounknowncountry'] = escape(str(detail.get("ApplyToUnknownCountry"))) if detail.get("ApplyToUnknownCountry") is not None else False
575
- loc['ipranges'] = "\n<br />".join(detail.get('CidrIpRanges'))
590
+ loc['ipranges'] = "\n<br />".join(detail.get('CidrIpRanges')) if detail.get("CidrIpRanges") else ""
576
591
  loc['categories'] = escape(", ".join(detail.get("Categories"))) if detail.get("Categories") is not None else ""
577
592
  loc['associated_policies'] = "\n<br />".join(self._parse_associated_polcies(detail.get('NetworkId'),loc['trusted'],condition_policy_list))
578
593
  loc['country_codes'] = escape(", ".join(detail.get("CountryIsoCodes"))) if detail.get("CountryIsoCodes") else None
@@ -597,6 +612,8 @@ class AccessPoliciesPlugin():
597
612
  table += '<tr><td>At locations</td><td>{0}</td></tr>'.format(out['locations'])
598
613
  if out['signinrisks'] != '':
599
614
  table += '<tr><td>Sign-in risks</td><td>{0}</td></tr>'.format(out['signinrisks'])
615
+ if out['authflows'] != '':
616
+ table += '<tr><td>Authentication flows</td><td>{0}</td></tr>'.format(out['authflows'])
600
617
  if out['controls'] != '':
601
618
  table += '<tr><td>Controls</td><td>{0}</td></tr>'.format(out['controls'])
602
619
  if out['sessioncontrols'] != '':
@@ -198,8 +198,8 @@ def main(args: Optional[argparse.Namespace] = None) -> Path:
198
198
 
199
199
  # Do some reflection to grab the tables
200
200
  # and their respective column types
201
- db_metadata = sqlalchemy.MetaData(bind=engine, schema="main")
202
- db_metadata.reflect()
201
+ db_metadata = sqlalchemy.MetaData(schema="main")
202
+ db_metadata.reflect(bind=engine)
203
203
 
204
204
  if not HAS_RTT_MODULES:
205
205
  print('Error importing required modules for road2timeline. Make sure pyyaml, numpy and pandas are installed.')
@@ -7,7 +7,7 @@ from marshmallow import fields
7
7
  from roadtools.roadlib.metadef.database import User, JSON, Group, DirectoryRole, ServicePrincipal, AppRoleAssignment, TenantDetail, Application, Device, OAuth2PermissionGrant, AuthorizationPolicy, DirectorySetting, AdministrativeUnit, RoleDefinition
8
8
  import os
9
9
  import argparse
10
- from sqlalchemy import func
10
+ from sqlalchemy import func, and_, or_, select
11
11
  import mimetypes
12
12
 
13
13
  app = Flask(__name__)
@@ -157,6 +157,10 @@ class TenantDetailSchema(RTModelSchema):
157
157
  class Meta(RTModelSchema.Meta):
158
158
  model = TenantDetail
159
159
 
160
+ class DirectorySettingSchema(RTModelSchema):
161
+ class Meta(RTModelSchema.Meta):
162
+ model = DirectorySetting
163
+
160
164
  class AuthorizationPolicySchema(RTModelSchema):
161
165
  class Meta(RTModelSchema.Meta):
162
166
  model = AuthorizationPolicy
@@ -167,6 +171,7 @@ device_schema = DeviceSchema()
167
171
  group_schema = GroupSchema()
168
172
  application_schema = ApplicationSchema()
169
173
  td_schema = TenantDetailSchema()
174
+ ds_schema = DirectorySettingSchema()
170
175
  serviceprincipal_schema = ServicePrincipalSchema()
171
176
  administrativeunit_schema = AdministrativeUnitSchema()
172
177
  authorizationpolicy_schema = AuthorizationPolicySchema(many=True)
@@ -288,10 +293,18 @@ def get_mfa():
288
293
  # for approle in per_user:
289
294
  # enabledusers.append(approle.principalId)
290
295
 
291
- # Filter out mailbox users by default
292
- all_mfa = db.session.query(User).filter(User.cloudMSExchRecipientDisplayType != 0, User.cloudMSExchRecipientDisplayType != 7, User.cloudMSExchRecipientDisplayType != 18).all()
296
+ # Filter out mailbox-only users by default
297
+ all_mfa = db.session.execute(select(User).where(
298
+ or_(User.cloudMSExchRecipientDisplayType is None,
299
+ and_(
300
+ User.cloudMSExchRecipientDisplayType != 0,
301
+ User.cloudMSExchRecipientDisplayType != 7,
302
+ User.cloudMSExchRecipientDisplayType != 18
303
+ )
304
+ )
305
+ ))
293
306
  out = []
294
- for user in all_mfa:
307
+ for user, in all_mfa: # pylint: disable=E1133
295
308
  mfa_methods = len(user.strongAuthenticationDetail['methods'])
296
309
  methods = [method['methodType'] for method in user.strongAuthenticationDetail['methods']]
297
310
  has_app = 'PhoneAppOTP' in methods or 'PhoneAppNotification' in methods
@@ -521,6 +534,11 @@ def get_tenantdetails():
521
534
  drs = db.session.query(TenantDetail).first()
522
535
  return td_schema.jsonify(drs)
523
536
 
537
+ @app.route("/api/directorysettings", methods=["GET"])
538
+ def get_directorysettings():
539
+ drs = db.session.query(DirectorySetting).first()
540
+ return ds_schema.jsonify(drs)
541
+
524
542
  @app.route("/api/authorizationpolicies", methods=["GET"])
525
543
  def get_authpolicies():
526
544
  drs = db.session.query(AuthorizationPolicy).all()
@@ -528,6 +546,7 @@ def get_authpolicies():
528
546
 
529
547
  @app.route("/api/stats", methods=["GET"])
530
548
  def get_stats():
549
+ # pylint: disable=not-callable
531
550
  stats = {
532
551
  'countUsers': db.session.query(func.count(User.objectId)).scalar(),
533
552
  'countGroups': db.session.query(func.count(Group.objectId)).scalar(),
@@ -563,6 +582,11 @@ def main(args=None):
563
582
  parser.add_argument('--profile',
564
583
  action='store_true',
565
584
  help='Enable flask profiler')
585
+ parser.add_argument('--port',
586
+ type=int,
587
+ action='store',
588
+ help='HTTP Server port (default=5000)',
589
+ default=5000)
566
590
  args = parser.parse_args()
567
591
  if not ':/' in args.database:
568
592
  if args.database[0] != '/':
@@ -575,7 +599,7 @@ def main(args=None):
575
599
  if args.profile:
576
600
  from werkzeug.middleware.profiler import ProfilerMiddleware
577
601
  app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[5])
578
- app.run(debug=args.debug)
602
+ app.run(debug=args.debug, port=args.port)
579
603
 
580
604
  if __name__ == '__main__':
581
605
  main()