How do you get a list of all project iterations using the Azure DevOps Services .NET SDK? - azure-devops

I'd like to get a list of all the iterations for a given project in a Azure DevOps repository, using the .NET API.
Is there any example of how to do this? The current documentation (https://learn.microsoft.com/en-us/dotnet/api/microsoft.teamfoundation.work.webapi.workhttpclientbase.getteamiterationsasync?view=azure-devops-dotnet) is pretty thin.

Below is a working example of how to achieve this.
You need to reference Microsoft.TeamFoundation.Work.WebApi.
public async Task<List<TeamSettingsIteration>> GetProjectIterations(string serverUrl, string projectName)
{
var uri = new Uri(serverUrl);
var creds = new VssClientCredentials(new WindowsCredential(true), new VssFederatedCredential(true), CredentialPromptType.PromptIfNeeded);
var azureDevopsConnection = new VssConnection(uri, creds);
await azureDevopsConnection.ConnectAsync();
WorkHttpClient azureDevOpsWorkHttpClient = azureDevopsConnection.GetClient<WorkHttpClient>();
TeamContext teamContext = new TeamContext(projectName);
List<TeamSettingsIteration> results= await azureDevOpsWorkHttpClient.GetTeamIterationsAsync(teamContext);
return results;
}

Related

Need help for Checkmarx.Api cake plugin

I am trying to incorporate "Checkmarx" Static code scans as a stage into my devops pipeline. Currently our code uses "cake" files to excute the stages (invoked by PowerShell).
I was checking the cake support for Checkmarx.Api but could not find any neither in the Checkmarx site or in the Cake website. The NuGet gallery has a tab for the cake addin - https://www.nuget.org/packages/Checkmarx.API/
but does not share any information on the contracts exposed.
So reaching out to the community to see if anyone has done any work on this or has any references. Any other way you have incorporated "Checkmarx" into your build pipeline (without directly using the plugin rather using the CxCLi) would also be helpful as well.
As answered in the GitHub discussion where you asked the same question:
Cake scripts based on "normal" C#, so whatever the usage of Checkmarx.API, you can simply incorporate that in your cake scripts. Probably something like:
Task("Scan")
.Does(() =>
{
// place your code here..
});
As for using Checkmarx.API, I would suggest asking in the Checkmarx.API repo.
Alternatively, it seems that there is a CLI available. You can use that using the one of the process aliases.
Probably something like:
Task("Scan")
.Does(() =>
{
StartProcess("runCxConsole.cmd", new ProcessSettings
{
Arguments = #"Scan -v -ProjectName ""CxServer/bookname j2"" -CxServer http://localhost -CxUser username -CxPassword admin -LocationType folder -LocationPath ""C:\Data\Projects\Java\bs java"" -preset ""Checkmarx Default"""
});
});
(Note: I took the Arguments to runCxConsole.cmd from the documentation - I did not test that.)
I will mark this as closed as I have been able to get around this using .net HttpClient but unfortunately could not implement using Checkmarx cake addin.
I will paste the sample code, i was getting some ssl eerror until i added the "ServerCertificateCustomValidationCallback" to return true
string accessToken = string.Empty;
try
{
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
using (var client = new HttpClient(httpClientHandler))
{
client.BaseAddress = new Uri(CXUrl+"/auth/identity/connect/token");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("Accept", "*/*");
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("scope", "access_control_api sast_api"),
new KeyValuePair<string, string>("username", username),
new KeyValuePair<string, string>("password", pwd),
new KeyValuePair<string, string>("client_id", "resource_owner_sast_client"),
new KeyValuePair<string, string>("client_secret", "****************************"),
});
var response = client.PostAsync("", content);
var result = JsonConvert.DeserializeObject<CXAccessToken>(response.Result.Content.ReadAsStringAsync().Result);
accessToken = result.access_token;
}
}
}

What is the right way to authenticate Azure Function against Azure DevOps REST API?

