works with ad groups

This commit is contained in:
Russell gove 2021-02-22 10:50:39 -05:00
parent 181f815aa7
commit bff42d763f
6 changed files with 245 additions and 81 deletions

View File

@ -1,15 +1,15 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
},
"serveConfigurations": {
"default": {
"pageUrl": "https://russellwgove.sharepoint.com/sites/workspaces/_layouts/15/workbench.aspx"
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
},
"serveConfigurations": {
"default": {
"pageUrl": "https://russellwgove.sharepoint.com/sites/testazad/_layouts/15/workbench.aspx"
}
}
}
}
}

View File

@ -1763,6 +1763,11 @@
"isomorphic-fetch": "^2.2.1"
}
},
"@microsoft/microsoft-graph-types": {
"version": "1.31.0",
"resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-1.31.0.tgz",
"integrity": "sha512-tx7EmPPaNU4ZYiUuhPYegnI+79Az2djzFmFoolFt/QLbI0jCyLu1OOWi9h7NSLAJo1Z7v4CgsACmy7Ewn4kUUg=="
},
"@microsoft/node-core-library": {
"version": "3.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/node-core-library/-/node-core-library-3.15.1.tgz",

View File

@ -10,6 +10,7 @@
"@types/react": "16.8.8"
},
"dependencies": {
"@microsoft/microsoft-graph-types": "^1.31.0",
"@pnp/common": "1.3.3",
"@pnp/logging": "1.3.3",
"@pnp/odata": "1.3.3",

View File

@ -1,7 +1,11 @@
import { sp } from "@pnp/sp";
import { find, indexOf, includes } from "lodash";
import { User } from "@microsoft/microsoft-graph-types";
import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { IODataUser } from "@microsoft/sp-odata-types";
import { SPPermission } from "@microsoft/sp-page-context";
import { AadHttpClient, HttpClientResponse, IAadHttpClientOptions } from "@microsoft/sp-http";
//import * as MicrosoftGraph from "@microsoft/microsoft-graph-types"
import { sp } from "@pnp/sp";
import { SiteGroup } from "@pnp/sp/src/sitegroups";
import { filter, find, includes, indexOf, lowerCase } from "lodash";
export interface ISPSecurableObject {
id: number;
@ -21,7 +25,18 @@ export class SPBasePermissions {
export enum securableType {
List
}
export class ADGroupId {
public ADId: string; // the goid id in azure
public SPId: number; // the numeric id in the sharepoint users list
}
export class ADGroup {
public id: ADGroupId;
public members: Array<User>;
}
export class SPSiteGroup {
public id: number;
@ -29,15 +44,23 @@ export class SPSiteGroup {
public isHiddenInUI: boolean;
public isShareByEmailGuestUse: boolean;
public isSiteAdmin: boolean;
public userIds: number[];
public userIds: number[];// to switch to ad groups need to make this a string[] with the UPN
public adGroupIds: ADGroupId[];
public constructor(id: number, title: string) {
this.id = id;
this.title = title;
this.userIds = [];
this.adGroupIds = [];
}
}
export class SPSiteUser {
public name: string;
public id: number;
public userId: SPExternalUser;
public id?: number;
public userId?: SPExternalUser;
public upn: string;
public isSelected: boolean; //should user be shown in UI
public principalType: number; //4=Security group, 1 = user, 2=DL, 8=SP Group
public principalType: number; //4=Security group, 1 = user, 2=DL, 8=SP Group, IN HERE A -1 MEANS AD USER
public adId: string;//id if the user in AD
}
export class SPRoleDefinition {
@ -60,6 +83,7 @@ export class SPSecurityInfo {
public siteUsers: SPSiteUser[];
public siteGroups: SPSiteGroup[];
public roleDefinitions: SPRoleDefinition[];
public adGroups: ADGroup[];
public lists: (SPList | SPListItem)[];
public constructor() {
@ -68,7 +92,7 @@ export class SPSecurityInfo {
this.roleDefinitions = new Array<SPRoleDefinition>();
this.siteUsers = new Array<SPSiteUser>();
this.lists = new Array<SPList>();
this.adGroups = new Array<ADGroup>();
}
}
@ -100,6 +124,20 @@ export class SPListItem {
public level: number;
}
// export class ADGroup {
// public id: string;
// public parentId: string;
// public listTitle: string;
// public type: string;
// public itemCount: number;
// public title: string;
// public serverRelativeUrl: string;
// public roleAssignments: SPRoleAssignment[];
// public isExpanded: boolean;
// public hasBeenRetrieved: boolean;
// public level: number;
// }
export class SPExternalUser {
public nameId: string;
public nameIdIssuer: string;
@ -111,21 +149,20 @@ export class SPRoleAssignment {
}
export class Helpers {
public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups): boolean {
public static doesUserHaveAnyPermission(securableObjects: any[], user, requestedpermissions: SPPermission[], roles, siteGroups, adGroups: ADGroup[]): boolean {
for (var securableObject of securableObjects) {
for (var requestedpermission of requestedpermissions) {
if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups)) {
if (Helpers.doesUserHavePermission(securableObject, user, requestedpermission, roles, siteGroups, adGroups)) {
return true;
}
}
}
return false;
}
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups) {
console.log(`check user ${user.name} obj ${securableObject.title} perm ${requestedpermission.value.High}${requestedpermission.value.Low} `);
public static doesUserHavePermission(securableObject, user, requestedpermission: SPPermission, roles, siteGroups, adGroups: ADGroup[]) {
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups);
const permissions: SPBasePermissions[] = Helpers.getUserPermissionsForObject(securableObject, user, roles, siteGroups, adGroups);
for (const permission of permissions) {
if (
((permission.low & requestedpermission.value.Low) === (requestedpermission.value.Low))
@ -158,22 +195,16 @@ export class Helpers {
// }
return basePermissions;
}
public static getUserPermissionsForObject(securableObject, user, roles: SPRoleDefinition[], siteGroups: SPSiteGroup[]) {
public static getUserPermissionsForObject(securableObject, user, roles: SPRoleDefinition[], siteGroups: SPSiteGroup[], adGroups: ADGroup[]) {
const roleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups);
const userRoleAssignments: SPRoleAssignment[] = Helpers.GetRoleAssignmentsForUser(securableObject, user, siteGroups, adGroups);
let roleDefinitionIds: number[] = [];
for (const roleAssignment of roleAssignments) {
for (const roleAssignment of userRoleAssignments) {
for (const roleDefinitionID of roleAssignment.roleDefinitionIds) {
roleDefinitionIds.push(roleDefinitionID);
}
}
// for (var rax = 0; rax < roleAssignments.length; rax++) {
// for (var rdx = 0; rdx < roleAssignments[rax].roleDefinitionIds.length; rdx++) {
// roleDefinitionIds.push(roleAssignments[rax].roleDefinitionIds[rdx]);
// }
// }
var userPermissions = Helpers.getBasePermissionsForRoleDefinitiuonIds(roleDefinitionIds, roles);
return userPermissions;
@ -186,17 +217,39 @@ export class Helpers {
let group: SPSiteGroup = this.findGroup(groupId, groups);
return includes(group.userIds, userId);
}
public static userIsInGroupsNestAdGroups(userAdId: String, groupId: number, groups: SPSiteGroup[], adGroups: ADGroup[]): boolean {
let group: SPSiteGroup = this.findGroup(groupId, groups);
debugger;
for (var adGrouId of group.adGroupIds) {
var adGroup = find(adGroups, (adg) => { return adg.id === adGrouId; });
if (adGroup) {
if (find(adGroup.members, (aduser) => { return aduser.id === userAdId; })) {
return true;
}
} else {
debugger;
alert(`adGroup ${ADGroupId} was not in the collection of ad groups.`);
}
}
}
public static GetRoleAssignmentsForUser(securableObject: ISPSecurableObject, user: SPSiteUser,
groups: SPSiteGroup[]): SPRoleAssignment[] {
groups: SPSiteGroup[], adGroups: ADGroup[]): SPRoleAssignment[] {
try {
let selectedRoleAssignments: SPRoleAssignment[] = [];
// for each role assignment, if the user is in the group, or its for this user, add it to his roleassignments
for (const roleAssignment of securableObject.roleAssignments) {
let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; });
if (group) {
if (this.userIsInGroup(user.id, group.id, groups)) {
if (this.userIsInGroup(user.id, group.id, groups)) { // this tests if a user is directly in the SP GROUP
selectedRoleAssignments.push(roleAssignment);
}
if (this.userIsInGroupsNestAdGroups(user.adId, group.id, groups, adGroups)) { // this tests if a user is in an ad group thats in the SP GROUP
selectedRoleAssignments.push(roleAssignment);
}
}
else {
// it must be a user
@ -205,6 +258,38 @@ export class Helpers {
}
}
}
debugger;
if (user.adId) { // if user is referenced in any groups, we stored his ad id in his user record
for (var adgroup of adGroups) {// for all adGroups
if (find(adgroup.members, (member) => {
return member.id === user.adId;
}) != -1) { // if user is in the adgroup
// for each role assignment, if the adGroup is in the group, or its for this adgroup, add it to his roleassignments
for (const roleAssignment of securableObject.roleAssignments) {
if (adgroup.id.SPId === roleAssignment.principalId) {
selectedRoleAssignments.push(roleAssignment);
}
// debugger;
// let group: SPSiteGroup = find(groups, (g) => { return g.id === roleAssignment.principalId; });
// if (group) {
// if (this.userIsInGroup(user.id, group.id, groups)) {
// selectedRoleAssignments.push(roleAssignment);
// }
// }
// else {
// // it must be a user
// if (user.id === roleAssignment.principalId) {
// selectedRoleAssignments.push(roleAssignment);
// }
// }
}
}
}
}
return selectedRoleAssignments;
} catch (exception) {
//debugger;
@ -321,17 +406,17 @@ export default class SPSecurityService {
let securityInfo: SPSecurityInfo = new SPSecurityInfo();
let batch: any = sp.createBatch();
let errors: Array<string> = [];
debugger;
sp.web.siteUsers.inBatch(batch).get()
.then((response) => {
console.table(response);
securityInfo.siteUsers = response.map((u) => {
var upn: string = u.LoginName.split('|')[2];
let user: SPSiteUser = new SPSiteUser();
user.isSelected = true;
user.id = u.Id;
user.name = u.Title;
user.principalType = u.PrincipalType;
user.upn = u.LoginName.split('|')[2];
user.upn = upn ? upn.toLocaleLowerCase() : u.Title;// switching key in react from id to upn. ensure upn is not undefoined
if (u.UserId) {
user.userId = new SPExternalUser();
user.userId.nameId = u.UserId.NameId;
@ -339,7 +424,9 @@ export default class SPSecurityService {
}
return user;
});
return securityInfo.siteUsers;
// securityInfo.siteUsers = securityInfo.siteUsers.filter((su) => { su.upn });
return securityInfo.siteUsers;// dont really need to return this// alreadey set it on securityinfo
}).catch((error) => {
debugger;
errors.push(`There was an error feting site users -- ${error.message}`);
@ -347,50 +434,47 @@ export default class SPSecurityService {
});
sp.web.siteGroups.filter(`Title ne 'Limited Access System Group'`).expand("Users").select("Title", "Id", "IsHiddenInUI", "IsShareByEmailGuestUse", "IsSiteAdmin", "IsSiteAdmin")
.inBatch(batch).get()
.then((response) => {
let AdGroupPromises: Array<Promise<any>> = [];
.then(async (response) => {
// if group contains an ad group(PrincipalType=4) expand it
securityInfo.siteGroups = response.map((grp) => {
let siteGroup: SPSiteGroup = new SPSiteGroup();
siteGroup.userIds = [];
siteGroup.id = grp.Id;
siteGroup.title = grp.Title;
//
//IMPORTANT:
//For groups created with 'Anyone in the orgranization with the link'
//LoginName: "SharingLinks.cf28991a-7f40-49c8-a68e-f4fa143a094f.OrganizationEdit.2671b36d-1681-4e39-82dc-a9f11166517d",
//
//For groups create with SPecific People
//LoginName: "SharingLinks.3d634d86-7136-4d59-8acf-c87d9a2c7d98.Flexible.9368eb69-6ca4-4b55-85e5-148c3e48e520",
//
//need to check for other options. Seems funny that the one labeled 'Flecible' is for specific people.
//
//So we wil need to add code one day to decipher all thes sharing groups!
let siteGroup: SPSiteGroup = new SPSiteGroup(grp.Id, grp.Title);
for (let user of grp.Users) {
if (user.PrincipalType === 4) {
// To make this work with AD groups, I need to stop using the integer UserId of the user as the
// key to the Users list and use UPN/Email instead. Users in an AD group may not have a accessed the site
// yet , and so will not be in the userinfo list.
// I can get the users from the AD group using the graph HTTPClient. and add them to the Users array
// in my state. Would also need to add a list of AD groups and their members to my state.
// then in DoesUserHavePermission method, Just inlude permissions of any ADQ Groups the user is in.
//
// Also should check for users that have been invited but not yet accessed the site.
// graphHttpClient.get("v1.0/groups?$filter=displayName eq '" + user.Title + "'&$expand=members", GraphHttpClient.configurations.v1).then((response2) => {
// response2.json().then((data) => {
// //debugger;
// });
// }).catch((err) => {
// });
if (user.PrincipalType === 4) { //4=Security group, 1 = user, 2=DL, 8=SP Group
var adgroupid = new ADGroupId();
adgroupid.ADId = user.LoginName.split('|')[2];//Loginname s c:0t,c|tenant|grpid for ad groups
adgroupid.SPId = user.Id;
siteGroup.adGroupIds.push(adgroupid);
} else {
siteGroup.userIds.push(user.Id);
}
}
return siteGroup;
});
return Promise.all(AdGroupPromises).then(() => {
return securityInfo.siteGroups;
});
return securityInfo.siteGroups;// dont really need to return this// alreadey set it on securityinfo
}).catch((error) => {
//error fetching groups
errors.push(`There was an error feting site Groups -- ${error.message}`);
//debugger;
throw error;
});
sp.web.roleDefinitions.expand("BasePermissions").inBatch(batch).get()
.then((response) => {
securityInfo.roleDefinitions = response.map((rd) => {
@ -437,7 +521,6 @@ export default class SPSecurityService {
mylist.isExpanded = false;
mylist.hasBeenRetrieved = false;
mylist.roleAssignments = listObject.RoleAssignments.map((roleAssignmentObject) => {
let roleAssignment: SPRoleAssignment = {
roleDefinitionIds: roleAssignmentObject.RoleDefinitionBindings.map((rdb) => { return rdb.Id; }),
principalId: roleAssignmentObject.PrincipalId
@ -454,13 +537,79 @@ export default class SPSecurityService {
throw error;
});
return batch.execute().then(() => {
// execute the batch to get sp stuff
return batch.execute().then(async () => {
// then get the ad stuff
var requests = [];
var adPromises: Array<Promise<void>> = [];
for (let sitegroup of securityInfo.siteGroups) {
for (let adGroupId of sitegroup.adGroupIds) {
// need to do this in batch
var adPromise = this.fetchAdGroup(aadHttpClient, adGroupId)
.then((adGroup) => {
securityInfo.adGroups.push(adGroup);
for (var adUser of adGroup.members) {
var siteUser = find(securityInfo.siteUsers, (su, key) => {
return su.upn === adUser.userPrincipalName.toLowerCase();
});
if (siteUser) {
siteUser.adId = adUser.id;
} else {
let user: SPSiteUser = new SPSiteUser();
user.adId = adUser.id; user.name = adUser.displayName + "*";
user.upn = adUser.userPrincipalName ? adUser.userPrincipalName : adUser.displayName;
user.isSelected = true;
user.principalType = -1;
securityInfo.siteUsers.push(user);
}
}
}
)
.catch((err) => {
debugger;
});
adPromises.push(adPromise);
}
}
await Promise.all(adPromises);
console.table(securityInfo.siteUsers);
debugger;
return securityInfo;
}).catch((error) => {
//debugger;
debugger;
// error in batch
throw errors;
});
}
private fetchAdGroup(aadHttpClient: AadHttpClient, adGrouId: ADGroupId): Promise<ADGroup> {
var aadHttpClientConfiguration: AadHttpClientConfiguration = new AadHttpClientConfiguration({}, {});
// note im not loading nested groups here. Will need to load them on the fly? or maybe here?nvrmind
return aadHttpClient.get(`https://graph.microsoft.com/v1.0/groups/${adGrouId.ADId}/transitiveMembers`, aadHttpClientConfiguration)
.then((adResponse) => {
return adResponse.json()
.then((data) => {
let adGroup: ADGroup = new ADGroup();
adGroup.id = adGrouId;
adGroup.members = data.value;
return adGroup;
}).catch((err) => {
debugger;
alert(`error converting to json`);
return null;
});
}).catch((err) => {
debugger;
//if 403 show message to grant security
alert(`grant app SharePoint Online Client Extensibility Web Application Principal graph permissions Group.Read.All & GroupMembers.Read.All`);
return null;
});
}
}

View File

@ -1,4 +1,5 @@
import { Version } from "@microsoft/sp-core-library";
import { AadHttpClient, AadHttpClientConfiguration, HttpClientResponse, IAadHttpClientConfiguration, IAadHttpClientConfigurations, IAadHttpClientOptions } from "@microsoft/sp-http";
import { SPPermission } from "@microsoft/sp-page-context";
import { IPropertyPaneConfiguration, IPropertyPaneDropdownOption, PropertyPaneCheckbox, PropertyPaneDropdown, PropertyPaneSlider, PropertyPaneTextField, PropertyPaneToggle } from "@microsoft/sp-property-pane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
@ -13,6 +14,7 @@ import { PropertyFieldSelectedPermissions } from "./containers/PropertyFieldSele
import { ISpSecurityWebPartProps } from "./ISpSecurityWebPartProps";
export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurityWebPartProps> {
private aadHttpClient: AadHttpClient;
public onInit(): Promise<void> {
return super.onInit().then(_ => {
sp.setup({
@ -21,6 +23,13 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
defaultCachingTimeoutSeconds: 30,
globalCacheDisable: true // or true to disable caching in case of debugging/testing
});
}).then(_ => {
this.context.aadHttpClientFactory.getClient(`https://graph.microsoft.com`)
.then((client: AadHttpClient) => {
// Search for the users with givenName, surname, or displayName equal to the searchFor value
this.aadHttpClient = client;
});
});
}
@ -43,7 +52,7 @@ export default class SpSecurityWebPart extends BaseClientSideWebPart<ISpSecurity
listTitleColumnWidth: this.properties.listTitleColumnWidth,
users: this.properties.users,
getPermissionTypes: this.getPermissionTypes,
aadHttpClient: null,//this.context.aadHttpClient,
aadHttpClient: this.aadHttpClient,
domElement: this.domElement
};

View File

@ -25,10 +25,10 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
private svc: SPSecurityService = new SPSecurityService("ss");
private userSelection = new Selection();
private listSelection = new Selection();
constructor(props: any) {
constructor(props: any) {
super(props);
this.state = {
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [] },
securityInfo: { siteUsers: [], siteGroups: [], roleDefinitions: [], lists: [], adGroups: [] },
//permission: this.props.permission,
selectedPermissions: this.props.selectedPermissions,
showUserPanel: false,
@ -52,7 +52,7 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
this.renderUserSelected = this.renderUserSelected.bind(this);
}
public componentDidMount(): void {
debugger;
initializeFileTypeIcons();
}
public componentDidUpdate(): void {
@ -257,13 +257,13 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
public renderUserItem(item: any, index: number, column: IColumn, effectivePermissions: ISelectedPermission[]): any {
let user: SPSiteUser = find(this.state.securityInfo.siteUsers, (su) => {
return su.id.toString() === column.key;
return su.upn.toString() === column.key;
});
// spin througg the selected permsiisopns and for the first hit, display that color. No Hit, then display empty
for (let selectedPermission of effectivePermissions ? effectivePermissions : []) {
if (Helpers.doesUserHavePermission(item, user, SPPermission[selectedPermission.permission],
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups)) {
this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups, this.state.securityInfo.adGroups)) {
return (
<Icon iconName={selectedPermission.iconName} style={{ color: selectedPermission.color }} onClick={(e) => {
this.expandCollapseList(item);
@ -290,13 +290,13 @@ export default class SpSecurity extends React.Component<ISpSecurityProps, ISpSec
for (let user of users) {
if (user.isSelected) {
if (
(user.principalType === 1 && this.props.showUsers)
((user.principalType === 1 || user.principalType === -1) && this.props.showUsers)
||
(user.principalType === 4 && this.props.showSecurityGroups)
)
if (!this.props.showOnlyUsersWithPermission || Helpers.doesUserHaveAnyPermission(this.state.securityInfo.lists, user, effectivePermissions.map((sp) => { return SPPermission[sp.permission] }), this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups))
if (!this.props.showOnlyUsersWithPermission || Helpers.doesUserHaveAnyPermission(this.state.securityInfo.lists, user, effectivePermissions.map((sp) => { return SPPermission[sp.permission] }), this.state.securityInfo.roleDefinitions, this.state.securityInfo.siteGroups, this.state.securityInfo.adGroups))
columns.push({
key: user.id.toString(),
key: user.upn,
name: this.state.showEmail ? user.upn : user.name,
fieldName: "",
minWidth: 20,