Controller action never calls - c#

after redirect to facebook for getting code token and when facebook redirect back to me, my app stop responding, action, which should be called never fires up. where is the problem?
my routes
routes.MapRoute(
"Enter facebook return",
"enter/facebook/return",
new { controller = "Users", action = "FacebookReturn" } //callback action, which not called -> problem
);
routes.MapRoute(
"Enter facebook",
"enter/facebook",
new { controller = "Users", action = "Facebook" }
);
and my controller
public ActionResult Facebook()
{
OAuth2 facebook = new OAuth2(
ConfigurationManager.AppSettings["oauth_facebook_id"].ToString(),
ConfigurationManager.AppSettings["oauth_facebook_secret"].ToString(),
ConfigurationManager.AppSettings["oauth_facebook_authorize"].ToString(),
ConfigurationManager.AppSettings["oauth_facebook_access"].ToString(),
ConfigurationManager.AppSettings["oauth_facebook_return"].ToString()
);
Session["enter_facebook"] = facebook; // save data to session
Dictionary<string, string> p = new Dictionary<string, string> { { "scope", "email" } };
string redirectURL = facebook.GetAuthCode(p); //redirect url
//redirect url looks like https://www.facebook.com/dialog/oauth?client_id=111111111&response_type=code&redirect_uri=http%3A%2F%localhost.loc%2Fenter%2Ffacebook%2Freturn&scope=email
return Redirect(redirectURL);
}
public ActionResult FacebookReturn(string code)
{
if (!string.IsNullOrEmpty(code))
{
//after returning to us
OAuth2 facebook = (OAuth2)Session["enter_facebook"];
facebook.Code = code; //getting code
Dictionary<string, string> p = new Dictionary<string, string> { { "client_secret", ConfigurationManager.AppSettings["oauth_facebook_secret"].ToString() } }; //additional params
OAuth2Token token = facebook.GetAccessToken(p, OAuth2.AccessTokenType.Dictionary); //getting marker access
string access_token = token.dictionary_token["access_token"];
if (!string.IsNullOrEmpty(access_token))
{
string user_data = OAuth2UserData.GetFacebookUserData(access_token); //getting data about user
string a = "";
}
}
return View("Enter");
}
my livehhttpheader
GET https://www.facebook.com/dialog/oauth?client_id=111111111&response_type=code&redirect_uri=http%3A%2F%localhost.loc%2Fenter%2Ffacebook%2Freturn&scope=email
Host: www.facebook.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:23.0) Gecko/20100101 Firefox/23.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Referer: http://localhost.loc/enter
Connection: keep-alive
so at the end of this i see when i click on "login with facebook":
1.https://www.facebook.com/dialog/oauth?client_id=111111111&response_type=code&redirect_uri=http%3A%2F%localhost.loc%2Fenter%2Ffacebook%2Freturn&scope=email
2.http://localhost.loc/enter/facebook/return?code=2222222222
3.my app stopped here, FacebookReturn(string code) never called, can't get address in p.2 (sign in firefox as loading and never ends), BUT if just call p.2 without redirect everything is fine
so question is: why after redirecting from facebook to my site action never calles and it seems like in application pool some problems

Related

Azure Blob Storage Authorization

