I'm using Azure Mobile Services to authorize users and am now trying to get additional user info from the providers. I have it working for all of them except Twitter. To authenticate for all the other I'm using something similar to this:
var identities = await user.GetIdentitiesAsync();
var result = new JObject();
var fb = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (fb != null)
{
var accessToken = fb.AccessToken;
result.Add("facebook", await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken));
}
Would I be able to do something like this:
var tw = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (tw != null)
{
var accessToken = tw.AccessToken;
var accessTokenSecret = tw.AccessTokenSecret;
result.Add("twitter", await
GetProviderInfo("https://api.twitter.com/1.1/account/verify_credentials.json?token=" + accessToken + "&token_secret=" + accessTokenSecret + "&consumer_key=***************" + "&consumer_secret=******************************"));
}
or would I have to do something completely different?
Woops, just found a similar question here: Twitter single url request
Yes, it is possible, but it's more work than for other providers.
This is the code for your api controller (maybe needs some refactoring)
[HttpPost]
[Route("current/identity")]
public async Task<HttpResponseMessage> GetIdentityInfo()
{
var currentUser = User as ServiceUser;
if (currentUser != null)
{
var identities = await currentUser.GetIdentitiesAsync();
var googleCredentials = identities.OfType<GoogleCredentials>().FirstOrDefault();
if (googleCredentials != null)
{
var infos = await GetGoolgeDetails(googleCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var facebookCredentials = identities.OfType<FacebookCredentials>().FirstOrDefault();
if (facebookCredentials!= null)
{
var infos = await GetFacebookDetails(facebookCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var microsoftCredentials = identities.OfType<MicrosoftAccountCredentials>().FirstOrDefault();
if (microsoftCredentials != null)
{
var infos = await GetMicrosoftDetails(microsoftCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
var twitterCredentials = identities.OfType<TwitterCredentials>().FirstOrDefault();
if (twitterCredentials != null)
{
var infos = await GetTwitterDetails(currentUser, twitterCredentials);
return Request.CreateResponse(HttpStatusCode.OK, infos);
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
private async Task<JToken> GetTwitterDetails(ServiceUser currentUser, TwitterCredentials twitterCredentials)
{
var twitterId = currentUser.Id.Split(':').Last();
var accessToken = twitterCredentials.AccessToken;
string consumerKey = ConfigurationManager.AppSettings["MS_TwitterConsumerKey"];
string consumerSecret = ConfigurationManager.AppSettings["MS_TwitterConsumerSecret"];
// Add this setting manually on your Azure Mobile Services Management interface.
// You will find the secret on your twitter app configuration
string accessTokenSecret = ConfigurationManager.AppSettings["FG_TwitterAccessTokenSecret"];
var parameters = new Dictionary<string, string>();
parameters.Add("user_id", twitterId);
parameters.Add("oauth_token", accessToken);
parameters.Add("oauth_consumer_key", consumerKey);
OAuth1 oauth = new OAuth1();
string headerString = oauth.GetAuthorizationHeaderString(
"GET", "https://api.twitter.com/1.1/users/show.json",
parameters, consumerSecret, accessTokenSecret);
var infos = await GetProviderInfo("https://api.twitter.com/1.1/users/show.json?user_id=" + twitterId, headerString);
return infos;
}
private async Task<JToken> GetMicrosoftDetails(MicrosoftAccountCredentials microsoftCredentials)
{
var accessToken = microsoftCredentials.AccessToken;
var infos = await GetProviderInfo("https://apis.live.net/v5.0/me/?method=GET&access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetFacebookDetails(FacebookCredentials facebookCredentials)
{
var accessToken = facebookCredentials.AccessToken;
var infos = await GetProviderInfo("https://graph.facebook.com/me?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetGoolgeDetails(GoogleCredentials googleCredentials)
{
var accessToken = googleCredentials.AccessToken;
var infos = await GetProviderInfo("https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + accessToken);
return infos;
}
private async Task<JToken> GetProviderInfo(string url, string oauth1HeaderString = null)
{
using (var client = new HttpClient())
{
if (oauth1HeaderString != null)
{
client.DefaultRequestHeaders.Authorization = System.Net.Http.Headers.AuthenticationHeaderValue.Parse(oauth1HeaderString);
}
var resp = await client.GetAsync(url).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
string rawInfo = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
return JToken.Parse(rawInfo);
}
}
Then you need this class to build a valid OAuth 1.0 authentication header:
(almost all of the following code ist from LinqToTwitter, https://linqtotwitter.codeplex.com)
public class OAuth1
{
const string OAUTH_VERSION = "1.0";
const string SIGNATURE_METHOD = "HMAC-SHA1";
const long UNIX_EPOC_TICKS = 621355968000000000L;
public string GetAuthorizationHeaderString(string method, string url, IDictionary<string, string> parameters, string consumerSecret, string accessTokenSecret)
{
string encodedAndSortedString = BuildEncodedSortedString(parameters);
string signatureBaseString = BuildSignatureBaseString(method, url, encodedAndSortedString);
string signingKey = BuildSigningKey(consumerSecret, accessTokenSecret);
string signature = CalculateSignature(signingKey, signatureBaseString);
string authorizationHeader = BuildAuthorizationHeaderString(encodedAndSortedString, signature);
return authorizationHeader;
}
internal void AddMissingOAuthParameters(IDictionary<string, string> parameters)
{
if (!parameters.ContainsKey("oauth_timestamp"))
parameters.Add("oauth_timestamp", GetTimestamp());
if (!parameters.ContainsKey("oauth_nonce"))
parameters.Add("oauth_nonce", GenerateNonce());
if (!parameters.ContainsKey("oauth_version"))
parameters.Add("oauth_version", OAUTH_VERSION);
if (!parameters.ContainsKey("oauth_signature_method"))
parameters.Add("oauth_signature_method", SIGNATURE_METHOD);
}
internal string BuildEncodedSortedString(IDictionary<string, string> parameters)
{
AddMissingOAuthParameters(parameters);
return
string.Join("&",
(from parm in parameters
orderby parm.Key
select parm.Key + "=" + PercentEncode(parameters[parm.Key]))
.ToArray());
}
internal virtual string BuildSignatureBaseString(string method, string url, string encodedStringParameters)
{
int paramsIndex = url.IndexOf('?');
string urlWithoutParams = paramsIndex >= 0 ? url.Substring(0, paramsIndex) : url;
return string.Join("&", new string[]
{
method.ToUpper(),
PercentEncode(urlWithoutParams),
PercentEncode(encodedStringParameters)
});
}
internal virtual string BuildSigningKey(string consumerSecret, string accessTokenSecret)
{
return string.Format(
CultureInfo.InvariantCulture, "{0}&{1}",
PercentEncode(consumerSecret),
PercentEncode(accessTokenSecret));
}
internal virtual string CalculateSignature(string signingKey, string signatureBaseString)
{
byte[] key = Encoding.UTF8.GetBytes(signingKey);
byte[] msg = Encoding.UTF8.GetBytes(signatureBaseString);
KeyedHashAlgorithm hasher = new HMACSHA1();
hasher.Key = key;
byte[] hash = hasher.ComputeHash(msg);
return Convert.ToBase64String(hash);
}
internal virtual string BuildAuthorizationHeaderString(string encodedAndSortedString, string signature)
{
string[] allParms = (encodedAndSortedString + "&oauth_signature=" + PercentEncode(signature)).Split('&');
string allParmsString =
string.Join(", ",
(from parm in allParms
let keyVal = parm.Split('=')
where parm.StartsWith("oauth") || parm.StartsWith("x_auth")
orderby keyVal[0]
select keyVal[0] + "=\"" + keyVal[1] + "\"")
.ToList());
return "OAuth " + allParmsString;
}
internal virtual string GetTimestamp()
{
long ticksSinceUnixEpoc = DateTime.UtcNow.Ticks - UNIX_EPOC_TICKS;
double secondsSinceUnixEpoc = new TimeSpan(ticksSinceUnixEpoc).TotalSeconds;
return Math.Floor(secondsSinceUnixEpoc).ToString(CultureInfo.InvariantCulture);
}
internal virtual string GenerateNonce()
{
return new Random().Next(111111, 9999999).ToString(CultureInfo.InvariantCulture);
}
internal virtual string PercentEncode(string value)
{
const string ReservedChars = #"`!##$^&*()+=,:;'?/|\[] ";
var result = new StringBuilder();
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
var escapedValue = Uri.EscapeDataString(value);
// Windows Phone doesn't escape all the ReservedChars properly, so we have to do it manually.
foreach (char symbol in escapedValue)
{
if (ReservedChars.IndexOf(symbol) != -1)
{
result.Append('%' + String.Format("{0:X2}", (int)symbol).ToUpper());
}
else
{
result.Append(symbol);
}
}
return result.ToString();
}
}
Related
I dont get any response using client.PostAsync.
I created button in xamarin forms. It should send to server some sentences(json) and return info about them(again in json).
Button code:
private async void button_Analyze_Clicked(object sender, EventArgs e)
{
Request req = new Request()
{
UserId = this.UserId,
Language = Convert.ToString(picker_Language.SelectedItem) + ".",
Text = Convert.ToString(editor1.Text)
};
string jsonStr = JsonConvert.SerializeObject(req);
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("s", jsonStr);
FormUrlEncodedContent form = new FormUrlEncodedContent(dict);
HttpResponseMessage response = await client.PostAsync(markTextUrl, form).ConfigureAwait(false);
string result = await response.Content.ReadAsStringAsync();
Answer answ = JsonConvert.DeserializeObject<Answer>(result);
answersList.Add(answ);
await DisplayAlert("", " ", "Ok");
}
Controller code:
public string MarkText(string s) //работа с запросом из приложения
{
Request req = JsonConvert.DeserializeObject<Request>(s);
if (req != null)
{
Models.Request request = new Models.Request()
{
Text = req.Text,
Lang = req.Language,
UserId = int.Parse(req.UserId)
};
AnalyzeRequest(request);
Answer answ = new Answer()
{
Language = req.Language,
Text = req.Text,
Sentences = db.Histories.Last().Text,
Labels = db.Histories.Last().Label
};
return JsonConvert.SerializeObject(answ);
}
return null;
}
Problem is this code
HttpResponseMessage response = await client.PostAsync(markTextUrl, form).ConfigureAwait(false);
never return response and it doesnt reach controller function. If I wait for this code to complete Ill get System.OperationCanceledExeption:"The operation was canceled"
I have the following code.
[HttpGet]
public async Task<List<TenantManagementWebApi.Entities.SiteCollection>> Get()
{
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
using (var context = new OfficeDevPnP.Core.AuthenticationManager().GetSharePointOnlineAuthenticatedContextTenant(tenantAdminUrl, tenant.Email, keyVaultHelper.SecretValue))
{
Tenant tenantOnline = new Tenant(context);
SPOSitePropertiesEnumerable siteProps = tenantOnline.GetSitePropertiesFromSharePoint("0", true);
context.Load(siteProps);
context.ExecuteQuery();
List<TenantManagementWebApi.Entities.SiteCollection> sites = new List<TenantManagementWebApi.Entities.SiteCollection>();
foreach (var site in siteProps)
{
if(site.Template.Contains("SITEPAGEPUBLISHING#0") || site.Template.Contains("GROUP#0"))
{
string strTemplate= default(string);
if(site.Template.Contains("SITEPAGEPUBLISHING#0"))
{
strTemplate = "CommunicationSite";
};
if (site.Template.Contains("GROUP#0"))
{
strTemplate = "Modern Team Site";
};
try
{
Guid id = Guid.NewGuid();
Entities.SiteCollection sc = new Entities.SiteCollection()
{
Id = id.ToString(),
Owner = site.Owner,
Template = strTemplate,
Title = site.Title,
Active = false,
Url = site.Url
};
var added = await siteCollectionStore.AddAsync(sc);
sites.Add(sc);
}
catch (System.Exception ex)
{
throw ex;
}
}
}
return sites;
};
}
However the following lines, I am repeating them on every method:
var tenant = await TenantHelper.GetActiveTenant();
var siteCollectionStore = CosmosStoreFactory.CreateForEntity<TenantManagementWebApi.Entities.SiteCollection>();
await siteCollectionStore.RemoveAsync(x => x.Title != string.Empty); // Removes all the entities that match the criteria
string domainUrl = tenant.TestSiteCollectionUrl;
string tenantName = domainUrl.Split('.')[0];
string tenantAdminUrl = tenantName + "-admin.sharepoint.com";
KeyVaultHelper keyVaultHelper = new KeyVaultHelper();
await keyVaultHelper.OnGetAsync(tenant.SecretIdentifier);
I will have lots of API controllers on my project
Is there an easy way (not refactor as a method), to make my code cleaner and inject the variables I need without copying and pasting every single time?
I'm trying to use service of gmail to read inbox data and to show them in my chatbot. I want to authenticate using my account of gmail. When i test the chatbot in bot emulator works fine, the problem start when i deploy on web..i cannot authenticate on web.
string jsonPath2 = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath + "client_secret_webapp.json";
UserCredential credential;
string[] Scopes = { GmailService.Scope.GmailReadonly };
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
using (var stream = new FileStream(jsonPath2, FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows for read-only access to the authenticated
// user's account, but not other types of account access.
new[] { GmailService.Scope.GmailReadonly,},
"xxxx#gmail.com",
CancellationToken.None,
new FileDataStore(this.GetType().ToString())
);
}
var gmailService = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
log.Info("ApplicationName" + ApplicationName);
var emailListRequest = gmailService.Users.Messages.List("xxxxx#gmail.com");
emailListRequest.LabelIds = "INBOX";
emailListRequest.MaxResults = 1;
emailListRequest.Q = "is:unread";
emailListRequest.IncludeSpamTrash = false;
if (emailListResponse != null && emailListResponse.Messages != null)
{
//loop through each email and get what fields you want...
foreach (var email in emailListResponse.Messages)
{
var emailInfoRequest = gmailService.Users.Messages.Get("xxxxxx#gmail.com", email.Id);
var emailInfoResponse = emailInfoRequest.Execute();
if (emailInfoResponse != null)
{
String from = "";
String date = "";
String subject = "";
String body = "";
//loop through the headers to get from,date,subject, body
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if (mParts.Name == "From")
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
body = DecodeBase64String(emailInfoResponse.Payload.Body.Data);
else
body = GetNestedBodyParts(emailInfoResponse.Payload.Parts, "");
}
}
await context.PostAsync("Email list for: Date " + date + " ::::::::::: From: " + from + " :::::::::::: Subject " + subject + " : ::::::::::::: Body : " + body + " Email.id eshte " + email.Id);
}
}
}
}
static String DecodeBase64String(string s)
{
var ts = s.Replace("-", "+");
ts = ts.Replace("_", "/");
var bc = Convert.FromBase64String(ts);
var tts = Encoding.UTF8.GetString(bc);
return tts;
}
static String GetNestedBodyParts(IList<MessagePart> part, string curr)
{
string str = curr;
if (part == null)
{
return str;
}
else
{
foreach (var parts in part)
{
if (parts.Parts == null)
{
if (parts.Body != null && parts.Body.Data != null)
{
var ts = DecodeBase64String(parts.Body.Data);
str += ts;
}
}
else
{
return GetNestedBodyParts(parts.Parts, str);
}
}
return str;
}
}
private static byte[] FromBase64ForUrlString(string base64ForUrlInput)
{
int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0 : (4 - (base64ForUrlInput.Length % 4));
StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars);
result.Append(String.Empty.PadRight(padChars, '='));
result.Replace('-', '+');
result.Replace('_', '/');
return Convert.FromBase64String(result.ToString());
}
client_id.json
{"installed":{"client_id":"xxxxxxx- yyyyyy.apps.googleusercontent.com","project_id":"reademailbot","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"yyyyyyyyyyy","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}
Edited:
I try to add the code below :
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new
ClientSecrets
{
ClientId = "xx-
xxxxxxxxx.apps.googleusercontent.com",
ClientSecret = "xxxxxxxxxxxxxxxxxxxxx-lLO"
},
new[] {
GmailService.Scope.GmailReadonly,
GmailService.Scope.MailGoogleCom,
GmailService.Scope.GmailMetadata
},
"user",
CancellationToken.None,
new
FileDataStore("Drive.Auth.Store")).Result;
var gmailService = new Google.Apis.Gmail.v1.GmailService(new
BaseClientService.Initializer()
{
HttpClientInitializer = credential,
});
But when i try on web i have still problem :
Error: redirect_uri_mismatch
The redirect URI in the request, http://127.0.0.1:52158/authorize/, does not match the ones authorized for the OAuth client. To update the authorized redirect URIs, visit: https://console.developers.google.com/apis/credentials/oauthclient/673194113721-45u2pgl6m2l74ecf13c51fmmihrsu3jh.apps.googleusercontent.com?project=673194113721
Edit 2 :
string[] Scopes = { GmailService.Scope.GmailReadonly };
string ApplicationName = "IkanbiBot";
var secrets = new ClientSecrets
{
ClientId = ConfigurationSettings.AppSettings["GMailClientId"],
ClientSecret = ConfigurationSettings.AppSettings["GMailClientSecret"]
};
var token = new Google.Apis.Auth.OAuth2.Responses.TokenResponse { RefreshToken = ConfigurationSettings.AppSettings["GmailRefreshToken"] };
var credential = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = secrets
}), "xxxx#gmail.com", token);
var gmailService = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
log4net.ILog log2 = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
await context.PostAsync("Aplication name is ..." + ApplicationName);
await context.PostAsync("secret name is ..." + secrets.ClientId + "_____ and .... " + secrets.ClientSecret + " ++++ token is " + token.RefreshToken);
var emailListRequest = gmailService.Users.Messages.List("xxxxx#gmail.com");
emailListRequest.LabelIds = "INBOX";
emailListRequest.MaxResults = 1;
emailListRequest.Q = "is:unread";
emailListRequest.IncludeSpamTrash = false;
// Get our emails
// Get our emails
var emailListResponse = emailListRequest.Execute();
//get our emails
log2.Info(emailListResponse.Messages);
await context.PostAsync("emailListResponse is ..." + emailListResponse.Messages.Count);
if (emailListResponse != null && emailListResponse.Messages != null)
{
//loop through each email and get what fields you want...
foreach (var email in emailListResponse.Messages)
{
var emailInfoRequest = gmailService.Users.Messages.Get("botikanbi#gmail.com", email.Id);
var emailInfoResponse = emailInfoRequest.Execute();
if (emailInfoResponse != null)
{
String from = "";
String date = "";
String subject = "";
String body = "";
//loop through the headers to get from,date,subject, body
foreach (var mParts in emailInfoResponse.Payload.Headers)
{
if (mParts.Name == "Date")
{
date = mParts.Value;
}
else if (mParts.Name == "From")
{
from = mParts.Value;
}
else if (mParts.Name == "Subject")
{
subject = mParts.Value;
}
if (date != "" && from != "")
{
if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
body = DecodeBase64String(emailInfoResponse.Payload.Body.Data);
else
body = GetNestedBodyParts(emailInfoResponse.Payload.Parts, "");
}
}
await context.PostAsync("Email list for: Date " + date + " ::::::::::: From: " + from + " :::::::::::: Subject " + subject + " : ::::::::::::: Body : " + body + " Email.id eshte " + email.Id);
}
}
}
}
static String DecodeBase64String(string s)
{
var ts = s.Replace("-", "+");
ts = ts.Replace("_", "/");
var bc = Convert.FromBase64String(ts);
var tts = Encoding.UTF8.GetString(bc);
return tts;
}
static String GetNestedBodyParts(IList<MessagePart> part, string curr)
{
string str = curr;
if (part == null)
{
return str;
}
else
{
foreach (var parts in part)
{
if (parts.Parts == null)
{
if (parts.Body != null && parts.Body.Data != null)
{
var ts = DecodeBase64String(parts.Body.Data);
str += ts;
}
}
else
{
return GetNestedBodyParts(parts.Parts, str);
}
}
return str;
}
}
private static byte[] FromBase64ForUrlString(string base64ForUrlInput)
{
int padChars = (base64ForUrlInput.Length % 4) == 0 ? 0 : (4 - (base64ForUrlInput.Length % 4));
StringBuilder result = new StringBuilder(base64ForUrlInput, base64ForUrlInput.Length + padChars);
result.Append(String.Empty.PadRight(padChars, '='));
result.Replace('-', '+');
result.Replace('_', '/');
return Convert.FromBase64String(result.ToString());
}
}
}
Now i'm using this code . I get answer from the bot till :
"secret name is ..." + secrets.ClientId + "_____ and .... " + secrets.ClientSecret + " ++++ token is " + token.RefreshToken
But stop when try to read email :(
Can you help me please what is going on ?
There are sevral ways to authcate to google.
moble
web
sevice account
native applications.
The client you create for each of the above authentication types is different. The code to use them is also different.
GoogleWebAuthorizationBroker.AuthorizeAsync is used to authenticate installed applications. Its wont work with a web application.
For a web application you should be following Web applications (ASP.NET MVC) and using GoogleAuthorizationCodeFlow
private static readonly IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "PUT_CLIENT_ID_HERE",
ClientSecret = "PUT_CLIENT_SECRET_HERE"
},
Scopes = new[] { GmailService.Scope.GmailReadonly },
DataStore = new FileDataStore(this.GetType().ToString()
});
redirect_uri_mismatch
In order to use the api your client must be set up corectly. In google developer console you simply need to add the proper redirect uri in the settings on your client and it will work. It must match exactly http://127.0.0.1:52158/authorize/ make sure that visual studio isnt creating random port numbers on you.
I'm getting a "Because this call is not awaited..." on
SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
Here's the button click:
private void btnCopyAllInvoices_Click(object sender, EventArgs e)
{
//sets up a list to store the incoming invoice numbers from the DB
List<string> InvoiceNums = new List<string>();
mySqlInterface.Connect();
InvoiceNums = mySqlInterface.GetNewInvoices();
//prep the visuals
lblStatus.Text = "";
InvoicePanel.Visible = true;
progressBarInvoice.Value = 0;
progressBarInvoice.Maximum = InvoiceNums.Count;
//for each invoice collected let's copy it
InvoiceNums.ForEach(delegate(string inv)
{
if (OrderDAL.CheckOrderExist(inv))
{
// the order already exist
Order myorder = new Order();
myorder = OrderDAL.GetOrder(inv);
CopyImages(myorder, true);
OrderDAL.UpdateFulfillment(string.Format("Images Copied"), inv);
}
});
//let the user know how we did
MessageBoxButtons buttons = MessageBoxButtons.OK;
string strError = string.Format("{0} Invoices copied.", InvoiceNums.Count);
MessageBox.Show(this, strError, "Copy New Invoices", buttons, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
InvoicePanel.Visible = false;
}
Here, CopyImages is called as part of the foreach loop above.
public void CopyImages(Order order, bool CopyAllInv)
{
string baseTarget = WorkSpace.Text;
string CLhotfolderTarget = string.Empty;
//check to see if the order has been photo released. If it has add "pr" to the end of the invoice number
string prInvoice = "";
if (order.Header.SignatureLine != "null" && order.Header.SignatureChecks != "null")
{
prInvoice = "pr";
}
string PackageName = null;
string CustomerName = null;
string Phone = null;
string email = null;
string PlayerInfo = null;
string PlayerName = null;
string PlayerNumber = null;
string MainEventName = null;
string MainEventCode = null;
string CLemail = null;
//go to the DB and get the info
mySqlInterface.Connect();
bool videoPackage = mySqlInterface.VideoInfo(order.Header.InvoiceNumber, out PackageName, out CustomerName, out Phone, out email, out PlayerName, out PlayerNumber, out MainEventName, out MainEventCode);
mySqlInterface.Close();
if (videoPackage)
{
if (PackageName.Contains("Video") || PackageName.Contains("Ultimate Ripken"))
{
CLemail = MainEventCode + "_" + email.Replace("#", "_").Replace(".", "_").Replace("+", "_");
PlayerInfo = PlayerName + " " + PlayerNumber;
int template_ID = 0;
if (txtCLtemplateID.Text != "")
{
template_ID = Convert.ToInt32(txtCLtemplateID.Text);
}
//we will always need a hotfolder. So let's set and create it now
CLhotfolderTarget = txtCLhotfolder.Text + "\\toUpload\\" + CLemail;
if (!System.IO.Directory.Exists(CLhotfolderTarget))
{
// create the directory
System.IO.Directory.CreateDirectory(CLhotfolderTarget);
}
int maxImages = 7;
int package_type = 2;
string[] favoritesArray = new string[maxImages];
//populate the array of images for the video
int count = 0;
foreach (Order.InvoiceImages image in order.ImageList)
{
favoritesArray[count] = image.ImageName;
count++;
}
//let's call the API and send info to CL
SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
}
}
}
public async Task SendPostAsync(string name, string email, string phone, int photo_count, string event_name, string event_id, string dir_name, int package_type, string video_text, int template_id, string[] favoritesArray)
{
string postURL = null;
string token = null;
int delivery_method = 2;
//production
postURL = "https://search.apicall.com/photographer/customer";
token = "token xxxxxxxxxxxxxxxxxxxxx";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(postURL);
client.DefaultRequestHeaders.Add("Authorization", token);
string POSTcall = JsonConvert.SerializeObject(new { name, email, phone, photo_count, event_id, event_name, dir_name, package_type, video_text, delivery_method, template_id, favorites = favoritesArray });
//Send string to log file for debug
WriteLog(POSTcall);
StringContent stringContent = new StringContent(POSTcall, UnicodeEncoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(new Uri(postURL), stringContent);
string POSTresponse = await response.Content.ReadAsStringAsync();
WriteLog(POSTresponse);
//simplified output for debug
if (POSTresponse.Contains("error") && POSTresponse.Contains("false"))
{
lblStatus.Text = "Error Sending to CL";
}
else
{
lblStatus.Text = "Successfully added to CL";
}
}
I have an await on the HttpResponseMessage response = await client.PostAsync
If I run this one at a time, it works. But when I run this through a loop and there are a bunch back to back, I think the PostAsyncs are getting stepped on. I'm missing entires in the WriteLog.
It seems I need to do the async/awaits further upstream, right? This way I can run the whole method.
Referencing Async/Await - Best Practices in Asynchronous Programming, event handlers allow async void so refactor the code to be async all the way through.
refactor CopyImages to await the posting of the data
public async Task CopyImages(Order order, bool CopyAllInv) {
//...omitted for brevity
if (videoPackage) {
if (PackageName.Contains("Video") || PackageName.Contains("Ultimate Ripken")) {
//...omitted for brevity
await SendPostAsync(CustomerName, email, Phone, maxImages, MainEventName, MainEventCode, CLemail, package_type, PlayerInfo, template_ID, favoritesArray);
}
}
}
And update the event handler
private async void btnCopyAllInvoices_Click(object sender, EventArgs e) {
//sets up a list to store the incoming invoice numbers from the DB
List<string> InvoiceNums = new List<string>();
mySqlInterface.Connect();
InvoiceNums = mySqlInterface.GetNewInvoices();
//prep the visuals
lblStatus.Text = "";
InvoicePanel.Visible = true;
progressBarInvoice.Value = 0;
progressBarInvoice.Maximum = InvoiceNums.Count;
//for each invoice collected let's copy it
foreach(string inv in InvoiceNums) {
if (OrderDAL.CheckOrderExist(inv)) {
// the order already exist
Order myorder = OrderDAL.GetOrder(inv);
await CopyImages(myorder, true);
OrderDAL.UpdateFulfillment(string.Format("Images Copied"), inv);
}
}
//let the user know how we did
MessageBoxButtons buttons = MessageBoxButtons.OK;
string strError = string.Format("{0} Invoices copied.", InvoiceNums.Count);
MessageBox.Show(this, strError, "Copy New Invoices", buttons, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
InvoicePanel.Visible = false;
}
I would also advise against creating a HttpClient for each post request. Extract that out and use a single client.
static Lazy<HttpClient> httpClient = new Lazy<HttpClient>(() => {
var postURL = "https://search.apicall.com/photographer/customer";
var token = "token xxxxxxxxxxxxxxxxxxxxx";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(postURL);
client.DefaultRequestHeaders.Add("Authorization", token);
return client
});
public async Task SendPostAsync(string name, string email, string phone, int photo_count, string event_name, string event_id, string dir_name, int package_type, string video_text, int template_id, string[] favoritesArray)
{
var postURL = "https://search.apicall.com/photographer/customer";
int delivery_method = 2;
string POSTcall = JsonConvert.SerializeObject(new { name, email, phone, photo_count, event_id, event_name, dir_name, package_type, video_text, delivery_method, template_id, favorites = favoritesArray });
//Send string to log file for debug
WriteLog(POSTcall);
StringContent stringContent = new StringContent(POSTcall, UnicodeEncoding.UTF8, "application/json");
HttpResponseMessage response = await httpClient.Value.PostAsync(new Uri(postURL), stringContent);
string POSTresponse = await response.Content.ReadAsStringAsync();
WriteLog(POSTresponse);
//simplified output for debug
if (POSTresponse.Contains("error") && POSTresponse.Contains("false")) {
lblStatus.Text = "Error Sending to CL";
} else {
lblStatus.Text = "Successfully added to CL";
}
}
Solved!!! - See last edit.
In my MVC app I make calls out to a Web API service with HMAC Authentication Filterign. My Get (GetMultipleItemsRequest) works, but my Post does not. If I turn off HMAC authentication filtering all of them work. I'm not sure why the POSTS do not work, but the GETs do.
I make the GET call from my code like this (this one works):
var productsClient = new RestClient<Role>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
var getManyResult = productsClient.GetMultipleItemsRequest("api/Role").Result;
I make the POST call from my code like this (this one only works when I turn off HMAC):
private RestClient<Profile> profileClient = new RestClient<Profile>(System.Configuration.ConfigurationManager.AppSettings["WebApiUrl"],
"xxxxxxxxxxxxxxx", true);
[HttpPost]
public ActionResult ProfileImport(IEnumerable<HttpPostedFileBase> files)
{
//...
var postResult = profileClient.PostRequest("api/Profile", newProfile).Result;
}
My RestClient builds like this:
public class RestClient<T> where T : class
{
//...
private void SetupClient(HttpClient client, string methodName, string apiUrl, T content = null)
{
const string secretTokenName = "SecretToken";
client.BaseAddress = new Uri(_baseAddress);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if (_hmacSecret)
{
client.DefaultRequestHeaders.Date = DateTime.UtcNow;
var datePart = client.DefaultRequestHeaders.Date.Value.UtcDateTime.ToString(CultureInfo.InvariantCulture);
var fullUri = _baseAddress + apiUrl;
var contentMD5 = "";
if (content != null)
{
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json); // <--- Javascript serialized version is hashed
}
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
var hmac = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
client.DefaultRequestHeaders.Add(secretTokenName, hmac);
}
else if (!string.IsNullOrWhiteSpace(_sharedSecretName))
{
var sharedSecretValue = ConfigurationManager.AppSettings[_sharedSecretName];
client.DefaultRequestHeaders.Add(secretTokenName, sharedSecretValue);
}
}
public async Task<T[]> GetMultipleItemsRequest(string apiUrl)
{
T[] result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "GET", apiUrl);
var response = await client.GetAsync(apiUrl).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T[]>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
public async Task<T> PostRequest(string apiUrl, T postObject)
{
T result = null;
try
{
using (var client = new HttpClient())
{
SetupClient(client, "POST", apiUrl, postObject);
var response = await client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter()).ConfigureAwait(false); //<--- not javascript formatted
response.EnsureSuccessStatusCode();
await response.Content.ReadAsStringAsync().ContinueWith((Task<string> x) =>
{
if (x.IsFaulted)
throw x.Exception;
result = JsonConvert.DeserializeObject<T>(x.Result);
});
}
}
catch (HttpRequestException exception)
{
if (exception.Message.Contains("401 (Unauthorized)"))
{
}
else if (exception.Message.Contains("403 (Forbidden)"))
{
}
}
catch (Exception)
{
}
return result;
}
//...
}
My Web API Controller is defined like this:
[SecretAuthenticationFilter(SharedSecretName = "xxxxxxxxxxxxxxx", HmacSecret = true)]
public class ProfileController : ApiController
{
[HttpPost]
[ResponseType(typeof(Profile))]
public IHttpActionResult PostProfile(Profile Profile)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
GuidValue = Guid.NewGuid();
Resource res = new Resource();
res.ResourceId = GuidValue;
var data23 = Resourceservices.Insert(res);
Profile.ProfileId = data23.ResourceId;
_profileservices.Insert(Profile);
return CreatedAtRoute("DefaultApi", new { id = Profile.ProfileId }, Profile);
}
}
Here is some of what SecretAuthenticationFilter does:
//now try to read the content as string
string content = actionContext.Request.Content.ReadAsStringAsync().Result;
var contentMD5 = content == "" ? "" : Hashing.GetHashMD5OfString(content); //<-- Hashing the non-JavaScriptSerialized
var datePart = "";
var requestDate = DateTime.Now.AddDays(-2);
if (actionContext.Request.Headers.Date != null)
{
requestDate = actionContext.Request.Headers.Date.Value.UtcDateTime;
datePart = requestDate.ToString(CultureInfo.InvariantCulture);
}
var methodName = actionContext.Request.Method.Method;
var fullUri = actionContext.Request.RequestUri.ToString();
var messageRepresentation =
methodName + "\n" +
contentMD5 + "\n" +
datePart + "\n" +
fullUri;
var expectedValue = Hashing.GetHashHMACSHA256OfString(messageRepresentation, sharedSecretValue);
// Are the hmacs the same, and have we received it within +/- 5 mins (sending and
// receiving servers may not have exactly the same time)
if (messageSecretValue == expectedValue
&& requestDate > DateTime.UtcNow.AddMinutes(-5)
&& requestDate < DateTime.UtcNow.AddMinutes(5))
goodRequest = true;
Any idea why HMAC doesn't work for the POST?
EDIT:
When SecretAuthenticationFilter tries to compare the HMAC sent, with what it thinks the HMAC should be they don't match. The reason is the MD5Hash of the content doesn't match the MD5Hash of the received content. The RestClient hashes the content using a JavaScriptSerializer.Serialized version of the content, but then the PostRequest passes the object as JsonMediaTypeFormatted.
These two types don't get formatted the same. For instance, the JavaScriptSerializer give's us dates like this:
\"EnteredDate\":\"\/Date(1434642998639)\/\"
The passed content has dates like this:
\"EnteredDate\":\"2015-06-18T11:56:38.6390407-04:00\"
I guess I need the hash to use the same data that's passed, so the Filter on the other end can confirm it correctly. Thoughts?
EDIT:
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the sent content (formatted via JSON) will match the hashed content.
I was not the person who wrote this code originally. :)
Found the answer, I needed to change the SetupClient code from using this line:
var json = new JavaScriptSerializer().Serialize(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
To using this:
var json = JsonConvert.SerializeObject(content);
contentMD5 = Hashing.GetHashMD5OfString(json);
Now the content used for the hash will be formatted as JSON and will match the sent content (which is also formatted via JSON).