re-acquire token in OneDrive2FileStorage after expiry, closes https://github.com/PhilippC/keepass2android/issues/983

This commit is contained in:
Philipp Crocoll
2019-10-10 02:42:45 +02:00
parent fc76e2042d
commit 12d2f961cf

View File

@@ -294,19 +294,37 @@ namespace keepass2android.Io
get { yield return ProtocolId; }
}
Dictionary<String /*userid*/, IGraphServiceClient> mClientByUser =
new Dictionary<String /*userid*/, IGraphServiceClient>();
class GraphServiceClientWithState
{
public IGraphServiceClient Client { get; set; }
public DateTime TokenExpiryDate { get; set; }
public bool RequiresUserInteraction { get; set; }
}
private IGraphServiceClient tryGetMsGraphClient(String path)
readonly Dictionary<String /*userid*/, GraphServiceClientWithState> mClientByUser =
new Dictionary<String /*userid*/, GraphServiceClientWithState>();
private async Task<IGraphServiceClient> TryGetMsGraphClient(String path, bool tryConnect)
{
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
if (mClientByUser.ContainsKey(userId))
return mClientByUser[userId];
{
GraphServiceClientWithState clientWithState = mClientByUser[userId];
if (!(clientWithState.RequiresUserInteraction || (clientWithState.TokenExpiryDate < DateTime.Now) || (clientWithState.Client == null)))
return clientWithState.Client;
}
if (tryConnect)
{
if (await TryLoginSilent(path) != null)
{
return mClientByUser[userId].Client;
}
}
return null;
}
private IGraphServiceClient buildClient(AuthenticationResult authenticationResult)
private IGraphServiceClient BuildClient(AuthenticationResult authenticationResult)
{
logDebug("buildClient...");
@@ -321,14 +339,20 @@ namespace keepass2android.Io
return Task.FromResult(0);
});
GraphServiceClient graphClient = new GraphServiceClient(authenticationProvider);
GraphServiceClientWithState clientWithState = new GraphServiceClientWithState()
{
Client = new GraphServiceClient(authenticationProvider),
RequiresUserInteraction = false,
TokenExpiryDate = authenticationResult.ExpiresOn.LocalDateTime
};
if (authenticationResult.Account == null)
throw new Exception("authenticationResult.Account == null!");
mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = graphClient;
mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = clientWithState;
return graphClient;
return clientWithState.Client;
}
@@ -341,7 +365,7 @@ namespace keepass2android.Io
protected abstract string SpecialFolder { get; }
private PathItemBuilder GetPathItemBuilder(String path)
private async Task<PathItemBuilder> GetPathItemBuilder(String path)
{
PathItemBuilder result = new PathItemBuilder(SpecialFolder);
@@ -351,12 +375,13 @@ namespace keepass2android.Io
{
throw new Exception("path does not contain user");
}
result.client = null;
if (!mClientByUser.TryGetValue(result.itemLocation.User.Id, out result.client))
{
throw new Exception("failed to get client for " + result.itemLocation.User.Id);
}
result.client = await TryGetMsGraphClient(path, true);
if (result.client == null)
throw new Exception("Failed to connect or authenticate to OneDrive!");
return result;
}
@@ -400,10 +425,14 @@ namespace keepass2android.Io
{
try
{
PathItemBuilder pathItemBuilder = GetPathItemBuilder(ioc.Path);
Task.Run(async () => await pathItemBuilder.getPathItem()
.Request()
.DeleteAsync()).Wait();
Task.Run(async () =>
{
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path);
await pathItemBuilder.getPathItem()
.Request()
.DeleteAsync();
}).Wait();
}
catch (Exception e)
{
@@ -426,13 +455,17 @@ namespace keepass2android.Io
try
{
string path = ioc.Path;
PathItemBuilder clientAndpath = GetPathItemBuilder(path);
logDebug("openFileForRead. Path=" + path);
Stream result = Task.Run(async () => await clientAndpath
.getPathItem()
.Content
.Request()
.GetAsync()).Result;
Stream result = Task.Run(async () =>
{
PathItemBuilder clientAndpath = await GetPathItemBuilder(path);
return await clientAndpath
.getPathItem()
.Content
.Request()
.GetAsync();
}).Result;
logDebug("ok");
return result;
@@ -479,14 +512,16 @@ namespace keepass2android.Io
{
try
{
PathItemBuilder pathItemBuilder = GetPathItemBuilder(path);
Task.Run(async () => await
pathItemBuilder
.getPathItem()
.Content
.Request()
.PutAsync<DriveItem>(stream)).Wait();
Task.Run(async () =>
{
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path);
return await
pathItemBuilder
.getPathItem()
.Content
.Request()
.PutAsync<DriveItem>(stream);
}).Wait();
}
catch (Exception e)
@@ -530,15 +565,16 @@ namespace keepass2android.Io
driveItem.Name = newDirName;
driveItem.Folder = new Folder();
PathItemBuilder pathItemBuilder = GetPathItemBuilder(parentIoc.Path);
DriveItem res = Task.Run(async () =>
{
logDebug("building request for " + pathItemBuilder.itemLocation);
DriveItem res = Task.Run(async () => await pathItemBuilder.getPathItem()
.Children
.Request()
.AddAsync(driveItem)).Result;
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parentIoc.Path);
return await pathItemBuilder.getPathItem()
.Children
.Request()
.AddAsync(driveItem);
}).Result;
}
@@ -552,43 +588,8 @@ namespace keepass2android.Io
{
try
{
PathItemBuilder pathItemBuilder = GetPathItemBuilder(ioc.Path);
logDebug("listing files for " + ioc.Path);
if (!pathItemBuilder.hasShare() && !pathItemBuilder.hasOneDrivePath())
{
logDebug("listing shares.");
return ListShares(pathItemBuilder.itemLocation, pathItemBuilder.client);
}
logDebug("listing regular children.");
List<FileDescription> result = new List<FileDescription>();
/*logDebug("parent before:" + parentPath);
parentPath = parentPath.substring(getProtocolPrefix().length());
logDebug("parent after: " + parentPath);*/
IDriveItemChildrenCollectionPage itemsPage = Task.Run(async () => await pathItemBuilder.getPathItem()
.Children
.Request()
.GetAsync()).Result;
while (true)
{
IList<DriveItem> items = itemsPage.CurrentPage;
if (!items.Any())
return result;
foreach (DriveItem i in items)
{
var e = GetFileDescription(pathItemBuilder.itemLocation.BuildLocalChildLocation(i.Name, i.Id, i.ParentReference?.DriveId), i);
result.Add(e);
}
var nextPageReqBuilder = itemsPage.NextPageRequest;
if (nextPageReqBuilder == null)
return result;
itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result;
}
return Task.Run(async () => await ListContentsAsync(ioc)).Result;
}
catch (Exception e)
{
@@ -596,6 +597,46 @@ namespace keepass2android.Io
}
}
private async Task<IEnumerable<FileDescription>> ListContentsAsync(IOConnectionInfo ioc)
{
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path);
logDebug("listing files for " + ioc.Path);
if (!pathItemBuilder.hasShare() && !pathItemBuilder.hasOneDrivePath())
{
logDebug("listing shares.");
return await ListShares(pathItemBuilder.itemLocation, pathItemBuilder.client);
}
logDebug("listing regular children.");
List<FileDescription> result = new List<FileDescription>();
/*logDebug("parent before:" + parentPath);
parentPath = parentPath.substring(getProtocolPrefix().length());
logDebug("parent after: " + parentPath);*/
IDriveItemChildrenCollectionPage itemsPage = await pathItemBuilder.getPathItem()
.Children
.Request()
.GetAsync();
while (true)
{
IList<DriveItem> items = itemsPage.CurrentPage;
if (!items.Any())
return result;
foreach (DriveItem i in items)
{
var e = GetFileDescription(pathItemBuilder.itemLocation.BuildLocalChildLocation(i.Name, i.Id, i.ParentReference?.DriveId), i);
result.Add(e);
}
var nextPageReqBuilder = itemsPage.NextPageRequest;
if (nextPageReqBuilder == null)
return result;
itemsPage = Task.Run(async () => await nextPageReqBuilder.GetAsync()).Result;
}
}
private FileDescription GetFileDescription(OneDrive2ItemLocation<OneDrive2PrefixContainerType> path, DriveItem i)
{
@@ -620,24 +661,7 @@ namespace keepass2android.Io
{
try
{
string filename = ioc.Path;
PathItemBuilder pathItemBuilder = GetPathItemBuilder(filename);
if (!pathItemBuilder.itemLocation.LocalPath.Any()
&& !pathItemBuilder.hasShare())
{
FileDescription rootEntry = new FileDescription();
rootEntry.CanRead = rootEntry.CanWrite = true;
rootEntry.Path = filename;
rootEntry.DisplayName = pathItemBuilder.itemLocation.User.Name;
rootEntry.IsDirectory = true;
return rootEntry;
}
IDriveItemRequestBuilder pathItem = pathItemBuilder.getPathItem();
DriveItem item = Task.Run(async () => await pathItem.Request().GetAsync()).Result;
return GetFileDescription(pathItemBuilder.itemLocation, item);
return Task.Run(async() => await GetFileDescriptionAsync(ioc)).Result;
}
catch (Exception e)
{
@@ -645,6 +669,28 @@ namespace keepass2android.Io
}
}
private async Task<FileDescription> GetFileDescriptionAsync(IOConnectionInfo ioc)
{
string filename = ioc.Path;
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(filename);
if (!pathItemBuilder.itemLocation.LocalPath.Any()
&& !pathItemBuilder.hasShare())
{
FileDescription rootEntry = new FileDescription();
rootEntry.CanRead = rootEntry.CanWrite = true;
rootEntry.Path = filename;
rootEntry.DisplayName = pathItemBuilder.itemLocation.User.Name;
rootEntry.IsDirectory = true;
return rootEntry;
}
IDriveItemRequestBuilder pathItem = pathItemBuilder.getPathItem();
DriveItem item = await pathItem.Request().GetAsync();
return GetFileDescription(pathItemBuilder.itemLocation, item);
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
@@ -663,13 +709,13 @@ namespace keepass2android.Io
}
private bool isConnected(String path)
private async Task<bool> IsConnectedAsync(string path, bool tryConnect)
{
try
{
logDebug("isConnected? " + path);
return tryGetMsGraphClient(path) != null;
return (await TryGetMsGraphClient(path, tryConnect)) != null;
}
catch (Exception e)
{
@@ -679,11 +725,15 @@ namespace keepass2android.Io
}
public bool IsConnected(string path)
{
return Task.Run(async () => await IsConnectedAsync(path, false)).Result;
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
if (isConnected(ioc.Path))
if (IsConnected(ioc.Path))
{
Intent intent = new Intent();
intent.PutExtra(FileStorageSetupDefs.ExtraPath, ioc.Path);
@@ -698,7 +748,7 @@ namespace keepass2android.Io
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
if (!isConnected(ioc.Path))
if (!Task.Run(async() => await IsConnectedAsync(ioc.Path, true)).Result)
{
throw new Exception("MsGraph login required");
}
@@ -714,7 +764,7 @@ namespace keepass2android.Io
}
protected void finishActivityWithSuccess(
protected void FinishActivityWithSuccess(
IFileStorageSetupActivity setupActivity)
{
//Log.d("KP2AJ", "Success with authenticating!");
@@ -751,19 +801,50 @@ namespace keepass2android.Io
if (activity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup))
activity.State.PutString(FileStorageSetupDefs.ExtraPath, activity.Ioc.Path);
string rootPathForUser = await TryLoginSilent(activity.Ioc.Path);
if (rootPathForUser != null)
{
FinishActivityWithSuccess(activity, rootPathForUser);
}
try
{
logDebug("try interactive");
AuthenticationResult res = await _publicClientApp.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow((Activity)activity)
.ExecuteAsync();
logDebug("ok interactive");
BuildClient(res);
FinishActivityWithSuccess(activity, BuildRootPathForUser(res));
}
catch (Exception e)
{
logDebug("authenticating not successful: " + e);
Intent data = new Intent();
data.PutExtra(FileStorageSetupDefs.ExtraErrorMessage, "authenticating not successful");
((Activity)activity).SetResult(Result.Canceled, data);
((Activity)activity).Finish();
}
}
private async Task<string> TryLoginSilent(string iocPath)
{
IAccount account = null;
try
{
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(activity.Ioc.Path).User?.Id;
if (mClientByUser.ContainsKey(userId))
if (IsConnected(iocPath))
{
finishActivityWithSuccess(activity);
return;
return iocPath;
}
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(iocPath).User?.Id;
logDebug("needs acquire token");
logDebug("trying silent login " + activity.Ioc.Path);
logDebug("trying silent login " + iocPath);
account = Task.Run(async () => await _publicClientApp.GetAccountAsync(userId)).Result;
logDebug("getting user ok.");
@@ -783,54 +864,46 @@ namespace keepass2android.Io
.ExecuteAsync();
logDebug("AcquireTokenSilent ok.");
var graphClient = buildClient(authResult);
BuildClient(authResult);
/*User me = await graphClient.Me.Request().WithForceRefresh(true).GetAsync();
logDebug("received name " + me.DisplayName);*/
finishActivityWithSuccess(activity, authResult);
return;
return BuildRootPathForUser(authResult);
}
catch (MsalUiRequiredException ex)
{
logDebug("ui required");
GraphServiceClientWithState clientWithState = new GraphServiceClientWithState()
{
Client = null,
RequiresUserInteraction = true
};
mClientByUser[account.HomeAccountId.Identifier] = clientWithState;
logDebug("ui required");
return null;
}
catch (Exception ex)
{
logDebug("silent login failed: " + ex.ToString());
return null;
}
}
try
{
logDebug("try interactive");
AuthenticationResult res = await _publicClientApp.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow((Activity)activity)
.ExecuteAsync();
logDebug("ok interactive");
buildClient(res);
finishActivityWithSuccess(activity, res);
}
catch (Exception e)
{
logDebug("authenticating not successful: " + e);
Intent data = new Intent();
data.PutExtra(FileStorageSetupDefs.ExtraErrorMessage, "authenticating not successful");
((Activity)activity).SetResult(Result.Canceled, data);
((Activity)activity).Finish();
}
return null;
}
string buildRootPathForUser(AuthenticationResult res)
string BuildRootPathForUser(AuthenticationResult res)
{
return OneDrive2ItemLocation<OneDrive2PrefixContainerType>.RootForUser(res.Account.Username, res.Account.HomeAccountId.Identifier).ToString();
}
private void finishActivityWithSuccess(IFileStorageSetupActivity activity, AuthenticationResult authResult)
private void FinishActivityWithSuccess(IFileStorageSetupActivity activity, string rootPathForUser)
{
activity.State.PutString(FileStorageSetupDefs.ExtraPath, buildRootPathForUser(authResult));
finishActivityWithSuccess(activity);
activity.State.PutString(FileStorageSetupDefs.ExtraPath, rootPathForUser);
FinishActivityWithSuccess(activity);
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
@@ -871,13 +944,13 @@ namespace keepass2android.Io
}
private List<FileDescription> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, IGraphServiceClient client)
private async Task<List<FileDescription>> ListShares(OneDrive2ItemLocation<OneDrive2PrefixContainerType> parentPath, IGraphServiceClient client)
{
List<FileDescription> result = new List<FileDescription>();
DriveItem root = Task.Run(async () => await client.Me.Drive.Root.Request().GetAsync()).Result;
DriveItem root = await client.Me.Drive.Root.Request().GetAsync();
FileDescription myEntry = GetFileDescription(parentPath.BuildShare("me","me","me", root.ParentReference?.DriveId), root);
myEntry.DisplayName = MyOneDriveDisplayName;
@@ -888,8 +961,7 @@ namespace keepass2android.Io
IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage =
Task.Run(async () => await client.Me.Drive.SharedWithMe().Request().GetAsync()).Result;
IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = await client.Me.Drive.SharedWithMe().Request().GetAsync();
while (true)
{
@@ -905,7 +977,7 @@ namespace keepass2android.Io
}
var b = sharedWithMeCollectionPage.NextPageRequest;
if (b == null) break;
sharedWithMeCollectionPage = Task.Run(async () => await b.GetAsync()).Result;
sharedWithMeCollectionPage = await b.GetAsync();
}
return result;
}
@@ -945,29 +1017,7 @@ namespace keepass2android.Io
{
try
{
DriveItem driveItem = new DriveItem();
driveItem.Name = newFilename;
driveItem.File = new File();
PathItemBuilder pathItemBuilder = GetPathItemBuilder(parent);
//see if such a file exists already:
var item = TryFindFile(pathItemBuilder, newFilename);
if (item != null)
{
return pathItemBuilder.itemLocation.BuildLocalChildLocation(item.Name, item.Id, item.ParentReference?.DriveId).ToString();
}
//doesn't exist. Create:
logDebug("building request for " + pathItemBuilder.itemLocation);
DriveItem res = Task.Run(async () => await pathItemBuilder.getPathItem()
.Children
.Request()
.AddAsync(driveItem)).Result;
return pathItemBuilder.itemLocation.BuildLocalChildLocation(res.Name, res.Id, res.ParentReference?.DriveId).ToString();
return Task.Run(async() => await CreateFilePathAsync(parent, newFilename)).Result;
}
catch (Exception e)
{
@@ -976,6 +1026,32 @@ namespace keepass2android.Io
}
private async Task<string> CreateFilePathAsync(string parent, string newFilename)
{
DriveItem driveItem = new DriveItem();
driveItem.Name = newFilename;
driveItem.File = new File();
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parent);
//see if such a file exists already:
var item = TryFindFile(pathItemBuilder, newFilename);
if (item != null)
{
return pathItemBuilder.itemLocation.BuildLocalChildLocation(item.Name, item.Id, item.ParentReference?.DriveId)
.ToString();
}
//doesn't exist. Create:
logDebug("building request for " + pathItemBuilder.itemLocation);
DriveItem res = await pathItemBuilder.getPathItem()
.Children
.Request()
.AddAsync(driveItem);
return pathItemBuilder.itemLocation.BuildLocalChildLocation(res.Name, res.Id, res.ParentReference?.DriveId)
.ToString();
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
return IOConnectionInfo.FromPath(OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path).Parent.ToString());