-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for configuration policies (#154)
* feat: add support for configuration policies * fix: more assignment changes
- Loading branch information
Showing
9 changed files
with
836 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
220 changes: 220 additions & 0 deletions
220
src/lib/deviceConfigurationPolicies/deviceConfigurationPolicies.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
import { Client } from '@microsoft/microsoft-graph-client' | ||
import { DeviceConfigurationPolicies } from './deviceConfigurationPolicies' | ||
import { mockClient } from '../../../__fixtures__/@microsoft/microsoft-graph-client' | ||
|
||
describe('Device Configuration Policies', () => { | ||
let graphClient: Client | ||
let configurationPolicies: DeviceConfigurationPolicies | ||
|
||
const configurationPolicy = { | ||
name: 'test policy', | ||
'@odata.type': '#microsoft.graph.deviceManagementConfigurationPolicy', | ||
} | ||
|
||
const policyAssignment = { | ||
'@odata.type': '#microsoft.graph.deviceManagementConfigurationPolicyAssignment', | ||
target: { | ||
'@odata.type': '#microsoft.graph.groupAssignmentTarget', | ||
groupId: '1', | ||
}, | ||
} | ||
|
||
beforeEach(() => { | ||
graphClient = mockClient() as never as Client | ||
configurationPolicies = new DeviceConfigurationPolicies(graphClient) | ||
}) | ||
|
||
it('should list all configuration policies', async () => { | ||
jest.spyOn(graphClient.api(''), 'get').mockResolvedValueOnce({ | ||
value: [configurationPolicy], | ||
'@odata.nextLink': 'next', | ||
}) | ||
jest.spyOn(graphClient.api(''), 'get').mockResolvedValueOnce({ | ||
value: [configurationPolicy], | ||
}) | ||
|
||
const result = await configurationPolicies.list() | ||
expect(result).toEqual([configurationPolicy, configurationPolicy]) | ||
}) | ||
|
||
it('should get a configuration policy', async () => { | ||
jest.spyOn(graphClient.api(''), 'get').mockResolvedValue(configurationPolicy) | ||
const result = await configurationPolicies.get('id') | ||
expect(result).toEqual(configurationPolicy) | ||
}) | ||
|
||
it('should create a configuration policy', async () => { | ||
jest.spyOn(graphClient.api(''), 'post').mockResolvedValue(configurationPolicy) | ||
const result = await configurationPolicies.create(configurationPolicy) | ||
expect(result).toEqual(configurationPolicy) | ||
}) | ||
|
||
it('should update a configuration policy', async () => { | ||
jest.spyOn(graphClient.api(''), 'patch') | ||
const result = await configurationPolicies.update('id', configurationPolicy) | ||
expect(result).toBeUndefined() | ||
}) | ||
|
||
it('should delete a configuration policy', async () => { | ||
jest.spyOn(graphClient.api(''), 'delete') | ||
const result = await configurationPolicies.delete('id') | ||
expect(result).toBeUndefined() | ||
}) | ||
|
||
describe('setAssignments', () => { | ||
it('should assign to all devices with exclusions', async () => { | ||
const spy = jest.spyOn(graphClient.api(''), 'post') | ||
|
||
await configurationPolicies.setAssignments('id', { | ||
allDevices: true, | ||
excludeGroups: ['group1', 'group2'], | ||
}) | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
assignments: [ | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.allDevicesAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
}, | ||
}, | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.exclusionGroupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group1', | ||
}, | ||
}, | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.exclusionGroupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group2', | ||
}, | ||
}, | ||
], | ||
}) | ||
}) | ||
|
||
it('should assign to all licensed users', async () => { | ||
const spy = jest.spyOn(graphClient.api(''), 'post') | ||
|
||
await configurationPolicies.setAssignments('id', { | ||
allUsers: true, | ||
}) | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
assignments: [ | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.allLicensedUsersAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
}, | ||
}, | ||
], | ||
}) | ||
}) | ||
|
||
it('should assign to specific included groups', async () => { | ||
const spy = jest.spyOn(graphClient.api(''), 'post') | ||
|
||
await configurationPolicies.setAssignments('id', { | ||
includeGroups: ['group1', 'group2'], | ||
}) | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
assignments: [ | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.groupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group1', | ||
}, | ||
}, | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.groupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group2', | ||
}, | ||
}, | ||
], | ||
}) | ||
}) | ||
|
||
it('should throw error when including groups with allDevices', async () => { | ||
await expect( | ||
configurationPolicies.setAssignments('id', { | ||
allDevices: true, | ||
includeGroups: ['group1'], | ||
}), | ||
).rejects.toThrow('Cannot include specific groups when allDevices is true') | ||
}) | ||
|
||
it('should support mix of include and exclude groups', async () => { | ||
const spy = jest.spyOn(graphClient.api(''), 'post') | ||
|
||
await configurationPolicies.setAssignments('id', { | ||
includeGroups: ['group1'], | ||
excludeGroups: ['group2'], | ||
}) | ||
|
||
expect(spy).toHaveBeenCalledWith({ | ||
assignments: [ | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.groupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group1', | ||
}, | ||
}, | ||
{ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.exclusionGroupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId: 'group2', | ||
}, | ||
}, | ||
], | ||
}) | ||
}) | ||
}) | ||
|
||
describe('pagination', () => { | ||
it('should handle pagination for list method', async () => { | ||
const firstPage = { | ||
value: [{ ...configurationPolicy, id: '1' }], | ||
'@odata.nextLink': 'https://graph.microsoft.com/beta/next-page', | ||
} | ||
const secondPage = { | ||
value: [{ ...configurationPolicy, id: '2' }], | ||
} | ||
|
||
jest.spyOn(graphClient.api(''), 'get') | ||
.mockResolvedValueOnce(firstPage) | ||
.mockResolvedValueOnce(secondPage) | ||
|
||
const result = await configurationPolicies.list() | ||
|
||
expect(result).toHaveLength(2) | ||
expect(result[0].id).toBe('1') | ||
expect(result[1].id).toBe('2') | ||
}) | ||
}) | ||
}) |
171 changes: 171 additions & 0 deletions
171
src/lib/deviceConfigurationPolicies/deviceConfigurationPolicies.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { Client } from '@microsoft/microsoft-graph-client' | ||
import { DeviceManagementConfigurationPolicy } from '@microsoft/microsoft-graph-types-beta' | ||
|
||
interface AssignmentTarget { | ||
'@odata.type': string | ||
deviceAndAppManagementAssignmentFilterType: 'none' | 'include' | 'exclude' | ||
groupId?: string | ||
} | ||
|
||
interface Assignment { | ||
id?: string | ||
source?: 'direct' | ||
target: AssignmentTarget | ||
} | ||
|
||
interface AssignmentOptions { | ||
includeGroups?: string[] | ||
excludeGroups?: string[] | ||
allDevices?: boolean | ||
allUsers?: boolean | ||
} | ||
|
||
export class DeviceConfigurationPolicies { | ||
constructor(private readonly graphClient: Client) {} | ||
|
||
/** | ||
* List all device management configuration policies | ||
* | ||
* @returns | ||
*/ | ||
async list(): Promise<DeviceManagementConfigurationPolicy[]> { | ||
let res = await this.graphClient.api('/deviceManagement/configurationPolicies').get() | ||
const configurationPolicies: DeviceManagementConfigurationPolicy[] = res.value | ||
|
||
while (res['@odata.nextLink']) { | ||
const nextLink = res['@odata.nextLink'].replace('https://graph.microsoft.com/beta', '') | ||
res = await this.graphClient.api(nextLink).get() | ||
const nextConfigurationPolicies = res.value as DeviceManagementConfigurationPolicy[] | ||
configurationPolicies.push(...nextConfigurationPolicies) | ||
} | ||
|
||
return configurationPolicies | ||
} | ||
|
||
/** | ||
* Get a device management configuration policy | ||
* @param configurationPolicyId | ||
* @returns | ||
*/ | ||
async get(configurationPolicyId: string): Promise<DeviceManagementConfigurationPolicy> { | ||
return await this.graphClient | ||
.api(`/deviceManagement/configurationPolicies/${configurationPolicyId}`) | ||
.get() | ||
} | ||
|
||
/** | ||
* Create a device management configuration policy | ||
* @param configurationPolicy | ||
* @returns | ||
*/ | ||
async create( | ||
configurationPolicy: DeviceManagementConfigurationPolicy, | ||
): Promise<DeviceManagementConfigurationPolicy> { | ||
return this.graphClient | ||
.api('/deviceManagement/configurationPolicies') | ||
.post(configurationPolicy) | ||
} | ||
|
||
/** | ||
* Update a device management configuration policy | ||
* @param configurationPolicyId | ||
* @param configurationPolicy | ||
*/ | ||
async update( | ||
configurationPolicyId: string, | ||
configurationPolicy: DeviceManagementConfigurationPolicy, | ||
): Promise<void> { | ||
await this.graphClient | ||
.api(`/deviceManagement/configurationPolicies/${configurationPolicyId}`) | ||
.patch(configurationPolicy) | ||
} | ||
|
||
/** | ||
* Delete a device management configuration policy | ||
* @param configurationPolicyId | ||
*/ | ||
async delete(configurationPolicyId: string): Promise<void> { | ||
await this.graphClient | ||
.api(`/deviceManagement/configurationPolicies/${configurationPolicyId}`) | ||
.delete() | ||
} | ||
|
||
/** | ||
* Set assignments for a configuration policy | ||
* | ||
* THIS WILL OVERWRITE ANY EXISTING ASSIGNMENTS! | ||
* @param id - The ID of the configuration policy | ||
* @param options - Assignment options including groups to include/exclude and whether to assign to all devices/users | ||
* @returns Promise<void> | ||
*/ | ||
async setAssignments(id: string, options: AssignmentOptions): Promise<void> { | ||
const assignments: Assignment[] = [] | ||
|
||
// Add all devices assignment if specified | ||
if (options.allDevices) { | ||
assignments.push({ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.allDevicesAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
}, | ||
}) | ||
|
||
// When all devices is selected, we can only have exclusion groups | ||
if (options.includeGroups?.length) { | ||
throw new Error('Cannot include specific groups when allDevices is true') | ||
} | ||
} | ||
|
||
// Add all licensed users assignment if specified | ||
if (options.allUsers) { | ||
assignments.push({ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.allLicensedUsersAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
}, | ||
}) | ||
} | ||
|
||
// Add included groups | ||
if (options.includeGroups?.length) { | ||
assignments.push( | ||
...options.includeGroups.map( | ||
(groupId): Assignment => ({ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.groupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId, | ||
}, | ||
}), | ||
), | ||
) | ||
} | ||
|
||
// Add excluded groups | ||
if (options.excludeGroups?.length) { | ||
assignments.push( | ||
...options.excludeGroups.map( | ||
(groupId): Assignment => ({ | ||
id: '', | ||
source: 'direct', | ||
target: { | ||
'@odata.type': '#microsoft.graph.exclusionGroupAssignmentTarget', | ||
deviceAndAppManagementAssignmentFilterType: 'none', | ||
groupId, | ||
}, | ||
}), | ||
), | ||
) | ||
} | ||
|
||
await this.graphClient | ||
.api(`/deviceManagement/configurationPolicies('${id}')/assign`) | ||
.post({ assignments }) | ||
} | ||
} |
Oops, something went wrong.