We're trying to automate some integrated tests. So we'd like to be able to programatically call the UI controllers in order to be as clause as what the reel users do. We don't want to use a UI test suite for different reasons.
Problem is that we are using SSO Windows authentication with the WS-Federation security with WIF. In configuration, we use passiveRedirectEnabled="true" so that every time the session cookie is absent, invalid or expired, the page gets redirected to the AD FS STS endpoint ("/adfs/ls/"). The result is again redirected back to page specify in the "reply" attribute in the Web.config file.
When I look in Fiddler, I clearly see the second redirect (coming back from the AD FS STS) with a 302 status returns a "Set-Cookie : FedAuth=77u/PD94bWwg..." instruction to the browser. The the call is made to the reply page with the FedAuth cookie and everything is OK from there.
Is there a way to emulate this behavior and be able to call the UI controller with the correct FedAuth cookie ? No SharePoint please, this has nothing to do with it.
I was finally able to reproduce the steps from what I saw in Fiddler to mimic the browser. I'll let the code here, hoping it can help some of you along the way. It's not very clean, it's more in a POC mode but it still can help. Note that on some requests I had to allow the automatic redirection an some others I had to prevent it.
Credits to my colleague Dominique Pothier who helped me a lot on that one.
//First request to the secured site
var request =
(HttpWebRequest)WebRequest.Create("https://mysite.mycompany.ca/");
request.Method = "GET";
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.AllowAutoRedirect = false;
var httpResponse = (HttpWebResponse)request.GetResponse();
//Redirects to the STS based on the response from the first call, posting the ws-federations infos along
request =
(HttpWebRequest)WebRequest.Create(httpResponse.Headers["Location"]);
request.UseDefaultCredentials = true;
request.PreAuthenticate = true;
request.Host = "sts.mycompany.ca";
request.AllowAutoRedirect = true;
request.UserAgent =
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36";
request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
var nameValueCollection = new NameValueCollection { { "Cache-Control", "max-age=0" } };
request.Headers.Add(nameValueCollection);
nameValueCollection = new NameValueCollection { { "Upgrade-Insecure-Requests", "1" } };
request.Headers.Add(nameValueCollection);
nameValueCollection = new NameValueCollection { { "Accept-Encoding", "gzip, deflate, sdch" } };
request.Headers.Add(nameValueCollection);
nameValueCollection = new NameValueCollection { { "Accept-Language", "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4" } };
request.Headers.Add(nameValueCollection);
httpResponse = (HttpWebResponse)request.GetResponse();
//Parse the response to get ws-federation infos
var responseStream = new StreamReader(httpResponse.GetResponseStream());
var responseData = responseStream.ReadToEnd();
var xmlReader = XmlReader.Create(new StringReader(responseData));
var wa = "";
var wresult = "";
var wctx = "";
while (xmlReader.Read())
{
if (xmlReader.GetAttribute("name") == "wa")
wa = xmlReader.GetAttribute("value");
if (xmlReader.GetAttribute("name") == "wresult")
wresult = xmlReader.GetAttribute("value");
if (xmlReader.GetAttribute("name") == "wctx")
wctx = xmlReader.GetAttribute("value");
}
httpResponse.Close();
//Redirects to the controller method we want to hit
request =
(HttpWebRequest)WebRequest.Create("https://mysite.mycompany.ca/Home/GetStates");
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.AllowAutoRedirect = false;
//Add the cookie container to the response so that we can get the FedAuth cookie after the response
request.CookieContainer = new CookieContainer();
//Add the ws-federation infos from the last http request to the body of the new request
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{
if (wa != null)
{
var waEncoded = HttpUtility.UrlEncode(wa);
var wresultEncoded = HttpUtility.UrlEncode(wresult);
var wctxEncoded = HttpUtility.UrlEncode(wctx);
var urlEncoded = "wa=" + waEncoded + "&wresult=" + wresultEncoded + "&wctx=" + wctxEncoded;
streamWriter.Write(urlEncoded);
streamWriter.Flush();
streamWriter.Close();
}
}
request.Referer = httpResponse.ResponseUri.OriginalString;
httpResponse = (HttpWebResponse)request.GetResponse();
var cookieContainer = request.CookieContainer;
//Use the FedAuth cookie that we got from last http call and add it to a new request to the controller and voila !
request =
(HttpWebRequest)WebRequest.Create("https://mysite.mycompany.ca/Home/GetStates");
request.Method = "GET";
nameValueCollection = new NameValueCollection { { "X-Requested-With", "XMLHttpRequest" } };
request.Headers.Add(nameValueCollection);
//Add the FedAuthCookie from last request
request.CookieContainer = cookieContainer;
request.Referer = "https://proacces-dev1.universitas.ca/";
httpResponse = (HttpWebResponse)request.GetResponse();
responseStream = new StreamReader(httpResponse.GetResponseStream());
responseData = responseStream.ReadToEnd();
Console.WriteLine(responseData);
Console.ReadLine();
Related
I have tried the method outlined in the Microsoft docs
which involves creating an app in Active Directory and then having code something very similar to:
var authContextUrl = "https://login.windows.net/common/oauth2/authorize";
var authenticationContext = new AuthenticationContext(authContextUrl);
var redirectUri = "https://dev.powerbi.com/Apps/SignInRedirect";
var pp = new PlatformParameters(PromptBehavior.Auto);
var result = authenticationContext.AcquireTokenAsync(PowerBiApiResource, clientId, new Uri(redirectUri), pp).GetAwaiter().GetResult();
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the PowerBI API token");
}
var token = result.AccessToken;
return token;
I got this code working but it always insisted on prompting for a username and password, which is a problem for a function app.
I have also tried the approach in the silent function specified here
https://community.powerbi.com/t5/Developer/Data-Refresh-by-using-API-Need-Steps/m-p/209371#M6614
static string getAccessTokenSilently()
{
HttpWebRequest request = System.Net.HttpWebRequest.CreateHttp("https://login.windows.net/common/oauth2/token");
//POST web request to create a datasource.
request.KeepAlive = true;
request.Method = "POST";
request.ContentLength = 0;
request.ContentType = "application/x-www-form-urlencoded";
//Add token to the request header
request.Headers.Add("Authorization", String.Format("Bearer {0}", token));
NameValueCollection parsedQueryString = HttpUtility.ParseQueryString(String.Empty);
parsedQueryString.Add("client_id", clientID);
parsedQueryString.Add("grant_type", "password");
parsedQueryString.Add("resource", resourceUri);
parsedQueryString.Add("username", username);
parsedQueryString.Add("password", password);
string postdata = parsedQueryString.ToString();
//POST web request
byte[] dataByteArray = System.Text.Encoding.ASCII.GetBytes(postdata); ;
request.ContentLength = dataByteArray.Length;
//Write JSON byte[] into a Stream
using (Stream writer = request.GetRequestStream())
{
writer.Write(dataByteArray, 0, dataByteArray.Length);
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
dynamic responseJson = JsonConvert.DeserializeObject<dynamic>(responseString);
return responseJson["access_token"];
}
}
This code doesn't work.
Also this has issues, although I haven't tried it:
https://learn.microsoft.com/en-us/power-bi/developer/get-azuread-access-token
There doesn't appear to be anything up to date available that works that explains how to do this. Does anyone know how?
This is the best I've got so far. I have to create the application in AD using https://dev.powerbi.com/apps and then login using a powerbi pro userid and password, using the following code:
public static string GetPowerBiAccessToken(string tenantId, string clientId, string userId, string password)
{
var url = $"https://login.windows.net/{tenantId}/oauth2/token";
var request = (HttpWebRequest)WebRequest.Create(url);
request.KeepAlive = true;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
var dataToPost = new Dictionary<string,string>
{
{"client_id", clientId},
{"grant_type", "password"},
{"resource", PowerBiApiResource},
{"username", userId},
{"password", password},
{"redirect_uri", "https://dev.powerbi.com/Apps/SignInRedirect" }
};
var postData = string.Empty;
foreach (var item in dataToPost)
{
if (!string.IsNullOrEmpty(postData))
postData += "&";
postData += $"{item.Key}={item.Value}";
}
var dataByteArray = System.Text.Encoding.ASCII.GetBytes(postData);
request.ContentLength = dataByteArray.Length;
using (var writer = request.GetRequestStream())
{
writer.Write(dataByteArray, 0, dataByteArray.Length);
}
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
var responseJson = JsonConvert.DeserializeObject<dynamic>(responseString);
return responseJson["access_token"];
}
(P.S. I appreciate the request part of this code could be much better but having spent over 2 days trying to work this out, I'm just glad to have something that is working right now. Also the redirectUrl matches the one in my app, I'm not sure if its essential or not).
In the event of errors, I used Fiddler 4 to tell me exactly what the error was rather than just getting 400 or 401 in the app.
After successful profile installation, i am unable to get device info from agent-check.jag. It onl returns username{}. Instead it should ideally pass the deviceid, access token, refresh token and client credentials.
var deviceCheckURL = mdmProps["iOSAPIRoot"] + "devices/udid";
var challengeToken = session.get("iOSChallengeToken");
var payload = {"challengeToken" : challengeToken};
serviceInvokers.XMLHttp.post(deviceCheckURL, stringify(payload), function (restAPIResponse) {
var status = restAPIResponse["status"];
if (status == 200) {
var responseContent = parse(restAPIResponse.responseText);
response["status"] = 200;
response["content"] = responseContent; # Here response is just username{}.
} else {
response["status"] = 200;
response["content"] = {"deviceID" : null};
}
});
Not sure why it is happening? Any issues related to roles?
I am trying to set a cookie in ESRI Arcgis online using ESRI runtime SDK for .net v100.
var cookie = new CookieHeaderValue("customCookie", cred.Token);
var response = Request.CreateResponse(HttpStatusCode.OK, new {
token = cred.Token,
expires = cred.ExpirationDate
});
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return response;
Now when I try to retrieve that cookie later on in subsequent requests using below I get null.
CookieHeaderValue cookie = context.Request.Headers.GetCookies("customCookie").FirstOrDefault();
I am wondering if there is another way to get the cookie which I set back?
Are you using v100?
If yes, you can try the following code:
ArcGISHttpClientHandler.HttpRequestBegin += (sender, request) =>
{
var cookieContainer = ((System.Net.Http.HttpClientHandler)sender).CookieContainer;
var cookies = cookieContainer.GetCookies(request.RequestUri);
var customCookie = new Cookie("customCookie", "someValue") { Domain = request.RequestUri.Host };
bool foundCookie = false;
foreach (Cookie cookie in cookies)
{
if (cookie.Name == customCookie.Name)
{
foundCookie = true;
break;
}
}
if (!foundCookie)
cookieContainer.Add(customCookie);
};
ArcGISHttpClientHandler has an event HttpRequestBegin which is invoked on every request. You can use CookieContainer.GetCookies and Add to retrieve/add cookies.
I send a HTTP Get Request with a Basic Authentification to the Login-Endpoint of the Host:
request = new HttpRequestMessage();
// Configuration Item: Login URL Suffix
request.RequestUri = new Uri(string.Format("https://{0}/{1}", Host, loginSuffix));
request.Method = Windows.Web.Http.HttpMethod.Get;
var info = User + ":" + Password;
var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(info));
request.Headers.Authorization = new HttpCredentialsHeaderValue("Basic", token);
_httpClient = CreateHttpClient(ref cookieManager);
response = await _httpClient.SendRequestAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
The HttpClient is created with a Filter, to set Cookies later:
private HttpClient CreateHttpClient(ref HttpCookieManager _cookieManager)
{
HttpBaseProtocolFilter _filter = new HttpBaseProtocolFilter();
HttpClient _httpClient = new Windows.Web.Http.HttpClient(_filter);
_cookieManager = _filter.CookieManager;
return _httpClient;
}
From the Response the SET-COOKIE Header can be read.
string[] Queries;
response.Headers.TryGetValue("Set-Cookie", out tmpString);
if (tmpString != null)
Queries = tmpString.Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
I´m looking for a Cookie with a defined Name (CookieKeyName), which will be set in the next Request.
foreach (var query in Queries)
{
if (query.Contains(CookieKeyName))
{
staticCookieKey = query.Substring(0, query.IndexOf("="));
staticCookieValue = query.Substring(query.IndexOf("=") + 1);
}
}
I would expect, that the HttpClient will use the received Set-Cookie in the response for this URL as Cookie in every following Request automatically.
I´m preparing the next Request and setting the Cookie by myself:
request.RequestUri = new Uri(string.Format("https://{0}/qcbin", Host));
request.Method = Windows.Web.Http.HttpMethod.Get;
HttpCookie _cookie = new Windows.Web.Http.HttpCookie(staticCookieKey, Host, "/");
_cookie.Value = staticCookieValue;
bool replaced = cookieManager.SetCookie(_cookie);
The following Sending of the Requests provides to a Web Exception 401, because the Server expects for this URL the previously in the Response received Cookie.
response = await _httpClient.SendRequestAsync(request, HttpCompletionOption.ResponseContentRead);
response.EnsureSuccessStatusCode();
responseBodyAsText = await response.Content.ReadAsStringAsync();
Looking with Fiddler on the Line, the second Request contains no Cookie Header. Even Setting of the Cookie in CookieManager nor the proceeding of the Set-Cookie i the first Response by the HttpClient is working.
Hint: The length of the value of the Cookies is about 6000 chars (coming from a IBM Data Power).
Thank you in advance for help.
I'm using the WebRequest object to post data to a login page, then post data to a seperate page on the same site. I am instantiating a CookieContainer and assigning it to the WebRequest object so that the cookies are handled. The problem is that I do not want to retain the cookie after I post data to the other page. How can I delete that cookie?
private CookieContainer cookie_m;
protected CookieContainer CookieContainer
{
get
{
if (cookie_m == null)
{
cookie_m = new CookieContainer();
}
return cookie_m;
}
set
{
cookie_m = value;
}
}
protected virtual void SetData(WebRequest request, string sData)
{
if (!String.IsNullOrEmpty(sData))
{
byte[] binPostData = System.Text.Encoding.ASCII.GetBytes(sData);
request.ContentLength = binPostData.Length;
System.IO.Stream sRequest = request.GetRequestStream();
try
{
sRequest.Write(binPostData, 0, binPostData.Length);
}
finally
{
sRequest.Close();
}
}
}
private HttpWebRequest GetNewRequest(string sUrl)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(sUrl);
request.CookieContainer = this.CookieContainer;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
return request;
}
public override void Submit()
{
//Login
HttpWebRequest request = GetNewRequest("http://mytest/login.asp");
base.SetData(request, "action=validate_login&login=test&password=test");
WebResponse response = request.GetResponse();
System.IO.StreamReader sr = new System.IO.StreamReader(response.GetResponseStream());
string sResponse = sr.ReadToEnd();
//Entry screen
request = GetNewRequest("http://mytest/CustCreate.asp");
base.SetData(request, "Site=xyz&Cust=test");
response = request.GetResponse();
sr = new System.IO.StreamReader(response.GetResponseStream());
sResponse = sr.ReadToEnd();
//Sutmit
request = request = GetNewRequest("http://mytest/CustCreate.asp");
base.SetData(request, "Site=xyz&mydatahere&B1=Submit");
response = request.GetResponse();
sr = new System.IO.StreamReader(response.GetResponseStream());
sResponse = sr.ReadToEnd();
//How to delete cookies that have been saved?
}
To delete a cookie, you need to set the expiration date on it to a date in the past. This tells the browser it's expired and the browser will delete it.
Here's an example from msdn on how to do this in C# (not sure which language you're using).
if (Request.Cookies["UserSettings"] != null)
{
HttpCookie myCookie = new HttpCookie("UserSettings");
myCookie.Expires = DateTime.Now.AddDays(-1d);
Response.Cookies.Add(myCookie);
}