I'm building an azure function that works as a approval checker for azure builds to be deployed, in the image below, you can see how it's being called for a pipeline that is trying to deploy.
What I need to do in C#, in some scenarios, is to CANCEL the pipeline that is calling my function to stop it from calling.
What I'm thinking to use is a PATCH method that Azure itself uses to cancel them, but I don't know how to call it with the proper headers and payload
This is what gets called when you hit the CANCEL button in a pipeline:
I'm building a request with a HTTPClient with this address, what else should I send as a param or header to make it work?
var _baseAddress = new Uri("https://dev.azure.com/myprojectname/");
var msg = new HttpRequestMessage(HttpMethod.Patch, new Uri(_baseAddress, $"{myProjectId}/_apis/build/builds/{buildRunId}"));
var serializedDoc = JsonConvert.SerializeObject(new { status = 4 });
msg.Content = new StringContent(serializedDoc, Encoding.UTF8);
msg.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
msg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json-patch+json");
var result = await _httpClient.SendAsync(msg);
I could not find any other similar question or implementation, so any answer is welcome
Related
I am trying to push a commit I made on my local repository to a remote counterpart, hosted on a private Azure DevOps server, using LibGit2Sharp programmatically.
As per the Azure documentation, the HTTPS OAuth enabled Personal Access Token needs to sent with the request in a custom Authentication header as 'Basic' with the Base64 encoded token:
var personalaccesstoken = "PATFROMWEB";
using (HttpClient client = new HttpClient()) {
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes($":{personalaccesstoken}")));
using (HttpResponseMessage response = client.GetAsync(
"https://dev.azure.com/{organization}/{project}/_apis/build/builds?api-version=5.0").Result) {
response.EnsureSuccessStatusCode();
}
}
The LibGit2Sharp.CloneOptions class has a FetchOptions field which in turn has a CustomHeaders array that can be used to inject the authentication header during the clone operation, like the following (as mentioned in this issue):
CloneOptions cloneOptions = new() {
CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials {
Username = $"{USERNAME}",
Password = $"{ACCESSTOKEN}"
},
FetchOptions = new FetchOptions {
CustomHeaders = new[] {
$"Authorization: Basic {encodedToken}"
}
}
};
Repository.Clone(AzureUrl, LocalDirectory, cloneOptions);
And the clone process succeeds (I tested it as well as checked the source code :) )
However, the LibGit2Sharp.PushOptions does not have any such mechanism to inject authentication headers. I am limited to the following code:
PushOptions pushOptions = new()
{
CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials
{
Username = $"{USERNAME}",
Password = $"{PASSWORD}"
}
};
This is making my push operation fail with the following message:
Too many redirects or authentication replays
I checked the source code for Repository.Network.Push() on Github.
public virtual void Push(Remote remote, IEnumerable<string> pushRefSpecs, PushOptions pushOptions)
{
Ensure.ArgumentNotNull(remote, "remote");
Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs");
// Return early if there is nothing to push.
if (!pushRefSpecs.Any())
{
return;
}
if (pushOptions == null)
{
pushOptions = new PushOptions();
}
// Load the remote.
using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true))
{
var callbacks = new RemoteCallbacks(pushOptions);
GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks();
Proxy.git_remote_push(remoteHandle,
pushRefSpecs,
new GitPushOptions()
{
PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism,
RemoteCallbacks = gitCallbacks,
ProxyOptions = new GitProxyOptions { Version = 1 },
});
}
}
As we can see above, the Proxy.git_remote_push method call inside the Push() method is passing a new GitPushOptions object, which indeed seems to have a CustomHeaders field implemented. But it is not exposed to a consumer application and is being instantiated in the library code directly!
It is an absolute necessity for me to use the LibGit2Sharp API, and our end-to-end testing needs to be done on Azure DevOps repositories, so this issue is blocking me from progressing further.
My questions are:
Is it possible to use some other way to authenticate a push operation on Azure from LibGit2Sharp? Can we leverage the PushOptions.CredentialsProvider handler so that it is compatible with the auth-n method that Azure insists on?
Can we cache the credentials by calling Commands.Fetch by injecting the header in a FetchOptions object before carrying out the Push command? I tried it but it fails with the same error.
To address the issue, is there a modification required on the library to make it compatible with Azure Repos? If yes, then I can step up and contribute if someone could give me pointers on how the binding to the native code is made :)
I will provide an answer to my own question as we have fixed the problem.
The solution to this is really simple; I just needed to remove the CredentialsProvider delegate from the PushOptions object, that is:
var pushOptions = new PushOptions();
instead of,
PushOptions pushOptions = new()
{
CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials
{
Username = $"{USERNAME}",
Password = $"{PASSWORD}"
}
};
¯\(ツ)/¯
I don't know why it works, but it does. (Maybe some folks from Azure can clarify it to us.)
It turns out that this works on windows (push options with no credentials provider). Perhaps because somewhere a native call the OS resolves the credentials using some other means. But in Linux / container environment, the issue persists.
"There was a problem pushing the repo: remote authentication required but no callback set"
I think as you mentioned, minimally the CustomHeaders implementation must be exposed for this to work.
Image of error on console
I have an API Gateway that uses IAM authorization. I have a C# application that I'm hoping to call the API with. I started with a GetMethodRequest but I don't see anyway to set the PathPart parameter.
var userId = _remoteCredentials.UserId;
var key = _remoteCredentials.Key;
var client = new AmazonAPIGatewayClient(userId, key, Amazon.RegionEndpoint.USEast2);
GetMethodRequest getMethodRequest = new GetMethodRequest();
getMethodRequest.HttpMethod = HttpMethod.Get.ToString();
getMethodRequest.ResourceId = "4abcde";
getMethodRequest.RestApiId = "aasfasdfs";
var task = Task.Run(async () => await client.GetMethodAsync(getMethodRequest).ConfigureAwait(false));
I was expecting something like the Test-AGInvokeMethod in the Powershell SDK which allows me to set the query string and the path.
$response = Test-AGInvokeMethod
-RestApiId aasfasdfs
-ResourceId 4abcde
-HttpMethod GET
-PathWithQueryString '/etl/upload_url'
Any help is greatly appreciated.
EDIT Below is something of a solution that I ended up using the AWS4RequestSigner is a library that I found on Github
var signer = new AWS4RequestSigner(userId, key);
var destinationUrl = string.Format("https://ad9vxabc123.execute-api.us-east-2.amazonaws.com/dev/etl/summary/latest?tms_id={0}&model_id={1}", _tmsId, _modelId);
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri(destinationUrl),
};
var signed = Task.Run(async () => await signer.Sign(request, "execute-api", "us-east-2").ConfigureAwait(false));
var signedResult = signed.Result;
The AmazonAPIGatewayClient is for managing your API Gateway e.g. adding new stages or deleting API keys.
You're looking to invoke a method on your API Gateway, like Test-AGInvokeMethod does.
To invoke your API gateway, you need to call the deployed API endpoint using a HTTP client.
.NET's in-built HttpClient is a good start.
I'm trying to create a conference call with a Moderator and several participants one of which is a bot.
The bot is controlled by the Moderator via a back channel so it can Say things etc to the conference call.
I setup the conference call when the Moderator calls in from a web client and then dial in the other participants and the bot using CallResource.CreateAsync with a callback url so I know which conference to add them to.
The bot needs to get a parameter so it knows which moderator to listen to for instructions.
However, I can't seem to pass any parameters to the bot (which is currently being triggered via another TwiML app) from the C# API using CallResource.CreateAsync.
Adding a participant to the call (callbackUrl adds the connected call to the conference) - this works fine:
var to = new PhoneNumber(callData.PhoneNumber);
var from = new PhoneNumber(_twilioSettings.PhoneNumber);
var callbackUrl = GetConnectConferenceUrl(callData.CallToken);
var url = new Uri(callbackUrl);
var participantCallResource = await CallResource.CreateAsync(to, from, url: url);
Adding the bot to call (Phone number is setup in Twilio as a TwiML app with a webhook back to my server) - how do I pass parameters to the TwiML app?
var toBot = new PhoneNumber(botNumber);
var fromBot = new PhoneNumber(_twilioSettings.PhoneNumber);
var botCallbackUrl = GetConnectConferenceUrl(callData.CallToken, isBot: true);
var botUrl = new Uri(botCallbackUrl);
var botCallResource = await CallResource.CreateAsync(toBot, fromBot, url: botUrl)
How do I pass parameters to a TwiML bin or Webhook or phonenumber from C#?
Do I need to add the bot in to the call a different way?
Parameters are passed either on the To or on the Url per https://www.twilio.com/docs/voice/how-share-information-between-your-applications
I believe your use case would put it on the Url. Assuming you have a moderatorId:
var botCallbackUrl = GetConnectConferenceUrl(callData.CallToken, isBot: true, moderatorId);
var botUrl = new Uri(botCallbackUrl);
where GetConnectConferenceUrl adds the parameter you need.
I've used it on the To line for a Client endpoint, so that looked like
var toBot = $"{new PhoneNumber(botNumber)}?moderatorId={moderatorId}";
I have a Teams bot that can send proactive messages to a user via webAPI. I can get a ConnectorClient from the Microsoft.Bot.Connector namespace, and then from there I can identify the relevant conversation, and call SendToConversationAsync to message a user.
If I want to use this to initiate a dialog though, the challenge seems to be that I don't have a TurnContext to reference. I found a post here that seemed promising, but that still depends on having a dialog turn running. Ideally, I'd like to be able to do this via the ConnectorClient reference that I already have, and trying with a null TurnContext doesn't seem to work. For example, trying this:
var dialogState = _accessors.ConversationState.CreateProperty<DialogState>(nameof(DialogState));
var dialogSet = new DialogSet(dialogState);
dialogSet.Add(new MyDialog());
DialogContext dc = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var turnResult = await dc.BeginDialogAsync("MyDialog");
Throws an exception if turnContext is null.
This answer here has some promising ideas too, suggesting faking an incoming event, but I can do something like CreateInvokeActivity(), but sending that to the conversation throws an exception. I'm also not sure how to trigger the pipeline to get the message through in the same process without going as far up as using an HTTPCLient to POST the raw message (which requires getting a token I believe). The bot already has a 1:1 conversation with the user, but I'd like to have this initiate a dialog if possible. Is there a way to have the ConnectorClient begin a dialog proactively, or trigger an invoke to the bot pipeline programmatically to allow it to kick off there?
I managed to figure out a way to do this, but it's probably not an ideal scenario. I wanted to start a dialog from the API, specifically an authentication dialog that gets a user's OAuth token for accessing graph. If the user is signed in, the token is returned immediately, and if not, they get a sign in prompt. I have something like this in my bot code (edited for brevity):
public static async Task<string> GetTokenAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
var dialogState = _accessors.ConversationState.CreateProperty<DialogState>(nameof(DialogState));
var dialogSet = new DialogSet(dialogState);
dialogSet.Add(new AuthDialog());
DialogContext dc = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var turnResult = await dc.BeginDialogAsync("AuthDialog");
await _accessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
if(turnResult.Status== DialogTurnStatus.Waiting)
{
_log.Debug("Got login request for user-waiting for response");
return string.Empty;
}
else if(turnResult.Result is TokenResponse)
{
return ((TokenResponse)turnResult.Result).Token;
}
return null;
}
This creates the dialog and, if possible, returns the token. In my webAPI, I have something like this to invoke it proactively:
string conversationID = "CONV_ID_FROM_STATE";
var members = await m_client.Conversations.GetConversationMembersAsync(conversationID);
BotFrameworkAdapter b = new BotFrameworkAdapter(new SimpleCredentialProvider("BOT ID", "BOT_SECRET"));
var message = Activity.CreateMessageActivity();
message.Text = "login";
message.From = new ChannelAccount(members[0].Id);
message.Conversation = new ConversationAccount(id: conversationID, conversationType: "personal", tenantId: :BOT_TENANT_ID);
message.ChannelId = "msteams";
TurnContext t = new TurnContext(b, (Activity)message);
ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim("aud", "BOT_ID"));
t.TurnState.Add("BotIdentity", id);
t.TurnState.Add("Microsoft.Bot.Builder.BotAdapter.OAuthScope", "https://api.botframework.com");
t.TurnState.Add("Microsoft.Bot.Connector.IConnectorClient", m_client);
string token = await myBot<AuthDialog>.GetTokenAsync(t, default);
At this point, if the token is an empty string, the user hasn't signed in, but otherwise it should be a valid token to make graph calls with. I've tested this with a few new accounts, and it seems to work, so I'm calling that a win for now. If there's something that's fundamentally busted here though, please comment.
You can create a turn context from a conversion reference using ContinueConversationAsync. Please refer to the docs and the sample for more information.
I want to test my Web API service using in-memory HttpServer.
The current setup looks the following:
var httpConfig = CreateTestHttpConfiguration();
var server = new HttpServer(httpConfig);
var handler = new WebRequestHandler();
var cert = new X509Certificate2("filename", "password");
handler.ClientCertificates.Add(cert);
var client = HttpClientFactory.Create(handler, server);
I can make requests to the server using these client and everything works except that certificate is not added to the request.
As I understand that happens since server executes before handler (I can't rearrange them since they implement different interfaces) and since server immediately responses handler is not even executed (I've tested this assumption using HttpClientHandler subclass instead of handler).
So my question is: How can I add the client certificate for in-memory testing?
This approach will do it:
var server = new HttpServer(configuration);
var invoker = new HttpMessageInvoker(server);
var certificate = GetCertificate();
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/YourPath");
request.Properties[HttpPropertyKeys.ClientCertificateKey] = certificate;
var result = await invoker.SendAsync(request, CancellationToken.None);