C# OneDrive Stop Constantly Asking For Permissions [duplicate] - c#

I have just started working with the OneDrive API and the sample program that comes with it (OneDriveApiBrowser).
As expected, the first time I logged in (using "Sign In to MSA...", I was asked for credentials, my 2-factor code, and finally a permissions screen asking if I approve of the access that the app wants against my OneDrive account.
But, after I exit the program and restart it, I am not logged in. I repeat going to "Sign In to MSA..." and I am no longer prompted for credentials (as I expected), but I am prompted with the permissions screen again.
Is there a way of having the app log back in without always prompting the user for permission?
For learning how to use the OneDrive API, I am just using the sample code that Microsoft supplied as part of the API located at https://github.com/OneDrive/onedrive-sdk-csharp/tree/master/samples/OneDriveApiBrowser. The code can't be downloaded directly from there, but at the root of this project, https://github.com/OneDrive/onedrive-sdk-csharp. This will download the source for the API as well as the sample code and unit tests.

After doing some more poking around, I finally found out how to do this. My explanation here will be in the context of the sample program mentioned in the original question above.
In the program, in the SignIn method, there was some setup being done which included calling OneDriveClient.GetMicrosoftAccountClient(...), then calling the following:
if (!this.oneDriveClient.IsAuthenticated)
{
await this.oneDriveClient.AuthenticateAsync();
}
So, two things needed to be done. We need the save the result from the code above, then save the RefreshToken value somewhere safe... (It's just a very long string).
if (!this.oneDriveClient.IsAuthenticated)
{
AccountSession accountSession = await this.oneDriveClient.AuthenticateAsync();
// Save accountSession.RefreshToken somewhere safe...
}
Finally, I needed to put an if around the OneDriveClient.GetMicrosoftAccountClient(...) call and only call it if the saved refresh token has not been saved yet (or as been deleted due to code added to the logout call...) If we have a saved refresh token, we call `OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(...) instead. The entire SignIn method looks like this when I am done.
private async Task SignIn(ClientType clientType)
{
string refreshToken = null;
AccountSession accountSession;
// NOT the best place to save this, but will do for an example...
refreshToken = Properties.Settings.Default.RefreshToken;
if (this.oneDriveClient == null)
{
if (string.IsNullOrEmpty(refreshToken))
{
this.oneDriveClient = clientType == ClientType.Consumer
? OneDriveClient.GetMicrosoftAccountClient(
FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
webAuthenticationUi: new FormsWebAuthenticationUi())
: BusinessClientExtensions.GetActiveDirectoryClient(FormBrowser.AadClientId, FormBrowser.AadReturnUrl);
}
else
{
this.oneDriveClient = await OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
refreshToken);
}
}
try
{
if (!this.oneDriveClient.IsAuthenticated)
{
accountSession = await this.oneDriveClient.AuthenticateAsync();
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = accountSession.RefreshToken;
Properties.Settings.Default.Save();
}
await LoadFolderFromPath();
UpdateConnectedStateUx(true);
}
catch (OneDriveException exception)
{
// Swallow authentication cancelled exceptions
if (!exception.IsMatch(OneDriveErrorCode.AuthenticationCancelled.ToString()))
{
if (exception.IsMatch(OneDriveErrorCode.AuthenticationFailure.ToString()))
{
MessageBox.Show(
"Authentication failed",
"Authentication failed",
MessageBoxButtons.OK);
var httpProvider = this.oneDriveClient.HttpProvider as HttpProvider;
httpProvider.Dispose();
this.oneDriveClient = null;
}
else
{
PresentOneDriveException(exception);
}
}
}
}
For completeness, I updated the logout code
private async void signOutToolStripMenuItem_Click(object sender, EventArgs e)
{
if (this.oneDriveClient != null)
{
await this.oneDriveClient.SignOutAsync();
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = null;
Properties.Settings.Default.Save();
}
UpdateConnectedStateUx(false);
}

The wl.offline_access scope is required by applications to save the user consent information and refresh the access token without a UI prompt.
For more details on the scopes that you can use in your application see https://dev.onedrive.com/auth/msa_oauth.htm#authentication-scopes

Related

How could I check if a user has a role in discord.NET?

I'm trying to find a way to check if a user has X role and performing something if they don't, similar on how it logs it to the console if you use [RequireUserPermission(GuildPermission.Administrator)], just I don't want it to log to the console, EX:
if (has role)
{
// do stuff
} else
{
// do stuff
}
The command I'm trying to implement it into
[Command("clear")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
As akac pointed out the precondition you mentioned [RequireUserPermission(...)] checks if any of the roles assigned to a user gives them the permission to do a specific task. Consider the following example. You create a role called "Moderators" and enable the permission "Manage Messages". You then add the precondition [RequireUserPermission(ChannelPermission.ManageMessages)] to your Clear() method. Your Clear() method will now work for anybody in the "Moderators" role because they have permission to manage messages. It will also allow anybody in any other roles with the same permission to use it.
However, if you later decide you don't want "Moderators" to be able to manage messages and remove the permission from that role, your precondition will then automatically stop anyone in that role from using the Clear command.
If you check the users roles instead of their permissions, the users with the "Moderators" role would still be able to use the Clear command to delete messages even though you've removed the permission to manage messages from that role.
If the only reason you want to check the role instead of using the precondition to check their permissions is because you don't want it to log to the console, then this is probably the wrong approach for you. Instead you should consider sticking with the precondition and look at how you're handling the logging to prevent that message from being logged to the console.
If you would still like to check the user's roles, then here is an example of how you could do that in the Clear() method you provided. You will need to add using System.Linq; to the top of the file and replace "RoleName" in the second if statement with the name of the role you want to check.
public async Task Clear(int amount)
{
// Get the user that executed the command cast it to SocketGuildUser
// so we can access the Roles property
if (Context.User is SocketGuildUser user)
{
// Check if the user has the requried role
if (user.Roles.Any(r => r.Name == "RoleName"))
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await((ITextChannel) Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
else
{
await Context.Channel.SendMessageAsync("Sorry, you don't have permission to do that.");
}
}
}
I encountered this issue a couple of days ago, my solution was to create a custom attribute that inherited from Discord.Commands.PreconditionAttribute
I discovered this as I use dotPeek on my Visual Studio install to decompile and read how the command service actually works, as I wanted to know how their attributes worked as well. As part of the command service's execution of the command it checks all preconditions for each command, and only when each are satisfied does it execute the function.
This class has a function called CheckPermissionsAsync
Here is a sample that should work for you:
public class RequiresRoleAttribute : PreconditionAttribute
{
private string m_role;
public RequiresRoleAttribute (string role)
{
m_role = role;
}
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{
if (context.User is SocketGuildUser sgu)
{
if (sgu.Roles.Select(x => x.Name == m_name).Contains(m_name)
{
return PreconditionResult.FromSuccess();
}
}
return PreconditionResult.FromError($"The user doesn't have the role '{m_name}'");
}
}
And the use:
[Command("clear")]
[RequiresRole("RoleName")]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
Here is what you do:
You use the RequireRoles attribute.
It has 4 various ways: All, any, specified only, none
Let's say you want to make a ban command, but only want admin and mods to be able to use it. You would do this:
public class Moderation : BaseCommandModule
{
[Command("ban")]
[RequireRoles(RoleCheckMode.Any, "admin", "mod")]
public async Task BanCommand(CommandContext ctx, DiscordMember name, [RemainingText] string reason)
{
}
}
What are you doing here is basically using a precondition to check if any of the user's roles have the ManageRoles permission, the description is confusing.
As far as I know, Discord.net doesn't log such errors, only puts the default error message into the Result of the command, which is then usually sent to the channel as a response. There clearly has to be some place where your code logs such errors.

Log out of an Azure Active Directory application from a WPF application?

I am developing a desktop application in WPF with C #, which connects to an Azure AD application, when trying to log out of the official documentation, it only deletes the cache but does not log out of the application, so when trying to connect again I already appear as disconnected
This is my code for login, but it doesn't work.
//cerrar sesion y volver a pantalla de menu
public async void cerrarSesion()
{
var accounts = await app2.PublicClientApp.GetAccountsAsync();
if (accounts.Any())
{
try
{
System.Diagnostics.Process.Start("https://login.microsoftonline.com/4fb44f4b-6f0e-46f2-acea-ac4538df0c9c/oauth2/v2.0/logout");
await app2.PublicClientApp.RemoveAsync(accounts.FirstOrDefault()).ConfigureAwait(false);
Console.WriteLine("User has signed-out");
//NavigationService.Navigate(new Menu());
// System.Diagnostics.Process.Start("https://login.microsoftonline.com/4fb44f4b-6f0e-46f2-acea-ac4538df0c9c/oauth2/v2.0/logout");
// Console.WriteLine(Resultado.Account.HomeAccountId + "");
}
catch (MsalException ex)
{
Console.WriteLine($"Error signing-out user: {ex.Message}");
}
}
}
Stay logged in
AFAIK there is no way by default to remove the Session cookies from WPF web browser.You can find the reference from here.
There is already a Github issue raised for the same which can be tracked from here, which currently in a blocked state. Will be prioritized later and worked upon:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/425
I would suggest you to try to suppress all your cookies for achieving the same.
Please take a look at this link and see if it helps.
Also check below link for reference.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/2871993e-744e-46cf-8adc-63c60018637c/deleting-cookies-in-wpf-web-browser-control?forum=wpf
Alternatively, suggested by Bigdan Gavril, in the below link
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/425
You have some control over the browser by using the .WithPrompt method. AFAIK Prompt.ForceLogin will always force the user to enter their password.
var result = await pca.AcquireTokenInteractive(_scopes)
.WithPrompt(Prompt.ForceLogin)
.ExecuteAsync()
Hope it helps.
According to my test, we can open an embedded web browser to send a logout request. Then it will help us clear cookie and session. When we login again, we need to enter username and password. For example
private async void SignOutButton_Click(object sender, RoutedEventArgs e)
{
var accounts = await App.PublicClientApp.GetAccountsAsync();
if (accounts.Any())
{
try
{
await App.PublicClientApp.RemoveAsync(accounts.FirstOrDefault());
NavigationWindow window = new NavigationWindow();
window.Source = new Uri("https://login.microsoftonline.com/common/oauth2/v2.0/logout");
window.Show();
this.ResultText.Text = "User has signed-out";
this.CallGraphButton.Visibility = Visibility.Visible;
this.SignOutButton.Visibility = Visibility.Collapsed;
}
catch (MsalException ex)
{
ResultText.Text = $"Error signing-out user: {ex.Message}";
}
}
}
First login
Login again

How do I login to OneDrive (after the initial time) without seeing the permissions screen

I have just started working with the OneDrive API and the sample program that comes with it (OneDriveApiBrowser).
As expected, the first time I logged in (using "Sign In to MSA...", I was asked for credentials, my 2-factor code, and finally a permissions screen asking if I approve of the access that the app wants against my OneDrive account.
But, after I exit the program and restart it, I am not logged in. I repeat going to "Sign In to MSA..." and I am no longer prompted for credentials (as I expected), but I am prompted with the permissions screen again.
Is there a way of having the app log back in without always prompting the user for permission?
For learning how to use the OneDrive API, I am just using the sample code that Microsoft supplied as part of the API located at https://github.com/OneDrive/onedrive-sdk-csharp/tree/master/samples/OneDriveApiBrowser. The code can't be downloaded directly from there, but at the root of this project, https://github.com/OneDrive/onedrive-sdk-csharp. This will download the source for the API as well as the sample code and unit tests.
After doing some more poking around, I finally found out how to do this. My explanation here will be in the context of the sample program mentioned in the original question above.
In the program, in the SignIn method, there was some setup being done which included calling OneDriveClient.GetMicrosoftAccountClient(...), then calling the following:
if (!this.oneDriveClient.IsAuthenticated)
{
await this.oneDriveClient.AuthenticateAsync();
}
So, two things needed to be done. We need the save the result from the code above, then save the RefreshToken value somewhere safe... (It's just a very long string).
if (!this.oneDriveClient.IsAuthenticated)
{
AccountSession accountSession = await this.oneDriveClient.AuthenticateAsync();
// Save accountSession.RefreshToken somewhere safe...
}
Finally, I needed to put an if around the OneDriveClient.GetMicrosoftAccountClient(...) call and only call it if the saved refresh token has not been saved yet (or as been deleted due to code added to the logout call...) If we have a saved refresh token, we call `OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(...) instead. The entire SignIn method looks like this when I am done.
private async Task SignIn(ClientType clientType)
{
string refreshToken = null;
AccountSession accountSession;
// NOT the best place to save this, but will do for an example...
refreshToken = Properties.Settings.Default.RefreshToken;
if (this.oneDriveClient == null)
{
if (string.IsNullOrEmpty(refreshToken))
{
this.oneDriveClient = clientType == ClientType.Consumer
? OneDriveClient.GetMicrosoftAccountClient(
FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
webAuthenticationUi: new FormsWebAuthenticationUi())
: BusinessClientExtensions.GetActiveDirectoryClient(FormBrowser.AadClientId, FormBrowser.AadReturnUrl);
}
else
{
this.oneDriveClient = await OneDriveClient.GetSilentlyAuthenticatedMicrosoftAccountClient(FormBrowser.MsaClientId,
FormBrowser.MsaReturnUrl,
FormBrowser.Scopes,
refreshToken);
}
}
try
{
if (!this.oneDriveClient.IsAuthenticated)
{
accountSession = await this.oneDriveClient.AuthenticateAsync();
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = accountSession.RefreshToken;
Properties.Settings.Default.Save();
}
await LoadFolderFromPath();
UpdateConnectedStateUx(true);
}
catch (OneDriveException exception)
{
// Swallow authentication cancelled exceptions
if (!exception.IsMatch(OneDriveErrorCode.AuthenticationCancelled.ToString()))
{
if (exception.IsMatch(OneDriveErrorCode.AuthenticationFailure.ToString()))
{
MessageBox.Show(
"Authentication failed",
"Authentication failed",
MessageBoxButtons.OK);
var httpProvider = this.oneDriveClient.HttpProvider as HttpProvider;
httpProvider.Dispose();
this.oneDriveClient = null;
}
else
{
PresentOneDriveException(exception);
}
}
}
}
For completeness, I updated the logout code
private async void signOutToolStripMenuItem_Click(object sender, EventArgs e)
{
if (this.oneDriveClient != null)
{
await this.oneDriveClient.SignOutAsync();
((OneDriveClient)this.oneDriveClient).Dispose();
this.oneDriveClient = null;
// NOT the best place to save this, but will do for an example...
Properties.Settings.Default.RefreshToken = null;
Properties.Settings.Default.Save();
}
UpdateConnectedStateUx(false);
}
The wl.offline_access scope is required by applications to save the user consent information and refresh the access token without a UI prompt.
For more details on the scopes that you can use in your application see https://dev.onedrive.com/auth/msa_oauth.htm#authentication-scopes

Getting "expired token" with Live SDK. Is example code correct?

I'm using the example code from http://msdn.microsoft.com/en-us/library/dn631823.aspx to perform the signing in ahead of performing any OneDrive operations. It seemed to work while I was initially coding but now that I've gone back to it after a break, any attempt to (say) read a folder gives me the error:
The access token that was provided has expired.
The code I'm using to log in is:
currentSession = null;
try
{
var authClient = new LiveAuthClient();
LiveLoginResult result = await authClient.LoginAsync(new string[] { "wl.signin", "wl.skydrive" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
currentSession = result.Session;
Debug.WriteLine("... succeeeded");
}
else
Debug.WriteLine("... not connected, status is {0}", result.Status);
}
catch (LiveAuthException ex)
{
// Display an error message.
Debug.WriteLine("LiveAuthException: {0}", ex.Message);
}
catch (LiveConnectException ex)
{
// Display an error message.
Debug.WriteLine("LiveConnectException: {0}", ex.Message);
}
where currentSession is declared as a private variable in the class and it then gets used in the folder code:
LiveConnectClient liveClient = new LiveConnectClient(currentSession);
LiveOperationResult operationResult = await liveClient.GetAsync("me/skydrive");
dynamic result = operationResult.Result;
What is slightly worrying me is that the documentation says:
Create a LiveAuthClient object and call the InitializeAsync method to initialize the Live SDK. Then call the LoginAsync method with the wl.signin and wl.skydrive scopes to enable single sign-in and allow the user to access OneDrive.
but the sample code DOESN'T make any reference to InitializeAsync and there seem to be variations on the call so it isn't really clear which one (if any) I should use.
This is for a Universal App, although currently I'm just working on the WP8.1 C#/XAML part of it. I'm using Live SDK 5.6.
Thanks.
As noted in the comment I added, it looks highly likely that the "expired token" error was being caused by an incorrect date/time setting on the emulator.

Windows Phone Live SDK API - get new session object after restarting the App

so I have succeeded in connecting my Windows Phone 8 Application to the Live API, I also succeeded in reading data from my hotmail account.
I have access to the needed client ID and the live access token.
But when I quit and restart my application, I lose all references to the session and the client objects and I have to start the process anew.
I don't want to annoy the user with the web mask in which he has to agree again that he provides me with the needed permissions every time he is starting the application. But I also haven't found a way to get a reference to a session object without this step.
The login mask is only shown the first time after installing the application, after that, only the screen mentioned above is shown.
But it is still quite annoying for the user to accept this every time.
I already tried serializing the session object, which is not possible, because the class does not have a standard constructor.
Maybe it is possible to create a new session by using the live access token, but I haven't found a way to do so.
Any ideas? What am I doing wrong, I know that there is a way to login again without prompting the user.
I'm thankful for every idea.
Some code I use:
/// <summary>
/// This method is responsible for authenticating an user asyncronesly to Windows Live.
/// </summary>
public void InitAuth()
{
this.authClient.LoginCompleted +=
new EventHandler<LoginCompletedEventArgs>(this.AuthClientLoginCompleted);
this.authClient.LoginAsync(someScopes);
}
/// <summary>
/// This method is called when the Login process has been completed (successfully or with error).
/// </summary>
private void AuthClientLoginCompleted(object sender, LoginCompletedEventArgs e)
{
if (e.Status == LiveConnectSessionStatus.Connected)
{
LiveConnector.ConnectSession = e.Session; // where do I save this session for reuse?
this.connectClient = new LiveConnectClient(LiveConnector.ConnectSession);
// can I use this access token to create a new session later?
LiveConnector.LiveAccessToken = LiveConnector.ConnectSession.AccessToken;
Debug.WriteLine("Logged in.");
}
else if (e.Error != null)
{
Debug.WriteLine("Error signing in: " + e.Error.ToString());
}
}
I have tried to use the LiveAuthClient.InitializeAsync - method to login in background after restarting the application, but the session object stays empty:
// this is called after application is restarted
private void ReLogin()
{
LiveAuthClient client = new LiveAuthClient(LiveConnector.ClientID);
client.InitializeCompleted += OnInitializeCompleted;
client.InitializeAsync(someScopes);
}
private void OnInitializeCompleted(object sender, LoginCompletedEventArgs e)
{
Debug.WriteLine("***************** Inititalisation completed **********");
Debug.WriteLine(e.Status); // is undefined
Debug.WriteLine(e.Session); // is empty
}
Does anyone have an idea how I could get access to a new session after restarting the application?
After two full days searching for the mistake I was making, I finally found out what I was doing wrong: I have to use the wl.offline_access scope to make this work!
Let me quote another user here:
"If your app uses wl.offline_access scope than the live:SignInButton control saves it for you and loads it automatically. Just use the SessionChanged event to capture the session object. This way the user will need to sign in only once."
(see WP7 how to store LiveConnectSession during TombStoning?)
Now everything is fun again. Can't believe that this was the problem. Tested & working. Nice!
Been struggling to get this working on a Windows Live + Azure Mobile Service app myself so thought I would post a complete working code sample here now that I've got it working.
The key parts are the wl.offline_access scope and the call to InitializeAsync.
Note: this sample also connects with Windows Azure Mobile Services. Just remove the stuff related to MobileService if you're not using that.
private static LiveConnectSession _session;
private static readonly string[] scopes = new[] {"wl.signin", "wl.offline_access", "wl.basic"};
private async System.Threading.Tasks.Task Authenticate()
{
try
{
var liveIdClient = new LiveAuthClient("YOUR_CLIENT_ID");
var initResult = await liveIdClient.InitializeAsync(scopes);
_session = initResult.Session;
if (null != _session)
{
await MobileService.LoginWithMicrosoftAccountAsync(_session.AuthenticationToken);
}
if (null == _session)
{
LiveLoginResult result = await liveIdClient.LoginAsync(scopes);
if (result.Status == LiveConnectSessionStatus.Connected)
{
_session = result.Session;
await MobileService.LoginWithMicrosoftAccountAsync(result.Session.AuthenticationToken);
}
else
{
_session = null;
MessageBox.Show("Unable to authenticate with Windows Live.", "Login failed :(", MessageBoxButton.OK);
}
}
}
finally
{
}
}

Categories

Resources