diff --git a/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs b/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs index 94e01dbb..b7f4cfa5 100644 --- a/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OneDrive2FileStorage.cs @@ -294,19 +294,37 @@ namespace keepass2android.Io get { yield return ProtocolId; } } - Dictionary mClientByUser = - new Dictionary(); + class GraphServiceClientWithState + { + public IGraphServiceClient Client { get; set; } + public DateTime TokenExpiryDate { get; set; } + public bool RequiresUserInteraction { get; set; } + } - private IGraphServiceClient tryGetMsGraphClient(String path) + readonly Dictionary mClientByUser = + new Dictionary(); + + private async Task TryGetMsGraphClient(String path, bool tryConnect) { String userId = OneDrive2ItemLocation.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 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(stream)).Wait(); + Task.Run(async () => + { + PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path); + return await + pathItemBuilder + .getPathItem() + .Content + .Request() + .PutAsync(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 result = new List(); - /*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 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> 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 result = new List(); + /*logDebug("parent before:" + parentPath); + parentPath = parentPath.substring(getProtocolPrefix().length()); + logDebug("parent after: " + parentPath);*/ + + IDriveItemChildrenCollectionPage itemsPage = await pathItemBuilder.getPathItem() + .Children + .Request() + .GetAsync(); + while (true) + { + IList 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 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 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 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 TryLoginSilent(string iocPath) + { IAccount account = null; try { - String userId = OneDrive2ItemLocation.FromString(activity.Ioc.Path).User?.Id; - if (mClientByUser.ContainsKey(userId)) + + if (IsConnected(iocPath)) { - finishActivityWithSuccess(activity); - return; + return iocPath; } - + String userId = OneDrive2ItemLocation.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.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 ListShares(OneDrive2ItemLocation parentPath, IGraphServiceClient client) + private async Task> ListShares(OneDrive2ItemLocation parentPath, IGraphServiceClient client) { List result = new List(); - 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 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.FromString(ioc.Path).Parent.ToString()); diff --git a/src/keepass2android/Resources/values-hu/strings.xml b/src/keepass2android/Resources/values-hu/strings.xml index 08e3491f..e1146265 100644 --- a/src/keepass2android/Resources/values-hu/strings.xml +++ b/src/keepass2android/Resources/values-hu/strings.xml @@ -635,7 +635,7 @@ Értesítés a jelenleg kiválasztott bejegyzés egyszerűsített eléréséhez. A billentyűzet megjelenítése a jelszóbeíráshoz ujjlenyomat-leolvasás közben. Lehetőségek kiterjesztése, hogy egy sörrel vagy valami mással támogasson - Version 0.6.2 + <b>Version 0.6.2</> * Google Drive/Dropbox/... integráció: Ha a hivatalos Google Drive vagy Dropbox applikációból nyit meg egy .kdbx fájlt, akkor az autómatikusan a KP2A-val kerül megnyitásra. * Továbbfejlesztett kereső dialógus * Továbbfejlesztett keresési eredmények ha a megosztott URL aldomaint tartalmaz diff --git a/src/keepass2android/Resources/values-ru/strings.xml b/src/keepass2android/Resources/values-ru/strings.xml index 26451e38..66658a23 100644 --- a/src/keepass2android/Resources/values-ru/strings.xml +++ b/src/keepass2android/Resources/values-ru/strings.xml @@ -543,7 +543,7 @@ Запрос учетных данных Плагину будет разрешено запрашивать учетные данные для веб-сайтов или приложений которые он использует. Больше типов хранилищ - Предупреждение: Ошибка проверки сертификата сервера: %1$ s. Установите соответствующий корневой сертификат на вашем устройстве или проверьте настройки! + Предупреждение: Ошибка проверки сертификата сервера: %1$s. Установите соответствующий корневой сертификат на вашем устройстве или проверьте настройки! Ошибка: Проверка сертификата сервера не удалась! Установите соответствующий корневой сертификат или проверьте настройки! Выберите формат файла Извините! Keepass2Android был убит ОС Android! По соображениям безопасности Keepass2Android не сохраняет выбранные учетные данные на диске, так что вам нужно повторно открыть базу данных. Примечание: Это должно происходить очень редко. Если это так, пожалуйста, напишите мне сообщение на crocoapps@gmail.com. diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml index 223dd4e0..fef65dbc 100644 --- a/src/keepass2android/Resources/values/strings.xml +++ b/src/keepass2android/Resources/values/strings.xml @@ -1,6 +1,7 @@ + Feedback Keepass2Android is a password manager providing read/write access to KeePass 2.x databases on Android. The User Interface is based on a port of KeepassDroid developed by Brian Pellin. Code for database operations is based on KeePass by Dominik Reichl. The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.