diff --git a/roadrecon/frontend/src/app/appmain/config/config.component.html b/roadrecon/frontend/src/app/appmain/config/config.component.html
index e87f32a..c630b7b 100644
--- a/roadrecon/frontend/src/app/appmain/config/config.component.html
+++ b/roadrecon/frontend/src/app/appmain/config/config.component.html
@@ -5,5 +5,9 @@
Show MFA columns (only accurate if you collected this data)
+
+
+ Show management links to Entra portal on objects
+
diff --git a/roadrecon/frontend/src/app/appmain/config/config.component.ts b/roadrecon/frontend/src/app/appmain/config/config.component.ts
index a62dd6f..a42b959 100644
--- a/roadrecon/frontend/src/app/appmain/config/config.component.ts
+++ b/roadrecon/frontend/src/app/appmain/config/config.component.ts
@@ -9,6 +9,8 @@ import { LocalStorage } from 'ngx-webstorage';
export class ConfigComponent implements OnInit {
@LocalStorage()
public mfa;
+ @LocalStorage()
+ public portallinks;
constructor() { }
diff --git a/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.html b/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.html
index fca181e..6dd4ab2 100644
--- a/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.html
+++ b/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.html
@@ -13,6 +13,7 @@
| Can be assigned to roles | {{ group.isAssignableToRole? 'Yes':'No' }} |
| Created | {{ group.createdDateTime }} |
| Group source | {{ group.dirSyncEnabled? 'Synced with AD':'Cloud-only' }} |
+ | Manage in Entra portal | open_in_new |
diff --git a/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.ts b/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.ts
index fadcbc7..cd7d037 100644
--- a/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.ts
+++ b/roadrecon/frontend/src/app/appmain/groups/groupsdialog/groupsdialog.component.ts
@@ -1,41 +1,46 @@
import { Component, OnInit, Inject, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { GroupsItem } from '../../aadobjects.service'
+import { GroupsItem } from '../../aadobjects.service';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { Location } from '@angular/common';
+import { LocalStorageService } from 'ngx-webstorage';
+
@Component({
template: ''
})
export class GroupsdialogInitComponent implements OnInit {
user: GroupsItem;
myurl: string;
+ showPortalLink: boolean;
+
constructor(
private route: ActivatedRoute,
private router: Router,
public dialog: MatDialog,
- private location: Location
+ private location: Location,
+ private localSt: LocalStorageService
) {
this.myurl = this.router.url;
+ this.showPortalLink = this.localSt.retrieve('portallinks');
}
ngOnInit() {
this.route.data
.subscribe((data: { user: GroupsItem }) => {
const dialogRef = this.dialog.open(GroupsdialogComponent, {
- data: data.user
+ data: {
+ group: data.user,
+ showPortalLink: this.showPortalLink
+ }
});
dialogRef.afterClosed().subscribe(result => {
- console.log(this.router.url);
- console.log(this.myurl);
if(this.router.url == this.myurl){
this.location.back();
}
-
});
});
}
-
}
@Component({
@@ -49,11 +54,16 @@ export class GroupsdialogComponent {
public displayedColumnsServicePrincipal: string[] = ['displayName', 'servicePrincipalType']
public displayedColumnsOwners: string[] = ['displayName', 'userPrincipalName']
public displayedColumnsDevices: string[] = ['displayName', 'deviceModel', 'deviceOSType', 'deviceTrustType'];
+ public showPortalLink: boolean;
+ public group: GroupsItem;
+
+ @ViewChild(MatSort, { static: true }) sort: MatSort;
- @ViewChild(MatSort, {static: true}) sort: MatSort;
constructor(
public dialogRef: MatDialogRef,
- @Inject(MAT_DIALOG_DATA) public group: GroupsItem
- ) { }
-
+ @Inject(MAT_DIALOG_DATA) public data: { group: GroupsItem, showPortalLink: boolean }
+ ) {
+ this.group = data.group;
+ this.showPortalLink = data.showPortalLink;
+ }
}
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 2216a98..d00b09e 100644
--- a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html
+++ b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.html
@@ -15,6 +15,7 @@
| Account source | {{ user.dirSyncEnabled? 'Synced with AD':'Cloud-only' }} |
| Account type | {{ user.userType }} |
| Status | {{ user.accountEnabled? 'Enabled':'Disabled' }} |
+ | Manage in Entra portal | open_in_new |
diff --git a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.ts b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.ts
index 0fe4162..be7254b 100644
--- a/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.ts
+++ b/roadrecon/frontend/src/app/appmain/users/usersdialog/usersdialog.component.ts
@@ -4,26 +4,34 @@ import { UsersItem } from '../../aadobjects.service'
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { Location } from '@angular/common';
+import { LocalStorageService } from 'ngx-webstorage';
+
@Component({
template: ''
})
export class UsersdialogInitComponent implements OnInit {
user: UsersItem;
myurl: string;
+ showPortalLink: boolean;
constructor(
private route: ActivatedRoute,
private router: Router,
public dialog: MatDialog,
- private location: Location
+ private location: Location,
+ private localSt:LocalStorageService
) {
this.myurl = this.router.url;
+ this.showPortalLink = this.localSt.retrieve('portallinks');
}
ngOnInit() {
this.route.data
.subscribe((data: { user: UsersItem }) => {
- const dialogRef = this.dialog.open(UsersdialogComponent, {
- data: data.user
+ const dialogRef = this.dialog.open(UsersdialogComponent,{
+ data: {
+ user: data.user,
+ showPortalLink: this.showPortalLink
+ }
});
dialogRef.afterClosed().subscribe(result => {
if(this.router.url == this.myurl){
@@ -46,11 +54,15 @@ export class UsersdialogComponent {
public displayedColumnsServicePrincipals: string[] = ['displayName', 'publisherName', 'microsoftFirstParty', 'passwordCredentials', 'keyCredentials', 'appRoles', 'oauth2Permissions'];
public displayedColumnsDevices: string[] = ['displayName', 'deviceManufacturer', 'accountEnabled', 'deviceModel', 'deviceOSType', 'deviceOSVersion', 'deviceTrustType', 'isCompliant', 'isManaged', 'isRooted'];
public displayedColumnsApplications: string[] = ['displayName', 'passwordCredentials', 'keyCredentials', 'appRoles', 'oauth2Permissions'];
-
+ public showPortalLink: boolean;
+ public user: UsersItem;
@ViewChild(MatSort, {static: true}) sort: MatSort;
constructor(
public dialogRef: MatDialogRef,
- @Inject(MAT_DIALOG_DATA) public user: UsersItem
- ) { }
+ @Inject(MAT_DIALOG_DATA) public data: {user: UsersItem, showPortalLink: boolean}
+ ) {
+ this.user = data.user;
+ this.showPortalLink = data.showPortalLink;
+ }
}
diff --git a/roadrecon/roadtools/roadrecon/server.py b/roadrecon/roadtools/roadrecon/server.py
index 180af81..1b0c6ff 100644
--- a/roadrecon/roadtools/roadrecon/server.py
+++ b/roadrecon/roadtools/roadrecon/server.py
@@ -507,9 +507,24 @@ def get_allroles():
'scopeNames': snames,
'scopeIds': sids
}
- _, principal = resolve_objectid(assignment.principalId)
+ principalType, principal = resolve_objectid(assignment.principalId)
aobj['principal'] = principal
+
roleobj['assignments'].append(aobj)
+ if principalType == 'Group':
+ group = db.session.get(Group, assignment.principalId)
+ for member in group.memberUsers:
+ mp = users_schema.dump([member])[0]
+ mp['displayName'] = f"{principal['displayName']} member: {mp['displayName']}"
+ roleobj['assignments'].append({
+ 'type': 'assignment',
+ 'scope': assignment.resourceScopes,
+ 'scopeTypes': stypes,
+ 'scopeNames': snames,
+ 'scopeIds': sids,
+ 'principal': mp
+ })
+
for assignment in role.eligibleAssignments:
stypes, snames, sids = translate_rolescopes(assignment.resourceScopes)
aobj = {
@@ -519,9 +534,22 @@ def get_allroles():
'scopeNames': snames,
'scopeIds': sids
}
- _, principal = resolve_objectid(assignment.principalId)
+ principalType, principal = resolve_objectid(assignment.principalId)
aobj['principal'] = principal
roleobj['assignments'].append(aobj)
+ if principalType == 'Group':
+ group = db.session.get(Group, assignment.principalId)
+ for member in group.memberUsers:
+ mp = users_schema.dump([member])[0]
+ mp['displayName'] = f"{principal['displayName']} member: {mp['displayName']}"
+ roleobj['assignments'].append({
+ 'type': 'eligible',
+ 'scope': assignment.resourceScopes,
+ 'scopeTypes': stypes,
+ 'scopeNames': snames,
+ 'scopeIds': sids,
+ 'principal': mp
+ })
allroles.append(roleobj)
return jsonify(allroles)