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; } get { yield return ProtocolId; }
} }
Dictionary<String /*userid*/, IGraphServiceClient> mClientByUser = class GraphServiceClientWithState
new Dictionary<String /*userid*/, IGraphServiceClient>(); {
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; String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
if (mClientByUser.ContainsKey(userId)) 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; return null;
} }
private IGraphServiceClient buildClient(AuthenticationResult authenticationResult) private IGraphServiceClient BuildClient(AuthenticationResult authenticationResult)
{ {
logDebug("buildClient..."); logDebug("buildClient...");
@@ -321,14 +339,20 @@ namespace keepass2android.Io
return Task.FromResult(0); 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) if (authenticationResult.Account == null)
throw new Exception("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; } protected abstract string SpecialFolder { get; }
private PathItemBuilder GetPathItemBuilder(String path) private async Task<PathItemBuilder> GetPathItemBuilder(String path)
{ {
PathItemBuilder result = new PathItemBuilder(SpecialFolder); PathItemBuilder result = new PathItemBuilder(SpecialFolder);
@@ -351,12 +375,13 @@ namespace keepass2android.Io
{ {
throw new Exception("path does not contain user"); throw new Exception("path does not contain user");
} }
result.client = null;
if (!mClientByUser.TryGetValue(result.itemLocation.User.Id, out result.client)) result.client = await TryGetMsGraphClient(path, true);
{
throw new Exception("failed to get client for " + result.itemLocation.User.Id);
}
if (result.client == null)
throw new Exception("Failed to connect or authenticate to OneDrive!");
return result; return result;
} }
@@ -400,10 +425,14 @@ namespace keepass2android.Io
{ {
try try
{ {
PathItemBuilder pathItemBuilder = GetPathItemBuilder(ioc.Path);
Task.Run(async () => await pathItemBuilder.getPathItem() Task.Run(async () =>
.Request() {
.DeleteAsync()).Wait(); PathItemBuilder pathItemBuilder = await GetPathItemBuilder(ioc.Path);
await pathItemBuilder.getPathItem()
.Request()
.DeleteAsync();
}).Wait();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -426,13 +455,17 @@ namespace keepass2android.Io
try try
{ {
string path = ioc.Path; string path = ioc.Path;
PathItemBuilder clientAndpath = GetPathItemBuilder(path);
logDebug("openFileForRead. Path=" + path); logDebug("openFileForRead. Path=" + path);
Stream result = Task.Run(async () => await clientAndpath Stream result = Task.Run(async () =>
.getPathItem() {
.Content PathItemBuilder clientAndpath = await GetPathItemBuilder(path);
.Request() return await clientAndpath
.GetAsync()).Result; .getPathItem()
.Content
.Request()
.GetAsync();
}).Result;
logDebug("ok"); logDebug("ok");
return result; return result;
@@ -479,14 +512,16 @@ namespace keepass2android.Io
{ {
try try
{ {
Task.Run(async () =>
PathItemBuilder pathItemBuilder = GetPathItemBuilder(path); {
Task.Run(async () => await PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path);
pathItemBuilder return await
.getPathItem() pathItemBuilder
.Content .getPathItem()
.Request() .Content
.PutAsync<DriveItem>(stream)).Wait(); .Request()
.PutAsync<DriveItem>(stream);
}).Wait();
} }
catch (Exception e) catch (Exception e)
@@ -530,15 +565,16 @@ namespace keepass2android.Io
driveItem.Name = newDirName; driveItem.Name = newDirName;
driveItem.Folder = new Folder(); driveItem.Folder = new Folder();
PathItemBuilder pathItemBuilder = GetPathItemBuilder(parentIoc.Path); DriveItem res = Task.Run(async () =>
{
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(parentIoc.Path);
logDebug("building request for " + pathItemBuilder.itemLocation);
return await pathItemBuilder.getPathItem()
DriveItem res = Task.Run(async () => await pathItemBuilder.getPathItem() .Children
.Children .Request()
.Request() .AddAsync(driveItem);
.AddAsync(driveItem)).Result; }).Result;
} }
@@ -552,43 +588,8 @@ namespace keepass2android.Io
{ {
try try
{ {
PathItemBuilder pathItemBuilder = GetPathItemBuilder(ioc.Path);
logDebug("listing files for " + ioc.Path); return Task.Run(async () => await ListContentsAsync(ioc)).Result;
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;
}
} }
catch (Exception e) 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) private FileDescription GetFileDescription(OneDrive2ItemLocation<OneDrive2PrefixContainerType> path, DriveItem i)
{ {
@@ -620,24 +661,7 @@ namespace keepass2android.Io
{ {
try try
{ {
string filename = ioc.Path; return Task.Run(async() => await GetFileDescriptionAsync(ioc)).Result;
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);
} }
catch (Exception e) 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) public bool RequiresSetup(IOConnectionInfo ioConnection)
{ {
return false; return false;
@@ -663,13 +709,13 @@ namespace keepass2android.Io
} }
private bool isConnected(String path) private async Task<bool> IsConnectedAsync(string path, bool tryConnect)
{ {
try try
{ {
logDebug("isConnected? " + path); logDebug("isConnected? " + path);
return tryGetMsGraphClient(path) != null; return (await TryGetMsGraphClient(path, tryConnect)) != null;
} }
catch (Exception e) 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, public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess) bool alwaysReturnSuccess)
{ {
if (isConnected(ioc.Path)) if (IsConnected(ioc.Path))
{ {
Intent intent = new Intent(); Intent intent = new Intent();
intent.PutExtra(FileStorageSetupDefs.ExtraPath, ioc.Path); intent.PutExtra(FileStorageSetupDefs.ExtraPath, ioc.Path);
@@ -698,7 +748,7 @@ namespace keepass2android.Io
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) 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"); throw new Exception("MsGraph login required");
} }
@@ -714,7 +764,7 @@ namespace keepass2android.Io
} }
protected void finishActivityWithSuccess( protected void FinishActivityWithSuccess(
IFileStorageSetupActivity setupActivity) IFileStorageSetupActivity setupActivity)
{ {
//Log.d("KP2AJ", "Success with authenticating!"); //Log.d("KP2AJ", "Success with authenticating!");
@@ -751,19 +801,50 @@ namespace keepass2android.Io
if (activity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup)) if (activity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup))
activity.State.PutString(FileStorageSetupDefs.ExtraPath, activity.Ioc.Path); 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; IAccount account = null;
try try
{ {
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(activity.Ioc.Path).User?.Id;
if (mClientByUser.ContainsKey(userId)) if (IsConnected(iocPath))
{ {
finishActivityWithSuccess(activity); return iocPath;
return;
} }
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(iocPath).User?.Id;
logDebug("needs acquire token"); 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; account = Task.Run(async () => await _publicClientApp.GetAccountAsync(userId)).Result;
logDebug("getting user ok."); logDebug("getting user ok.");
@@ -783,54 +864,46 @@ namespace keepass2android.Io
.ExecuteAsync(); .ExecuteAsync();
logDebug("AcquireTokenSilent ok."); logDebug("AcquireTokenSilent ok.");
var graphClient = buildClient(authResult); BuildClient(authResult);
/*User me = await graphClient.Me.Request().WithForceRefresh(true).GetAsync(); /*User me = await graphClient.Me.Request().WithForceRefresh(true).GetAsync();
logDebug("received name " + me.DisplayName);*/ logDebug("received name " + me.DisplayName);*/
finishActivityWithSuccess(activity, authResult); return BuildRootPathForUser(authResult);
return;
} }
catch (MsalUiRequiredException ex) 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 return null;
{
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();
}
} }
string buildRootPathForUser(AuthenticationResult res) string BuildRootPathForUser(AuthenticationResult res)
{ {
return OneDrive2ItemLocation<OneDrive2PrefixContainerType>.RootForUser(res.Account.Username, res.Account.HomeAccountId.Identifier).ToString(); 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)); activity.State.PutString(FileStorageSetupDefs.ExtraPath, rootPathForUser);
finishActivityWithSuccess(activity); FinishActivityWithSuccess(activity);
} }
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) 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>(); 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); FileDescription myEntry = GetFileDescription(parentPath.BuildShare("me","me","me", root.ParentReference?.DriveId), root);
myEntry.DisplayName = MyOneDriveDisplayName; myEntry.DisplayName = MyOneDriveDisplayName;
@@ -888,8 +961,7 @@ namespace keepass2android.Io
IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = await client.Me.Drive.SharedWithMe().Request().GetAsync();
Task.Run(async () => await client.Me.Drive.SharedWithMe().Request().GetAsync()).Result;
while (true) while (true)
{ {
@@ -905,7 +977,7 @@ namespace keepass2android.Io
} }
var b = sharedWithMeCollectionPage.NextPageRequest; var b = sharedWithMeCollectionPage.NextPageRequest;
if (b == null) break; if (b == null) break;
sharedWithMeCollectionPage = Task.Run(async () => await b.GetAsync()).Result; sharedWithMeCollectionPage = await b.GetAsync();
} }
return result; return result;
} }
@@ -945,29 +1017,7 @@ namespace keepass2android.Io
{ {
try try
{ {
DriveItem driveItem = new DriveItem(); return Task.Run(async() => await CreateFilePathAsync(parent, newFilename)).Result;
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();
} }
catch (Exception e) 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) public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{ {
return IOConnectionInfo.FromPath(OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path).Parent.ToString()); return IOConnectionInfo.FromPath(OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(ioc.Path).Parent.ToString());