diff --git a/src/java/JavaFileStorage/app/build.gradle b/src/java/JavaFileStorage/app/build.gradle index 789f4131..625c12b8 100644 --- a/src/java/JavaFileStorage/app/build.gradle +++ b/src/java/JavaFileStorage/app/build.gradle @@ -37,6 +37,10 @@ dependencies { } compile 'com.pcloud.sdk:java-core:1.0.1' compile 'com.pcloud.sdk:android:1.0.1' + compile('com.microsoft.graph:msgraph-sdk-android:1.2.+') + compile ('com.microsoft.identity.client:msal:0.1.+') { + exclude group: 'com.android.support', module: 'appcompat-v7' + } compile 'com.google.code.gson:gson:2.3.1' compile 'com.microsoft.services.msa:msa-auth:0.8.6' compile 'com.microsoft.aad:adal:1.14.0' 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 new file mode 100644 index 00000000..a412c0ad --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/OneDriveStorage2.java @@ -0,0 +1,484 @@ +package keepass2android.javafilestorage; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + + +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.GraphServiceClient; +import com.microsoft.graph.extensions.IDriveItemCollectionPage; +import com.microsoft.graph.extensions.IDriveItemCollectionRequestBuilder; +import com.microsoft.graph.extensions.IDriveItemRequestBuilder; +import com.microsoft.graph.extensions.IGraphServiceClient; +import com.microsoft.identity.client.AuthenticationCallback; +import com.microsoft.identity.client.AuthenticationResult; +import com.microsoft.identity.client.MsalException; +import com.microsoft.identity.client.PublicClientApplication; +import com.microsoft.identity.client.User; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import keepass2android.javafilestorage.onedrive2.GraphServiceClientManager; + + +/** + * Created by Philipp on 20.11.2016. + */ +public class OneDriveStorage2 extends JavaFileStorageBase +{ + PublicClientApplication mPublicClientApp; + + final HashMap mClientByUser = new HashMap(); + + private static final String[] scopes = {"offline_access", "https://graph.microsoft.com/Files.ReadWrite","https://graph.microsoft.com/User.Read"}; + + + public OneDriveStorage2(final Activity context, final String clientId) { + + mPublicClientApp = new PublicClientApplication(context, clientId); + initAuthenticator(context); + + + } + + + @Override + public boolean requiresSetup(String path) + { + + return !isConnected(null); + } + + @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); + } + } + + private boolean isConnected(String path) { + try { + if (tryGetMsGraphClient(path) == null) + try { + final CountDownLatch latch = new CountDownLatch(1); + + 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) + { + return false; + } + + } + + private IGraphServiceClient tryGetMsGraphClient(String path) throws Exception + { + String userId = extractUserId(path); + if (mClientByUser.containsKey(userId)) + return mClientByUser.get(userId); + return null; + } + + private String extractUserId(String path) throws Exception { + String pathWithoutProtocol = removeProtocol(path); + String[] parts = pathWithoutProtocol.split("/",1); + if (parts.length != 2 || ("".equals(parts[0]))) + { + throw new Exception("path does not contain user"); + } + 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(); + intent.putExtra(EXTRA_PATH, path); + activity.onImmediateResult(requestCode, RESULT_FILEUSAGE_PREPARED, intent); + } + else + { + activity.startFileUsageProcess(path, requestCode, alwaysReturnSuccess); + } + + } + + @Override + public String getProtocolId() { + return "onedrive"; + } + + @Override + public void prepareFileUsage(Context appContext, String path) throws UserInteractionRequiredException { + if (!isConnected(null)) + { + throw new UserInteractionRequiredException(); + } + + } + + @Override + public void onCreate(FileStorageSetupActivity activity, Bundle savedInstanceState) { + + Log.d("KP2AJ", "OnCreate"); + + } + + @Override + public void onResume(final FileStorageSetupActivity activity) { + + + + + } + + + + private IGraphServiceClient buildClient(AuthenticationResult authenticationResult) throws InterruptedException { + + IGraphServiceClient newClient = new GraphServiceClient.Builder() + .fromConfig(DefaultClientConfig.createWithAuthenticationProvider(new GraphServiceClientManager(authenticationResult.getAccessToken()))) + .buildClient(); + mClientByUser.put(authenticationResult.getUser().getUserIdentifier(), newClient); + + return newClient; + } + + + + String removeProtocol(String path) throws Exception { + if (path == null) + return null; + return path.substring(getProtocolId().length()+3); + } + + @Override + public String getDisplayName(String path) { + + if (path == null) + return null; + + return path; + } + + @Override + public String getFilename(String path) throws Exception { + return path.substring(path.lastIndexOf("/")+1); + } + + @Override + public boolean checkForFileChangeFast(String path, String previousFileVersion) throws Exception { + return false; + } + + @Override + public String getCurrentFileVersionFast(String path) { + return null; + } + + class ClientAndPath + { + public IGraphServiceClient client; + public String oneDrivePath; + public IDriveItemRequestBuilder getPathItem() + { + IDriveItemRequestBuilder pathItem = client.getDrive().getRoot(); + if ("".equals(oneDrivePath) == false) { + pathItem = pathItem.getItemWithPath(oneDrivePath); + } + return pathItem; + } + } + + @Override + public InputStream openFileForRead(String path) throws Exception { + try { + ClientAndPath clientAndpath = getOneDriveClientAndPath(path); + logDebug("openFileForRead. Path="+path); + InputStream result = clientAndpath.client.getDrive() + .getRoot() + .getItemWithPath(clientAndpath.oneDrivePath) + .getContent() + .buildRequest() + .get(); + logDebug("ok"); + return result; + + } + catch (ClientException e) + { + throw convertException(e); + } + } + + private ClientAndPath getOneDriveClientAndPath(String path) throws Exception { + ClientAndPath result = new ClientAndPath(); + + String pathWithoutProtocol = removeProtocol(path); + String[] parts = pathWithoutProtocol.split("/",2); + 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]; + return result; + + } + + + private Exception convertException(ClientException e) { + if (e.isError(GraphErrorCodes.ItemNotFound)) + return new FileNotFoundException(e.getMessage()); + return e; + } + + @Override + public void uploadFile(String path, byte[] data, boolean writeTransactional) throws Exception { + try { + ClientAndPath clientAndPath = getOneDriveClientAndPath(path); + clientAndPath.client.getDrive() + .getRoot() + .getItemWithPath(clientAndPath.oneDrivePath) + .getContent() + .buildRequest() + .put(data); + } catch (ClientException e) { + throw convertException(e); + } + } + + @Override + public String createFolder(String parentPath, String newDirName) throws Exception { + throw new Exception("not implemented."); + } + + @Override + public String createFilePath(String parentPath, String newFileName) throws Exception { + String path = parentPath; + if (!path.endsWith("/")) + path = path + "/"; + path = path + newFileName; + + return path; + } + + @Override + public List listFiles(String parentPath) throws Exception { + try { + ArrayList result = new ArrayList(); + ClientAndPath clientAndPath = getOneDriveClientAndPath(parentPath); + parentPath = clientAndPath.oneDrivePath; + + IDriveItemCollectionPage itemsPage = clientAndPath.getPathItem() + .getChildren() + .buildRequest() + .get(); + if (parentPath.endsWith("/")) + parentPath = parentPath.substring(0,parentPath.length()-1); + while (true) + { + List items = itemsPage.getCurrentPage(); + if (items.isEmpty()) + return result; + + for (DriveItem i: items) + { + FileEntry e = getFileEntry(parentPath + "/" + i.name, i); + Log.d("KP2AJ", e.path); + result.add(e); + } + IDriveItemCollectionRequestBuilder nextPageReqBuilder = itemsPage.getNextPage(); + if (nextPageReqBuilder == null) + return result; + itemsPage = nextPageReqBuilder.buildRequest().get(); + + } + } catch (ClientException e) { + throw convertException(e); + } + } + + private FileEntry getFileEntry(String path, DriveItem i) { + FileEntry e = new FileEntry(); + if (i.size != null) + e.sizeInBytes = i.size; + else if ((i.remoteItem != null) && (i.remoteItem.size != null)) + e.sizeInBytes = i.remoteItem.size; + + e.displayName = i.name; + e.canRead = e.canWrite = true; + e.path = getProtocolId() +"://"+path; + if (i.lastModifiedDateTime != null) + e.lastModifiedTime = i.lastModifiedDateTime.getTimeInMillis(); + else if ((i.remoteItem != null)&&(i.remoteItem.lastModifiedDateTime != null)) + e.lastModifiedTime = i.remoteItem.lastModifiedDateTime.getTimeInMillis(); + e.isDirectory = (i.folder != null) || ((i.remoteItem != null) && (i.remoteItem.folder != null)); + return e; + } + + @Override + public FileEntry getFileEntry(String filename) throws Exception { + try { + + ClientAndPath clientAndPath = getOneDriveClientAndPath(filename); + DriveItem item = clientAndPath.getPathItem() + .buildRequest() + .get(); + return getFileEntry(filename, item); + } catch (ClientException e) { + throw convertException(e); + } + } + + @Override + public void delete(String path) throws Exception { + try { + ClientAndPath clientAndPath = getOneDriveClientAndPath(path); + clientAndPath.client.getDrive() + .getRoot() + .getItemWithPath(clientAndPath.oneDrivePath) + .buildRequest() + .delete(); + } catch (ClientException e) { + throw convertException(e); + } + } + + boolean acquireTokenRunning = false; + @Override + public void onStart(final FileStorageSetupActivity activity) { + Log.d("KP2AJ", "onStart " + activity.getPath()); + 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); + return; + } + + + JavaFileStorage.FileStorageSetupActivity storageSetupAct = activity; + + final CountDownLatch latch = new CountDownLatch(1); + final AuthenticationResult[] _authenticationResult = {null}; + MsalException _exception[] = {null}; + + 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; + } + + @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; + } + + @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; + } + }); + } + } + + @Override + public void onActivityResult(FileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) { + mPublicClientApp.handleInteractiveRequestRedirect(requestCode, resultCode, data); + } +} diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java index 48d9461c..aea249ec 100644 --- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java @@ -24,7 +24,6 @@ import com.jcraft.jsch.UserInfo; import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; public class SftpStorage extends JavaFileStorageBase { @@ -358,12 +357,10 @@ public class SftpStorage extends JavaFileStorageBase { } - @NonNull private String getBaseDir() { return _appContext.getFilesDir().getAbsolutePath(); } - @NonNull private String getKeyFileName() { return getBaseDir() + "/id_kp2a_rsa"; } diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/onedrive2/GraphServiceClientManager.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/onedrive2/GraphServiceClientManager.java new file mode 100644 index 00000000..124e75f1 --- /dev/null +++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/onedrive2/GraphServiceClientManager.java @@ -0,0 +1,45 @@ +package keepass2android.javafilestorage.onedrive2; + +import com.microsoft.graph.authentication.IAuthenticationProvider; +import com.microsoft.graph.core.DefaultClientConfig; +import com.microsoft.graph.core.IClientConfig; +import com.microsoft.graph.extensions.GraphServiceClient; +import com.microsoft.graph.extensions.IGraphServiceClient; +import com.microsoft.graph.http.IHttpRequest; + +/** + * Singleton class that manages a GraphServiceClient object. + * It implements IAuthentication provider to authenticate requests using an access token. + */ +public class GraphServiceClientManager implements IAuthenticationProvider { + private IGraphServiceClient mGraphServiceClient; + private String mAccessToken; + + public GraphServiceClientManager(String accessToken) { + mAccessToken = accessToken; + } + + @Override + public void authenticateRequest(IHttpRequest request) { + try { + request.addHeader("Authorization", "Bearer " + mAccessToken); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + public synchronized IGraphServiceClient getGraphServiceClient() { + return getGraphServiceClient(this); + } + + public synchronized IGraphServiceClient getGraphServiceClient(IAuthenticationProvider authenticationProvider) { + if (mGraphServiceClient == null) { + IClientConfig clientConfig = DefaultClientConfig.createWithAuthenticationProvider( + authenticationProvider + ); + mGraphServiceClient = new GraphServiceClient.Builder().fromConfig(clientConfig).buildClient(); + } + + return mGraphServiceClient; + } +} diff --git a/src/java/JavaFileStorageTest-AS/app/build.gradle b/src/java/JavaFileStorageTest-AS/app/build.gradle index 27e3252e..daa45dca 100644 --- a/src/java/JavaFileStorageTest-AS/app/build.gradle +++ b/src/java/JavaFileStorageTest-AS/app/build.gradle @@ -10,6 +10,8 @@ android { targetSdkVersion 23 versionCode 1 versionName "1.0" + multiDexEnabled true + } buildTypes { diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/AndroidManifest.xml b/src/java/JavaFileStorageTest-AS/app/src/main/AndroidManifest.xml index e7740ee6..cccbd169 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/AndroidManifest.xml +++ b/src/java/JavaFileStorageTest-AS/app/src/main/AndroidManifest.xml @@ -59,6 +59,18 @@ + + + + + + + + + + @@ -68,6 +80,8 @@ + + \ No newline at end of file diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/FileStorageSetupActivity.java b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/FileStorageSetupActivity.java index 9c8ef64c..1eaefbe2 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/FileStorageSetupActivity.java +++ b/src/java/JavaFileStorageTest-AS/app/src/main/java/com/crocoapps/javafilestoragetest/FileStorageSetupActivity.java @@ -89,6 +89,8 @@ extends Activity implements JavaFileStorage.FileStorageSetupActivity { @Override public String getPath() { // TODO Auto-generated method stub + if (getState().containsKey(JavaFileStorage.EXTRA_PATH)) + return getState().getString(JavaFileStorage.EXTRA_PATH); return getIntent().getStringExtra(JavaFileStorage.EXTRA_PATH); } 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 763034f9..f47330bc 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 @@ -135,7 +135,6 @@ package com.crocoapps.javafilestoragetest; import group.pals.android.lib.ui.filechooser.FileChooserActivity; import group.pals.android.lib.ui.filechooser.providers.BaseFileProviderUtils; -import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -146,11 +145,9 @@ import java.util.ArrayList; import java.util.List; //import keepass2android.javafilestorage.DropboxCloudRailStorage; -import keepass2android.javafilestorage.DropboxV2Storage; -import keepass2android.javafilestorage.ICertificateErrorHandler; import keepass2android.javafilestorage.JavaFileStorage; import keepass2android.javafilestorage.JavaFileStorage.FileEntry; -import keepass2android.javafilestorage.OneDriveStorage; +import keepass2android.javafilestorage.OneDriveStorage2; import keepass2android.javafilestorage.SftpStorage; import keepass2android.javafilestorage.UserInteractionRequiredException; import keepass2android.javafilestorage.WebDavStorage; @@ -206,10 +203,17 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag } catch (Exception e) { + Log.d("KP2AJ",e.toString()); //if exception because folder exists path = fs.createFilePath(parentPath, testPath); } + String textToUpload2 = "abcdefg"; + String filename2 = fs.createFilePath(parentPath, "file.txt"); + /*if (!path.endsWith("/")) + path += "/"; + String filename = path+"file.text";*/ + fs.uploadFile(filename2,textToUpload2.getBytes(),true); FileEntry e1 = fs.getFileEntry(parentPath); FileEntry e2 = fs.getFileEntry(path); @@ -531,9 +535,11 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag } static JavaFileStorage createStorageToTest(Context ctx, Context appContext, boolean simulateRestart) { - storageToTest = new SftpStorage(ctx.getApplicationContext()); + //storageToTest = new SftpStorage(ctx.getApplicationContext()); //storageToTest = new SkyDriveFileStorage("000000004010C234", appContext); - //storageToTest = new OneDriveStorage(appContext, "000000004010C234"); + storageToTest = new OneDriveStorage2((Activity) ctx, "8374f801-0f55-407d-80cc-9a04fe86d9b2"); + + //storageToTest = new GoogleDriveFileStorage(); /*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { @Override @@ -620,7 +626,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag Toast.makeText(this, "requestCode: "+requestCode, Toast.LENGTH_LONG).show(); if (requestCode == 1) //new PerformTestTask().execute(path,"TestFileStorage�", storageToTest); //use an umlaut to see how that works - new PerformTestTask().execute(path,"TestFileStorage", storageToTest); + new PerformTestTask().execute(path,"TestFileStorage", storageToTest); else if (requestCode == 2) { diff --git a/src/java/JavaFileStorageTest-AS/app/src/main/res/values/strings.xml b/src/java/JavaFileStorageTest-AS/app/src/main/res/values/strings.xml index 9763f29d..436f3385 100644 --- a/src/java/JavaFileStorageTest-AS/app/src/main/res/values/strings.xml +++ b/src/java/JavaFileStorageTest-AS/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - + msal8374f801-0f55-407d-80cc-9a04fe86d9b2 Settings Hello world! FileStorageSetupActivity