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

Commit 609f4729 authored by Nihar Thakkar's avatar Nihar Thakkar
Browse files

Create new /e/ account login screen, remove OAuth implementation

parent bffd20fc
Loading
Loading
Loading
Loading
Loading
+35 −326
Original line number Original line Diff line number Diff line
package at.bitfire.davdroid.ui.setup
package at.bitfire.davdroid.ui.setup



import android.accounts.Account
import android.accounts.AccountManager
import android.app.Activity
import android.app.PendingIntent
import android.content.Context
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.*
import android.os.*
import android.support.v4.app.Fragment
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.LayoutInflater
import android.view.View
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup
import at.bitfire.dav4android.Constants

import at.bitfire.davdroid.authorization.IdentityProvider


import at.bitfire.davdroid.R
import at.bitfire.davdroid.R
import kotlinx.android.synthetic.main.login_credentials_fragment.view.*
import net.openid.appauth.*
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.net.*
import java.util.HashMap
import java.util.logging.Level
import android.net.ConnectivityManager
import android.net.ConnectivityManager
import android.net.Uri
import android.widget.Toast
import android.widget.Toast
import at.bitfire.davdroid.AccountSettings
import at.bitfire.dav4android.Constants
import at.bitfire.davdroid.model.Credentials
import kotlinx.android.synthetic.main.fragment_eelo_authenticator.*
import kotlinx.android.synthetic.main.fragment_eelo_authenticator.*
import kotlinx.android.synthetic.main.fragment_eelo_authenticator.view.*
import java.net.IDN
import java.net.URI
import java.net.URISyntaxException
import java.util.logging.Level




class EeloAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponseCallback {
class EeloAuthenticatorFragment : Fragment() {

    private val extraAuthServiceDiscovery = "authServiceDiscovery"
    private val extraClientSecret = "clientSecret"

    private var authState: AuthState? = null
    private var authorizationService: AuthorizationService? = null

    private val bufferSize = 1024
    private var userInfoJson: JSONObject? = null


    private fun isNetworkAvailable(): Boolean {
    private fun isNetworkAvailable(): Boolean {
        val connectivityManager = activity!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val connectivityManager = activity!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -57,305 +32,49 @@ class EeloAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponse
                              savedInstanceState: Bundle?): View? {
                              savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_eelo_authenticator, container, false)
        val view = inflater.inflate(R.layout.fragment_eelo_authenticator, container, false)


        if (!isNetworkAvailable()) {
        view.login.setOnClickListener { login() }
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }

        // Initialise the authorization service
        authorizationService = AuthorizationService(context!!)

        activity?.intent?.let {
            if (!it.getBooleanExtra(LoginActivity.ACCOUNT_PROVIDER_EELO_AUTH_COMPLETE, false)) {
                // Get all the account providers
                val providers = IdentityProvider.getEnabledProviders(context)

                // Iterate over the account providers
                for (idp in providers) {
                    val retrieveCallback = AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex ->
                        if (ex == null && serviceConfiguration != null) {
                            makeAuthRequest(serviceConfiguration, idp)
                        }
                        else {
                            Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
                            activity!!.finish()
                        }
                    }

                    if (idp.name == getString(R.string.eelo_name)) {
                        // Get configurations for the eelo account provider
                        idp.retrieveConfig(context, retrieveCallback)
                    }
                }
            }
            else {
                val response = AuthorizationResponse.fromIntent(activity!!.intent)
                val ex = AuthorizationException.fromIntent(activity!!.intent)
                authState = AuthState(response, ex)

                if (response != null) {
                    exchangeAuthorizationCode(response)
                }
                else {
                    Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
                    activity!!.finish()
                }
            }
        }


