Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 82d375a2 authored by David Luhmer's avatar David Luhmer
Browse files

Merge branch 'add-annotations' of...

Merge branch 'add-annotations' of https://github.com/nextcloud/Android-SingleSignOn into add-annotations

# Conflicts:
#	build.gradle
parents b1cecc68 461fdf4f
Loading
Loading
Loading
Loading
+90 −16
Original line number Diff line number Diff line
# Nextcloud Single Sign On
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8aa66fac0af94ef2836d386fad69f199)](https://www.codacy.com/app/Nextcloud/Android-SingleSignOn?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/Android-SingleSignOn&utm_campaign=Badge_Grade)

[![](https://jitpack.io/v/nextcloud/Android-SingleSignOn.svg)](https://jitpack.io/#nextcloud/Android-SingleSignOn)


This library allows you to use accounts as well as the network stack provided by the [nextcloud files app](https://play.google.com/store/apps/details?id=com.nextcloud.client). Therefore you as a developer don't need to worry about asking the user for credentials as well as you don't need to worry about self-signed ssl certificates, two factor authentication, save credential storage etc.

*Please note that the user needs to install the [nextcloud files app](https://play.google.com/store/apps/details?id=com.nextcloud.client) in order to use those features.* While this might seem like a "no-go" for some developers, we still think that using this library is worth consideration as it makes the account handling much faster and safer.
@@ -38,9 +41,30 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
    AccountImporter.onActivityResult(requestCode, resultCode, data, LoginDialogFragment.this, new AccountImporter.IAccountAccessGranted() {
        @Override
        public void accountAccessGranted(SingleSignOnAccount account) {
            // TODO init api here..  
            // As this library supports multiple accounts we created some helper methods if you only want to use one.
            // The following line stores the selected account as the "default" account which can be queried by using 
            // the SingleAccountHelper.getCurrentSingleSignOnAccount(context) method
            SingleAccountHelper.setCurrentAccount(getActivity(), account.name);
            
            // Get the "default" account
            SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
            NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), callback);

            // TODO ... (see code in section 3 and below)
        }
    });
    
    NextcloudAPI.ApiConnectedListener callback = new NextcloudAPI.ApiConnectedListener() {
        @Override
        public void onConnected() { 
            // ignore this one..
        }

        @Override
        public void onError(Exception ex) { 
            // TODO handle errors
        }
    }
}

// Complete example: https://github.com/nextcloud/news-android/blob/master/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/LoginDialogFragment.java
@@ -49,8 +73,12 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
3) How to get account information?

```java
// If you stored the "default" account using setCurrentAccount(...) you can get the account by using the following line:
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);

// Otherwise (for multi-account support): (you'll have to keep track of the account names yourself. Note: this has to be the name of SingleSignOnAccount.name)
AccountImporter.getSingleSignOnAccount(context, accountName);

// ssoAccount.name // Name of the account used in the android account manager
// ssoAccount.username
// ssoAccount.token
@@ -59,6 +87,13 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou

4) How to make a network request?

    You'll notice that there is an callback parameter in the constructor of the `NextcloudAPI`.
    ```java
    public NextcloudAPI(Context context, SingleSignOnAccount account, Gson gson, ApiConnectedListener callback) {
    ```
    
    You can use this callback to subscribe to errors that might occur during the initialization of the API. You can start making requests to the API as soon as you instantiated the `NextcloudAPI` object. For a minimal example to get started (without retrofit) take a look at section 4.2. The callback method `onConnected` will be called once the connection to the files app is established. You can start making calls to the api before that callback is fired as the library will queue your calls until the connection is established.

    4.1) **Using Retrofit**

    4.1.1) Before using this single sign on library, your interface for your retrofit API might look like this:
@@ -118,7 +153,7 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou
        @Override
        public Call<List<Feed>> createFeed(Map<String, Object> feedMap) {
            Type feedListType = new TypeToken<List<Feed>>() {}.getType();
            String body = GsonConfig.GetGson().toJson(feedMap);
            String body = new GsonBuilder().create().toJson(feedMap);
            NextcloudRequest request = new NextcloudRequest.Builder()
                    .setMethod("POST")
                    .setUrl(mApiEndpoint + "feeds")
@@ -140,6 +175,8 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou
    }
    ```
    
    Note: If you need a different mapping between your json-structure and your java-structure you might want to create a custom type adapter using `new GsonBuilder().create().registerTypeAdapter(...)`. Take a look at [this](https://github.com/nextcloud/news-android/blob/783836390b4c27aba285bad1441b53154df16685/News-Android-App/src/main/java/de/luhmer/owncloudnewsreader/helper/GsonConfig.java) example for more information.

    4.1.3) Use of new API using the nextcloud app network stack

    ```java
@@ -149,7 +186,7 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou

        public ApiProvider(NextcloudAPI.ApiConnectedListener callback) {
           SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
           NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, GsonConfig.GetGson(), callback);
           NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), callback);
           mApi = new API_SSO(nextcloudAPI);
       }
    }
@@ -168,27 +205,45 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou

        @Override
        protected void onStart() {
            SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
            mNextcloudAPI = new NextcloudAPI(context, ssoAccount, GsonConfig.GetGson(),  new NextcloudAPI.ApiConnectedListener() {
            super.onStart();
            try {
                SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(this);
                mNextcloudAPI = new NextcloudAPI(this, ssoAccount, new GsonBuilder().create(), apiCallback);

                // Start download of file in background thread (otherwise you'll get a NetworkOnMainThreadException)
                new Thread() {
                    @Override
                public void onConnected() {
                    public void run() {
                        downloadFile();
                    }

                @Override
                public void onError(Exception ex) {
                    // TODO handle error here..
                }.start();
            } catch (NextcloudFilesAppAccountNotFoundException e) {
                // TODO handle errors
            } catch (NoCurrentAccountSelectedException e) {
                // TODO handle errors
            }
            });
        }

        @Override
        protected void onStop() {
            super.onStop();
            // Close Service Connection to Nextcloud Files App and
            // disconnect API from Context (prevent Memory Leak)
            mNextcloudAPI.stop();
        }
        
        private NextcloudAPI.ApiConnectedListener apiCallback = new NextcloudAPI.ApiConnectedListener() {
            @Override
            public void onConnected() {
                // ignore this one..
            }

            @Override
            public void onError(Exception ex) {
                // TODO handle error in your app
            }
        };

        private void downloadFile() {
            NextcloudRequest nextcloudRequest = new NextcloudRequest.Builder()
                    .setMethod("GET")
@@ -204,12 +259,31 @@ SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccou
                }
                inputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
                // TODO handle errors
            }
        }
    }
    ```


5) WebDAV

The following WebDAV Methods are supported: `PROPFIND` / `MKCOL`

The following examples shows how to use the `PROPFIND` method. With a depth of 0.

```java
List<String>depth = new ArrayList<>();
depth.add("0");
header.put("Depth", depth);

NextcloudRequest nextcloudRequest = new NextcloudRequest.Builder()
        .setMethod("PROPFIND")
        .setHeader(header)
        .setUrl("/remote.php/webdav/"+remotePath)
        .build();
```

## Additional Info:

In case that you require some sso features that were introduced in a specific nextcloud files app version, you can run a simple version check using the following helper method:
+2 −2
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ buildscript {
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.0'
        classpath 'com.android.tools.build:gradle:3.3.1'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
    }
}
@@ -20,7 +20,7 @@ group='com.github.nextcloud'

