From f99843737f941f7cb1c2adb93e831daa46f7c6d1 Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 10 Sep 2019 05:26:38 +0200 Subject: [PATCH] implement java side of OneDriveStorage2 --- src/java/JavaFileStorage/app/build.gradle | 2 +- .../javafilestorage/OneDriveStorage2.java | 364 ++++++++++++------ .../javafilestoragetest/MainActivity.java | 4 +- 3 files changed, 248 insertions(+), 122 deletions(-) diff --git a/src/java/JavaFileStorage/app/build.gradle b/src/java/JavaFileStorage/app/build.gradle index a5207498..cbb8cbcf 100644 --- a/src/java/JavaFileStorage/app/build.gradle +++ b/src/java/JavaFileStorage/app/build.gradle @@ -37,7 +37,7 @@ dependencies { } compile 'com.pcloud.sdk:java-core:1.1.0' compile 'com.pcloud.sdk:android:1.1.0' - compile('com.microsoft.graph:msgraph-sdk-android:1.2.+') + compile('com.microsoft.graph:msgraph-sdk-android:1.7.0') compile ('com.microsoft.identity.client:msal:0.1.+') { exclude group: 'com.android.support', module: 'appcompat-v7' } diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/OneDriveStorage2.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/OneDriveStorage2.java index 2215bc36..398318f6 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/OneDriveStorage2.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/OneDriveStorage2.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.util.Base64; import android.util.Log; @@ -11,12 +12,16 @@ import com.microsoft.graph.core.ClientException; import com.microsoft.graph.core.DefaultClientConfig; import com.microsoft.graph.core.GraphErrorCodes; import com.microsoft.graph.extensions.DriveItem; +import com.microsoft.graph.extensions.Folder; import com.microsoft.graph.extensions.GraphServiceClient; import com.microsoft.graph.extensions.IDriveItemCollectionPage; import com.microsoft.graph.extensions.IDriveItemCollectionRequestBuilder; import com.microsoft.graph.extensions.IDriveItemRequest; import com.microsoft.graph.extensions.IDriveItemRequestBuilder; +import com.microsoft.graph.extensions.IDriveSharedWithMeCollectionPage; +import com.microsoft.graph.extensions.IDriveSharedWithMeCollectionRequestBuilder; import com.microsoft.graph.extensions.IGraphServiceClient; +import com.microsoft.graph.http.GraphServiceException; import com.microsoft.identity.client.AuthenticationCallback; import com.microsoft.identity.client.AuthenticationResult; import com.microsoft.identity.client.MsalException; @@ -48,8 +53,6 @@ public class OneDriveStorage2 extends JavaFileStorageBase public OneDriveStorage2(final Activity context, final String clientId) { mPublicClientApp = new PublicClientApplication(context, clientId); - initAuthenticator(context); - } @@ -57,76 +60,27 @@ public class OneDriveStorage2 extends JavaFileStorageBase @Override public boolean requiresSetup(String path) { - - return !isConnected(null); + return false; } @Override public void startSelectFile(FileStorageSetupInitiatorActivity activity, boolean isForSave, int requestCode) { - initAuthenticator((Activity)activity.getActivity()); - String path = getProtocolId()+":///"; - Log.d("KP2AJ", "startSelectFile "+path+", connected: "+path); - if (isConnected(null)) - { - Intent intent = new Intent(); - intent.putExtra(EXTRA_IS_FOR_SAVE, isForSave); - intent.putExtra(EXTRA_PATH, path); - activity.onImmediateResult(requestCode, RESULT_FILECHOOSER_PREPARED, intent); - } - else - { - activity.startSelectFileProcess(path, isForSave, requestCode); - } + Log.d("KP2AJ", "startSelectFile "+path); + activity.startSelectFileProcess(path, isForSave, requestCode); + } private boolean isConnected(String path) { try { - if (tryGetMsGraphClient(path) == null) - try { - final CountDownLatch latch = new CountDownLatch(1); + logDebug("isConnected? " + path); - Log.d("KP2AJ", "trying silent login"); - - String userId = extractUserId(path); - final MsalException[] _exception = {null}; - final AuthenticationResult[] _result = {null}; - User user = mPublicClientApp.getUser(userId); - mPublicClientApp.acquireTokenSilentAsync(scopes, user, - new AuthenticationCallback() { - - @Override - public void onSuccess(AuthenticationResult authenticationResult) { - _result[0] = authenticationResult; - latch.countDown(); - } - - @Override - public void onError(MsalException exception) { - _exception[0] = exception; - latch.countDown(); - } - - @Override - public void onCancel() { - latch.countDown(); - - } - }); - latch.await(); - if (_result[0] != null) { - buildClient(_result[0]); - } else if (_exception[0] != null){ - _exception[0].printStackTrace(); - } - } catch (Exception e) { - e.printStackTrace(); - } return tryGetMsGraphClient(path) != null; } catch (Exception e) { + logDebug("exception in isConnected: " + e.toString()); return false; } @@ -142,23 +96,21 @@ public class OneDriveStorage2 extends JavaFileStorageBase private String extractUserId(String path) throws Exception { String pathWithoutProtocol = removeProtocol(path); - String[] parts = pathWithoutProtocol.split("/",1); - if (parts.length != 2 || ("".equals(parts[0]))) + String[] parts = pathWithoutProtocol.split("/",2); + logDebug("extractUserId for path " + path); + logDebug("# parts: " + parts.length); + if (parts.length < 1 || ("".equals(parts[0]))) { throw new Exception("path does not contain user"); } + logDebug("parts[0]: " + parts[0]); return parts[0]; } - private void initAuthenticator(Activity activity) { - - - } - @Override public void prepareFileUsage(FileStorageSetupInitiatorActivity activity, String path, int requestCode, boolean alwaysReturnSuccess) { - initAuthenticator((Activity)activity.getActivity()); + if (isConnected(path)) { Intent intent = new Intent(); @@ -174,12 +126,12 @@ public class OneDriveStorage2 extends JavaFileStorageBase @Override public String getProtocolId() { - return "onedrive"; + return "onedrive2"; } @Override public void prepareFileUsage(Context appContext, String path) throws UserInteractionRequiredException { - if (!isConnected(null)) + if (!isConnected(path)) { throw new UserInteractionRequiredException(); } @@ -203,11 +155,15 @@ public class OneDriveStorage2 extends JavaFileStorageBase - private IGraphServiceClient buildClient(AuthenticationResult authenticationResult) throws InterruptedException { + private IGraphServiceClient buildClient(AuthenticationResult authenticationResult) throws Exception { + logDebug("buildClient..."); IGraphServiceClient newClient = new GraphServiceClient.Builder() .fromConfig(DefaultClientConfig.createWithAuthenticationProvider(new GraphServiceClientManager(authenticationResult.getAccessToken()))) .buildClient(); + logDebug("authToken = " + authenticationResult.getAccessToken()); + if (authenticationResult.getUser() == null) + throw new Exception("authenticationResult.getUser() == null!"); mClientByUser.put(authenticationResult.getUser().getUserIdentifier(), newClient); return newClient; @@ -249,14 +205,30 @@ public class OneDriveStorage2 extends JavaFileStorageBase { public IGraphServiceClient client; public String oneDrivePath; - public IDriveItemRequestBuilder getPathItem() - { - IDriveItemRequestBuilder pathItem = client.getDrive().getRoot(); + public String share; + public IDriveItemRequestBuilder getPathItem() throws Exception { + IDriveItemRequestBuilder pathItem; + if (!hasShare()) { + logDebug("p: " + oneDrivePath); + for (StackTraceElement ste : Thread.currentThread().getStackTrace()) { + logDebug(ste.toString()); + } + throw new Exception("Cannot get path item without share"); + } + if ("me".equals(share)) + pathItem = client.getMe().getDrive().getRoot(); + else + pathItem = client.getShares(share).getRoot(); + if ("".equals(oneDrivePath) == false) { pathItem = pathItem.getItemWithPath(oneDrivePath); } return pathItem; } + + public boolean hasShare() { + return !(share == null || "".equals(share)); + } } @Override @@ -264,9 +236,8 @@ public class OneDriveStorage2 extends JavaFileStorageBase try { ClientAndPath clientAndpath = getOneDriveClientAndPath(path); logDebug("openFileForRead. Path="+path); - InputStream result = clientAndpath.client.getDrive() - .getRoot() - .getItemWithPath(clientAndpath.oneDrivePath) + InputStream result = clientAndpath + .getPathItem() .getContent() .buildRequest() .get(); @@ -284,13 +255,24 @@ public class OneDriveStorage2 extends JavaFileStorageBase ClientAndPath result = new ClientAndPath(); String pathWithoutProtocol = removeProtocol(path); - String[] parts = pathWithoutProtocol.split("/",2); - if (parts.length != 2 || ("".equals(parts[0]))) + String[] parts = pathWithoutProtocol.split("/",3); + if (parts.length < 2 || ("".equals(parts[0]))) { throw new Exception("path does not contain user"); } result.client = mClientByUser.get(parts[0]); - result.oneDrivePath = parts[1]; + if (result.client == null) + throw new Exception("failed to get client for " + parts[0]); + + + logDebug("building client for " + path + " results in " + parts.length + " segments"); + logDebug("share is " + parts[1]); + result.share = parts[1]; + + if (parts.length > 2) { + result.oneDrivePath = parts[2]; + + } return result; } @@ -299,6 +281,8 @@ public class OneDriveStorage2 extends JavaFileStorageBase private Exception convertException(ClientException e) { if (e.isError(GraphErrorCodes.ItemNotFound)) return new FileNotFoundException(e.getMessage()); + if (e.getMessage().contains("\n\n404 : ")) //hacky solution to check for not found. errorCode was null in my tests so I had to find a workaround. + return new FileNotFoundException(e.getMessage()); return e; } @@ -306,9 +290,8 @@ public class OneDriveStorage2 extends JavaFileStorageBase public void uploadFile(String path, byte[] data, boolean writeTransactional) throws Exception { try { ClientAndPath clientAndPath = getOneDriveClientAndPath(path); - clientAndPath.client.getDrive() - .getRoot() - .getItemWithPath(clientAndPath.oneDrivePath) + clientAndPath + .getPathItem() .getContent() .buildRequest() .put(data); @@ -319,7 +302,24 @@ public class OneDriveStorage2 extends JavaFileStorageBase @Override public String createFolder(String parentPath, String newDirName) throws Exception { - throw new Exception("not implemented."); + try { + DriveItem driveItem = new DriveItem(); + driveItem.name = newDirName; + driveItem.folder = new Folder(); + + ClientAndPath clientAndPath = getOneDriveClientAndPath(parentPath); + + + logDebug("building request for " + clientAndPath.oneDrivePath); + + DriveItem res = clientAndPath.getPathItem() + .getChildren() + .buildRequest() + .post(driveItem); + return createFilePath(parentPath, newDirName); + } catch (ClientException e) { + throw convertException(e); + } } @Override @@ -332,12 +332,79 @@ public class OneDriveStorage2 extends JavaFileStorageBase return path; } + private List listShares(String parentPath, IGraphServiceClient client) throws Exception { + ArrayList result = new ArrayList(); + logDebug("listShares: " + (client == null)); + if (!parentPath.endsWith("/")) + parentPath += "/"; + + logDebug("listShares"); + FileEntry myEntry = getFileEntry(parentPath+"me/", client.getMe().getDrive().getRoot().buildRequest().get()); + //if ((myEntry.displayName == null) || "".equals(myEntry.displayName)) + myEntry.displayName = "My OneDrive"; + + logDebug("myEntry.path = " + myEntry.path + ", isDir = " + myEntry.isDirectory); + result.add(myEntry); + + IDriveSharedWithMeCollectionPage sharedWithMeCollectionPage = client.getMe().getDrive().getSharedWithMe().buildRequest().get(); + + while (true) { + List sharedWithMeItems = sharedWithMeCollectionPage.getCurrentPage(); + if (sharedWithMeItems.isEmpty()) + break; + + for (DriveItem i : sharedWithMeItems) { + Log.d("kp2aSHARE",i.name + " " + i.description + " " + i.id + " " + i.webUrl); + String urlToEncode = i.webUrl; + //calculate shareid according to https://docs.microsoft.com/en-us/graph/api/shares-get?view=graph-rest-1.0&tabs=java + String shareId = "u!"+android.util.Base64.encodeToString(urlToEncode.getBytes(), Base64.NO_PADDING).replace('/','_').replace('+','_') + .replace("\n",""); //encodeToString adds a newline character add the end - remove + Log.d("kp2aSHARE","shareId: " +shareId); + FileEntry sharedFileEntry = getFileEntry(parentPath + shareId +"/", i); + result.add(sharedFileEntry); +/* + try { + DriveItem x2 = client.shares(shareId).root().buildRequest().get(); + Log.d("kp2aSHARE","x2: " + x2.name + " " + x2.description + " " + x2.id + " "); + } + catch (ClientException e) + { + if (e.getCause() != null) + Log.d("kp2aSHARE","cause: " + e.getCause().toString()); + Log.d("kp2aSHARE","exception: " + e.toString()); + } + catch (Exception e) + { + Log.d("kp2aSHARE","share item exc: " + e.toString()); + } +*/ + + } + IDriveSharedWithMeCollectionRequestBuilder b = sharedWithMeCollectionPage.getNextPage(); + if (b == null) break; + sharedWithMeCollectionPage =b.buildRequest().get(); + } + return result; + } + + @Override public List listFiles(String parentPath) throws Exception { try { - ArrayList result = new ArrayList(); ClientAndPath clientAndPath = getOneDriveClientAndPath(parentPath); - parentPath = clientAndPath.oneDrivePath; + + logDebug("listing files for " + parentPath +", " + clientAndPath.share + clientAndPath.hasShare()); + if (!clientAndPath.hasShare()) + { + logDebug("listing shares."); + return listShares(parentPath, clientAndPath.client); + } + + logDebug("listing regular children."); + ArrayList result = new ArrayList(); + /*logDebug("parent before:" + parentPath); + parentPath = parentPath.substring(getProtocolPrefix().length()); + logDebug("parent after: " + parentPath);*/ IDriveItemCollectionPage itemsPage = clientAndPath.getPathItem() .getChildren() @@ -377,7 +444,7 @@ public class OneDriveStorage2 extends JavaFileStorageBase e.displayName = i.name; e.canRead = e.canWrite = true; - e.path = getProtocolId() +"://"+path; + e.path = path; if (i.lastModifiedDateTime != null) e.lastModifiedTime = i.lastModifiedDateTime.getTimeInMillis(); else if ((i.remoteItem != null)&&(i.remoteItem.lastModifiedDateTime != null)) @@ -391,6 +458,16 @@ public class OneDriveStorage2 extends JavaFileStorageBase try { ClientAndPath clientAndPath = getOneDriveClientAndPath(filename); + + if (((clientAndPath.oneDrivePath == null) || "".equals(clientAndPath.oneDrivePath)) + && !clientAndPath.hasShare()) + { + FileEntry rootEntry = new FileEntry(); + rootEntry.displayName = ""; + rootEntry.isDirectory = true; + return rootEntry; + } + IDriveItemRequestBuilder pathItem = clientAndPath.getPathItem(); IDriveItemRequest request = pathItem.buildRequest(); @@ -405,9 +482,7 @@ public class OneDriveStorage2 extends JavaFileStorageBase public void delete(String path) throws Exception { try { ClientAndPath clientAndPath = getOneDriveClientAndPath(path); - clientAndPath.client.getDrive() - .getRoot() - .getItemWithPath(clientAndPath.oneDrivePath) + clientAndPath.getPathItem() .buildRequest() .delete(); } catch (ClientException e) { @@ -422,63 +497,114 @@ public class OneDriveStorage2 extends JavaFileStorageBase if (activity.getProcessName().equals(PROCESS_NAME_SELECTFILE)) activity.getState().putString(EXTRA_PATH, activity.getPath()); - String userId = activity.getState().getString("OneDriveUser"); - if (mClientByUser.containsKey(userId)) { - finishActivityWithSuccess(activity); + + + User user = null; + try { + String userId = extractUserId(activity.getPath()); + if (mClientByUser.containsKey(userId)) { + finishActivityWithSuccess(activity); + return; + } + + logDebug("needs acquire token"); + + Log.d("KP2AJ", "trying silent login " + activity.getPath()); + + final MsalException[] _exception = {null}; + final AuthenticationResult[] _result = {null}; + logDebug("getting user for " + userId); + user = mPublicClientApp.getUser(userId); + logDebug("getting user ok."); + + } catch (Exception e) { + logDebug(e.toString()); + e.printStackTrace(); + } + if (user != null) + { + mPublicClientApp.acquireTokenSilentAsync(scopes, user, + new AuthenticationCallback() { + @Override + public void onSuccess(AuthenticationResult authenticationResult) { + successAuthCallback(authenticationResult, activity); + } + + @Override + public void onError(MsalException exception) { + startInteractiveAcquireToken(activity); + } + + @Override + public void onCancel() { + + cancelAuthCallback((Activity) activity); + } + }); return; } - JavaFileStorage.FileStorageSetupActivity storageSetupAct = activity; - - final CountDownLatch latch = new CountDownLatch(1); - final AuthenticationResult[] _authenticationResult = {null}; - MsalException _exception[] = {null}; + startInteractiveAcquireToken(activity); + } + private void startInteractiveAcquireToken(FileStorageSetupActivity activity) { if (!acquireTokenRunning) { acquireTokenRunning = true; mPublicClientApp.acquireToken((Activity) activity, scopes, new AuthenticationCallback() { @Override public void onSuccess(AuthenticationResult authenticationResult) { - Log.i(TAG, "authenticating successful"); - - try { - buildClient(authenticationResult); - } catch (InterruptedException e) { - e.printStackTrace(); - } - activity.getState().putString(EXTRA_PATH, getProtocolPrefix() + authenticationResult.getUser().getUserIdentifier() + "/"); - - finishActivityWithSuccess(activity); - acquireTokenRunning = false; - return; + successAuthCallback(authenticationResult, activity); } @Override public void onError(MsalException exception) { - Log.i(TAG, "authenticating not successful"); - Intent data = new Intent(); - data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not successful"); - ((Activity) activity).setResult(Activity.RESULT_CANCELED, data); - ((Activity) activity).finish(); - acquireTokenRunning = false; + errorAuthCallback((Activity) activity); } @Override public void onCancel() { - Log.i(TAG, "authenticating cancelled"); - Intent data = new Intent(); - data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not cancelled"); - ((Activity) activity).setResult(Activity.RESULT_CANCELED, data); - ((Activity) activity).finish(); - acquireTokenRunning = false; + cancelAuthCallback((Activity) activity); } }); } } + private void successAuthCallback(AuthenticationResult authenticationResult, FileStorageSetupActivity activity) { + Log.i(TAG, "authenticating successful"); + + try { + buildClient(authenticationResult); + } catch (Exception e) { + logDebug(e.toString()); + e.printStackTrace(); + } + activity.getState().putString(EXTRA_PATH, getProtocolPrefix() + authenticationResult.getUser().getUserIdentifier() + "/"); + + finishActivityWithSuccess(activity); + acquireTokenRunning = false; + } + + private void errorAuthCallback(Activity activity) { + Log.i(TAG, "authenticating not successful"); + Intent data = new Intent(); + data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not successful"); + activity.setResult(Activity.RESULT_CANCELED, data); + activity.finish(); + acquireTokenRunning = false; + } + + private void cancelAuthCallback(Activity activity) { + Log.i(TAG, "authenticating cancelled"); + Intent data = new Intent(); + data.putExtra(EXTRA_ERROR_MESSAGE, "authenticating not cancelled"); + activity.setResult(Activity.RESULT_CANCELED, data); + activity.finish(); + acquireTokenRunning = false; + } + @Override public void onActivityResult(FileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) { mPublicClientApp.handleInteractiveRequestRedirect(requestCode, resultCode, data); diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/MainActivity.java b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/MainActivity.java index f47330bc..d9935754 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/MainActivity.java +++ b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/MainActivity.java @@ -430,11 +430,11 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag && (e.canWrite == file.canWrite) && (e.isDirectory == file.isDirectory) && (e.displayName.equals(file.displayName)) - && (e.sizeInBytes == file.sizeInBytes )) + && (file.isDirectory || (e.sizeInBytes == file.sizeInBytes ))) return; } - throw new Exception("didn't find file " + file.path + " in file list!"); + throw new Exception("didn't find file " + file.path + " (" + file.displayName + ") in file list!"); } }