        return view
        return view
    }
    }


    private fun makeAuthRequest(
    private fun login() {
            serviceConfig: AuthorizationServiceConfiguration,
            idp: IdentityProvider) {
        if (!isNetworkAvailable()) {
        if (!isNetworkAvailable()) {
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
            activity!!.finish()
        }
        }


        val authRequest = AuthorizationRequest.Builder(
        if ((urlpwd_user_name.text.toString() != "") && (urlpwd_password.text.toString() != "")) {
                serviceConfig,
            validateLoginData()?.let { info ->
                idp.clientId,
                ResponseTypeValues.CODE,
                idp.redirectUri)
                .setScope(idp.scope)
                .build()

        authorizationService?.performAuthorizationRequest(
                authRequest,
                createPostAuthorizationIntent(
                        context!!,
                        authRequest,
                        serviceConfig.discoveryDoc,
                        idp.clientSecret),
                authorizationService?.createCustomTabsIntentBuilder()!!
                        .build())

        requireActivity().setResult(Activity.RESULT_OK)
        requireActivity().finish()
    }

    private fun createPostAuthorizationIntent(
            context: Context,
            request: AuthorizationRequest,
            discoveryDoc: AuthorizationServiceDiscovery?,
            clientSecret: String?): PendingIntent {
        val intent = Intent(context, LoginActivity::class.java)

        if (discoveryDoc != null) {
            intent.putExtra(extraAuthServiceDiscovery, discoveryDoc.docJson.toString())
        }

        if (clientSecret != null) {
            intent.putExtra(extraClientSecret, clientSecret)
        }

        intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_EELO)
        intent.putExtra(LoginActivity.ACCOUNT_PROVIDER_EELO_AUTH_COMPLETE, true)

        return PendingIntent.getActivity(context, request.hashCode(), intent, 0)
    }

    private fun exchangeAuthorizationCode(authorizationResponse: AuthorizationResponse) {
        if (!isNetworkAvailable()) {
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }

        val additionalParams = HashMap<String, String?>()
        if (getClientSecretFromIntent(activity!!.intent) != null) {
            additionalParams["client_secret"] = getClientSecretFromIntent(activity!!.intent)
        }
        performTokenRequest(authorizationResponse.createTokenExchangeRequest(additionalParams))
    }

    private fun getClientSecretFromIntent(intent: Intent): String? {
        return if (!intent.hasExtra(extraClientSecret)) {
            null
        }
        else intent.getStringExtra(extraClientSecret)
    }


    private fun performTokenRequest(request: TokenRequest) {
        if (!isNetworkAvailable()) {
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }

        authorizationService?.performTokenRequest(
                request, this)
    }

    override fun onTokenRequestCompleted(response: TokenResponse?, ex: AuthorizationException?) {
        authState?.update(response, ex)

        validateLoginData("user1", authState!!)?.let { info ->
                DetectConfigurationFragment.newInstance(info).show(fragmentManager, null)
                DetectConfigurationFragment.newInstance(info).show(fragmentManager, null)
            }
            }

        } else {
        // TODO Get the userId for future requests
            Toast.makeText(context, "Please enter a valid username and password", Toast.LENGTH_LONG).show()
        //getAccountInfo()
    }

    private fun getAccountInfo() {
        if (!isNetworkAvailable()) {
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }

        val discoveryDoc = getDiscoveryDocFromIntent(activity!!.intent)

        if (!authState!!.isAuthorized
                || discoveryDoc == null
                || discoveryDoc.userinfoEndpoint == null) {
            Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }
        else {
            object : AsyncTask<Void, Void, Void>() {
                override fun doInBackground(vararg params: Void): Void? {
                    if (fetchUserInfo()) {
                        Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
                        activity!!.finish()
                    }
                    return null
                }
            }.execute()
        }
    }

    private fun getDiscoveryDocFromIntent(intent: Intent): AuthorizationServiceDiscovery? {
        if (!intent.hasExtra(extraAuthServiceDiscovery)) {
            return null
        }
        val discoveryJson = intent.getStringExtra(extraAuthServiceDiscovery)
        try {
            return AuthorizationServiceDiscovery(JSONObject(discoveryJson))
        }
        catch (ex: JSONException) {
            throw IllegalStateException("Malformed JSON in discovery doc")
        }
        catch (ex: AuthorizationServiceDiscovery.MissingArgumentException) {
            throw IllegalStateException("Malformed JSON in discovery doc")
        }

    }

    private fun fetchUserInfo(): Boolean {
        var error = false

        if (authState!!.authorizationServiceConfiguration == null) {
            return true
        }

        authState!!.performActionWithFreshTokens(authorizationService!!, AuthState.AuthStateAction { accessToken, _, ex ->
            if (ex != null) {
                error = true
                return@AuthStateAction
            }

            val discoveryDoc = getDiscoveryDocFromIntent(activity!!.intent)
                    ?: throw IllegalStateException("no available discovery doc")

            val userInfoEndpoint: URL
            try {
                userInfoEndpoint = URL(discoveryDoc.userinfoEndpoint!!.toString())
            }
            catch (urlEx: MalformedURLException) {
                error = true
                return@AuthStateAction
            }

            var userInfoResponse: InputStream? = null
            try {
                val conn = userInfoEndpoint.openConnection() as HttpURLConnection
                conn.setRequestProperty("Authorization", "Bearer " + accessToken!!)
                conn.instanceFollowRedirects = false
                userInfoResponse = conn.inputStream
                val response = readStream(userInfoResponse)
                updateUserInfo(JSONObject(response))
            }
            catch (ioEx: IOException) {
                error = true
            }
            catch (jsonEx: JSONException) {
                error = true
            }
            finally {
                if (userInfoResponse != null) {
                    try {
                        userInfoResponse.close()
                    }
                    catch (ioEx: IOException) {
                        error = true
                    }

        }
        }
    }
    }
        })


        return error
    private fun validateLoginData(): LoginInfo? {
    }
        var valid = true

    @Throws(IOException::class)
    private fun readStream(stream: InputStream?): String {
        val br = BufferedReader(InputStreamReader(stream!!))
        val buffer = CharArray(bufferSize)
        val sb = StringBuilder()
        var readCount = br.read(buffer)
        while (readCount != -1) {
            sb.append(buffer, 0, readCount)
            readCount = br.read(buffer)
        }
        return sb.toString()
    }

    private fun updateUserInfo(jsonObject: JSONObject) {
        Handler(Looper.getMainLooper()).post {
            userInfoJson = jsonObject
            onAccountInfoGotten()
        }
    }

    private fun onAccountInfoGotten() {
        if (!isNetworkAvailable()) {
            Toast.makeText(context, "Please check your internet connection", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }


        if (userInfoJson != null) {
        val baseUrl = Uri.parse("https://drive.eelo.io")
            try {
        val uri = validateBaseUrl(baseUrl, false) { message ->
                var emailAddress = ""
            Toast.makeText(context, "Something went wrong. Please try again later", Toast.LENGTH_LONG).show()
                if (userInfoJson!!.has("email")) {
            valid = false
                    emailAddress = userInfoJson!!.getString("email")
        }
        }


                validateLoginData(emailAddress, authState!!)?.let { info ->
        val userName = view!!.urlpwd_user_name.text.toString()
                    DetectConfigurationFragment.newInstance(info).show(fragmentManager, null)
        if (userName.isBlank()) {
                }
            Toast.makeText(context, "Invalid email address", Toast.LENGTH_LONG).show()
            }
            valid = false
            catch (ex: JSONException) {
                Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
                activity!!.finish()
        }
        }


        val password = view!!.urlpwd_password.text.toString()
        if (password.isEmpty()) {
            Toast.makeText(context, "Invalid password", Toast.LENGTH_LONG).show()
            valid = false
        }
        }
        else {
            Toast.makeText(context, "Login failed, please try again later", Toast.LENGTH_LONG).show()
            activity!!.finish()
        }
    }

    private fun validateLoginData(emailAddress: String, authState: AuthState): LoginInfo? {
        val baseUrl = Uri.parse("https://nc.test.eelo.io/remote.php/dav/")
        val uri = validateBaseUrl(baseUrl, false, { message ->
            view!!.urlpwd_base_url.error = message
        })


        return if (uri != null)
        return if (valid && uri != null)
            LoginInfo(uri, emailAddress, null, authState, null)
            LoginInfo(uri, userName, password)
        else
        else
            null
            null
    }
    }
