I am building a chatbot for Teams where the bot needs to connect to graph on behalf of the user. I'm working in C# with BotFramework v3 (for business reasons I cannot move to v4), my BotBuilder package is on version 3.20.1.
The problem: On Webchat, no problem, the bot displays the sign in card, I get redirected to a login page. When logged in, I get the magic number, which I paste into my bot and everything is fine.
On Teams, however, I click on the button, get directed to the login page and once logged in, the tab closes, no magic number and the channel doesn't get anything.
Here is what is configured:
Permissions:
The AAD v2 connection to my Azure app:
My valid domains in the teams manifest:
I've also changed my sign in button to an open url button when in Teams channel as it is a problem mentioned here https://github.com/microsoft/botframework-sdk/issues/2104 (and indeed sign in button doesn't react).
The code:
public override async Task StartAsync(IDialogContext context)
{
var token = await context.GetUserTokenAsync(ConnectionName).ConfigureAwait(false);
var oauthToken = context.Activity.ChannelData.oauthToken?.Value as string;
if (!string.IsNullOrWhiteSpace(oauthToken))
await DoThingsWithToken(context, oauthToken);
else
context.Call(await CreateGetTokenDialog(context), DoThingsWithToken);
}
private async Task<GetTokenDialog> CreateGetTokenDialog(IDialogContext context)
{
return new GetTokenDialog(
ConnectionName,
$"Please sign in to {ConnectionName} to proceed.",
"Sign In",
2,
"Hmm. Something went wrong, let's try again.");
}
private async Task DoThingsWithToken(IDialogContext context, IAwaitable<GetTokenResponse> tokenResponse)
{
var token = (await tokenResponse).Token;
// Do stuff...
}
I've looked online, but couldn't find any doc for SDK v3 anymore, nor people having this exact issue. Could anyone point me if it's a config problem, SDK version problem, or something else? And how to fix it?
[Edit] The documentation followed to configure the Teams SSO: https://learn.microsoft.com/en-us/microsoftteams/platform/sbs-bots-with-sso
Thank you, Best regards.
Related
We have a bot design in Bot framework-4 using .Net c# sdk. This bot is hosted on IIS and available
on different channel such as Directline, MS Teams etc. We want to send proactive messages to all the user in MS teams to notify them irrespective of if they communicated with bot or not. The Proactive messages will be 1:1 message.
After doing lot of R&D we found that we will be only able to send Proactive message to user only when there conversation reference is present. (let me know if other way is also possible.)
Using below link and Sample to send Proactive message to user:
Proactive Message Sample
Document Referred
We are using cosmos DB container and auto save middleware for bot conversation state and user state management.
Code in ConfigureServices method of Startup.cs file:
var blobDbService = botConfig.Services.FirstOrDefault(s => s.Type == ServiceTypes.BlobStorage) ?? throw new Exception("Please configure your Blob service in your .bot file.");
var BlobDb = blobDbService as BlobStorageService;
var dataStore = new AzureBlobStorage(BlobDb.ConnectionString, BlobDb.Container);
var userState = new UserState(dataStore);
var conversationState = new ConversationState(dataStore);
services.AddSingleton(dataStore);
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();
services.AddSingleton(new BotStateSet(userState, conversationState));
services.AddBot<EnterpriseTiBOT>(options =>
{
// Autosave State Middleware (saves bot state after each turn)
options.Middleware.Add(new AutoSaveStateMiddleware(userState, conversationState));
}
Code to Store Conversation Reference for each user:
private void AddConversationReference(Activity activity)
{
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}
protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
AddConversationReference(dc.Context, cancellationToken);
}
Code in notifyContoller is same as the code from GitHub Sample. There are 2 issues we are facing :
The concurrent dictionary having conversation reference become blank when the IIS pool is recycled and we are not able to send the proactive message to the user, how to store it in Blob storage and access the same in Notify controller?
We want to send proactive message to all the user whether they have communicated with bot or not, any way to achieve this? Tried 3rd approach from this article. But the challenge is, we are not able to send message to user based on User ID or user principle name.
There are multiple ways to store the conversation and user info. You should store these details in more persistent place rather than in memory. Here is a sample app code which stores user detail and along with the conversation Id in cosmos DB at the time of installation of the app. You could look into the implementation part. It can be any storage (blob, SQL).
For sending proactive message, User must have access to your app. You could make your app install for everyone in the tenant from Teams admin portal. Here is a reference documentation for managing the app from admin portal.
You need to have the conversation ID (between a bot and user) for sending a proactive message. And the conversation Id is created when the bot is installed for an user Or in team/group where user is part of.
I want to add comment to the answer but I do not have enough reputation to do so. Just to eleborate point 1 , if you want to ask where can we get and save the conversation refences, you can get it via method named: OnConversationUpdateActivityAsync.
It took me some time to get to this, so I think it is useful to share.
You can get a lot of information eg user ID, channel ID from the activity, here is some sample code:
public class ProactiveBot : ActivityHandler
{
...
...
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var conversationReferences = turnContext.Activity.GetConversationReference();
//this is your user's ID
string userId = conversationReference.User.Id;
//this is the bot's ID and will be the same for all activities under same bot.
string botId = conversationReference.Bot.Id;
...
...
...
}
}
I am creating a UWP application and I want to use the Onedrive API so that users can save a copy of their files in their Onedrive account, but I don't get it, so far I have only managed to login what I want is:
upload files
download files
and synchronize if any of them is modified
Create folders
Delete Files
This code achieves the login, but I can not move beyond this, as would proceed for upload files or download them
private async void btn_Login_Click(object sender, RoutedEventArgs e)
{
if (this.oneDriveClient == null)
{
try
{
// Setting up the client here, passing in our Client Id, Return Url,
// Scopes that we want permission to, and building a Web Broker to
// go do our authentication.
this.oneDriveClient = await OneDriveClient.GetAuthenticatedMicrosoftAccountClient(
clientId,
returnUrl,
scopes,
webAuthenticationUi: new WebAuthenticationBrokerWebAuthenticationUi());
// Show in text box that we are connected.
txtBox_Response.Text = "We are now connected";
// We are either just autheticated and connected or we already connected,
// either way we need the drive button now.
btn_GetDriveId.Visibility = Visibility.Visible;
}
catch (OneDriveException exception)
{
// Eating the authentication cancelled exceptions and resetting our client.
if (!exception.IsMatch(OneDriveErrorCode.AuthenticationCancelled.ToString()))
{
if (exception.IsMatch(OneDriveErrorCode.AuthenticationFailure.ToString()))
{
txtBox_Response.Text = "Authentication failed/cancelled, disposing of the client...";
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
}
else
{
// Or we failed due to someother reason, let get that exception printed out.
txtBox_Response.Text = exception.Error.ToString();
}
}
else
{
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
}
}
}
}
I created a sample repository in github:Onedrive Sync Files Sample
I have already tried using Dropbox, Gdrive, but its implementation for UWP seems to be much more complex, so I chose OneDrive. any answer will be very helpful thanks in advance
How to Sync files with OneDrive API in C#
For using OneDrive, we suggest you implement OneDrive feature with OneDrive Service that is part of Windows Community Toolkit .
Getting Started
To use the OneDrive API, you need to have an access token that authenticates your app to a particular set of permissions for a user. In this section, you'll learn how to:
Register your application to get a client ID and a client secret.
Sign your user in to OneDrive with the specified scopes using the token flow or code flow.
Sign the user out (optional).
And this is official code sample that you could refer.
I'm trying to implement authentication in BOT using OAuthPrompt in an WaterFallDialog class which contains 2 steps:
Calls the OathPrompt which provides sign in button to provide credentials
Gets the token, based on token retrieval success message is displayed and user is navigated to another dialog or else failure message is displayed with
user to re-prompt with login
Issue: I have to make user to navigate the user automatically to step#2 without any manual intervention upon successful validation.
Current Situation: I'm unable to do it as I have to type something to make user to navigate to step#2
Problem in: Emulator and Webchat channel
Language: C#
Bot SDK: V4
This is a new BOT i am trying to build as based on authentication i am displaying other options i.e. navigating user to another dialog for performing other options.
I have already tried using below in STEP#1:
stepContext.NextAsync()
This did not work, i had to eventually type something to navigate and more over it gave an exception invalid step index.
I have also tried by providing the index number which also did not work along with Cancellation token
Expected Result: User to navigate automatically to step#2 upon successful authentication using OAUTH Prompt
Actual Result: Not able to navigate it until typed anything
Adding code Below:
public class LoginDialog : WaterfallDialog
{
public LoginDialog(string dialogId, IEnumerable<WaterfallStep> steps = null)
: base(dialogId, steps)
{
AddStep(async (stepContext, cancellationToken) =>
{
await stepContext.Context.SendActivityAsync("Please login using below option in order to continue with other options, if already logged in type anything to continue...");
await stepContext.BeginDialogAsync(EchoWithCounterBot.LoginPromptName, cancellationToken: cancellationToken); // This actually calls the dialogue of OAuthPrompt whose name is is in EchoWithCounterBot.LoginPromptName.
return await stepContext.NextAsync(); // It comes here throws the error as explained above but also i have to type for it to navigate to below step
});
AddStep(async (stepContext, cancellationToken) =>
{
Tokenresponse = (TokenResponse)stepContext.Result;
if (Tokenresponse != null)
{
await stepContext.Context.SendActivityAsync($"logged in successfully... ");
return await stepContext.BeginDialogAsync(DisplayOptionsDialog.Id); //Here it goes To another dialogue class where options are displayed
}
else
{
await stepContext.Context.SendActivityAsync("Login was not successful, Please try again...", cancellationToken: cancellationToken);
await stepContext.BeginDialogAsync(EchoWithCounterBot.LoginPromptName, cancellationToken: cancellationToken);
}
return await stepContext.EndDialogAsync();
});
}
public static new string Id => "LoginDialog";
public static LoginDialog Instance { get; } = new LoginDialog(Id);
}
BeginDialogAsync or PromptAsync should always be the last call in a step, so you'll need to get rid of NextAsync. OAuthPrompt.BeginDialogAsync is special because you don't know if it's going to end the turn or not. If there's already an available token then it will return that token and automatically continue the calling dialog, which would be the next step in your case. If there's no token available then it will prompt the user to sign in, which needs to be the end of the turn. Clearly in that case it makes no sense to continue to the next step before giving the user a chance to sign in.
Other than that, your code should work. Please update both your Bot Builder packages and your Emulator to the latest versions (currently 4.4.3 for Bot Builder and 4.4.0 for Emulator). The Emulator should continue to the next step automatically when you sign in, but Web Chat will likely require the user to enter a magic code.
EDIT: If you are using a version of Emulator that does not handle automatic sign-ins correctly, you can configure Emulator to use a magic code instead in the settings.
I'm using the following function to sign into Azure AD on macOS
public async Task<bool> SignIn()
{
PublicClientApplication client = new PublicClientApplication(clientId, auth);
client.RedirectUri = redirectUrl;
AuthenticationResult res = await client.AcquireTokenAsync(scopes);
Token = new Token();
Token.AccessToken = res.AccessToken;
return true;
}
It works correctly on my developer Mac:
I get prompted to enter my email in a MS dialog,
"taking you to your organisations sign in page"
asks for my password.
When I run the same function on a another Mac in the organisation:
I get prompted to enter my email in the same MS dialog
"taking you to your organisations sign in page"
the sign-in dialog turn blank, white and the sign-in flow stops
Any idea what's going on? I don't get any errors.
It turned out to be related to companys proxy and/or firewall setting; After trying to use a phone as a personal hotspot it was clear that the code works fine, but some redirects gets blocked
I'm working on a UWP app and I was thinking about moving from the old LiveSDK (which is discontinued and was last updated around 2015) to the new OneDriveSDK (the Graph APIs), specifically using the UWP Community Toolkit Services package and its APIs.
The library seems pretty easy to use as far as login and files/folders management go, but so far I haven't been able to find a way to retrieve the user full name, the user email and the profile picture.
Here's the code I'm currently using to do so, using LiveSDK (code simplified here):
public static async Task<(String username, String email)> GetUserProfileNameAndEmailAsync(LiveConnectSession session)
{
LiveConnectClient connect = new LiveConnectClient(session);
LiveOperationResult operationResult = await connect.GetAsync("me");
IDictionary<String, object> results = operationResult.Result;
String username = results["name"] as String;
if (!(results["emails"] is IDictionary<string, object> emails)) return default;
String email = emails["preferred"] as String ?? emails["account"] as String;
return (username, email);
}
public static async Task<ImageSource> GetUserProfileImageAsync([NotNull] LiveConnectSession session)
{
LiveConnectClient liveClient = new LiveConnectClient(session);
LiveOperationResult operationResult = await liveClient.GetAsync("me/picture");
String url = operationResult.Result?["location"] as String;
// The URL points to the raw image data for the user profile picture, just download it
return default;
}
I've looked at the guide here and I see there seems to be a replacement for all of the above, but I haven't been able to integrate that with the UWP Toolkit service. For example, to retrieve the user info, here's what I've tried:
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me/");
await OneDriveService.Instance.Provider.AuthenticationProvider.AuthenticateRequestAsync(request);
using (HttpResponseMessage response = await OneDriveService.Instance.Provider.HttpProvider.SendAsync(request))
{
String content = await response.Content.ReadAsStringAsync();
}
But this fails with an exception at the SendAsync call.
NOTE: I know there are the Graph APIs too in the UWP Toolkit, with ready-to-use methods to retrieve the user info and profile picture, but apparently you need an office 365 subscription to use those APIs (both as a dev, and probably as a user too), so I guess that's not what I'm looking for here, since I've always been able to retrieve these info using a normal OneDrive client.
Is there a way to do this on UWP, either through some method within the UWP Toolkit, or with some other solution?
Thanks!
EDIT: I've reused the code from the sample app, registered my app to get a clientID and made a quick test, but it's not working as expected and I'm getting this exception:
Fixed, see below
EDIT #2: According to this question, I had to switch to https://graph.microsoft.com/beta to get the profile picture, as the 1.0 version of the APIs doesn't support it for normal MS accounts right now. All things considered, it seems to be working just fine now 👍
I followed the MSDN document to register my app for Microsoft Graph. After that, I will get an application ID(in API, it's called as clientId).
Then, I used the Microsoft Graph Connect Sample for UWP to login in with my general MS account. It worked well. I could get the username, email etc.
Please note that if you want to run this sample successfully, you would need to use the application ID to initialize the PublicClientApplication object in AuthenticationHelper.cs.
public static PublicClientApplication IdentityClientApp = new PublicClientApplication("your client id");