EDIT 2: The following is the output of the Authorization error:
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<Error>
<Code>AuthenticationFailed</Code>
<Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.\nRequestId:34d738a5-101e-000d-5a14-ed5956000000\nTime:2021-01-17T21:07:38.6231913Z
</Message>
<AuthenticationErrorDetail>Signature did not match. String to sign used was cw\n2021-01-17T19:06:42Z\n2021-01-18T21:06:42Z\n/blob/example-account/example-container/example-blob.json\n\n\nhttps\n2019-02-02\nb\n\n\n\n\n\n
</AuthenticationErrorDetail>
</Error>
I don't really understand... I updated the C# code below to print out the string_to_sign with the \n characters, and it's exactly the same as the string_to_sign from the output above.
NOTE: The SAS token generated from Azure Storage that does work is an Account SAS, while the one I'm generating is a Service SAS. Could Service SAS be restricted in Azure Storage?
EDIT: I tried generating a SAS token directly from Azure Storage, and this did seem to work. It appears to be an account SAS, NOT the service SAS I'm trying to use below.
?sv=2019-12-12&ss=b&srt=o&sp=wac&se=2021-01-18T01:15:13Z&st=2021-01-17T17:15:13Z&spr=https&sig=<signature>
I'm looking to be able to send upload a file to Azure Storage using it's REST API. However, I'm having some trouble getting it to Authorize. I find the documentation a bit conflicting, in some places it says I can include a SAS token in the URI, in others it's in the Authorize header. For context, I'm trying to do it directly from APIM, so in the sample code below it's written with its limited API. This is just a general concept I'm using to generate the authorization string, but I keep getting a 403 when I use it (I'm not sure if I need to do something from the Azure Storage side).
/**
Based on https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
*/
using System;
using System.Collections.Generic;
namespace sas_token
{
class Program
{
static void Main(string[] args)
{
string key = args[0];
Console.WriteLine(generate_blob_sas_token(key));
}
public static string generate_blob_sas_token(string key)
{
const string canonicalizedResource = "canonicalizedResource";
// NOTE: this only works for Blob type files, Tables have a different
// structure
// NOTE: use a List instead of Dictionary since the order of keys in
// Dictionaries is undefined and the signature string requires a very
// specific order
List<KeyValuePair<string, string>> sas_token_properties = new List<KeyValuePair<string, string>>(){
// signedPermissions, select 1..* from [racwdxltmeop], MUST be in that order
new KeyValuePair<string, string>("sp", "cw"),
// signedStart time, date from when the token is valid
// NOTE: because of clock skew between services, even setting the time to
// now may not create an immediately usable token
new KeyValuePair<string, string>("st", DateTime.UtcNow.AddMinutes(-120).ToString("yyyy-MM-ddTHH:mm:ssZ")),
// signedExpiry time, date until the token is valid
new KeyValuePair<string, string>("se", DateTime.UtcNow.AddDays(1).ToString("yyyy-MM-ddTHH:mm:ssZ")),
// canonicalizedResource, must be prefixed with /blob in recent versions
// NOTE: this is NOT included as a query parameter, but is in the signature
// URL = https://myaccount.blob.core.windows.net/music/intro.mp3
// canonicalizedResource = "/blob/myaccount/music/intro.mp3"
new KeyValuePair<string, string>(canonicalizedResource, "/blob/example-account/example-container"),
// signedIdentifier, can be used to identify a Stored Access Policy
new KeyValuePair<string, string>("si", ""),
// signedIP, single or range of allowed IP addresses
new KeyValuePair<string, string>("sip", ""),
// signedProtocol
// [http, https]
new KeyValuePair<string, string>("spr", "https"),
// signedVersion, the version of SAS used (defines which keys are
// required/available)
new KeyValuePair<string, string>("sv", "2019-02-02"),
// signedResource, the type of resource the token is allowed to access
// [b = blob, d = directory, c = container, bv, bs]
new KeyValuePair<string, string>("sr", "b"),
// signedSnapshotTime
new KeyValuePair<string, string>("sst", ""),
// the following specify how the response should be formatted
// Cache-Control
new KeyValuePair<string, string>("rscc", ""),
// Content-Disposition
new KeyValuePair<string, string>("rscd", ""),
// Content-Encoding
new KeyValuePair<string, string>("rsce", ""),
// Content-Language
new KeyValuePair<string, string>("rscl", ""),
// Content-Type
new KeyValuePair<string, string>("rsct", "")
};
// the format is a very specific text string, where values are delimited by new
// lines, and the order of the properties in the string is important!
List<string> values = new List<string>();
foreach (KeyValuePair<string, string> entry in sas_token_properties)
{
values.Add(entry.Value);
}
string string_to_sign = string.Join("\n", new List<string>(values));
Console.WriteLine(string_to_sign.Replace("\n", "\\n"));
System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(key));
var signature = System.Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(string_to_sign)));
// create the query parameters of any set values + the signature
// NOTE: all properties that contribute to the signature must be added
// as query params EXCEPT canonicalizedResource
List<string> parameters = new List<string>();
foreach (KeyValuePair<string, string> entry in sas_token_properties)
{
if (!string.IsNullOrEmpty(entry.Value) && entry.Key != canonicalizedResource)
{
parameters.Add(entry.Key + "=" + System.Net.WebUtility.UrlEncode(entry.Value));
}
}
parameters.Add("sig=" + System.Net.WebUtility.UrlEncode(signature));
string sas_token_querystring = string.Join("&", parameters);
return sas_token_querystring;
}
}
}
I use the output in the following (simplified) APIM policy (I set "sas_token" variable to the output of the function to test the process):
<set-variable name="x-request-body" value="#(context.Request.Body.As<string>())" />
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
<set-url>#("https://example-account.blob.core.windows.net/example-container/test.json")</set-url>
<set-method>PUT</set-method>
<set-header name="x-ms-date" exists-action="override">
<value>#(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>2019-02-02</value>
</set-header>
<set-header name="x-ms-blob-type" exists-action="override">
<value>BlockBlob</value>
</set-header>
<set-header name="Authorization" exists-action="override">
<value>#("SharedAccessSignature " + (string)context.Variables["sas_token"])</value>
</set-header>
<set-body>#((string)context.Variables["x-request-body"])</set-body>
</send-request>
For completeness, here's the result from APIM when I trace a test request using {"hello": "then"}:
{
"message": "Request is being forwarded to the backend service. Timeout set to 20 seconds",
"request": {
"method": "PUT",
"url": "https://example-account.blob.core.windows.net/example-container/test.json",
"headers": [
{
"name": "Host",
"value": "example-account.blob.core.windows.net"
},
{
"name": "Content-Length",
"value": 17
},
{
"name": "x-ms-date",
"value": "2021-01-17T16:53:28Z"
},
{
"name": "x-ms-version",
"value": "2019-02-02"
},
{
"name": "x-ms-blob-type",
"value": "BlockBlob"
},
{
"name": "Authorization",
"value": "SharedAccessSignature sp=cw&st=2021-01-17T13%3A42%3A02Z&se=2021-01-18T15%3A42%3A02Z&spr=https&sv=2019-02-02&sr=b&sig=<signature>"
},
{
"name": "X-Forwarded-For",
"value": "205.193.94.40"
}
]
}
}
send-request (92.315 ms)
{
"response": {
"status": {
"code": 403,
"reason": "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
},
"headers": [
{
"name": "x-ms-request-id",
"value": "185d86f5-601e-0038-5cf1-ec3542000000"
},
{
"name": "Content-Length",
"value": "321"
},
{
"name": "Content-Type",
"value": "application/xml"
},
{
"name": "Date",
"value": "Sun, 17 Jan 2021 16:53:28 GMT"
},
{
"name": "Server",
"value": "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0"
}
]
}
}
Also, still newish to C#, so if something can be done better please let me know.
Azure Storage supports below authorizing method:
But SAS token can not be the Authorization header of REST API.
https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage
I encapsulated several authentication methods:
using Azure.Storage;
using Azure.Storage.Sas;
using Microsoft.Azure.Services.AppAuthentication;
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace ConsoleApp31
{
class Program
{
static void Main(string[] args)
{
string storageKey = "xxxxxx";
string storageAccount = "yourstorageaccountname";
string containerName = "test";
string blobName = "test.txt";
string mimeType = "text/plain";
string test = "This is a test of bowman.";
byte[] byteArray = Encoding.UTF8.GetBytes(test);
MemoryStream stream = new MemoryStream(byteArray);
UseRestApiToUpload(storageKey,storageAccount,containerName,blobName,stream,mimeType);
Console.WriteLine("*******");
Console.ReadLine();
}
//Upload blob with REST API
static void UseRestApiToUpload(string storageKey, string storageAccount, string containerName, string blobName, Stream stream, string mimeType)
{
string method = "PUT";
long contentlength = stream.Length;
string requestUri = $"https://{storageAccount}.blob.core.windows.net/{containerName}/{blobName}";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
string utcnow = DateTime.UtcNow.ToString("R");
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
var content = memoryStream.ToArray();
request.Method = method;
request.Headers.Add("Content-Type", mimeType);
request.Headers.Add("x-ms-version", "2019-12-12");
request.Headers.Add("x-ms-date", utcnow);
request.Headers.Add("x-ms-blob-type", "BlockBlob");
request.Headers.Add("Content-Length", contentlength.ToString());
//Use SharedKey to authorize.
request.Headers.Add("Authorization", AuthorizationHeaderWithSharedKey(method, utcnow, request, storageAccount, storageKey, containerName, blobName));
//Can not use SAS token in REST API header to authorize.
//Use Bearer token to authorize.
//request.Headers.Add("Authorization",AuthorizationHeaderWithAzureActiveDirectory());
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(content, 0, (int)contentlength);
}
using (HttpWebResponse resp = (HttpWebResponse)request.GetResponse())
{
}
}
//Use shared key to authorize.
public static string AuthorizationHeaderWithSharedKey(string method, string now, HttpWebRequest request, string storageAccount, string storageKey, string containerName, string blobName)
{
string headerResource = $"x-ms-blob-type:BlockBlob\nx-ms-date:{now}\nx-ms-version:2019-12-12";
string urlResource = $"/{storageAccount}/{containerName}/{blobName}";
string stringToSign = $"{method}\n\n\n{request.ContentLength}\n\n{request.ContentType}\n\n\n\n\n\n\n{headerResource}\n{urlResource}";
HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(storageKey));
string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
String SharedKey = String.Format("{0} {1}:{2}", "SharedKey", storageAccount, signature);
return SharedKey;
}
//Use Shared access signature(SAS) to authorize.
public static string AuthorizationHeaderWithSharedAccessSignature(string storageAccount, string storageKey)
{
// Create a SAS token that's valid for one hour.
AccountSasBuilder sasBuilder = new AccountSasBuilder()
{
Services = AccountSasServices.Blobs | AccountSasServices.Files,
ResourceTypes = AccountSasResourceTypes.Service,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),
Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(AccountSasPermissions.Read |
AccountSasPermissions.Write);
// Use the key to get the SAS token.
StorageSharedKeyCredential key = new StorageSharedKeyCredential(storageAccount, storageKey);
string sasToken = sasBuilder.ToSasQueryParameters(key).ToString();
Console.WriteLine("SAS token for the storage account is: {0}", sasToken);
Console.WriteLine();
return sasToken;
}
//Use Azure Active Directory(Bearer token) to authorize.
public static string AuthorizationHeaderWithAzureActiveDirectory()
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
string bearertoken = azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/").Result;
return "Bearer " + bearertoken;
}
}
}
Although the interaction between many software packages and azure is based on REST API, for operations like uploading blobs, I don't recommend you to use rest api to complete. Azure officially provides many packaged packages that you can use directly, such as:
https://learn.microsoft.com/en-us/dotnet/api/azure.storage.blobs?view=azure-dotnet
And example for .Net:
https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet
In the above SDK, you can use sas token for authentication.
I don't think you can put a SAS token in an Authorization header. I can't find any relevant sample, so I used the
Using the Azure.Storage.Blob C# client library from NuGet to do this
var data = System.Text.Encoding.UTF8.GetBytes("Hello Azure Storage");
var keyCred = new StorageSharedKeyCredential(account, key);
var sasBuilder = new AccountSasBuilder()
{
Services = AccountSasServices.Blobs,
ResourceTypes = AccountSasResourceTypes.Object,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),
Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(AccountSasPermissions.All);
var sasToken = sasBuilder.ToSasQueryParameters(keyCred).ToString();
var blobClient = new BlobServiceClient(new Uri($"https://{account}.blob.core.windows.net/?{sasToken}"), null);
var containter = blobClient.GetBlobContainerClient("test");
containter.UploadBlob("test.txt", new MemoryStream(data));
Generates an HTTP request like this:
PUT https://xxxxxx.blob.core.windows.net/test/test.txt?sv=2020-04-08&ss=b&srt=o&spr=https&se=2021-01-17T18%3A13%3A55Z&sp=rwdxlacuptf&sig=RI9It3O6mcmw********S%2B1r91%2Bj5zGbk%3D HTTP/1.1
Host: xxxxxx.blob.core.windows.net
x-ms-blob-type: BlockBlob
x-ms-version: 2020-04-08
If-None-Match: *
x-ms-client-request-id: c6e93312-af95-4a04-a207-2e2062b1dd26
x-ms-return-client-request-id: true
User-Agent: azsdk-net-Storage.Blobs/12.8.0 (.NET Core 3.1.10; Microsoft Windows 10.0.19042)
Request-Id: |ffa2da23-45c79d128da40651.
Content-Length: 19
Hello Azure Storage
Then using the SAS token directly with WebClient,
var wc = new WebClient();
wc.Headers.Add("x-ms-blob-type: BlockBlob");
wc.UploadData($"https://{account}.blob.core.windows.net/test/test2.txt?{sasToken}", "PUT", data);
works too, which should be the minimal request:
PUT https://xxxxx.blob.core.windows.net/test/test2.txt?sv=2020-04-08&ss=b&srt=o&spr=https&se=2021-01-17T18%3A50%3A01Z&sp=rwdxlacuptf&sig=Fj4QVfwIfjXP10G%xxxxxxxx%2FF%2FcjikizKggY%3D HTTP/1.1
Host: xxxx.blob.core.windows.net
x-ms-blob-type: BlockBlob
Connection: Keep-Alive
Content-Length: 19
Hello Azure Storage
Removing the x-ms-blob-type header fails with:
The remote server returned an error: (400) An HTTP header that's
mandatory for this request is not specified..
You are free to poke through the source code on GitHub for more details.
Thanks to David's help to confirm that it was my error, I was incorrectly converting the key to generate the HMAC. Below is the correct code, notice the Base64 decode, whereas originally I was just getting the byte array:
string string_to_sign = string.Join("\n", new List<string>(values));
Console.WriteLine(string_to_sign.Replace("\n", "\\n"));
System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(System.Convert.FromBase64String(key));
var signature = System.Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(string_to_sign)));
And then I can use it like so in the APIM Policy:
<set-variable name="x-request-body" value="#(context.Request.Body.As<string>())" />
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
<set-url>#(string.Format("https://example-account.blob.core.windows.net/example-container/test.json?{0}", context.Variables["sas_token"]))</set-url>
<set-method>PUT</set-method>
<set-header name="x-ms-date" exists-action="override">
<value>#(DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))</value>
</set-header>
<set-header name="x-ms-version" exists-action="override">
<value>2019-02-02</value>
</set-header>
<set-header name="x-ms-blob-type" exists-action="override">
<value>BlockBlob</value>
</set-header>
<set-body>#((string)context.Variables["x-request-body"])</set-body>
</send-request>