@@ -370,8 +89,7 @@ class EeloAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponse
            else
            else
                try {
                try {
                    host = IDN.toASCII(host)
                    host = IDN.toASCII(host)
                }
                } catch (e: IllegalArgumentException) {
                catch (e: IllegalArgumentException) {
                    Constants.log.log(Level.WARNING, "Host name not conforming to RFC 3490", e)
                    Constants.log.log(Level.WARNING, "Host name not conforming to RFC 3490", e)
                }
                }


@@ -379,23 +97,14 @@ class EeloAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponse
            val port = baseUrl.port
            val port = baseUrl.port
            try {
            try {
                uri = URI(baseUrl.scheme, null, host, port, path, null, null)
                uri = URI(baseUrl.scheme, null, host, port, path, null, null)
            }
            } catch (e: URISyntaxException) {
            catch (e: URISyntaxException) {
                reportError(e.localizedMessage)
                reportError(e.localizedMessage)
            }
            }
        }
        } else
        else
            reportError(getString(if (httpsRequired)
            reportError(getString(if (httpsRequired)
                R.string.login_url_must_be_https
                R.string.login_url_must_be_https
            else
            else
                R.string.login_url_must_be_http_or_https))
                R.string.login_url_must_be_http_or_https))
        return uri
        return uri
    }
    }

    override fun onDestroy() {
        super.onDestroy()
        authorizationService?.dispose()
    }


}
}
+94 −8
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!--
  ~ Copyright © Ricki Hirner (bitfire web engineering).
  ~ All rights reserved. This program and the accompanying materials
  ~ are made available under the terms of the GNU Public License v3.0
  ~ which accompanies this distribution, and is available at
  ~ http://www.gnu.org/licenses/gpl.html
  -->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_height="match_parent">
    tools:context=".ui.setup.GoogleAuthenticatorFragment">

    <!-- We don't want the keyboard up when the user arrives in this initial screen -->
    <View android:layout_height="0dp"
        android:layout_width="0dp"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:contentDescription="@null"
        android:importantForAccessibility="no" tools:ignore="UnusedAttribute">
        <requestFocus/>
    </View>


    <ProgressBar
    <ScrollView android:layout_width="match_parent"
        android:id="@+id/progress_bar"
        android:layout_height="0dp"
        android:layout_width="wrap_content"
        android:layout_weight="1">

        <LinearLayout
            android:id="@+id/login_type_urlpwd_details"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingBottom="16dp"
            android:paddingTop="8dp">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="32dp"
                android:layout_marginTop="32dp"
                android:gravity="center"
                android:text="@string/login_eelo_title"
                android:textColor="#000000"
                android:textSize="22sp" />

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingEnd="16dp"
                android:paddingStart="16dp">

                <android.support.design.widget.TextInputEditText
                    android:id="@+id/urlpwd_user_name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_height="wrap_content"
        android:layout_centerInParent="true" />
                    android:hint="@string/login_user_name"
                    android:inputType="textEmailAddress" />
            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:paddingEnd="16dp"
                android:paddingStart="16dp"
                app:passwordToggleEnabled="true">

                <android.support.design.widget.TextInputEditText
                    android:id="@+id/urlpwd_password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:fontFamily="monospace"
                    android:hint="@string/login_password"
                    android:inputType="textPassword" />
            </android.support.design.widget.TextInputLayout>

        </LinearLayout>
    </ScrollView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="@style/stepper_nav_bar">

        <Space
            android:layout_width="0dp"
            android:layout_weight="1"
            style="@style/stepper_nav_button"/>

        <Button
            android:id="@+id/login"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:text="@string/login_login"
            style="@style/stepper_nav_button"/>


</RelativeLayout>
    </LinearLayout>
 No newline at end of file
</LinearLayout>
+1 −0
Original line number Original line Diff line number Diff line
@@ -142,6 +142,7 @@


    <!-- AddAccountActivity -->
    <!-- AddAccountActivity -->
    <string name="login_help_url">https://www.davdroid.com/tested-with/?pk_campaign=davdroid-app</string>
    <string name="login_help_url">https://www.davdroid.com/tested-with/?pk_campaign=davdroid-app</string>
    <string name="login_eelo_title">Login with an /e/ account</string>
    <string name="login_title">Add account</string>
    <string name="login_title">Add account</string>
    <string name="login_type_email">Login with email address</string>
    <string name="login_type_email">Login with email address</string>
    <string name="login_email_address">Email address</string>
    <string name="login_email_address">Email address</string>