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

Unverified Commit 39fdb225 authored by Tobias Kaminsky's avatar Tobias Kaminsky Committed by GitHub
Browse files

Merge pull request #8 from nextcloud/sso

SingleSignOn
parents aab4a0d5 5ebec7fc
Loading
Loading
Loading
Loading
+176 −25
Original line number Diff line number Diff line
ownCloud-Account-Importer
Nextcloud Single Sign On
=========================

Account Importer (Android Library Project)

This library allows you to use the network stack provided by the nextcloud app. Therefore you don't need to ask for the users credentials anymore as well as you don't need to worry about self-signed ssl certificates, two factor authentication, etc.


How to use it?
--------------

1) you'll need to extend **IAccountImport**
1) Add this library to your project

```gradle
dependencies {
    implementation "com.github.nextcloud:android-SingleSignOn:sso-SNAPSHOT"
}
```
2) Add the following permission to your `AndroidManifest.xml` 

That means that you have to implement the following method:
```xml
<uses-permission android:name="com.owncloud.android.sso"/>
```

    public void accountAccessGranted(OwnCloudAccount account);
3) To choose an account, include the following code in your login dialog:

```java
final int CHOOSE_ACCOUNT = 12;

As you can see in the following example, it's really easy to get the account data
private void showAccountChooserLogin() {
    Intent intent = AccountManager.newChooseAccountIntent(null, null, new String[] {"nextcloud"}, true, null, null, null, null);
    startActivityForResult(intent, CHOOSE_ACCOUNT);
}

@Override
    public void accountAccessGranted(OwnCloudAccount account) {
        mUsernameView.setText(account.getUsername());
        mPasswordView.setText(account.getPassword());
        mOc_root_path_View.setText(account.getUrl());
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_OK) {
        if (requestCode == CHOOSE_ACCOUNT) {
            String accountName =  data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            Account account = AccountImporter.GetAccountForName(getActivity(), accountName);
            if(account != null) {
                AccountImporter.SetCurrentAccount(getActivity(), account);
            }
        }
    }
}

// Hint: Checkout the LoginDialogFragment.java from the nextcloud-news app (sso-branch) to see a fully working example
```

3) How to get account information?

```java
Account account = AccountImporter.GetCurrentAccount(getActivity());
SingleSignOnAccount ssoAccount = AccountImporter.GetAuthTokenInSeparateThread(getActivity(), account);

// ssoAccount.name // Name of the account used in the android account manager
// ssoAccount.username
// ssoAccount.token 
// ssoAccount.url 
```

4) How to make a network request?

4.1.1) Retrofit: 

If you have an interface such as the following: 

```java
public interface API {
    
    @GET("user")
    Observable<UserInfo> user();

    @GET("status")
    Observable<NextcloudStatus> status();

    @GET("version")
    Observable<NextcloudNewsVersion> version();

    
}
```

Typical use of API using Retrofit
```java
public class ApiProvider {

    private API mApi;

    public ApiProvider() {
        mApi = retrofit.create(API.class);
    } 
}
```

4.1.2) Use Nextcloud network stack:

And then you can call the dialog with the following code:
You can implement that interface and use the nextcloud network stack instead of the retrofit one.

    public static void show(FragmentActivity activity, IAccountImport accountImport)
