1+ const crypto = require ( 'crypto' ) ;
2+ const https = require ( 'https' ) ;
3+ const { URL } = require ( 'url' ) ;
4+ // Replace <ACS_Resource_Endpoint> and <ACS_Resource_Key> with your actual ACS resource endpoint and key
5+ // You can find these in the Azure portal under your Communication Services resource
6+ const endpoint = "<ACS_Resource_Endpoint>" ;
7+ const key = "<ACS_Resource_Key>" ;
8+ const apiVersion = '2025-03-02-preview' ;
9+
10+ async function main ( ) {
11+ const objectId = "<OBJECT_ID>" ; // Replace with the actual object ID
12+ const tenantId = "<TENANT_ID>" ; // Replace with the actual tenant ID
13+ const clientId = "<CLIENT_ID>" ; // Replace with the actual client ID
14+ const assignment = {
15+ tenantId : tenantId ,
16+ // Type of the principal accessing the resource. Possible values are: "user", "group" or "tenant".
17+ principalType : "user" ,
18+ clientIds : [ clientId ] ,
19+ } ;
20+ try {
21+ // List all assignments
22+ const assignments = await listAssignments ( ) ;
23+ console . log ( "Assignments:" , assignments ) ;
24+
25+ // Create or update an assignment
26+ const createResponse = await createOrUpdateAssignment ( objectId , assignment ) ;
27+ console . log ( "Create or Update Assignment Response:" , createResponse ) ;
28+
29+ // Get a specific assignment by objectId
30+ const getResponse = await getAssignment ( objectId ) ;
31+ console . log ( "Get Assignment Response:" , getResponse ) ;
32+
33+ // Update assignments
34+ const updateAssignmentsDict = { } ;
35+ updateAssignmentsDict [ objectId ] = {
36+ principalType : "group" ,
37+ tenantId : tenantId ,
38+ clientIds : [ clientId ] ,
39+ } ;
40+ const updateResponse = await updateAssignments ( updateAssignmentsDict ) ;
41+ console . log ( "Update Assignments Response:" , updateResponse ) ;
42+
43+ // Delete an assignment by objectId
44+ const deleteResponse = await deleteAssignment ( objectId ) ;
45+ console . log ( "Delete Assignment Response:" , deleteResponse ) ;
46+
47+ } catch ( error ) {
48+ console . error ( "Error:" , error ) ;
49+ }
50+ }
51+
52+ function listAssignments ( ) {
53+ const apiUrl = endpoint . replace ( / \/ + $ / , '' ) + '/access/entra/assignments?api-version=' + apiVersion ;
54+ const urlObj = new URL ( apiUrl ) ;
55+ const getMethod = 'GET' ;
56+
57+ const options = {
58+ hostname : urlObj . hostname ,
59+ path : urlObj . pathname + urlObj . search ,
60+ method : getMethod ,
61+ headers : generateSignedHeaders ( {
62+ url : apiUrl ,
63+ method : getMethod ,
64+ body : ''
65+ } )
66+ } ;
67+
68+ return sendRequest ( options ) ;
69+ }
70+
71+ async function createOrUpdateAssignment ( objectId , assignment ) {
72+ const path = `/access/entra/assignments/${ objectId } ?api-version=${ apiVersion } ` ;
73+ const apiUrl = endpoint . replace ( / \/ + $ / , '' ) + path ;
74+ const urlObj = new URL ( apiUrl ) ;
75+ const putMethod = 'PUT' ;
76+ const body = JSON . stringify ( assignment ) ;
77+ const options = {
78+ hostname : urlObj . hostname ,
79+ path : urlObj . pathname + urlObj . search ,
80+ method : putMethod ,
81+ headers : generateSignedHeaders ( {
82+ url : apiUrl ,
83+ method : putMethod ,
84+ body : body
85+ } )
86+ } ;
87+
88+ return sendRequest ( options , body ) ;
89+ }
90+
91+ async function updateAssignments ( assignments ) {
92+ const path = `/access/entra/assignments?api-version=${ apiVersion } ` ;
93+ const apiUrl = endpoint . replace ( / \/ + $ / , '' ) + path ;
94+ const urlObj = new URL ( apiUrl ) ;
95+ const patchMethod = 'PATCH' ;
96+ const body = JSON . stringify ( assignments ) ;
97+
98+ const options = {
99+ hostname : urlObj . hostname ,
100+ path : urlObj . pathname + urlObj . search ,
101+ method : patchMethod ,
102+ headers : generateSignedHeaders ( {
103+ url : apiUrl ,
104+ method : patchMethod ,
105+ body : body ,
106+ contentType : "application/merge-patch+json"
107+ } )
108+ } ;
109+
110+ return sendRequest ( options , body ) ;
111+ }
112+
113+ async function getAssignment ( objectId ) {
114+ const path = `/access/entra/assignments/${ objectId } ?api-version=${ apiVersion } ` ;
115+ const apiUrl = endpoint . replace ( / \/ + $ / , '' ) + path ;
116+ const urlObj = new URL ( apiUrl ) ;
117+ const getMethod = 'GET' ;
118+
119+ const options = {
120+ hostname : urlObj . hostname ,
121+ path : urlObj . pathname + urlObj . search ,
122+ method : getMethod ,
123+ headers : generateSignedHeaders ( {
124+ url : apiUrl ,
125+ method : getMethod ,
126+ body : ''
127+ } )
128+ } ;
129+
130+ return sendRequest ( options ) ;
131+ }
132+
133+ async function deleteAssignment ( objectId ) {
134+ const path = `/access/entra/assignments/${ objectId } ?api-version=${ apiVersion } ` ;
135+ const apiUrl = endpoint . replace ( / \/ + $ / , '' ) + path ;
136+ const urlObj = new URL ( apiUrl ) ;
137+ const deleteMethod = 'DELETE' ;
138+
139+ const options = {
140+ hostname : urlObj . hostname ,
141+ path : urlObj . pathname + urlObj . search ,
142+ method : deleteMethod ,
143+ headers : generateSignedHeaders ( {
144+ url : apiUrl ,
145+ method : deleteMethod ,
146+ body : ''
147+ } )
148+ } ;
149+
150+ return sendRequest ( options ) ;
151+ }
152+
153+ function generateSignedHeaders ( { url, method = 'GET' , body = '' , contentType = 'application/json' } ) {
154+ const verb = method . toUpperCase ( ) ;
155+ const utcNow = new Date ( ) . toUTCString ( ) ;
156+ const contentHash = crypto . createHash ( 'sha256' ) . update ( body , 'utf8' ) . digest ( 'base64' ) ;
157+ const dateHeader = "x-ms-date" ;
158+ const signedHeaders = `${ dateHeader } ;host;x-ms-content-sha256` ;
159+
160+ const urlObj = new URL ( url ) ;
161+ const query = urlObj . searchParams . toString ( ) ;
162+ const urlPathAndQuery = query ? `${ urlObj . pathname } ?${ query } ` : urlObj . pathname ;
163+ const port = urlObj . port ;
164+ const hostAndPort = port ? `${ urlObj . host } :${ port } ` : urlObj . host ;
165+
166+ const stringToSign = `${ verb } \n${ urlPathAndQuery } \n${ utcNow } ;${ hostAndPort } ;${ contentHash } ` ;
167+ const signature = crypto . createHmac ( 'sha256' , Buffer . from ( key , 'base64' ) ) . update ( stringToSign , 'utf8' ) . digest ( 'base64' ) ;
168+
169+ return {
170+ Host : hostAndPort ,
171+ [ dateHeader ] : utcNow ,
172+ "x-ms-content-sha256" : contentHash ,
173+ "Content-Type" : contentType ,
174+ "Authorization" : `HMAC-SHA256 SignedHeaders=${ signedHeaders } &Signature=${ signature } `
175+ } ;
176+ }
177+
178+ function sendRequest ( requestOptions , body = '' ) {
179+ return new Promise ( ( resolve , reject ) => {
180+ const req = https . request ( requestOptions , ( res ) => {
181+ let data = '' ;
182+ res . on ( 'data' , ( chunk ) => { data += chunk ; } ) ;
183+ res . on ( 'end' , ( ) => {
184+ try {
185+ const json = data ? JSON . parse ( data ) : { } ;
186+ resolve ( json ) ;
187+ } catch ( err ) {
188+ reject ( err ) ;
189+ }
190+ } ) ;
191+ } ) ;
192+
193+ req . on ( 'error' , ( err ) => {
194+ reject ( err ) ;
195+ } ) ;
196+
197+ // Write body if present and not empty
198+ if ( body && body . length > 0 ) {
199+ req . write ( body ) ;
200+ }
201+
202+ req . end ( ) ;
203+ } ) ;
204+ }
205+
206+ main ( ) ;
0 commit comments