diff --git a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html
index d3159a5..9238df1 100644
--- a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html
+++ b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html
@@ -549,6 +549,7 @@
Policy processing for guest users is currently not completely accurate. Policies scoped to guest types are not processed.
+ This table shows Conditional Access policies scoped to the current user. It only shows enabled policies, policies in report only mode or disabled policies are skipped.
@@ -661,19 +662,83 @@
+
+ Sign in risk
+
+ {{risk}}
+
+
+
+
+ User risk
+
+ {{risk}}
+
+
+
0" class="policy-chip-section">
Sign-in controls
-
- {{control.Control ? control.Control.join(' or ') : ''}}{{control.AuthStrengthIds ? 'Auth strength: ' + control.AuthStrengthIds.join(', ') : ''}}
-
+
+
+ {{control.Control.join(' or ')}}
+
+
+
+ Auth strength: {{utils.translateAuthStrength(guid)}}
+
+
+
+
+
+
+ 0" class="policy-chip-section">
+ Sign-in Extensible Controls
+
+
+
+ Microsoft Managed risk remediation
+
+
+ {{control}}
+
+
Session controls
- {{control}}
+
+
+
+
+
+ SignInFrequency (Unknown setting)
+
+
+ SignInFrequency (Every time)
+
+
+ SignInFrequency (Every {{row.policyDetail.SignInFrequencyTimeSpan}})
+
+
+ SignInFrequency (Unknown SIF type {{row.policyDetail.SignInFrequencyType}})
+
+
+
+
+
+ PersistentBrowserSession: {{row.policyDetail.PersistentBrowserSessionMode}}
+
+
+
+
+ {{control}}
+
+
+
diff --git a/roadrecon/frontend/src/app/appmain/utils.service.ts b/roadrecon/frontend/src/app/appmain/utils.service.ts
index 9dd68cf..57b9b6c 100644
--- a/roadrecon/frontend/src/app/appmain/utils.service.ts
+++ b/roadrecon/frontend/src/app/appmain/utils.service.ts
@@ -335,4 +335,13 @@ export class UtilitiesService {
return dateString;
}
}
+
+ public translateAuthStrength(guid: string): string {
+ const builtIn: { [key: string]: string } = {
+ '00000000-0000-0000-0000-000000000002': 'Multi-factor authentication',
+ '00000000-0000-0000-0000-000000000003': 'Passwordless MFA',
+ '00000000-0000-0000-0000-000000000004': 'Phishing-resistant MFA'
+ };
+ return builtIn[guid] ?? `Unknown auth strength policy: ${guid} (probably custom)`;
+ }
}
diff --git a/roadrecon/roadtools/roadrecon/plugins/policies.py b/roadrecon/roadtools/roadrecon/plugins/policies.py
index 2c0e9ab..9bba2af 100644
--- a/roadrecon/roadtools/roadrecon/plugins/policies.py
+++ b/roadrecon/roadtools/roadrecon/plugins/policies.py
@@ -337,36 +337,28 @@ class AccessPoliciesPlugin():
return ot
def _parse_signinrisks(self, cond):
- try:
- srcond = cond['SignInRisks']
- except KeyError:
- return ''
-
- ot = 'Including: '
- for icrit in srcond['Include']:
- ot += ', '.join([escape(crit) for crit in icrit['SignInRisks']])
-
- if 'Exclude' in srcond:
- ot += '\n
Excluding: '
- for icrit in srcond['Exclude']:
- ot += ', '.join([escape(crit) for crit in icrit['SignInRisks']])
-
- return ot
+ return self._parse_risks(cond, 'SignInRisks')
def _parse_agentrisks(self, cond):
+ return self._parse_risks(cond, 'AgentIdRisks')
+
+ def _parse_userrisks(self, cond):
+ return self._parse_risks(cond, 'UserRisks')
+
+ def _parse_risks(self, cond, riskskey):
try:
- srcond = cond['AgentIdRisks']
+ srcond = cond[riskskey]
except KeyError:
return ''
ot = 'Including: '
for icrit in srcond['Include']:
- ot += ', '.join([escape(crit) for crit in icrit['AgentIdRisks']])
+ ot += ', '.join([escape(crit) for crit in icrit[riskskey]])
if 'Exclude' in srcond:
ot += '\n
Excluding: '
for icrit in srcond['Exclude']:
- ot += ', '.join([escape(crit) for crit in icrit['AgentIdRisks']])
+ ot += ', '.join([escape(crit) for crit in icrit[riskskey]])
return ot
@@ -540,6 +532,17 @@ class AccessPoliciesPlugin():
ucond.append(condition)
return ', '.join(ucond)
+ def _parse_extensiblecontrols(self, cond):
+ if not 'ExtensibleControls' in cond:
+ return ''
+ ucond = []
+ for control in cond['ExtensibleControls']:
+ if control == 'MicrosoftManagedRemediation':
+ ucond.append('Microsoft Managed risk remediation')
+ else:
+ ucond.append(f'Unknown extensible control: {control}')
+ return ', '.join(ucond)
+
def _parse_compressed_cidr(self,detail):
if not 'CompressedCidrIpRanges' in detail:
return ''
@@ -598,8 +601,10 @@ class AccessPoliciesPlugin():
out['locations'] = self.parse_wrapper(self._parse_locations, conditions, policy)
out['clients'] = self.parse_wrapper(self._parse_clients, conditions, policy)
out['signinrisks'] = self.parse_wrapper(self._parse_signinrisks, conditions, policy)
+ out['userrisks'] = self.parse_wrapper(self._parse_userrisks, conditions, policy)
out['agentrisks'] = self.parse_wrapper(self._parse_agentrisks, conditions, policy)
out['sessioncontrols'] = self._parse_sessioncontrols(detail)
+ out['extensiblecontrols'] = self._parse_extensiblecontrols(detail)
out['devices'] = self.parse_wrapper(self._parse_devices, conditions, policy)
try:
@@ -683,12 +688,16 @@ class AccessPoliciesPlugin():
table += '| At locations | {0} |
'.format(out['locations'])
if out['signinrisks'] != '':
table += '| Sign-in risks | {0} |
'.format(out['signinrisks'])
+ if out['userrisks'] != '':
+ table += '| User risks | {0} |
'.format(out['userrisks'])
if out['agentrisks'] != '':
table += '| Agent risks | {0} |
'.format(out['agentrisks'])
if out['authflows'] != '':
table += '| Authentication flows | {0} |
'.format(out['authflows'])
if out['controls'] != '':
table += '| Controls | {0} |
'.format(out['controls'])
+ if out['extensiblecontrols'] != '':
+ table += '| Extensible controls | {0} |
'.format(out['extensiblecontrols'])
if out['sessioncontrols'] != '':
table += '| Session controls | {0} |
'.format(out['sessioncontrols'])
table += ''
diff --git a/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py b/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py
index 298f558..64795fd 100644
--- a/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py
+++ b/roadrecon/roadtools/roadrecon/plugins/policyanalysis.py
@@ -131,7 +131,8 @@ class PoliciesPlugin():
'Roles': self._get_role,
'ServicePrincipals': self._get_serviceprincipal,
'ServicePrincipalFilterRule': self._get_serviceprincipalrule,
- 'GuestsOrExternalUsers': self._translate_guestsexternal
+ 'GuestsOrExternalUsers': self._translate_guestsexternal,
+ 'AgenticServicePrincipals': self._get_serviceprincipal
}
inscope_uids = []
for ctype, clist in crit.items():