{"id":36743,"date":"2019-05-19T06:00:54","date_gmt":"2019-05-19T13:00:54","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/premier-developer\/?p=36743"},"modified":"2019-05-28T14:00:09","modified_gmt":"2019-05-28T21:00:09","slug":"using-azure-devops-rest-api-with-node-js-to-retrieve-repo-permissions","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/using-azure-devops-rest-api-with-node-js-to-retrieve-repo-permissions\/","title":{"rendered":"Using Azure DevOps REST API with Node.js to retrieve repo permissions"},"content":{"rendered":"<p>I, <a href=\"http:\/\/www.linkedin.com\/in\/brian-blackman-8012b612\">Brian<\/a>, have been at Microsoft a very long time. How long? When I joined Microsoft straight out of graduate school, how I remember things, it was a time when the Mac division lead the way in revenue, we also had the Office products for the Mac, we wrote <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microsoft_Mail\">Microsoft Mail<\/a> for Mac, and I used an Unix email system at work which I remember was one of our email products at the time, and I did my debugging over a serial port. Today, I feel like we are the Microsoft I initially joined; we write software and we don\u2019t care where it runs. Software is our forte.<\/p>\n<p>Today, I have had the great fortune of working with someone that was not raised on the Microsoft stack as I have been, and it has been inspiring and invigorating sharing our knowledge of different languages and platforms.<\/p>\n<p>Allow me to introduce Sidi Merzouk, one of our newest members of Premier Developer. See the following link on <a href=\"https:\/\/www.forbes.com\/sites\/ninaroberts\/2017\/12\/29\/entrepreneurial-brothers-from-algeria-launch-dynamic-pricing-food-delivery-app-in-nyc\/#21191f56573d\">Forbes<\/a> to get an introduction and a sense of Sidi\u2019s developer vigor. Sidi comes with strengths in languages and platforms that is not customary to find in a Microsoft stack developer and has supercharged me with his talents; for example, the node.js code project below, Sidi wrote this code with input from me.<\/p>\n<p>You can find the full REST API Reference at <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0\">https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0<\/a> used in the sample solution.<\/p>\n<p>Sidi and I had a challenge of pulling\/getting permissions of an Azure DevOps Organization programmatically, but we managed to get something going. Thus, we decided to share our findings with you in this blog post.<\/p>\n<p>The following sample can be download from our repo in GitHub using the following link <a href=\"https:\/\/github.com\/PremierDeveloper\/Azure-DevOps\">https:\/\/github.com\/PremierDeveloper\/Azure-DevOps<\/a>. You will need the code to go along with this post.<\/p>\n<p><img decoding=\"async\" width=\"3220\" height=\"1434\" class=\"wp-image-36744\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-20.png\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-20.png 3220w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-20-300x134.png 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-20-768x342.png 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-20-1024x456.png 1024w\" sizes=\"(max-width: 3220px) 100vw, 3220px\" \/><\/p>\n<p>You will need npm which is distributed with <a href=\"https:\/\/nodejs.org\/en\/\">Node.js<\/a>. So, when you download Node.js, you automatically get npm installed on your computer. After downloading, check that you have node and npm installed by running this command in your shell: node -v. If you have Visual Studio installed, you will have Node.exe but it may not be on your path.<\/p>\n<p>Once you have the project downloaded or cloned, confirmed that Node is installed by navigating to the project directory and run npm install to install the needed dependencies; in this case we will be installing the request library and azure-devops-node-api library.<\/p>\n<p>To begin, you will need to create a personal token from the Azure DevOps dashboard portal as seen in figures 1 and 2. <img decoding=\"async\" width=\"2714\" height=\"1275\" class=\"wp-image-36745\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-21.png\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-21.png 2714w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-21-300x141.png 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-21-768x361.png 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-21-1024x481.png 1024w\" sizes=\"(max-width: 2714px) 100vw, 2714px\" \/><\/p>\n<p>Figure 1: Navigate to Security<\/p>\n<p><img decoding=\"async\" width=\"2750\" height=\"1328\" class=\"wp-image-36746\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-22.png\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-22.png 2750w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-22-300x145.png 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-22-768x371.png 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-22-1024x494.png 1024w\" sizes=\"(max-width: 2750px) 100vw, 2750px\" \/><\/p>\n<p>Figure 2: Create new token<\/p>\n<p>Edit the index.js file in the project directory; you will be inserting the personal token you just created and your Azure DevOps services organization URL and saving your file. The URL should look like the this: <a href=\"https:\/\/dev.azure.com\/YOURORGNAME\">https:\/\/dev.azure.com\/YOURORGNAME<\/a> as in the following figure.<\/p>\n<p><img decoding=\"async\" width=\"1506\" height=\"225\" class=\"wp-image-36747\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-23.png\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-23.png 1506w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-23-300x45.png 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-23-768x115.png 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-23-1024x153.png 1024w\" sizes=\"(max-width: 1506px) 100vw, 1506px\" \/><\/p>\n<p>Figure 3: Azure DevOps Services organization URL.<\/p>\n<pre class=\"lang:default decode:true\">const request = require('request'); \r\nconst vsoNodeApi = require('azure-devops-node-api'); \r\n \r\n\/\/Insert the newly created personal token below \r\nconst token = \"&lt;INSERT TOKEN&gt;\"; \r\n \r\n\/\/ Url to your organization \r\nconst serverUrl = '&lt;INSERT REPO URL&gt;';  \r\nlet authHandler = vsoNodeApi.getPersonalAccessTokenHandler(token);  \r\nlet AzDO = new vsoNodeApi.WebApi(serverUrl, authHandler, undefined);<\/pre>\n<p>Before we can run our script, we will need to do one last thing which is replacing this line with the actual personal token and URL that points to your Azure DevOps Organization.<\/p>\n<pre class=\"lang:default decode:true\">const url = `https:\/\/&lt;INSERT TOKEN&gt;@&lt;INSERT URL&gt;\/${projectId}\/_api\/_identity\/Display?__v=5&amp;tfid=${teamId}`<\/pre>\n<p>E.g.:<\/p>\n<ul>\n<li>let\u2019s say your token is the following string \u201cjdfnjdngfjn238fbeifbisdnksknjfdf12\u201d<\/li>\n<li>Your organization URL is the following dev.azure.com\/simerzou0646<\/li>\n<\/ul>\n<p>The result should look something like this:<\/p>\n<pre class=\"lang:default decode:true\">const url = `https:\/\/ jdfnjdngfjn238fbeifbisdnksknjfdf12@ dev.azure.com\/simerzou0646\/${projectId}\/_api\/_identity\/Display?__v=5&amp;tfid=${teamId}`<\/pre>\n<p>Now we can safely open the terminal navigate to the folder and run node index.js<\/p>\n<p><img decoding=\"async\" width=\"1879\" height=\"1288\" class=\"wp-image-36748\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-24.png\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-24.png 1879w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-24-300x206.png 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-24-768x526.png 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2019\/05\/word-image-24-1024x702.png 1024w\" sizes=\"(max-width: 1879px) 100vw, 1879px\" \/><\/p>\n<p>The result would look something like this:<\/p>\n<pre class=\"lang:js decode:true\">Permissions: { \r\n    \"identity\": { \r\n      \"IdentityType\": \"team\", \r\n      \"FriendlyDisplayName\": \"RayProject Team\", \r\n      \"DisplayName\": \"[RayProject]\\RayProject Team\", \r\n      \"SubHeader\": \"[RayProject]\", \r\n      \"TeamFoundationId\": \"73544b31-8d7d-47c0-a474-f0bd7db7c5b2\", \r\n      \"EntityId\": \"vss.ds.v1.ims.group.73544b318d7d47c0a474f0bd7db7c5b2\", \r\n      \"Errors\": [], \r\n      \"Warnings\": [], \r\n      \"IsWindowsGroup\": false, \r\n      \"IsAadGroup\": false, \r\n      \"Description\": \"The default project team.\", \r\n      \"Scope\": \"RayProject\", \r\n      \"MemberCountText\": \"1\", \r\n      \"IsTeam\": true, \r\n      \"IsProjectLevel\": true \r\n    } \r\n  } \r\n  Permissions: { \r\n    \"identity\": { \r\n      \"IdentityType\": \"team\", \r\n      \"FriendlyDisplayName\": \"Bing Team\", \r\n      \"DisplayName\": \"[Bing]\\Bing Team\", \r\n      \"SubHeader\": \"[Bing]\", \r\n      \"TeamFoundationId\": \"432f4d89-46aa-4072-84e9-6ccc59c4a3f3\", \r\n      \"EntityId\": \"vss.ds.v1.ims.group.432f4d8946aa407284e96ccc59c4a3f3\", \r\n      \"Errors\": [], \r\n      \"Warnings\": [], \r\n      \"IsWindowsGroup\": false, \r\n      \"IsAadGroup\": false, \r\n      \"Description\": \"The default project team.\", \r\n      \"Scope\": \"Bing\", \r\n      \"MemberCountText\": \"2\", \r\n      \"IsTeam\": true, \r\n      \"IsProjectLevel\": true \r\n    } \r\n  } \r\n  Org: { \r\n    \"RayProject\": { \r\n      \"RayProject Team\": \"Sidi Merzouk\" \r\n    }, \r\n    \"Bing\": { \r\n      \"Bing Team\": \"Kevin Kraus, Sidi Merzouk\" \r\n    } \r\n  }<\/pre>\n<p>Et Voila!<\/p>\n<p>For those of you who want to know what\u2019s happening let me give you a quick walkthrough of what\u2019s happening in the index.js file.<\/p>\n<p>There three major components to the code:<\/p>\n<table>\n<tbody>\n<tr>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul style=\"list-style-type: circle;\">\n<li>First, JavaScript is async by default and when we look closely at the code in index.js, you\u2019d find that we are making multiple http request using the azure-devops-node-api library. As you might have picked up that could be a challenge because what if our <strong>for<\/strong> loop ends before we get the response back from the server (which is more than likely to be the case), the result will be an empty object. Thus, I had to create a custom <strong>for<\/strong> loop function (<strong>asyncForEach()<\/strong>) that takes two arguments an array and callback, and we are using async await keyword to halt the code and wait to get the response from our server.<\/li>\n<\/ul>\n<pre class=\"lang:js decode:true\">async function asyncForEach(array, callback) { \r\n    for (let index = 0; index &lt; array.length; index++) { \r\n      await callback(array[index], index, array); \r\n    } \r\n  } \r\n<\/pre>\n<table>\n<tbody>\n<tr>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<ul style=\"list-style-type: circle;\">\n<li>Second, Our <strong>run()<\/strong> function simply calls Azure DevOps library and get the teams for our org. then we loop through it construct a temporary object called <strong>teamsToUse<\/strong> and we pass that our <b>constructorTeams()<\/b> function.<\/li>\n<\/ul>\n<pre class=\"lang:js decode:true\">async function run() { \r\n    var constructedTeams = {} \r\n    try { \r\n      var coreApi = await AzDO.getCoreApi();  \r\n      var teams = await coreApi.getAllTeams(); \r\n      var i; \r\n        for (i = 0; i &lt; teams.length; i++) {  \r\n          const team = teams[i] \r\n          const obj = { \r\n            url: team['url'], \r\n            projectId: team['projectId'], \r\n            teamName: team['name'], \r\n            teamId: team['id'] \r\n          } \r\n          if (!constructedTeams[team['projectName']]){ \r\n            constructedTeams[team['projectName']] = [obj] \r\n          } else { \r\n            constructedTeams[team['projectName']].push(obj) \r\n          } \r\n        } \r\n        const teamsToUse = await constructedTeams \r\n        const finalConstruct = await constructTeams(teamsToUse) \r\n        return finalConstruct \r\n    } catch(err) { \r\n      console.log(`err ${JSON.stringify(err, null, 2)}`) \r\n    } \r\n  }<\/pre>\n<p>&nbsp;<\/p>\n<ul style=\"list-style-type: circle;\">\n<li>Lastly, <strong>contructorTeams()<\/strong> function is first taking the object that was passed to it (teamsToUse) turning it into an array of keys and looping through it. In the for loop we are using the REST API to get the permission that a given group has. Once we get the response, we will either create a new object or append the permissions to the existing object. Last but not least, when the for loop ends, we then return the final object called <strong>obj<\/strong>.<\/li>\n<\/ul>\n<pre class=\"lang:js decode:true \">  const constructTeams = async (teams) =&gt; { \r\n    const obj = {} \r\n    var coreApi = await AzDO.getCoreApi();  \r\n    const ids = Object.keys(teams) \r\n    await asyncForEach(ids, async (key) =&gt; { \r\n      await asyncForEach(teams[key], async (el) =&gt; { \r\n        const temp = {} \r\n        const {projectId, teamId} = el \r\n        const url = `https:\/\/&lt;INSERT TOKEN&gt;@&lt;INSERT URL&gt;\/${projectId}\/_api\/_identity\/Display?__v=5&amp;tfid=${teamId}` \r\n        request.get(url \r\n        , function(error, response, body) { \r\n            const parsedBody = JSON.parse(body) \r\n            if (parsedBody &amp;&amp; parsedBody['security'] &amp;&amp; parsedBody['security']['permissions']) { \r\n              delete parsedBody['security'] \r\n              console.log(`Permissions: ${JSON.stringify(parsedBody, null, 2)}`) \r\n            } else { \r\n              console.log(`not found`) \r\n            } \r\n        } ); \r\n        const members = await coreApi.getTeamMembersWithExtendedProperties(el.projectId, el.teamId); \r\n        const NameMembers = await members.map(member =&gt; member.identity.displayName).join(', ') \r\n        temp[el.teamName] = NameMembers \r\n        if (!obj[key] || Object.keys(obj[key]).length == 0) { \r\n          obj[key] = temp \r\n        } else { \r\n          Object.assign(obj[key], temp)  \r\n        } \r\n      }) \r\n    }) \r\n    console.log(`Org: ${JSON.stringify(obj, null, 2)}`) \r\n    return obj \r\n  }<\/pre>\n<p>With that we\u2019ve concluded our little tour that we\u2019ve put together for you. We hope that you\u2019ve enjoyed reading it as much as we\u2019ve enjoyed putting it together. If you have any feedback, questions, comments or suggestions please share your thoughts with us.<\/p>\n<p>Happy Hacking!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, we will show you how to retrieve Azure DevOps permissions using the REST APIs with NodeJS. You can use this as a starter to consume other Azure DevOps REST APIs as documented at https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0.<\/p>\n","protected":false},"author":582,"featured_media":37840,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[22,1],"tags":[2571,3],"class_list":["post-36743","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","category-permierdev","tag-azure-devops","tag-team"],"acf":[],"blog_post_summary":"<p>In this post, we will show you how to retrieve Azure DevOps permissions using the REST APIs with NodeJS. You can use this as a starter to consume other Azure DevOps REST APIs as documented at https:\/\/docs.microsoft.com\/en-us\/rest\/api\/azure\/devops\/?view=azure-devops-rest-5.0.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36743","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=36743"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/36743\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=36743"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=36743"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=36743"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}