I am new to Asp.Net Identity 2.0. We have implemented functionality to lock the account after 5 successful invalid attempts. But currently the account is not getting locked out even though user reaches the max invalid attempts.
The userManager.IsLockedOutAsync(user1.Id) is always returning false. Not sure what is the issue here.
Below is my code. Could you please help me out if I am doing anything wrong here.
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
var user1 = await userManager.FindByNameAsync(context.UserName);
if (user1 != null)
{
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
// When a user is lockedout, this check is done to ensure that even if the credentials are valid
// the user can not login until the lockout duration has passed
if (await userManager.IsLockedOutAsync(user1.Id))
{
context.SetError("", string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", userManager.MaxFailedAccessAttemptsBeforeLockout.ToString()));
return;
}
// if user is subject to lockouts and the credentials are invalid
// record the failure and check if user is lockedout and display message, otherwise,
// display the number of attempts remaining before lockout
else if (await userManager.GetLockoutEnabledAsync(user1.Id) && user == null)
{
// Record the failure which also may cause the user to be locked out
await userManager.AccessFailedAsync(user1.Id);
string message;
if (await userManager.IsLockedOutAsync(user1.Id))
{
message = string.Format("Your account has been locked out for {0} minutes due to multiple failed login attempts.", userManager.MaxFailedAccessAttemptsBeforeLockout.ToString());
}
else
{
int accessFailedCount = await userManager.GetAccessFailedCountAsync(user1.Id);
int attemptsLeft =
Convert.ToInt32(
userManager.MaxFailedAccessAttemptsBeforeLockout.ToString()) - accessFailedCount;
message = string.Format(
"Invalid credentials. You have {0} more attempt(s) before your account gets locked out.", attemptsLeft);
}
context.SetError("", message);
return;
}
else if (user == null)
{
context.SetError("invalid_grant", "The User ID or Password is incorrect.");
return;
}
else if (!user.IsApproved)
{
context.SetError("Not Approved", "The user has to be approved manually by Admin.");
return;
}
else
{
DataController datacontroller = new DataController();
Contact vendor = datacontroller.CheckVendorIsActive(user.AccountNumber);
if (!string.IsNullOrEmpty(vendor.ApplicationUser.AccountNumber) && vendor.Description.ToLower().Equals("false"))
{
context.SetError("Inactive User", "Sorry! Your account status is inactive. Please contact helpdesk for more support.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
List<Claim> roles = oAuthIdentity.Claims.Where(c => c.Type == ClaimTypes.Role).ToList();
AuthenticationProperties properties = CreateProperties(user.UserName, Newtonsoft.Json.JsonConvert.SerializeObject(roles.Select(x => x.Value)), user.LastLoginDate.ToString(), user.AccountNumber, user.FirstName, user.LastName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
user.LastLoginDate = DateTime.Now;
await userManager.UpdateAsync(user);
}
}
else if (user1 == null)
{
context.SetError("invalid_grant", "The User ID or Password is incorrect.");
return;
}
}
Related
I am using the bot framework of V4. using an API call, I'm trying to get data and display it to the user and I defined a custom method to capture data from API and preprocess it, before sending it to the user through waterfall dialog and I made this method async and also using await where it is being called.
There are 2 scenarios where I'm facing the issue -
When two users send questions at an instance one of the responses captured as null instead of value acquired from API.
We are using the suggestive card to display result and when the user clicks on button cross-session was observed sporadically.
Help in this area is much appreciated.
The custom method defined to make API call and get data:
public static async Task<string>
GetEPcallsDoneAsync(ConversationData conversationData)
{
LogWriter.LogWrite("Info: Acessing end-points");
string responseMessage = null;
try
{
conversationData.fulFillmentMap = await
AnchorUtil.GetFulfillmentAsync(xxxxx);//response from API call to get data
if (conversationData.fulFillmentMap == null || (conversationData.fulFillmentMap.ContainsKey("status") && conversationData.fulFillmentMap["status"].ToString() != "200"))
{
responseMessage = "Sorry, something went wrong. Please try again later!";
}
else
{
conversationData.NLGresultMap = await
AnchorUtil.GetNLGAsync(conversationData.fulFillmentMap ,xxxx);//API call to get response to be displayed
if (conversationData.errorCaptureDict.ContainsKey("fulfillmentError") || conversationData.NLGresultMap.ContainsKey("NLGError"))
{
responseMessage = "Sorry, something went wrong:( Please try again later!!!";
}
else
{
responseMessage = FormatDataResponse(conversationData.NLGresultMap["REPLY"].ToString()); //response message
}
}
return responseMessage;
}
catch (HttpRequestException e)
{
LogWriter.LogWrite("Error: " + e.Message);
System.Console.WriteLine("Error: " + e.Message);
return null;
}
}
And the waterfall step of dialog class where the above function is being called:
private async Task<DialogTurnResult> DoProcessInvocationStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
conversationData.index = 0; //var for some other purpose
conversationData.result = await
AnchorUtil.GetEPcallsDoneAsync(conversationData);
await _conversationStateAccessor.SetAsync(stepContext.Context, conversationData, cancellationToken);
return await stepContext.NextAsync(cancellationToken);
}
ConversationData contains variables that are required to process data through waterfall dialogs, values to the object has been set and accessed through accessor as below in each step:
In a dialog class,
public class TopLevelDialog : ComponentDialog
{
private readonly IStatePropertyAccessor<ConversationData> _conversationStateAccessor;
ConversationData conversationData;
public TopLevelDialog(ConversationState conversationState)
: base(nameof(TopLevelDialog))
{
_conversationStateAccessor = conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new ReviewSelectionDialog(conversationState));
AddDialog(new ESSelectionDialog());
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
StartSelectionStepAsync,
GetESResultStep,
DoProcessInvocationStep,
ResultStepAsync,
IterationStepAsync
}));
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> StartSelectionStepAsync (WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
conversationData = await _conversationStateAccessor.GetAsync(stepContext.Context, () => new ConversationData());
//code for functionality
await _conversationStateAccessor.SetAsync(stepContext.Context, conversationData, cancellationToken);
return await stepContext.NextAsync(null, cancellationToken);
}
//other dialog steps
}
Both of your issues likely stem from the same thing. You can't declare conversationData as a class-level property. You'll run into concurrency issues like this as every user will overwrite the conversationData for every other user. You must re-declare conversationData in each step function.
For example,
User A starts the waterfall dialog and gets half of the way through. conversationData is correct at this point and represents exactly what it should.
Now User B starts a dialog. At the StartSelectionStepAsync, they just reset conversationData for everybody because of conversationData = await _conversationStateAccessor.GetAsync(stepContext.Context, () => new ConversationData()); and all users share the same conversationData because of ConversationData conversationData;.
So now, when User A continues their conversation, conversationData will be null/empty.
How State Should Be Saved
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { Channels, MessageFactory } = require('botbuilder');
const {
AttachmentPrompt,
ChoiceFactory,
ChoicePrompt,
ComponentDialog,
ConfirmPrompt,
DialogSet,
DialogTurnStatus,
NumberPrompt,
TextPrompt,
WaterfallDialog
} = require('botbuilder-dialogs');
const { UserProfile } = require('../userProfile');
const ATTACHMENT_PROMPT = 'ATTACHMENT_PROMPT';
const CHOICE_PROMPT = 'CHOICE_PROMPT';
const CONFIRM_PROMPT = 'CONFIRM_PROMPT';
const NAME_PROMPT = 'NAME_PROMPT';
const NUMBER_PROMPT = 'NUMBER_PROMPT';
const USER_PROFILE = 'USER_PROFILE';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
/**
* This is a "normal" dialog, where userState is stored properly using the accessor, this.userProfile.
* In this dialog example, we create the userProfile using the accessor in the first step, transportStep.
* We then pass prompt results through the remaining steps using step.values.
* In the final step, summaryStep, we save the userProfile using the accessor.
*/
class UserProfileDialogNormal extends ComponentDialog {
constructor(userState) {
super('userProfileDialogNormal');
this.userProfileAccessor = userState.createProperty(USER_PROFILE);
this.addDialog(new TextPrompt(NAME_PROMPT));
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new NumberPrompt(NUMBER_PROMPT, this.agePromptValidator));
this.addDialog(new AttachmentPrompt(ATTACHMENT_PROMPT, this.picturePromptValidator));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.transportStep.bind(this),
this.nameStep.bind(this),
this.nameConfirmStep.bind(this),
this.ageStep.bind(this),
this.pictureStep.bind(this),
this.confirmStep.bind(this),
this.saveStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
/**
* The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system.
* If no dialog is active, it will start the default dialog.
* #param {*} turnContext
* #param {*} accessor
*/
async run(turnContext, accessor) {
const dialogSet = new DialogSet(accessor);
dialogSet.add(this);
const dialogContext = await dialogSet.createContext(turnContext);
const results = await dialogContext.continueDialog();
if (results.status === DialogTurnStatus.empty) {
await dialogContext.beginDialog(this.id);
}
}
async transportStep(step) {
// Get the userProfile if it exists, or create a new one if it doesn't.
const userProfile = await this.userProfileAccessor.get(step.context, new UserProfile());
// Pass the userProfile through step.values.
// This makes it so we don't have to call this.userProfileAccessor.get() in every step.
step.values.userProfile = userProfile;
// Skip this step if we already have the user's transport.
if (userProfile.transport) {
// ChoicePrompt results will show in the next step with step.result.value.
// Since we don't need to prompt, we can pass the ChoicePrompt result manually.
return await step.next({ value: userProfile.transport });
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Running a prompt here means the next WaterfallStep will be run when the user's response is received.
return await step.prompt(CHOICE_PROMPT, {
prompt: 'Please enter your mode of transport.',
choices: ChoiceFactory.toChoices(['Car', 'Bus', 'Bicycle'])
});
}
async nameStep(step) {
// Retrieve the userProfile from step.values.
const userProfile = step.values.userProfile;
// Set the transport property of the userProfile.
userProfile.transport = step.result.value;
// Pass the userProfile through step.values.
// This makes it so we don't have to call this.userProfileAccessor.get() in every step.
step.values.userProfile = userProfile;
// Skip the prompt if we already have the user's name.
if (userProfile.name) {
// We pass in a skipped bool so we know whether or not to send messages in the next step.
return await step.next({ value: userProfile.name, skipped: true });
}
return await step.prompt(NAME_PROMPT, 'Please enter your name.');
}
async nameConfirmStep(step) {
// Retrieve the userProfile from step.values and set the name property
const userProfile = step.values.userProfile;
// If userState is working correctly, we'll have userProfile.transport from the previous step.
if (!userProfile || !userProfile.transport) {
throw new Error(`transport property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
}
// Text prompt results normally end up in step.result, but if we skipped the prompt, it will be in step.result.value.
userProfile.name = step.result.value || step.result;
// step.values.userProfile.name is already set by reference, so there's no need to set it again to pass it to the next step.
// We can send messages to the user at any point in the WaterfallStep. Only do this if we didn't skip the prompt.
if (!step.result.skipped) {
await step.context.sendActivity(`Thanks ${ step.result }.`);
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
// Skip the prompt if we already have the user's age.
if (userProfile.age) {
return await step.next('yes');
}
return await step.prompt(CONFIRM_PROMPT, 'Do you want to give your age?', ['yes', 'no']);
}
async ageStep(step) {
// Retrieve the userProfile from step.values
const userProfile = step.values.userProfile;
// If userState is working correctly, we'll have userProfile.name from the previous step.
if (!userProfile || !userProfile.name) {
throw new Error(`name property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
}
// Skip the prompt if we already have the user's age.
if (userProfile.age) {
// We pass in a skipped bool so we know whether or not to send messages in the next step.
return await step.next({ value: userProfile.age, skipped: true });
}
if (step.result) {
// User said "yes" so we will be prompting for the age.
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
const promptOptions = { prompt: 'Please enter your age.', retryPrompt: 'The value entered must be greater than 0 and less than 150.' };
return await step.prompt(NUMBER_PROMPT, promptOptions);
} else {
// User said "no" so we will skip the next step. Give -1 as the age.
return await step.next(-1);
}
}
async pictureStep(step) {
// Retrieve the userProfile from step.values and set the age property
const userProfile = step.values.userProfile;
// We didn't set any additional properties on userProfile in the previous step, so no need to check for them here.
// Confirm prompt results normally end up in step.result, but if we skipped the prompt, it will be in step.result.value.
userProfile.age = step.result.value || step.result;
// step.values.userProfile.age is already set by reference, so there's no need to set it again to pass it to the next step.
if (!step.result.skipped) {
const msg = userProfile.age === -1 ? 'No age given.' : `I have your age as ${ userProfile.age }.`;
// We can send messages to the user at any point in the WaterfallStep. Only send it if we didn't skip the prompt.
await step.context.sendActivity(msg);
}
// Skip the prompt if we already have the user's picture.
if (userProfile.picture) {
return await step.next(userProfile.picture);
}
if (step.context.activity.channelId === Channels.msteams) {
// This attachment prompt example is not designed to work for Teams attachments, so skip it in this case
await step.context.sendActivity('Skipping attachment prompt in Teams channel...');
return await step.next(undefined);
} else {
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
var promptOptions = {
prompt: 'Please attach a profile picture (or type any message to skip).',
retryPrompt: 'The attachment must be a jpeg/png image file.'
};
return await step.prompt(ATTACHMENT_PROMPT, promptOptions);
}
}
async confirmStep(step) {
// Retrieve the userProfile from step.values and set the picture property
const userProfile = step.values.userProfile;
// If userState is working correctly, we'll have userProfile.age from the previous step.
if (!userProfile || !userProfile.age) {
throw new Error(`age property does not exist in userProfile.\nuserProfile:\n ${ JSON.stringify(userProfile) }`);
}
userProfile.picture = (step.result && typeof step.result === 'object' && step.result[0]) || 'no picture provided';
// step.values.userProfile.picture is already set by reference, so there's no need to set it again to pass it to the next step.
let msg = `I have your mode of transport as ${ userProfile.transport } and your name as ${ userProfile.name }`;
if (userProfile.age !== -1) {
msg += ` and your age as ${ userProfile.age }`;
}
msg += '.';
await step.context.sendActivity(msg);
if (userProfile.picture && userProfile.picture !== 'no picture provided') {
try {
await step.context.sendActivity(MessageFactory.attachment(userProfile.picture, 'This is your profile picture.'));
} catch (err) {
await step.context.sendActivity('A profile picture was saved but could not be displayed here.');
}
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog.
return await step.prompt(CONFIRM_PROMPT, { prompt: 'Would you like me to save this information?' });
}
async saveStep(step) {
if (step.result) {
// Get the current profile object from user state.
const userProfile = step.values.userProfile;
// Save the userProfile to userState.
await this.userProfileAccessor.set(step.context, userProfile);
await step.context.sendActivity('User Profile Saved.');
} else {
// Ensure the userProfile is cleared
await this.userProfileAccessor.set(step.context, {});
await step.context.sendActivity('Thanks. Your profile will not be kept.');
}
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return await step.endDialog();
}
async agePromptValidator(promptContext) {
// This condition is our validation rule. You can also change the value at this point.
return promptContext.recognized.succeeded && promptContext.recognized.value > 0 && promptContext.recognized.value < 150;
}
async picturePromptValidator(promptContext) {
if (promptContext.recognized.succeeded) {
var attachments = promptContext.recognized.value;
var validImages = [];
attachments.forEach(attachment => {
if (attachment.contentType === 'image/jpeg' || attachment.contentType === 'image/png') {
validImages.push(attachment);
}
});
promptContext.recognized.value = validImages;
// If none of the attachments are valid images, the retry prompt should be sent.
return !!validImages.length;
} else {
await promptContext.context.sendActivity('No attachments received. Proceeding without a profile picture...');
// We can return true from a validator function even if Recognized.Succeeded is false.
return true;
}
}
}
module.exports.UserProfileDialogNormal = UserProfileDialogNormal;
This article explains if anyone wants to integrate mobile app with Salesforce App and require to authenticate the app with salesforce OAuth
iOS Implementation-
Step 1: To implement Deep Link define URL Schemes in info.plist according to connected app : The callback URL
Defining URL Schemes
Step 2- Implement 'OpenUrl' method in AppDelegate to receive the deep link call in your app
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
var deepLinkURL = url;
url = null;
App myApp = App.Current as myApp.App;
if (myApp != null && deepLinkURL != null)
{
LandingPage.DeepLinkURL = deepLinkURL;
}
return true;
}
Step 3- Now you have to check whether user is coming first time or already authenticated:
public async static Task HandleDeepLink()
{
//await SecureStorage.SetAsync(AppConstant.userId, "");
/* Deep Link
* Check if user is same
* user is already loggedIn
* Token expired
*/
try
{
string data = DeepLinkURL.ToString();
if (data.Contains("userId"))
{
// extract and save all the parameters
userId = HttpUtility.ParseQueryString(DeepLinkURL.Query).Get("userId");
// if user not authenticated OR if different user
isSameAsLastUser = await oAuth.ValidateUser(userId);
if (isSameAsLastUser)
{
// is already logged in
var isUserLoggedIn = await oAuth.IsUserLoggedIn();
if (isUserLoggedIn)
{
// navigate to scan
await SecureStorage.SetAsync(AppConstant.uniqueId, uniqueId);
Application.Current.MainPage = new NavigationPage(new MainPage());
}
else
{
Application.Current.MainPage = new NavigationPage(new Authentication());
}
}
else
{
// clear previous values in secure storage
AppConfig.SaveSFParameters("false");
Application.Current.MainPage = new NavigationPage(new Authentication());
}
}
// Handle oAuth
//Extract the Code
else if (data.Contains("code"))
{
var code = HttpUtility.ParseQueryString(DeepLinkURL.Query).Get("code");
/*Exchange the code for an access token
* Save token
* LoggedInTrue
*/
if (CrossConnectivity.Current.IsConnected)
{
var authInfo = await oAuth.GetAccessToken(code);
Console.WriteLine("auth token - " + authInfo.AccessToken);
oAuth.SaveAuthInfo(authInfo);
// save user info | set user logged in to true
AppConfig.SaveSFParameters(userId,"true");
// retrieve all the parameters and pass here
Application.Current.MainPage = new NavigationPage(new MainPage(userId));
}
else
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.DisplayAlert(AppConstant.Alert, Resource.Resources.AlertMissingConnectivity, AppConstant.Ok);
});
}
}
else if (data.Contains("error"))
{
var error = HttpUtility.ParseQueryString(DeepLinkURL.Query).Get("error");
//TODO: where the user should be redirected in case of OAuth error
}
}
catch(Exception ex)
{
Device.BeginInvokeOnMainThread(() =>
{
Application.Current.MainPage.DisplayAlert("Alert", "There is some error while redirecting user to scan app, Please contact administrator", "Ok");
});
}
}
}
public partial class Authentication : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
if (CrossConnectivity.Current.IsConnected)
{
var authUrl = Common.FormatAuthUrl(AppConfiguration.authEndPointURL,
ResponseTypes.Code, AppConfiguration.clientId,
HttpUtility.UrlEncode(AppConfiguration.redirectUri));
Browser.OpenAsync(new Uri(authUrl), BrowserLaunchMode.SystemPreferred);
}
}
}
Step 4 - Once user is authenticated successfully salesforce returns the authorization code
Step 5- Now you have to make salesforce API call to retrieve the access token by supplying the authorization code
Step 6- Once you receive the access toekn then save access token, refresh token in your app using 'Xamarin SecureStorage'
async public static void SaveAuthInfo(AuthToken authInfo)
{
try
{
await SecureStorage.SetAsync("oauth_token", authInfo.AccessToken);
await SecureStorage.SetAsync("oauth_Id", authInfo.Id);
await SecureStorage.SetAsync("oauth_InstanceUrl", authInfo.InstanceUrl);
await SecureStorage.SetAsync("oauth_RefreshToken", authInfo.RefreshToken);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
// Possible that device doesn't support secure storage on device.
}
}
Step 7 - Now redirect user to landing page
I have used below code for showing a welcome message to user.
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
string replyMessage = string.Empty;
replyMessage = Responses.Greeting;
return message.CreateReply(replyMessage);
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
Below method is used to call HandleSystemMessage, if activity type is not message.
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
string reply = "";
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
stLuis = await LuisHelper.ParseUserInput(activity.Text);
string userResponse = activity.Text.ToLower();
switch (stLuis.topScoringIntent.intent)
{
case "Greetings":
reply = Responses.Greeting;
break;
case "None":
reply = Responses.None;
break;
default:
break;
}
}
if (reply != "")
await connector.Conversations.ReplyToActivityAsync(activity.CreateReply(reply));
}
else
{
var reply1 = HandleSystemMessage(activity);
if (reply1 != null)
await connector.Conversations.ReplyToActivityAsync(reply1);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
This code works with Skype. But when I add same bot in Microsoft teams, it doesn't show a welcome message.
By now (2016-12-30) MSFT Teams does not send any message at all when you add a bot to "contact list". This is a known limitation and to be addressed in the nearest future, as MSFT guys say.
In the meantime to get a ConversationUpdate message to the bot, the user will have to first initiate a conversation with the bot.
As a workaround you can handle special text sent from user, like "start", or just the very first incoming message, if your bot is stateful enough.
This is a fairly long piece of code but I am getting nowhere with this and cannot see any issues, although I am new to using notification hubs. I am trying to register for targeted notifications (the logged on user) using the notification hub in Azure. After the registration, a test notification is sent.
The issue I am having is that sometimes the notification is sent to the device, and sometimes it is not. It mostly isn't but occasionally when I step through the code on the server, i will get the notification on the emulator come through. Once when I deployed the app to my phone the notification came though on the emulator! I cannot discover a pattern.
My Controller class looks like this;
private NotificationHelper hub;
public RegisterController()
{
hub = NotificationHelper.Instance;
}
public async Task<RegistrationDescription> Post([FromBody]JObject registrationCall)
{
var obj = await hub.Post(registrationCall);
return obj;
}
And the helper class (which is used elsewhere so is not directly in the controller) looks like this;
public static NotificationHelper Instance = new NotificationHelper();
public NotificationHubClient Hub { get; set; }
// Create the client in the constructor.
public NotificationHelper()
{
var cn = "<my-cn>";
Hub = NotificationHubClient.CreateClientFromConnectionString(cn, "<my-hub>");
}
public async Task<RegistrationDescription> Post([FromBody] JObject registrationCall)
{
// Get the registration info that we need from the request.
var platform = registrationCall["platform"].ToString();
var installationId = registrationCall["instId"].ToString();
var channelUri = registrationCall["channelUri"] != null
? registrationCall["channelUri"].ToString()
: null;
var deviceToken = registrationCall["deviceToken"] != null
? registrationCall["deviceToken"].ToString()
: null;
var userName = HttpContext.Current.User.Identity.Name;
// Get registrations for the current installation ID.
var regsForInstId = await Hub.GetRegistrationsByTagAsync(installationId, 100);
var updated = false;
var firstRegistration = true;
RegistrationDescription registration = null;
// Check for existing registrations.
foreach (var registrationDescription in regsForInstId)
{
if (firstRegistration)
{
// Update the tags.
registrationDescription.Tags = new HashSet<string>() {installationId, userName};
// We need to handle each platform separately.
switch (platform)
{
case "windows":
var winReg = registrationDescription as MpnsRegistrationDescription;
winReg.ChannelUri = new Uri(channelUri);
registration = await Hub.UpdateRegistrationAsync(winReg);
break;
case "ios":
var iosReg = registrationDescription as AppleRegistrationDescription;
iosReg.DeviceToken = deviceToken;
registration = await Hub.UpdateRegistrationAsync(iosReg);
break;
}
updated = true;
firstRegistration = false;
}
else
{
// We shouldn't have any extra registrations; delete if we do.
await Hub.DeleteRegistrationAsync(registrationDescription);
}
}
// Create a new registration.
if (!updated)
{
switch (platform)
{
case "windows":
registration = await Hub.CreateMpnsNativeRegistrationAsync(channelUri,
new string[] {installationId, userName});
break;
case "ios":
registration = await Hub.CreateAppleNativeRegistrationAsync(deviceToken,
new string[] {installationId, userName});
break;
}
}
// Send out a test notification.
await SendNotification(string.Format("Test notification for {0}", userName), userName);
return registration;
And finally, my SendNotification method is here;
internal async Task SendNotification(string notificationText, string tag)
{
try
{
var toast = PrepareToastPayload("<my-hub>", notificationText);
// Send a notification to the logged-in user on both platforms.
await NotificationHelper.Instance.Hub.SendMpnsNativeNotificationAsync(toast, tag);
//await hubClient.SendAppleNativeNotificationAsync(alert, tag);
}
catch (ArgumentException ex)
{
// This is expected when an APNS registration doesn't exist.
Console.WriteLine(ex.Message);
}
}
I suspect the issue is in my phone client code, which is here and SubscribeToService is called immediately after WebAPI login;
public void SubscribeToService()
{
_channel = HttpNotificationChannel.Find("mychannel");
if (_channel == null)
{
_channel = new HttpNotificationChannel("mychannel");
_channel.Open();
_channel.BindToShellToast();
}
_channel.ChannelUriUpdated += async (o, args) =>
{
var hub = new NotificationHub("<my-hub>", "<my-cn>");
await hub.RegisterNativeAsync(args.ChannelUri.ToString());
await RegisterForMessageNotificationsAsync();
};
}
public async Task RegisterForMessageNotificationsAsync()
{
using (var client = GetNewHttpClient(true))
{
// Get the info that we need to request registration.
var installationId = LocalStorageManager.GetInstallationId(); // a new Guid
var registration = new Dictionary<string, string>()
{
{"platform", "windows"},
{"instId", installationId},
{"channelUri", _channel.ChannelUri.ToString()}
};
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(ApiUrl + "api/Register/RegisterForNotifications"));
request.Content = new StringContent(JsonConvert.SerializeObject(registration), Encoding.UTF8, "application/json");
string message;
try
{
HttpResponseMessage response = await client.SendAsync(request);
message = await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
message = ex.Message;
}
_registrationId = message;
}
}
Any help would be greatly appriciated as I have been stuck on this now for days! I know this is a lot of code to paste up here but it is all relevant.
Thanks,
EDIT: The SubscribeToService() method is called when the user logs in and authenticates with the WebAPI. The method is here;
public async Task<User> SendSubmitLogonAsync(LogonObject lo)
{
_logonObject = lo;
using (var client = GetNewHttpClient(false))
{
var logonString = String.Format("grant_type=password&username={0}&password={1}", lo.username, lo.password);
var sc = new StringContent(logonString, Encoding.UTF8);
var response = await client.PostAsync("Token", sc);
if (response.IsSuccessStatusCode)
{
_logonResponse = await response.Content.ReadAsAsync<TokenResponseModel>();
var userInfo = await GetUserInfoAsync();
if (_channel == null)
SubscribeToService();
else
await RegisterForMessageNotificationsAsync();
return userInfo;
}
// ...
}
}
I have solved the issue. There are tons of fairly poorly organised howto's for azure notification hubs and only one of them has this note toward the bottom;
NOTE:
You will not receive the notification when you are still in the app.
To receive a toast notification while the app is active, you must
handle the ShellToastNotificationReceived event.
This is why I was experiencing intermittent results, as i assumed you would still get a notification if you were in the app. And this little note is pretty well hidden.
Have you used proper tag / tag expressions while register/send the message. Also, Where are you storing the id back from the notification hub. It should be used when you update the channel uri (it will expire).
I would suggest to start from scratch.
Ref: http://msdn.microsoft.com/en-us/library/dn530749.aspx
I am developing an asp.net mvc 3.0 application which has a simple authentication process. User fills a form which is sent to server by ajax call and gets response, but the problem here is that using the following method :
FormsAuthentication.SetAuthCookie(person.LoginName,false);
is not enough to fill 'HttpContext.Current.User' and it needs the below method to be run :
FormsAuthentication.RedirectFromLoginPage("...");
Problem here is that as i mentioned, the loggin form uses an ajax form, and get responses with json, so redirecting is not possible.
How could I fill 'HttpContext.Current.User' ?
Thanks.
Update :
Here is register method :
[HttpPost]
public ActionResult Register(Person person)
{
var q = da.Persons.Where(x => x.LoginName == person.LoginName.ToLower()).FirstOrDefault();
if (q != null)
{
ModelState.AddModelError("", "Username is repettive, try other one");
return Json(new object[] { false, this.RenderPartialViewToString("RegisterControl", person) });
}
else
{
if (person.LoginName.ToLower() == "admin")
{
person.IsAdmin = true;
person.IsActive = true;
}
da.Persons.Add(person);
da.SaveChanges();
FormsAuthentication.SetAuthCookie(person.LoginName,false);
return Json(new object[] { true, "You have registered successfully!" });
}
}
FormsAuthentication doesn't support immediate setting of user's identity, but you should be able to fake it by something like this:
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(person.LoginName),
new string[] { /* fill roles if any */ } );
Here is the version I ended up using, which is based on the answer by #AdamTuliper-MSFT. It is only meant to be used right after logging in, but before redirect, to allow other code to access HttpContext.User.
Don't do anything if already authenticated
Doesn't modify the cookie, since this should only be used for the lifetime of this request
Shorten some things, and a little safer with userdata (should never be null, but...)
Call this after you call SetAuthCookie(), like below:
// in login function
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
AuthenticateThisRequest();
private void AuthenticateThisRequest()
{
//NOTE: if the user is already logged in (e.g. under a different user account)
// then this will NOT reset the identity information. Be aware of this if
// you allow already-logged in users to "re-login" as different accounts
// without first logging out.
if (HttpContext.User.Identity.IsAuthenticated) return;
var name = FormsAuthentication.FormsCookieName;
var cookie = Response.Cookies[name];
if (cookie != null)
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null && !ticket.Expired)
{
string[] roles = (ticket.UserData as string ?? "").Split(',');
HttpContext.User = new GenericPrincipal(new FormsIdentity(ticket), roles);
}
}
}
Edit: Remove call to Request.Cookies, as #AdamTuplier-MSFT mentioned.
You need to manually set it. Rather than reinventing the wheel, note the section here on updating the current principal for the request - thats your option here.
How to set Request.IsAuthenticated to true when not using FormsAuthentication.RedirectFromLoginPage?
public void RenewCurrentUser()
{
System.Web.HttpCookie authCookie =
System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
FormsAuthenticationTicket newAuthTicket = authTicket;
if (FormsAuthentication.SlidingExpiration)
{
newAuthTicket = FormsAuthentication.RenewTicketIfOld(authTicket);
}
string userData = newAuthTicket.UserData;
string[] roles = userData.Split(',');
System.Web.HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(new FormsIdentity(newAuthTicket), roles);
}
}
}