I'm building some function apps in C# (via REST API) to make refreshes of tabular cube located on an azure ssas server. So far, no problem. However, I can't find a way to pause/start the ssas server (I saw some doc in powershell but I'd like to stay in C# so as not to mix languages)
Has anyone ever created anything like this?
I tried to make a POST suspend but no solution for now.
See the ResumeAzureAS() method here:
protected async Task<bool> ResumeAzureAS()
{
HttpClient client = new HttpClient();
var apiURI = new Uri(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.AnalysisServices/servers/{2}/resume?api-version=2016-05-16", subscriptionID, resourcegroup, server));
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.PostAsync(apiURI.ToString(), null);
response.EnsureSuccessStatusCode();
return true;
}
The rest of the API calls (such as suspend) are documented here.
private async Task<string> AASAcquireToken()
{
// Get auth token and add the access token to the authorization header of the request.
string authority = "https://login.windows.net/" + tenant + "/oauth/authorize";
AuthenticationContext ac = new AuthenticationContext(authority);
ClientCredential cred = new ClientCredential(clientID, keyID);
AuthenticationResult ar = await ac.AcquireTokenAsync(audience, cred);
return ar.AccessToken;
}
With audience set as "https://management.azure.com"
and for the "pause" itself :
I use as servername the complete name mention in the portal azure as "asazure://northeurope.asazure.windows...."
For the version of the api , well I don't know where to find it so I use one I found on the net.
var apiURI = new Uri(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.AnalysisServices/servers/{2}/suspend?api-version=2016-05-16", subscription, ressourceID, servername));
audience = "https://management.azure.com";
myClient.BaseAddress = new Uri(location);
myClient.DefaultRequestHeaders.Accept.Clear();
myClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
myClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await AASAcquireToken());
HttpResponseMessage response = await myClient.PostAsync(apiURI.ToString(), null);
var output = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
The right audience was :
audience = "https://management.core.windows.net/";
Related
I am attempting to wire up source control with continuous integration to an Azure Web App programmatically via C#. Historically I've used Azure PowerShell to do the same.
On the C# side, I am using the Microsoft.Azure.Management.Fluent libraries. The code to wire up source control is pretty straight forward:
await webApp.Update().DefineSourceControl().WithContinuouslyIntegratedGitHubRepository(GIT_URL).WithBranch(GIT_BRANCH).WithGitHubAccessToken(GIT_TOKEN).Attach().ApplyAsync();
This code runs for about 5 minutes and then returns the error:
{"Code":"BadRequest","Message":"Parameter x-ms-client-principal-name
is null or empty."}
I initially interpreted this to mean the Fluent libraries weren't passing a necessary value to the API, so I attempted to hit the API directly using Fluent libraries to abstract the authorization piece:
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal( CLIENT_ID, CLIENT_SECRET, TENANT_ID, AzureEnvironment.AzureGlobalCloud);
var client = RestClient.Configure().WithEnvironment(AzureEnvironment.AzureGlobalCloud).WithCredentials(credentials).Build();
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(HttpMethod.Get, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourcegroups/{RESOURCE_GROUP}?api-version=2019-10-01");
request.Headers.Add("x-ms-client-principal-name", USERNAME);
client.Credentials.ProcessHttpRequestAsync(request, cancellationToken).GetAwaiter().GetResult();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Several variations of this resulted in the same error referencing x-ms-client-principal-name. This lead me to believe the problem was with the token being used and the associated permissions. To test this, I ran the PowerShell script mentioned above, watched it run through Fiddler, and grabbed the token it used to complete. Using that token with basically the same code, it worked fine:
var token = "<THE TOKEN I COPIED FROM FIDDLER>";
CancellationToken cancellationToken = new CancellationToken();
var request = new HttpRequestMessage(requestType, $"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}/resourceGroups/{RESOURCE_GROUP}/providers/Microsoft.Web/sites/{WEB_APP}/sourcecontrols/web?api-version=2015-08-01");
request.Headers.Add("Authorization", $"Bearer {token}");
if ((requestType == HttpMethod.Put || requestType == HttpMethod.Post) && !string.IsNullOrEmpty(postData))
{
request.Content = new StringContent(postData, Encoding.UTF8, "application/json");
}
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
So now, it seems to just be a matter of getting the access token myself. This is where things have become difficult. If I obtain the token as the service principal, I end up with the same x-ms-client-principal-name that I started with:
public static string GetAuthToken(string tenantId, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
When I attempt to obtain token using my username and password, I get an error back telling me:
AADSTS90002: Tenant '' not found. This may happen if there are no
active subscriptions for the tenant. Check to make sure you have the
correct tenant ID. Check with your subscription administrator.
Here's the code I use for that:
public static string GetAuthToken(string tenantId, string username, string password, string clientId, string clientSecret)
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://login.microsoftonline.com/{tenantId}/oauth2/token");
request.Content = new StringContent($"grant_type=password&username={username}&password={password}&client_id={clientId}&client_secret={clientSecret}&resource=https://management.azure.com", Encoding.UTF8, "application/x-www-form-urlencoded");
CancellationToken cancellationToken = new CancellationToken();
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).GetAwaiter().GetResult();
var result = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var auth = JsonConvert.DeserializeObject<AuthResponse>(result);
return auth.AccessToken;
}
I cannot have the C# code invoke the PS script as a work-around as it requires Azure PowerShell which is not guaranteed to be on the machine running the code (Azure Web App) and cannot be installed due to Admin restrictions.
I need to be able to obtain an access token that also has permissions for azure dev ops (formally VSTS) so I can bind/wire-up source control for continuous integration. Any guidance that can help me get past this is much appreciated.
I was finally able to get it working. First, I had to create a new Azure AD User and grant it the necessary permissions; this would not work with my regular Azure Login. With the new user and permissions, I am able to successfully obtain token like so:
public static async Task<string> GetAccessToken(string clientId, string userName, string password)
{
AuthenticationContext authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/<TENANT_ID>");
var resourceId = "https://management.azure.com";
var result = await authenticationContext.AcquireTokenAsync(resourceId, clientId, new UserPasswordCredential(userName, password));
return result.AccessToken;
}
This token then works for wiring up source control and continuous integration.
I have some limited skills in c++ and have recently moved in C# (asp.net) and azure Web services. As a PoC I'm trying to make REST calls into PayPal (which I'll need to be using professionally in 3 -6 months).
I've set up my personal PayPal account using the instructions here and I get a bearer token back using curl as described in the link. Awesome.
I'm now trying to do this from .NET Core C# and all I get is a 401 error. I've examined the request and it seems the same as the curl in terms of headers; the base64 encoded credentials I think I'm adding are the same as the ones in the verbose curl log (I examined the two base64 strings by eye) so it must be something I'm doing (or not doing) in the set up of the call. I'm looking for suggestions, pointers, or flat out laughter at the obvious mistake I've made.
I've set up what I believe to be a named client thus:
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("PayPal", c =>
{
c.BaseAddress = new Uri("https://api.sandbox.paypal.com/v1/");
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Accept-Language", "en_US");
});
(with all the other stuff that comes free with VS under it omitted for brevity).
I attempt the call thus:
string clientCredString = CLIENTID + ":" + SECRET;
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
var client = _clientFactory.CreateClient("PayPal");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(clientCreds));
var messageBody = new Dictionary<string,string > ();
messageBody.Add("grant_type", "client_credientials");
var request = new HttpRequestMessage(HttpMethod.Get, "oauth2/token")
{
Content = new FormUrlEncodedContent(messageBody)
};
string token;
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<string>(json);
}
else
{
throw new ApplicationException("Well that failed");
}
and get a 401 code for my trouble.
Suggestions for troubleshooting, better methods of doing this and laughter at my foolishness all welcomed.
Update:
I read the documentation, a couple of items stand out to me:
Requires a verb of post.
Uses FormUrlEncodedContent for client credentials.
Basic auth requires username and password (Client Id & Secret)
I believe the syntax should be:
var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "...");
request.Content = new Dictionary<string, string>() { "grant_type", "client_credentials" };
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", $"{Encoding.UTF8.GetBytes($"{id}:{secret}")}");
HttpResponseMEssage = response = await client.PostAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
For the benefit of future readers:
It was, as suggested, an encoding problem. The line:
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
needed to be
var clientCreds = System.Text.Encoding.ASCII.GetBytes(clientCredString);
It should also be noted that this particular operation requires a POST not a GET as I was using, but once I started sending properly encoded requests the errors started to make a lot more sense.
I have a WebAPI that sends BASIC authorization information as following.
var client = new HttlpClient();
client.BaseAddress = new Uri(GlobalConstants.LdapUri);
var contentType = new MediaTypeWithQualityHeaderValue("application/json");
client.DefaultRequestHeaders.Accept.Add(contentType);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password))));
Task<HttpResponseMessage> results = client.GetAsync(GlobalConstants.FortressAPIUriDev);
var response = await results;
I've built this API using MVC Core 1.x and the receiving API is built using MVC5.
The problem is that this GetAsync sends two requests at the same time, and I have no clue how to resolve this. I've done some Googling myself to see if I can find a fix for this but so far no luck. Did anyone experience this problem and know how to resolve it?
Thank you very much in advance.
Long story short, found a solution as follows:
using (var client = new HttpClient())
{
var requestMessage = new HttpRequestMessage(HttpMethod.Get, GlobalConstants.LdapUri + GlobalConstants.FortressAPIUriDev);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, password))));
var response = await client.SendAsync(requestMessage);
}
After replacing with this code, it is sending one request at a time.
Found a hint at :
Adding headers when using httpClient.GetAsync
I am trying to make a httprequest to a web that is authenticated with ADFS of a private company.
I am able to login and get the token of my App. I am sure I am doing it correct due to I can get the contacts of my O365.After getting the token I try to make a request to the web. As I already have the token, I try to include it in the header of the request. The answer that I receive from the web is always the html with the login web not the result that I am requesting. As additional information I have added a "Connected Service" O365 API from VisualStudio.
This is my code:
public static async Task<string> GetAnswer(string wwweb)
{
var token = await GetAccessToken();
using (var client = new HttpClient())
{
var url = wwweb;
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
//client.DefaultRequestHeaders.ProxyAuthorization= new AuthenticationHeaderValue("Bearer", token);
// client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetStringAsync(url);
return response;
}
}
I have tried with the 3 lines ( 2 commented and one not) without success, always giving back company's login web. Am I doing something wrong?
Thanks
I configured a Concur Sandbox and played around with the api. Since the User api is not supported by the .Net SDK I wrote following code.
Issue is that code for Expenses work (returns 200 with valida result), but code for users returns 401 Unauthorized.
Expenses
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", oauthAccessToken);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = "https://www.concursolutions.com/api/v3.0/expense/receiptimages";
var respone = await httpClient.GetAsync(requestUri);
if (respone.IsSuccessStatusCode)
{
var result = respone.Content.ReadAsStringAsync();
//throw new InvalidUriException(string.Format("Invalid uri: {0}", requestUri));
}
}
Ideally this should also work as the previous code works,
Users
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("OAuth", oauthAccessToken);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = "http://www.concursolutions.com/api/v3.0/common/users?user=user1%40company.net";
var respone = await httpClient.GetAsync(requestUri);
if (!respone.IsSuccessStatusCode)
{
//throw new InvalidUriException(string.Format("Invalid uri: {0}", requestUri));
}
}
}
I thought it's a permission issue, but I given allowed enough permission.
Here is the Administration->Webservice app configuration,
Issue is that the User Api call needs to be https.