Blazor with ODataClient - Location Header is missing - .net-core

I'm creating client side Blazor app with Microsoft.OData.Client. When I create new entity like this:
var dataServiceContext = this.ClientFactory.CreateClient<Container>(new Uri("http://localhost:5000/odata"));
var newAsset = new CreateAssetDto()
{
TechnicalName = "from_client_4",
DisplayNameFormat = "format from client",
Icon = "client/icon",
InheritedFrom = Guid.NewGuid(),
IsActive = true,
Translation = new AssetTranslationDto
{
Title = "Client Asset",
Language = "en",
Description = "This is asset from client"
}
};
dataServiceContext.AddToAssets(newAsset);
await dataServiceContext.SaveChangesAsync();
I get an exception stating that response to this POST request is missing Location header. When I run fiddle to see what's going on I can see that it actually made 2 requests.
The first request is POST but doesn't include the body and recieves 204 response.
The second request is the one that actually contains the data creating new Asset and response contains Location header as it should.
I guess OData Client is complaining about Location header missing in the response for the first request (since response for second request does contain the header). But why is it even making the first request?
Any idea how to deal with this problem?

It's possible that the first request is a preflight request sent by the browser. But normally CORS preflight requests are sent using OPTIONS method, not POST. So this case is curious.
I am a contributor to the project but do not have enough reputation to add comments here to get clarifications. Could you create an issue on https://github.com/OData/odata.net ?

Related

how to change dotnet core outgoing http request hostname from the default localhost

