roadrecon: support more policy controls in UI and policies plugin

This commit is contained in:
Dirk-jan Mollema
2026-05-21 14:25:36 +02:00
parent c785d0cb2f
commit df70458ff7
4 changed files with 107 additions and 23 deletions
@@ -549,6 +549,7 @@
<div *ngIf="user.userType == 'Guest'" class="mat-elevation-z4 pagedescription">
Policy processing for guest users is currently not completely accurate. Policies scoped to guest types are not processed.
</div>
<div class="mat-elevation-z4 pagedescription">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.</div>
<table mat-table [dataSource]="policies" class="full-width-table">
<!-- Name + state -->
@@ -661,19 +662,83 @@
</mat-chip-list>
</div>
<div *ngIf="row.policyDetail.Conditions.SignInRisks?.Include" class="policy-chip-section">
<span class="policy-section-label">Sign in risk</span>
<mat-chip-list>
<mat-chip *ngFor="let risk of row.policyDetail.Conditions.SignInRisks.Include[0].SignInRisks" disableRipple>{{risk}}</mat-chip>
</mat-chip-list>
</div>
<div *ngIf="row.policyDetail.Conditions.UserRisks?.Include" class="policy-chip-section">
<span class="policy-section-label">User risk</span>
<mat-chip-list>
<mat-chip *ngFor="let risk of row.policyDetail.Conditions.UserRisks.Include[0].UserRisks" disableRipple>{{risk}}</mat-chip>
</mat-chip-list>
</div>
<div *ngIf="row.policyDetail.Controls?.length > 0" class="policy-chip-section">
<span class="policy-section-label">Sign-in controls</span>
<mat-chip-list>
<mat-chip *ngFor="let control of row.policyDetail.Controls" disableRipple>
{{control.Control ? control.Control.join(' or ') : ''}}{{control.AuthStrengthIds ? 'Auth strength: ' + control.AuthStrengthIds.join(', ') : ''}}
</mat-chip>
<ng-container *ngFor="let control of row.policyDetail.Controls">
<mat-chip disableRipple *ngIf="control.Control">
{{control.Control.join(' or ')}}
</mat-chip>
<ng-container *ngIf="control.AuthStrengthIds">
<mat-chip disableRipple *ngFor="let guid of control.AuthStrengthIds"
[matTooltip]="guid">
Auth strength: {{utils.translateAuthStrength(guid)}}
</mat-chip>
</ng-container>
</ng-container>
</mat-chip-list>
</div>
<div *ngIf="row.policyDetail.ExtensibleControls?.length > 0" class="policy-chip-section">
<span class="policy-section-label">Sign-in Extensible Controls</span>
<mat-chip-list>
<ng-container *ngFor="let control of row.policyDetail.ExtensibleControls">
<mat-chip disableRipple *ngIf="control == 'MicrosoftManagedRemediation'">
Microsoft Managed risk remediation
</mat-chip>
<mat-chip disableRipple *ngIf="control != 'MicrosoftManagedRemediation'">
{{control}}
</mat-chip>
</ng-container>
</mat-chip-list>
</div>
<div *ngIf="row.policyDetail.SessionControls" class="policy-chip-section">
<span class="policy-section-label">Session controls</span>
<mat-chip-list>
<mat-chip *ngFor="let control of row.policyDetail.SessionControls" disableRipple>{{control}}</mat-chip>
<ng-container *ngFor="let control of row.policyDetail.SessionControls">
<!-- SignInFrequency with sub-type branching -->
<ng-container *ngIf="control === 'SignInFrequency'">
<mat-chip disableRipple *ngIf="!row.policyDetail.SignInFrequencyType">
SignInFrequency (Unknown setting)
</mat-chip>
<mat-chip disableRipple *ngIf="row.policyDetail.SignInFrequencyType === 30">
SignInFrequency (Every time)
</mat-chip>
<mat-chip disableRipple *ngIf="row.policyDetail.SignInFrequencyType === 10">
SignInFrequency (Every {{row.policyDetail.SignInFrequencyTimeSpan}})
</mat-chip>
<mat-chip disableRipple *ngIf="row.policyDetail.SignInFrequencyType !== 30 && row.policyDetail.SignInFrequencyType !== 10 && row.policyDetail.SignInFrequencyType">
SignInFrequency (Unknown SIF type {{row.policyDetail.SignInFrequencyType}})
</mat-chip>
</ng-container>
<!-- PersistentBrowserSessionMode -->
<mat-chip disableRipple *ngIf="control === 'PersistentBrowserSessionMode'">
PersistentBrowserSession: {{row.policyDetail.PersistentBrowserSessionMode}}
</mat-chip>
<!-- Everything else -->
<mat-chip disableRipple *ngIf="control !== 'SignInFrequency' && control !== 'PersistentBrowserSessionMode'">
{{control}}
</mat-chip>
</ng-container>
</mat-chip-list>
</div>
@@ -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)`;
}
}
@@ -337,36 +337,28 @@ class AccessPoliciesPlugin():
return ot
def _parse_signinrisks(self, cond):
try:
srcond = cond['SignInRisks']
except KeyError:
return ''
ot = '<strong>Including</strong>: '
for icrit in srcond['Include']:
ot += ', '.join([escape(crit) for crit in icrit['SignInRisks']])
if 'Exclude' in srcond:
ot += '\n<br /><strong>Excluding</strong>: '
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 = '<strong>Including</strong>: '
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<br /><strong>Excluding</strong>: '
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 += '<tr><td>At locations</td><td>{0}</td></tr>'.format(out['locations'])
if out['signinrisks'] != '':
table += '<tr><td>Sign-in risks</td><td>{0}</td></tr>'.format(out['signinrisks'])
if out['userrisks'] != '':
table += '<tr><td>User risks</td><td>{0}</td></tr>'.format(out['userrisks'])
if out['agentrisks'] != '':
table += '<tr><td>Agent risks</td><td>{0}</td></tr>'.format(out['agentrisks'])
if out['authflows'] != '':
table += '<tr><td>Authentication flows</td><td>{0}</td></tr>'.format(out['authflows'])
if out['controls'] != '':
table += '<tr><td>Controls</td><td>{0}</td></tr>'.format(out['controls'])
if out['extensiblecontrols'] != '':
table += '<tr><td>Extensible controls</td><td>{0}</td></tr>'.format(out['extensiblecontrols'])
if out['sessioncontrols'] != '':
table += '<tr><td>Session controls</td><td>{0}</td></tr>'.format(out['sessioncontrols'])
table += '</tbody>'
@@ -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():