The goal is to develop an Azure function which should do some changes in Azure DevOps (like update work items, wiki pages etc), being triggered by Azure pipeline service hook.
Can I use function system identity in this case? And how can I give permissions for this identity to call DevOps REST APIs?
I'm not sure if this is the best way but you can create PAT token. Since you will use it for Azure Function I woudl recommend to use Azure KeyVault to store that token.
Here you have the example how you can use it to fetch projects from Azure DevOps:
public static async void GetProjects()
{
try
{
var personalaccesstoken = "PAT_FROM_WEBSITE";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(
System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", personalaccesstoken))));
using (HttpResponseMessage response = await client.GetAsync(
"https://dev.azure.com/{organization}/_apis/projects"))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Here you have documentation for updating work items. I tested that with Postman, but I was able to edit work item using PAT.

"ResourceContainerAccessDenied" returned as value of CloudTask.ExecutionInformation.FailureInformation.Code but not in TaskFailureInformationCodes

I have a .net core 3.0 application using the Microsoft.Azure.Batch 12.0.0 C# nuget package.
I create a job containing one task with a resource file like this (pseudo codeish):
var source = ResourceFile.FromStorageContainerUrl(settings.Input.Container.GetAccessUrl());
var cloudTask = new CloudTask(_taskId, commandline)
{
...
ResourceFiles = new[] { source, },
...
}
await _batchClient.JobOperations.AddTaskAsync("jobid", cloudTask,
cancellationToken: cancellationToken);
when i now request the status of the task
var cloudJob = await _batchClient.JobOperations.GetJobAsync("jobId", cancellationToken:
cancellationToken);
var cloudTask = cloudJob.ListTasks().SingleOrDefault();
var code = cloudTask.ExecutionInformation.FailureInformation,Code
code can be of value "ResourceContainerAccessDenied" if indeed we do not have access to the ResourceCondainer - "ResourceContainerAccessDenied" is not
a member of Microsoft.Azure.Batch.Common.TaskFailureInformationCodes and not documented anywhere as far as i can see.
Is this a bug in the Azure Batch C# SDK? Am i overlooking something? Where can i get a list of all possible code values?
The fact that this error code is not included in the C# SDK is indeed a bug.
I will be fixing this bug as part of an upcoming SDK release (ETA ~1 week).

How to get/identity the Azure DevOps Server base URL from the extension?

I’m developing an extension for both Azure DevOps Services and Server but I’m struggling to get the base URL for the Azure DevOps Server version once I have some navigations to the target resource, such as: Pull Request details.
Is there any way to get it? For instance:
Azure DevOps Services
dev.azure.com/organization
organization.visualstudio.com
Azure DevOps Server
serverName/{?}
serverName:8080/{tfs}/{?}
I am using this code:
Use document.referrer from the plugin as a URL source.
Locate the project name and extract the part of the URL before the project name.
const url = document.referrer;
// how to detect base Url: get projectName and find 'ProjectName/_'
// http://tfs2017-test:8080/tfs/Org/MyProject/_apps/hub/...
const projectService = await SDK.getService<IProjectPageService>(CommonServiceIds.ProjectPageService);
const project = await projectService.getProject();
if (!project) {
throw new Error("Cannot get project.")
}
const findStr = `${project.name}/_`;
const index = url.indexOf(findStr);
if (index < 0) {
throw new Error(`URL '${url}' does not contain '${findStr}' substring`);
}
// extract from url without '_'
this._baseUrl = url.substring(0, index + findStr.length - 1);
Edit 04.05.2021:
Because document.referrer is not working good for some browsers, I am using now more "DevOps way":
// https://github.com/microsoft/azure-devops-extension-sdk/issues/28
this._locationService = await SDK.getService<ILocationService>(CommonServiceIds.LocationService);
const hostBaseUrl = await this._locationService.getResourceAreaLocation(
CoreRestClient.RESOURCE_AREA_ID
);
console.log(`hostBaseUrl: ${hostBaseUrl}`);
const projectService = await SDK.getService<IProjectPageService>(CommonServiceIds.ProjectPageService);
const project = await projectService.getProject();
if (!project) {
throw new Error("Cannot get project.")
}
this._baseUrl = `${hostBaseUrl}${project.name}/`;
console.log(`baseUrl: ${this._baseUrl}`);

How do I create an AlertsClient from an Azure Active Directory secret? [duplicate]

My company is looking into reporting on Azure. We only want our customers to give us read only credentials for us to use. I did some research and it looks like Azure Active Directory does just that. So I'm looking to authenticate using a read only Azure Directory Application.
To get me started I was following this blog on using the Management API via Azure Active Directory.
https://msdn.microsoft.com/en-us/library/azure/dn722415.aspx
Aside from the approach show being very unfriendly, it doesn't work =(
I get this error after logging in as a global administrator:
"AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'."
Did some research and found this style of authentication was for native app and NOT web apps (despite what the blog post saying other wise..). So I made a tweak. My GetAuthorizationHeader now looks like this:
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
I am able to get the Access Token (yay). But now when I try to use this with the Azure Management library client I get this error:
"ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription."
I double checked my permissions in my application. It looked good. I tried giving full access to everything to see if that would have made a difference.
I double checked my tenantId, clientId, and subscriptionId, all looked good.
I made sure the subscription I'm using is pointed to the AD my application is in.
I tried making a new secret key.
My guess is this is the issue:
However in this UI I am unable to select any values for that property. I'm unsure if this is the result of a bug or an unfinished feature.
Am I missing something here?
Thanks
Here's my full code for reference:
class Program
{
static void Main(string[] args)
{
var token = GetAuthorizationHeader();
var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);
using (var computeClient = new ComputeManagementClient(credential))
{
var images = computeClient.VirtualMachineOSImages.List();
}
}
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
}
EDIT:
Progress has been made. As I discussed with Gaurav, I needed to ditch the Azure Management Library because as of right now it does not seem to support Azure Resource Manager (ARM) API! So instead I did raw web requests. And it works as intended. If I remove role access off my AD Application I get access denied. When I have it I get back data.
One thing I'm not sure about is making it so my application is auto-adding to new resources.
Also, Is there a way to list Resource Groups that are accessible for my AD Application?
New code:
class Program
{
static void Main(string[] args)
{
var token = GetAuthorizationHeader();
string subscriptionId = ConfigurationManager.AppSettings["subscriptionId"];
string resourceGroupName = ConfigurationManager.AppSettings["resourceGroupName"];
var uriListMachines = string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/virtualmachines?api-version=2015-05-01-preview", subscriptionId, resourceGroupName);
var t = WebRequest.Create(uriListMachines);
t.ContentType = "application/json";
t.Headers.Add("Authorization", "Bearer " + token);
var response = (HttpWebResponse)t.GetResponse();
string result = "";
using (var reader = new StreamReader(response.GetResponseStream()))
{
result = reader.ReadToEnd();
}
//Original Attempt:
//var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);
//using (var client = CloudContext.Clients.CreateComputeManagementClient(credential))
//{
// var images = client.VirtualMachineVMImages.List();
//}
}
private static string GetAuthorizationHeader()
{
AuthenticationResult result = null;
var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);
string clientId = ConfigurationManager.AppSettings["clientId"];
string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
var thread = new Thread(() =>
{
result = context.AcquireToken(
"https://management.core.windows.net/",
clientCred);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Name = "AquireTokenThread";
thread.Start();
thread.Join();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
}
EDIT EDIT:
I figured out my hung up. Resources created in the OLD portal will get it's own distinct resource group.
From what I can tell you can not add a resource made in the old portal existing resource group (boooo). Resources created in the new portal will be able to assign the resource to an existing group (aka one that gives a role access to my AD Application).
This is such a mess! But at least I know what is going on now.
I believe you're on the right track as to why you're running into this problem.
Here's what's happening:
Essentially permission to execute Service Management API is a delegated permission and not an application permission. In other words, the API is executed in context of the user for which the token is acquired. Now you are getting this token for your application (specified by client id/secret). However your application doesn't have access to your Azure Subscription because the user record created for this application in your Azure AD is of type Service Principal. Since this Service Principal doesn't have access to your Azure Subscription, you're getting this Forbidden Error (I must say that the error is misleading because you're not using certificate at all).
There are a few things you could do:
Switch to Azure Resource Manager (ARM) API - ARM API is the next generation of Service Management API (SM API) and Azure is moving towards this direction only. It exclusively works off of Azure AD token. If possible, make use of that to manage your Azure resources (though you need to keep in mind that as of today not all Azure resources can be managed through ARM API). They way you do it is take your Service Principal and assign it to a particular role using new Azure Portal. Please see this link for more details on this: https://azure.microsoft.com/en-in/documentation/articles/resource-group-create-service-principal-portal/.
Use X509 Certificate - You can always use X509 Certificate based authorization to authorize your SM API requests. Please see this link for more details on that: https://msdn.microsoft.com/en-us/library/azure/ee460782.aspx#bk_cert. The downside of this approach is that the application (or whosoever has access to this certificate) will get full access to your Azure Subscription and can do everything there (including deleting resources).
Acquire token for a user instead of an application - This is another approach you can take. Essentially ask your users to login into Azure AD through your console application and acquire token for that user. Again, please keep in mind that this user must be a Co-Admin in your Azure Subscription and will have full access to your Azure Subscription as with SM API there's no concept of Role-based access control.