I am able to successfully send requests to a sandbox via postman, given by a provider following their specs (see images below)
Successful request (see below)
In order to do that, aside from the respective headers and parameters (see image 2) I have to add a ssl/Tls certificate (.pfx) given that the server requires a 2 way handshake so it needs SSl client certificate:
Authorization (see below).
Headers (see below)
Body (see below)
Now, I am trying to do ir programatically using dotnet core 6, but I keep running into the same problem:
And here is my code:
public static string GetAccessToken(IConfiguration _config)
{
string UserName = Environment.GetEnvironmentVariable("USER_NAME");
string Password = Environment.GetEnvironmentVariable("PASSWORD");
var client = new RestClient("https://connect2.xyz.com/auth/token");
var request = new RestRequest();
X509Certificate2 FullChainCertificate = new X509Certificate2("Path/to/Cert/cert.pfx", "test");
client.Options.ClientCertificates = new X509CertificateCollection() { FullChainCertificate };
client.Options.Proxy = new WebProxy("connect2.xyz.com");
var restrequest = new RestRequest();
restrequest.Method = Method.Get;
restrequest.AddHeader("Accept", "*/*");
restrequest.AddHeader("Cache-Control", "no-cache");
restrequest.AddHeader("Content-Type", "application/x-www-form-urlencoded");
restrequest.AddHeader("Authorization", "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes($"{UserName}:{Password}")));
restrequest.AddParameter("grant_type", "client_credentials");
RestResponse response = client.Execute(restrequest);
AccessTokenPointClickCare accessToken = JsonConvert.DeserializeObject<AccessTokenPointClickCare>(response.Content);
string strToken = accessToken.access_token;
return strToken;
}
Now, as the error seems to show, it has to do with the certificates (apparently), but I don't know if something in the code is wrong, or if I'm missing something, etc...
It is worth noting that this code did run in someone else's pc with the same set-up, but of course with that person's own pfx, but for the rest, it is essentially the same, and not to mention that it does work on my postman.
Finally, as the title on this question, the only thing I can think it might also be affecting the request is the host. If I reference the postman, there is a field where I have to place the host name of the server https://connect2.xyz.com/auth/token
So made it work by changing to a new Windows 10. Researching in other Stackoverflow threads found the answer: .NET CORE 5 '''HandshakeFailure'" when making HTTPS request
So I conclude it has to do with the cyphers

'Access-Control-Allow-Origin' missing using actix-web

Stuck on this problem where I received this error everytime making POST request to my actix-web server.
CORS header 'Access-Control-Allow-Origin' missing
my javascript (VueJs running on localhost:3000) :
let data = //some json data
let xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8080/abc");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onload = () => {
console.log(xhr.responseText);
}
xhr.send(JSON.stringify(data));
My Actix_Web server (running on localhost:8080) :
#[actix_web::main]
async fn main() {
HttpServer::new(move || {
let cors = Cors::default()
.allowed_origin("http://localhost:3000/")
.allowed_methods(vec!["GET", "POST"])
.allowed_header(actix_web::http::header::ACCEPT)
.allowed_header(actix_web::http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.wrap(cors)
.service(myfunc)
})
.bind(("0.0.0.0", 8080))
.unwrap()
.run()
.await
.unwrap();
}
my cargo.toml dependencies
[dependencies]
actix-web = "4"
actix-cors = "0.6.1"
...
Got any idea?
Okay, so I've done some testing. If you're writing a public API, you probably want to allow all origins. For that you may use the following code:
HttpServer::new(|| {
let cors = Cors::default().allow_any_origin().send_wildcard();
App::new().wrap(cors).service(greet)
})
If you're not writing a public API... well, I'm not sure what they want you to do. I've not figured out how to tell the library to send that header. I guess I will look at the code.
UPDATE:
So funny story, this is how you allow specific origins:
let cors = Cors::default()
.allowed_origin("localhost:3000")
.allowed_origin("localhost:2020");
BUT, and oh boy, is that but juicy. The Access-Control-Allow-Origin response header is only set when there is a Origin request header. That header is normally added by the browser in certain cases 1. So I did that (using the Developer tools in the browser). What did I get? "Origin is not allowed to make this request". I set my origin header to localhost:3000. Turns out, the arctix library simply discards that header if no protocol was provided... (e.g. http://) (I assume it discards it, if it deems its format invalid). That internally results in the header being the string "null". Which is, checks notes, not in the list of allowed origins.
And now the grand finale:
Your origin header needs to be set to (by either you or the browser): "http://localhost:3000".
Your configuration needs to include: .allowed_origin("http://localhost:3000").
After doing that, the server will happily echo back your origin header in the Access-Control-Allow-Origin header. And it will only send that one.
I've no idea if any of that is what the standard specifies (or not). I encourage you to read through it, and if it doesn't comply, please open an issue on GitHub. I would do it myself, but I'm done with programming for today.
Cheers!

Widevine DRM Content on Exoplayer 2.0

I am trying to play Widevine encrypted content on an Android TV application using Exoplayer. I have my video URL which is served from a CDN and acquired with a ticket. I have my widevine license URL, a ticket and a auth token for the license server.
I am creating a drmSessionManager, putting the necessary headers needed by the license server as follows:
UUID drmSchemeUuid = C.WIDEVINE_UUID;
mediaDrm = FrameworkMediaDrm.newInstance(drmSchemeUuid);
static final String USER_AGENT = "user-agent";
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback("my-license-server", new DefaultHttpDataSourceFactory(USER_AGENT));
keyRequestProperties.put("ticket-header", ticket);
keyRequestProperties.put("token-header", token);
drmCallback.setKeyRequestProperty("ticket-header", ticket);
drmCallback.setKeyRequestProperty("token-header", token);
new DefaultDrmSessionManager(drmSchemeUuid, mediaDrm, drmCallback, keyRequestProperties)
After this Exoplayer handles most of the stuff, the following breakpoints are hit.
response = callback.executeKeyRequest(uuid, (KeyRequest) request);
in class DefaultDrmSession
return executePost(dataSourceFactory, url, request.getData(), requestProperties) in HttpMediaDrmCallback
I can observe that everything is fine till this point, the URL is correct, the headers are set fine.
in the following piece of code, I can observe that the dataSpec is fine, trying to POST a request to the license server with the correct data, but when making the connection the response code returns 405.
in class : DefaultHttpDataSource
in method : public long open(DataSpec dataSpec)
this.dataSpec = dataSpec;
this.bytesRead = 0;
this.bytesSkipped = 0;
transferInitializing(dataSpec);
try {
connection = makeConnection(dataSpec);
} catch (IOException e) {
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
dataSpec, HttpDataSourceException.TYPE_OPEN);
}
try {
responseCode = connection.getResponseCode();
responseMessage = connection.getResponseMessage();
} catch (IOException e) {
closeConnectionQuietly();
throw new HttpDataSourceException("Unable to connect to " + dataSpec.uri.toString(), e,
dataSpec, HttpDataSourceException.TYPE_OPEN);
}
When using postman to make a request to the URL, a GET request returns the following body with a response code of 405.
{
"Message": "The requested resource does not support http method 'GET'." }
a POST request also returns response code 405 but returns an empty body.
In both cases the following header is also returned, which I suppose the request must be accepting GET and POST requests.
Access-Control-Allow-Methods →GET, POST
I have no access to the configuration of the DRM server, and my contacts which are responsible of the DRM server tells me that POST requests must be working fine since there are clients which have managed to get the content to play from the same DRM server.
I am quite confused at the moment and think maybe I am missing some sort of configuration in exoplayer since I am quite new to the concept of DRMs.
Any help would be greatly appreciated.
We figured out the solution. The ticket supplied for the DRM license server was wrong. This works as it is supposed to now and the content is getting played. Just in case anyone somehow gets the same problem or is in need of a basic Widevine content playing code, this works fine at the moment.
Best regards.

How do I inject new request header with json data in proxy request flow?

I am trying to inject a new request header in the proxy request flow using JS policy to be sent to the backend server. When I look at the debug trace, I see that the json data in the request header is distorted.
I am trying to inject some string like
{"scope":"","time_till":2264,"id_1":"hUXLXVqpA1J4vA9sayk2UttWNdM","custom_data":{"c_id":"test_data"}}
But when I look at the trace window I see this
{"scope":"","time_till":2264,id_1":"hUXLXVqpA1J4vA9sayk2UttWNdM,"custom_data":{"c_id":"test_data"}}
what am I doing wrong?
var obj = {"scope":"","time_till":2264,"id_1":"hUXLXVqpA1J4vA9sayk2UttWNdM","custom_data":{"c_id":"test_data"}};
var header_str = JSON.stringify(obj);
context.setVariable('json-header',header_str);
request.headers['x-json-hedar']= header_str;
I tested your code and it seems to work. Here's an example response where I set the header string as a response:
HTTP/1.1 200 OK
User-Agent: curl/7.30.0
Accept: */*
x-json-header: {"scope":"","time_till":2264,"id_1":"hUXLXVqpA1J4vA9sayk2UttWNdM","custom_data":{"c_id":"test_data"}}
Content-Length: 0
It appears this is only an issue with the Apigee debug session / trace tool as the header value was set correctly. Here was the JSON download of the debug session showing this header value:
{
"name": "x-json-header",
"value": "{\"scope\":\"\",\"time_till\":2264,id_1\":\"hUXLXVqpA1J4vA9sayk2UttWNdM,\"custom_data\":{\"c_id\":\"test_data\"}}"
}
You can see that the value passed to the UI for displaying the debug info has the malformed json:
id_1\":\"hUXLXVqpA1J4vA9sayk2UttWNdM,
This does not appear to be a problem with the Apigee debug/trace UI. I see the malformed JSON trickle down to my backend service.
Here is the header I'm trying to send -
{"timeStamp":"2349218349381274","latitude":"34.589","longitude":"-37.343","clientIp":"127.0.0.0","deviceId":"MOBILE_TEST_DEVICE_AGAIN","macAddress":"23:45:345:345","deviceType":"phone","deviceOS":"iOS","deviceModel":"iPhone 5S","connection":"5G","carrier":"Vodafone","refererURL":"http://www.google.com","xforwardedFor":"129.0.0.0","sessionId":"kfkls498327ksdjf","application":"mobile-app","appVersion":"7.6.5","serviceVersion":"1.0","userAgent":"Gecko"}
But Apigee reads the header as below. Note the missing start quotes from some fields.
{"timeStamp":"2349218349381274",latitude":"34.589,longitude":"-37.343,clientIp":"127.0.0.0,deviceId":"MOBILE_TEST_DEVICE_AGAIN,macAddress":"23:45:345:345,deviceType":"phone,deviceOS":"iOS,deviceModel":"iPhone 5S,connection":"5G,carrier":"Vodafone,refererURL":"http://www.google.com,xforwardedFor":"129.0.0.0,sessionId":"kfkls498327ksdjf,application":"mobile-app,appVersion":"7.6.5,serviceVersion":"1.0,"userAgent":"Gecko"}
The header is used in a service callout to a backend service which parses it. And rightly so, I get the below error -
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('l' (code 108)): was expecting double-quote to start field name
at [Source: java.io.StringReader#22549cdc; line: 1, column: 35]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1378)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:599)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:520)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleUnusualFieldName(ReaderBasedJsonParser.java:1275)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._parseFieldName(ReaderBasedJsonParser.java:1170)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:611)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:301)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2796)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1942)
I encounter strange behaviour when adding JSON to a context variable for example like the following:
var header_str = JSON.stringify(obj);
context.setVariable('json-header',header_str);
I appreciate this is an example so you may not have included the full extent of the problem but this normally works (now it is not added to a variable first):
request.headers['x-json-header'] = JSON.stringify(obj);
Code like this also works if you can send the request from JavaScript
var headers = {"Accept": "application/json", "Accept-Language": "en"};
var sessionRequest = new Request(url, 'POST', headers, body);
var exchange = httpClient.send(sessionRequest);
exchange.waitForComplete()
if (exchange.isSuccess()){
var responseObj = exchange.getResponse().content.asJSON;
if (responseObj.error){
request.content += JSON.stringify(responseObj);
}
}
Also, I have had success with using an AssignMessage policy to build a request, followed by a Callout policy to read the stored request and then make that request and store the result in a response object which can then be read by an Extract Variables policy.

ASP.Net Web API - Authorization header blank

I am having to re-write an existing REST API using .NET (originally written with Ruby). From the client's perspective, it has to work exactly the same way as the old API - i.e. the client code mustn't need to change. The current API requires Basic Authentication. So to call the old API, the following works perfectly:-
var wc = new System.Net.WebClient();
var myCache = new CredentialCache();
myCache.Add(new Uri(url), "Basic", new NetworkCredential("XXX", "XXX"));
wc.Credentials = myCache;
var returnBytes = wc.DownloadData("http://xxxx");
(I have had to ommit the real URL / username / password etc for security reasons).
Now I am writing the new API using ASP.Net Web API with MVC4. I have a weird problem and cannot find anybody else with exactly the same problem. In order to support Basic Authentication, I have followed the guidelines here:
http://sixgun.wordpress.com/2012/02/29/asp-net-web-api-basic-authentication/
One thing, I put the code to "hook in the handler" in the Global.asax.cs file in the Application_Start() event (that wasn't explained so I guessed).
Anyway, if I call my API (which I have deployed in IIS) using the above code, the Authorization header is always null, and the above fails with 401 Unauthorized. However, if I manually set the header using this code, it works fine - i.e. the Authorization header now exists and I am able to Authenticate the user.
private void SetBasicAuthHeader(WebClient request, String userName, String userPassword)
{
string authInfo = userName + ":" + userPassword;
authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
request.Headers["Authorization"] = "Basic " + authInfo;
}
.......
var wc = new System.Net.WebClient();
SetBasicAuthHeader(request, "XXXX", "XXXX");
var returnBytes = wc.DownloadData("http://xxxx");
Although that works, it's no good to me because existing users of the existing API are not going to be manually setting the header.
Reading up on how Basic Authentication works, the initial request is meant to be anonymous, then the client is returned 401, then the client is meant to try again. However if I put a break point in my code, it will never hit the code again in Antony's example. I was expecting my breakpoint to be hit twice.
Any ideas how I can get this to work?
You're expecting the right behavior. System.Net.WebClient does not automatically include the Authorization headers upon initial request. It only sends them when properly challenged by a response, which to my knowledge is a 401 status code and a proper WWW-Authenticate header. See here and here for further info.
I'm assuming your basic authentication handler is not returning the WWW-Authenticate header and as such WebClient never even attempts to send the credentials on a second request. You should be able to watch this in Fiddler or a similar tool.
If your handler did something like this, you should witness the WebClient approach working:
//if is not authenticated or Authorization header is null
return base.SendAsync(request, cancellationToken).ContinueWith(task =>
{
var response = task.Result;
response.StatusCode = HttpStatusCode.Unauthorized;
response.Headers.Add("WWW-Authenticate", "Basic realm=\"www.whatever.com\"");
return response;
});
//else (is authenticated)
return base.SendAsync(request, cancellationToken);
As you noticed, if you include the Authorization headers on every request (like you did in your alternative approach) then your handler already works as is. So it may be sufficient - it just isn't for WebClient and other clients that operate in the same way.

Resources