android {
    compileSdkVersion 28
    buildToolsVersion '28.0.2'
    buildToolsVersion '28.0.3'
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 28
+1 −0
Original line number Diff line number Diff line
@@ -18,5 +18,6 @@
package com.nextcloud.android.sso.aidl;

interface IInputStreamService {
    ParcelFileDescriptor performNextcloudRequestAndBodyStream(in ParcelFileDescriptor input, in ParcelFileDescriptor requestBodyParcelFileDescriptor);
    ParcelFileDescriptor performNextcloudRequest(in ParcelFileDescriptor input);
}
+34 −6
Original line number Diff line number Diff line
@@ -253,7 +253,6 @@ public class NextcloudAPI {
        return result;
    }


     /**
     * The InputStreams needs to be closed after reading from it
     *
@@ -262,14 +261,27 @@ public class NextcloudAPI {
     * @throws Exception or SSOException
     */
    public InputStream performNetworkRequest(NextcloudRequest request) throws Exception {
        return performNetworkRequest(request, null);
    }

    /**
     * The InputStreams needs to be closed after reading from it
     *
     * @param request {@link NextcloudRequest} request to be executed on server via Files app
     * @param requestBodyInputStream inputstream to be sent to the server
     * @return InputStream answer from server as InputStream
     * @throws Exception or SSOException
     */
    public InputStream performNetworkRequest(NextcloudRequest request, InputStream requestBodyInputStream) throws Exception {
        InputStream os = null;
        Exception exception = null;
        Exception exception;
        try {
            ParcelFileDescriptor output = performAidlNetworkRequest(request);
            ParcelFileDescriptor output = performAidlNetworkRequest(request, requestBodyInputStream);
            os = new ParcelFileDescriptor.AutoCloseInputStream(output);
            exception = deserializeObject(os);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            //e.printStackTrace();
            exception = e;
        }

        // Handle Remote Exceptions
@@ -290,7 +302,7 @@ public class NextcloudAPI {
     * @return
     * @throws IOException
     */
    private ParcelFileDescriptor performAidlNetworkRequest(NextcloudRequest request)
    private ParcelFileDescriptor performAidlNetworkRequest(NextcloudRequest request, InputStream requestBodyInputStream)
            throws IOException, RemoteException, NextcloudApiNotRespondingException {

        // Check if we are on the main thread
@@ -320,7 +332,23 @@ public class NextcloudAPI {
                    }
                });

        ParcelFileDescriptor output = mService.performNextcloudRequest(input);
        ParcelFileDescriptor requestBodyParcelFileDescriptor = null;
        if(requestBodyInputStream != null) {
            requestBodyParcelFileDescriptor = ParcelFileDescriptorUtil.pipeFrom(requestBodyInputStream,
                    new IThreadListener() {
                        @Override
                        public void onThreadFinished(Thread thread) {
                            Log.d(TAG, "copy data from service finished");
                        }
                    });
        }

        ParcelFileDescriptor output;
        if(requestBodyParcelFileDescriptor != null) {
            output = mService.performNextcloudRequestAndBodyStream(input, requestBodyParcelFileDescriptor);
        } else {
            output = mService.performNextcloudRequest(input);
        }

        return output;
    }
+26 −5
Original line number Diff line number Diff line
package com.nextcloud.android.sso.helper;

import android.support.annotation.NonNull;

import com.nextcloud.android.sso.aidl.NextcloudRequest;
import com.nextcloud.android.sso.api.NextcloudAPI;

import java.lang.reflect.Type;

import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okio.BufferedSource;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@@ -49,7 +53,7 @@ public final class Retrofit2Helper {

            @Override
            public void enqueue(Callback<T> callback) {
                callback.onResponse(null, execute());
                callback.onResponse(this, execute());
            }

            @Override
@@ -92,16 +96,16 @@ public final class Retrofit2Helper {
                if(success) {
                    return Response.success(null);
                } else {
                    return Response.error(520, null);
                    return Response.error(520, emptyResponseBody);
                }
            }

            @Override
            public void enqueue(Callback callback) {
            public void enqueue(@NonNull Callback callback) {
                if(success) {
                    callback.onResponse(null, Response.success(null));
                    callback.onResponse(this, Response.success(null));
                } else {
                    callback.onResponse(null, Response.error(520, null));
                    callback.onResponse(this, Response.error(520, emptyResponseBody));
                }
            }

@@ -131,4 +135,21 @@ public final class Retrofit2Helper {

    }

    private static ResponseBody emptyResponseBody = new ResponseBody() {
        @Override
        public MediaType contentType() {
            return null;
        }

        @Override
        public long contentLength() {
            return 0;
        }

        @Override
        public BufferedSource source() {
            return null;
        }
    };

}
Loading