The caller's YouTube account is not connected to Google+

good day
I'm using the API explorer at the bottom of https://developers.google.com/youtube/v3/docs/commentThreads/insert?apix=true&apix_params=%7B%22part%22%3A%5B%22snippet%22%5D%2C%22resource%22%3A%7B%22snippet%22%3A%7B%22videoId%22%3A%22s-VhIdn0q5E%22%2C%22topLevelComment%22%3A%7B%22snippet%22%3A%7B%22textOriginal%22%3A%22Tsssssnt%20thread.%22%7D%7D%7D%7D%7D
to test this call. I am requesting the scope https://www.googleapis.com/auth/youtube.force-ssl.
Code connect:
GoogleWebAuthorizationBroker.Folder = idapp.ToString();
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(60));
CancellationToken ct = cts.Token;
using (var stream = new FileStream(client_secret_file, FileMode.Open, FileAccess.Read))
{
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeForceSsl},userid, ct).Result;
}
youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApiKey = apikey,
HttpClientInitializer = credential,
ApplicationName = idapp.ToString()
});
RESPONSE:
cache-control: private
content-encoding: gzip
content-length: 204
content-type: application/json; charset=UTF-8
date: Mon, 29 Jun 2020 13:07:32 GMT
server: scaffolding on HTTPServer2
vary: Origin, X-Origin, Referer
{
"error": {
"code": 403,
"message": "The caller's YouTube account is not connected to Google+.",
"errors": [
{
"message": "The caller's YouTube account is not connected to Google+.",
"domain": "youtube.commentThread",
"reason": "ineligibleAccount",
"location": "Authorization",
"locationType": "header"
}
]
}
}
In apps Exception like this:
Exception Text
The service youtube has thrown an exception: Google.GoogleApiException: Google.Apis.Requests.RequestError
The caller's YouTube account is not connected to Google+. [403]
Errors [
Message[The caller's YouTube account is not connected to Google+.] Location[Authorization - header] Reason[ineligibleAccount] Domain[youtube.commentThread]
]
at Google.Apis.Requests.ClientServiceRequest`1.d__31.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
Google account was created a week ago. Please help how to use api to add comments on youtube via c # application
public bool Comment(string Channel_Id, string Video_Id, string commentmsg)
{
try
{
CommentThreadSnippet commentThreadSnippetz = new CommentThreadSnippet();
commentThreadSnippetz.ChannelId = Channel_Id;
commentThreadSnippetz.VideoId = Video_Id;
commentThreadSnippetz.TopLevelComment = new Comment() { Snippet = new CommentSnippet() { TextOriginal = commentmsg } };
CommentThreadsResource.InsertRequest commentReq = youtubeService.CommentThreads.Insert(new CommentThread() { Snippet = commentThreadSnippetz }, "snippet");
CommentThread commentRes = commentReq.Execute();
return true;
}
catch (AggregateException ex)
{
foreach (var f in ex.InnerExceptions)
{
MessageTip.ShowError(f.Message, 5000);
}
coonector = false;
return false;
}
}
Well, it's actually too late, but for anyone else encountering this problem, your account that you're trying to use is google only - it has no associated youtube CHANNEL. Just login in on youtube, click your profile pic and create a channel. Worked for me.

how to impersonate a user via odata

We have been succesful in using the odata v8.1 endpoint in 2016 to impersonate a user.
Please note that the intended request flow is: Postman-->LocalHost Microservice-->CRM
Example of a working request from Postman-->CRM (directly, without going through the microservice)
Accept:application/json
Content-Type:application/json; charset=utf-8
OData-MaxVersion:4.0
OData-Version:4.0
MSCRMCallerID:d994d6ff-5531-e711-9422-00155dc0d345
Cache-Control:no-cache
Against the odata endpoint: ..../api/data/v8.1/leads
Note that this has been successful only when issued directly against the odata v8.1 endpoint via postman.
When attempting to do the same, having a service running locally (Postman-->LocalHost Service-->CRM), this fails, and simply ignores??? the MSCRMCallerID header.
Upon examining headers that were passed to the LocalHost Microservice from Postman, the request, as examined by the debugger in VS 2017:
{Method: POST, RequestUri: 'https://.../api/data/v8.1/leads', Version: 1.1, Content: System.Net.Http.StringContent, Headers:
{
OData-Version: 4.0
OData-MaxVersion: 4.0
MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
Cache-Control: no-cache
Accept: application/json
Content-Type: application/json; charset=utf-8
}}
The record is created succesfully, however on the CreatedBy field is the service username NOT the MSCRMCallerID username (d994d6ff-5531-e711-9422-00155dc0d345), and the CreatedOnBehalf field is empty.
What are we doing wrong?
How do we get this impersonation working from our service?
EDIT + More Info
Please note that I do believe that I've included all the relevant info, but if I have not, please let me know what other input I should provide on this issue.
What have I tried?
changed the order of headers
played with the case of the headers
ensured that the guid is correct of the user for impersonation
ensured that the user has both delegate and sys admin role (although this is irrelevant because this works when executing requesting directly against crm odata endpoint, rather than the endpoint that the our service exposes
have tried to execute the request against both https AND http
fiddler trace as shown below
Please note that this fiddler trace is a trace showing Postman --> Microservice request. It does not show the communication from the localhost microservice to CRM. (I'm not sure why, perhaps because it is encrypted)
POST https://localhost:19081/.....Leads/API/leads HTTP/1.1
Host: localhost:19081
Connection: keep-alive
Content-Length: 84
Cache-Control: no-cache
Origin: chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
MSCRMCallerID: D994D6FF-5531-E711-9422-00155DC0D345
X-Postman-Interceptor-Id: d79b1d2e-2155-f2ec-4ad7-e9b63e7fb90d
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36
Content-Type: application/json; charset=UTF-8
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: ai_user=Ka2Xn|2017-05-25T17:30:57.941Z
{
"subject": "created by mscrmcaller user2: d994d6ff-5531-e711-9422-00155dc0d345"
}
#Ram has suggested that we use the organization service to authenticate, is this an option, considering we are executing against Web API? Will the requested token still be valid. (Please note that this may be a silly question, and the reason is because I am not understanding how authentication works).
The following is a code snippet from how we are authenticating currently on every call:
//check headers to see if we got a redirect to the new location
var shouldAuthenticate = redirectUri.AbsoluteUri.Contains("adfs/ls");
if (!shouldAuthenticate)
{
return;
}
var adfsServerName = redirectUri.Authority;
var queryParams = HttpUtility.ParseQueryString(redirectUri.Query);
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
WSTrustChannelFactory factory = null;
try
{
// use a UserName Trust Binding for username authentication
factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
$"https://{adfsServerName}/adfs/services/trust/13/usernamemixed")
{
Credentials =
{
UserName =
{
UserName = $"{credential.Domain}\\{credential.UserName}",
Password = credential.Password
}
},
TrustVersion = TrustVersion.WSTrust13
};
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(_client.BaseAddress.AbsoluteUri),
TokenType = "urn:oasis:names:tc:SAML:1.0:assertion",
KeyType = KeyTypes.Bearer
};
var channel = factory.CreateChannel();
channel.Issue(rst, out RequestSecurityTokenResponse rstr);
var fedSerializer = new WSFederationSerializer();
var rstrContent = fedSerializer.GetResponseAsString(rstr, new WSTrustSerializationContext());
// construct a authentication form
var crmauthenticaionPostDictionary = new Dictionary<string, string>
{
{"wa", queryParams["wa"]},
{"wresult", rstrContent},
{"wctx", queryParams["wctx"]}
};
// post the authentication form to the website.
var crmAuthorizationPostResponse = _client.PostAsync(_client.BaseAddress.AbsoluteUri, new FormUrlEncodedContent(crmauthenticaionPostDictionary)).Result;
var crmAuthorizationPostResponseString = crmAuthorizationPostResponse.Content.ReadAsStringAsync().Result;
//we should be authenticated here
if (
!(
// we are correctly authorized if we got redirected to the correct address that we
// were trying to reach in the first place.
crmAuthorizationPostResponse.StatusCode == HttpStatusCode.Redirect
&& crmAuthorizationPostResponse.Headers.Location == authenticationTestUri
)
)
{
throw new Exception("ADFS Authentication to CRM failed.");
}
When you are doing Postman to CRM request, its direct call & CRM handles it in expected way.
But in Postman -> Microservice -> CRM, the header get lost between Microservice to CRM.
In your Microservice, you have to handle the Header forward manually to CRM SDK call.
HttpWebRequest myHttpWebRequest1= (HttpWebRequest)WebRequest.Create(uri);
myHttpWebRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");
Or HTTP Header Forwarding (Sorry I could not find one for Azure / C#)
Update:
Am assuming you are following this MSDN samples to do your CRM web api call in c# microservice. I have included our header in need - MSCRMCallerID. See if it helps you.
public async Task BasicCreateAndUpdatesAsync()
{
Console.WriteLine("--Section 1 started--");
string queryOptions; //select, expand and filter clauses
//First create a new contact instance, then add additional property values and update
// several properties.
//Local representation of CRM Contact instance
contact1.Add("firstname", "Peter");
contact1.Add("lastname", "Cambel");
HttpRequestMessage createRequest1 =
new HttpRequestMessage(HttpMethod.Post, getVersionedWebAPIPath() + "contacts");
createRequest1.Content = new StringContent(contact1.ToString(),
Encoding.UTF8, "application/json");
createRequest1.Headers.Add("MSCRMCallerID", "D994D6FF-5531-E711-9422-00155DC0D345");
HttpResponseMessage createResponse1 =
await httpClient.SendAsync(createRequest1);
if (createResponse1.StatusCode == HttpStatusCode.NoContent) //204
{
Console.WriteLine("Contact '{0} {1}' created.",
contact1.GetValue("firstname"), contact1.GetValue("lastname"));
contact1Uri = createResponse1.Headers.
GetValues("OData-EntityId").FirstOrDefault();
entityUris.Add(contact1Uri);
Console.WriteLine("Contact URI: {0}", contact1Uri);
}
else
{
Console.WriteLine("Failed to create contact for reason: {0}",
createResponse1.ReasonPhrase);
throw new CrmHttpResponseException(createResponse1.Content);
}
}
There are fews things that you have to take care while impersonating
1. To impersonate a user, set the CallerId property on an instance of
OrganizationServiceProxy before calling the service’s Web methods.
2. The user (impersonator) must have the ActOnBehalfOf privilege or be a member of the PrivUserGroup group in Active Directory
Code Example
SystemUser user = null;
user = new SystemUser(systemUser);
OrganizationServiceProxy service = CrmService.Proxy;
service.CallerID = user.Id;
Since your code is not available please ensure all the above fields are set properly
For detailed understanding use the link
https://crmbusiness.wordpress.com/2015/07/21/crm-2015-understanding-impersonation-in-plugins-and-knowing-when-to-use-it/

web api url conflicting with the ravenDb Id format

I came across a weird problem while working with ravenDb in a Asp.net Web api 2 project. I have a controller called "TasksController" with a route of "api/tasks". Corresponding to this I also have a tasks collection in my ravendb with Ids like "tasks/1000". When you create a new task by http-post, the api creates a new task as expected and redirects to "tasks/{id}" route. However because the Id of the newly created task is "tasks/1029" the redirect URL coming back becomes "../api/tasks/tasks/1029" which obviously does not exist. Any suggestions please?
Code
post method
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> Post([FromBody]Task task)
{
task.Notes.Add(DateTime.Now.ToString("F"));
await _repositoryFactory.TaskRepository.SaveTask(task);
await _repositoryFactory.SaveChanges();
return Redirect(Url.Link("Get", new {id = task.Id}));
}
Get
[HttpGet]
[Route("{id}", Name = "Get")]
public async Task<IHttpActionResult> Get(string id)
{
var task = await _repositoryFactory.TaskRepository.GetById(id);
return Ok(task);
}
POSTMAN - Post request
POST /Hoxro.Web.Api/api/tasks/ HTTP/1.1
Host: localhost
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 86fba9d3-cfcc-4060-226b-1db0eb189c7a
{
"priority": 1,
"notes": [
"02 January 2016 20:52:59"
],
"linkedTasks": [],
"createdOn": "02/01/2016",
"assignedTo": "TaskTestUser",
"matterIds": [],
"createdBy": "TaskTestUser",
"completionDate": null,
"dueDate": null,
"completed": false
}
Result -
{
"message": "No HTTP resource was found that matches the request URI 'http://localhost/Hoxro.Web.Api/api/tasks/tasks/1091'.",
"messageDetail": "No action was found on the controller 'Tasks' that matches the name 'tasks'."
}
Correct result should be - http://localhost/Hoxro.Web.Api/api/tasks/1091
I know this is because of the RavenDb Id format, but if I do not go with the standard format Its really difficult to load related documents or I just don't know how to?
If you want redirect to the named route you can use RedirectToRoute method instead of manually generation redirection url.
[HttpPost]
[Route("")]
public async Task<IHttpActionResult> Post([FromBody]Task task)
{
task.Notes.Add(DateTime.Now.ToString("F"));
await _repositoryFactory.TaskRepository.SaveTask(task);
await _repositoryFactory.SaveChanges();
return RedirectToRoute("Get", new {id = task.Id});
}
Also the "right way" is illustrated in web api overview where url add to the response header:
[Route("api/books")]
public HttpResponseMessage Post(Book book)
{
// Validate and add book to database (not shown)
var response = Request.CreateResponse(HttpStatusCode.Created);
// Generate a link to the new book and set the Location header in the response.
string uri = Url.Link("GetBookById", new { id = book.BookId });
response.Headers.Location = new Uri(uri);
return response;
}

Twilio SMS messaging not working in Console Application

I funded my Twilio account and am working in a console application. When to go to the documentation (Here: https://www.twilio.com/user/account/developer-tools/api-explorer/message-create) and enter my phone number the request works. However, when I copy the code to a local console application nothing happens. I literally copy the code line for line and make sure the SID, Token, and Numbers are correct and nothing happens at all, the console app just runs to the end of execution.
string AccountSid = "MySID";
string AuthToken = "MyAuthToken";
var twilio = new TwilioRestClient(AccountSid, AuthToken);
var message = twilio.SendSmsMessage("+12222222222", "+13333333333","Hello World");
Console.WriteLine(message.Sid);
I run Fiddler and I get this for the Raw Packet. Also Fiddler says the result is a 401 status code.
POST https://api.twilio.com/2010-04-01/Accounts/MySID/SMS/Messages.json HTTP/1.1
Authorization: Basic {TonsOfRandomCharactersThatLookLikeISHouldHide}
Accept: application/json, application/xml, text/json, text/x-json, text/javascript, text/xml
Accept-charset: utf-8
User-Agent: twilio-csharp/3.4.1.0 (.NET 4.0.30319.17929)
Content-Type: application/x-www-form-urlencoded
Host: api.twilio.com
Content-Length: 56
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
From=%2B14697891380&To=%2B12146630105&Body=New%20Message
Any ideas on what could be going one? I know others are having this issue, I see it posted in other places, but I have yet to see a response.
Also here is a link to another person having this issue. I would comment, but I do not have the reputation to enter a comment, hence why I made another thread (Why is Twilio not sending sms?)
I was unable to get Twilio to work, this is not the answer to the technical issues (I think for some reason Twilio just has not authorized my account), but for those of you prototyping and need something asap I ended up using Plive and has a call and text message working within an hour. Here is my Sample code and it is actually cheaper than Twilio. I really like Twilio and have used it in the past, but never with C#. So maybe I can still get the issue resolved asap.
using System;
using System.Collections.Generic;
using System.Reflection;
using RestSharp;
using Plivo.API;
namespace Plivo2
{
class Program
{
static void Main(string[] args)
{
string auth_id = "MyAuthID"; // obtained from Plivo account dashboard
string auth_token = "MyAuthTokey"; // obtained from Plivo account dashboard
// Making a Call
string from_number = "MyPliveNumber";
string to_number = "TheNumberYouWantToContact";
SendMessage(auth_id, auth_token, from_number, to_number,"Hello World!");
}
private static void CallPhone(string auth_id,string auth_token, string fromNumber, string toNumber){
// Creating the Plivo Client
RestAPI plivo = new RestAPI(auth_id, auth_token);
IRestResponse<Call> response = plivo.make_call(new Dictionary<string, string>() {
{ "from", fromNumber },
{ "to", toNumber },
{ "answer_url", "http://some.domain.com/answer/" },
{ "answer_method", "GET" }
});
// The "Outbound call" API response has four properties -
// message, request_uuid, error, and api_id.
// error - contains the error response sent back from the server.
if (response.Data != null)
{
PropertyInfo[] proplist = response.Data.GetType().GetProperties();
foreach (PropertyInfo property in proplist)
Console.WriteLine("{0}: {1}", property.Name, property.GetValue(response.Data, null));
}
else
Console.WriteLine(response.ErrorMessage);
}
private static void SendMessage(string auth_id,string auth_token, string fromNumber, string toNumber, string message) {
RestAPI plivo = new RestAPI(auth_id, auth_token);
IRestResponse<MessageResponse> resp = plivo.send_message(new Dictionary<string, string>()
{
{ "src", fromNumber },
{ "dst", toNumber },
{ "text", message },
{ "url", "http://some.domain/receivestatus/" },
{ "method", "GET" }
});
if (resp.Data != null)
{
PropertyInfo[] proplist = resp.Data.GetType().GetProperties();
foreach (PropertyInfo property in proplist)
Console.WriteLine("{0}: {1}", property.Name, property.GetValue(resp.Data, null));
}
else
Console.WriteLine(resp.ErrorMessage);
}
}
}
Twilio evangelist here.
The code you posted looks correct to me, but based on the Fiddler output it sounds like your getting an authentication error so I would double check that you've copy and pasted your account sid and auth token from your Twilio account dashboard correctly.
Hope that helps.

Categories