```java
public class API_SSO implements API {

    private static final String mApiEndpoint = "/index.php/apps/news/api/v1-2/";
    private NextcloudAPI nextcloudAPI;

Here a small example:
    public API_SSO(NextcloudAPI nextcloudAPI) {
        this.nextcloudAPI = nextcloudAPI;
    }

    view.findViewById(R.id.btn_importAccount).setOnClickListener(new View.OnClickListener() {
    @Override
        public void onClick(View view) {
            ImportAccountsDialogFragment.show(getActivity(), LoginDialogFragment.this);
    public Observable<UserInfo> user() {
        final Type type = UserInfo.class;
        NextcloudRequest request = new NextcloudRequest.Builder()
                .setMethod("GET")
                .setUrl(mApiEndpoint + "user")
                .build();
        return nextcloudAPI.performRequestObservable(type, request);
    }

    @Override
    public Observable<NextcloudStatus> status() {
        Type type = NextcloudStatus.class;
        NextcloudRequest request = new NextcloudRequest.Builder()
                .setMethod("GET")
                .setUrl(mApiEndpoint + "status")
                .build();
        return nextcloudAPI.performRequestObservable(type, request);
    }

    @Override
    public Observable<NextcloudNewsVersion> version() {
        Type type = NextcloudNewsVersion.class;
        NextcloudRequest request = new NextcloudRequest.Builder()
                .setMethod("GET")
                .setUrl(mApiEndpoint + "version")
                .build();
        return nextcloudAPI.performRequestObservable(type, request);
    }

    
}
    });
```

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

```java
public class ApiProvider {

    //If no other accounts (from other apps) are available.. hide the button
    if(AccountImporter.findAccounts(getActivity()).size() <= 0) {
        view.findViewById(R.id.btn_importAccount).setVisibility(View.GONE);
    private API mApi;

    public ApiProvider(NextcloudAPI.ApiConnectedListener callback) {
        SingleSignOnAccount ssoAccount = 
            AccountImporter.GetAuthTokenInSeparateThread(context, account);
        NextcloudAPI nextcloudAPI = new NextcloudAPI(ssoAccount, GsonConfig.GetGson());
        nextcloudAPI.start(context, callback);
        mApi = new API_SSO(nextcloudAPI);
    } 
}
```
Enjoy! If you're already using retrofit, you don't need to modify your application logic. Just exchange the API and you're good to go!

4.2) Without Retrofit

NextcloudAPI provides a method called `performNetworkRequest(NextcloudRequest request)` that allows you to handle the server response yourself.

```java
// create nextcloudAPI instance
SingleSignOnAccount ssoAccount = AccountImporter.GetAuthTokenInSeparateThread(context, account);
NextcloudAPI nextcloudAPI = new NextcloudAPI(ssoAccount, GsonConfig.GetGson());

private void downloadFile() {
    NextcloudRequest nextcloudRequest = new NextcloudRequest.Builder()
            .setMethod("GET")
            .setUrl("/remote.php/webdav/sample.mp4")
            .build();

    try {
        InputStream inputStream = nextcloudAPI.performNetworkRequest(nextcloudRequest);
        while(inputStream.available() > 0) {
            inputStream.read();
            // TODO do something useful with the data here..
            // like writing it to a file..?
        }
        inputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
```

# Flow Diagram

Note that the "Make network request" section in the diagram only shows the workflow if you use the "retrofit" api. 

![](doc/NextcloudSingleSignOn.png)
 No newline at end of file
+10 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ buildscript {
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        classpath 'com.android.tools.build:gradle:3.1.3'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
    }
}
@@ -40,4 +40,13 @@ android {

dependencies {
    implementation 'com.android.support:support-v4:27.1.1'

    implementation 'com.google.code.gson:gson:2.8.2'

    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    // Because RxAndroid releases are few and far between, it is recommended you also
    // explicitly depend on RxJava's latest version for bug fixes and new features.
    implementation 'io.reactivex.rxjava2:rxjava:2.1.4'

    implementation 'commons-io:commons-io:2.6'
}
+274 KiB
Loading image diff...
+1 −0
Original line number Diff line number Diff line
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36" version="8.6.5" editor="www.draw.io" type="device"><diagram name="Page-1" id="9361dd3d-8414-5efd-6122-117bd74ce7a7">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</diagram></mxfile>
 No newline at end of file
+8 −3
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.luhmer.owncloud.accountimporter">

    <application
        android:label="@string/app_name">
    <uses-permission android:name="com.owncloud.android.sso" />
    <uses-permission android:name="android.permission.INTERNET"/>

    </application>
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission
        android:name="android.permission.USE_CREDENTIALS"
        android:maxSdkVersion="22" />

    <application android:label="@string/app_name" />
</manifest>
Loading