Finish authentication

This commit is contained in:
Matthieu 2020-03-06 18:24:20 +01:00
parent b214238457
commit 33f0711b1f
5 changed files with 174 additions and 52 deletions

View File

@ -51,6 +51,7 @@ dependencies {
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.material:material:1.1.0'
}
@ -69,9 +70,13 @@ task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'crea
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def kotlinDebugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([kotlinDebugTree])
executionData = fileTree(dir: project.buildDir, includes: [
'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/debugAndroidTest/connected/*coverage.ec'
])
getSourceDirectories().from(files([mainSrc]))
getClassDirectories().from(files([kotlinDebugTree]))
getExecutionData().from(fileTree(dir: project.buildDir, includes: [
'outputs/code_coverage/debugAndroidTest/connected/*coverage.ec',
'jacoco/testDebugUnitTest.exec'
]))
}

View File

@ -9,8 +9,19 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".LoginActivity"></activity>
<activity android:name=".MainActivity">
<activity
android:name=".LoginActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="${applicationId}"
android:scheme="oauth2redirect" />
</intent-filter>
</activity> <activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -2,88 +2,182 @@ package com.h.pixeldroid
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.Application
import com.h.pixeldroid.objects.Token
import kotlinx.android.synthetic.main.activity_login.*
import okhttp3.HttpUrl
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginActivity : AppCompatActivity() {
private val OAUTH_SCHEME = "oauth2redirect"
private val PACKAGE_ID = "com.h.pixeldroid"
private val SCOPE = "read write follow"
private val APP_NAME = "PixelDroid"
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
connect_instance_button.setOnClickListener { onClickConnect() }
preferences = getSharedPreferences(
"$PACKAGE_ID.pref", Context.MODE_PRIVATE
)
}
fun connexion(view: View) {
override fun onStart(){
super.onStart()
val url = intent.data
if (url != null && url.toString().startsWith("$OAUTH_SCHEME://$PACKAGE_ID")) {
val code = url.getQueryParameter("code")
val error = url.getQueryParameter("error")
// Restore previous values from preferences
val domain = preferences.getString("domain", "")
val clientId = preferences.getString("clientID", "")
val clientSecret = preferences.getString("clientSecret", "")
if (code != null && !domain.isNullOrEmpty() && !clientId.isNullOrEmpty() && !clientSecret.isNullOrEmpty()) {
//Successful authorization
val callback = object : Callback<Token> {
override fun onResponse(call: Call<Token>, response: Response<Token>) {
if (response.isSuccessful) {
authenticationSuccessful(domain, response.body()?.access_token)
} else {
failedRegistration("Error getting token")
}
}
override fun onFailure(call: Call<Token>, t: Throwable) {
failedRegistration("Error getting token")
}
}
val pixelfedAPI = PixelfedAPI.create("https://$domain")
pixelfedAPI.obtainToken(clientId, clientSecret, "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, code,
"authorization_code").enqueue(callback)
} else if (error != null) {
failedRegistration("Authentication denied")
} else {
failedRegistration("Unknown response")
}
}
}
private fun authenticationSuccessful(domain: String, accessToken: String?) {
Log.e("Token", accessToken!!)
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
private fun onClickConnect() {
connect_instance_button.isEnabled = false
val domain = editText.text.toString()
val normalizedDomain = normalizeDomain(domain)
try{
HttpUrl.Builder().host(domain).scheme("https").build()
} catch (e: IllegalArgumentException) {
failedRegistration("Invalid domain")
return
}
preferences.edit()
.putString("domain", normalizedDomain)
.apply()
val callback = object : Callback<Application> {
override fun onResponse(call: Call<Application>, response: Response<Application>) {
if (!response.isSuccessful) {
// TODO
failedRegistration()
return
}
val credentials = response.body()
val clientId = credentials!!.client_id
val clientId = credentials?.client_id ?: return failedRegistration()
val clientSecret = credentials.client_secret
val domain = editText.text.toString()
preferences = getSharedPreferences(
"com.h.PixelDroid.pref", Context.MODE_PRIVATE
)
preferences.edit().putString("clientID", clientId)
preferences.edit()
.putString("clientID", clientId)
.putString("clientSecret", clientSecret)
.putString("domain", domain)
.apply()
if (clientId != null) {
redirect(domain, clientId)
}
redirect(normalizedDomain, clientId)
}
override fun onFailure(call: Call<Application>, t: Throwable) {
failedRegistration()
return
}
}
val api = PixelfedAPI.create("https://pixelfed.de")
api.registerApplication(
"pixeldroid",
"oauth2redirect://com.h.pixeldroid", "read write follow"
val pixelfedAPI = PixelfedAPI.create("https://$normalizedDomain")
pixelfedAPI.registerApplication(
APP_NAME,
"$OAUTH_SCHEME://$PACKAGE_ID", SCOPE
)
.enqueue(callback)
}
fun redirect(domain: String, client_id: String) {
private fun failedRegistration(message: String =
"Could not register the application with this server"){
connect_instance_button.isEnabled = true
editText.error = message
}
private fun normalizeDomain(domain: String): String {
var d = domain.replace("http://", "")
d = d.replace("https://", "")
return d.trim(Char::isWhitespace)
}
val url = "https://" + domain + "/oauth/authorize" + "?" +
fun redirect(normalizedDomain: String, client_id: String) {
val url = "https://$normalizedDomain/oauth/authorize?" +
"client_id" + "=" + client_id + "&" +
"redirect_uri" + "=" + "oauth2redirect://com.h.pixeldroid" + "&" +
"redirect_uri" + "=" + "$OAUTH_SCHEME://$PACKAGE_ID" + "&" +
"response_type=code" + "&" +
"scope=read write follow"
"scope=$SCOPE"
browser(this, url)
}
fun browser(context: Context, url: String) {
private fun browser(context: Context, url: String) {
val intent = CustomTabsIntent.Builder().build()
try {
intent.launchUrl(context, Uri.parse(url))
} catch (e: ActivityNotFoundException) {
Log.w("login", "Could not launch browser")
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
if (browserIntent.resolveActivity(packageManager) != null) {
startActivity(browserIntent)
} else {
failedRegistration(message="Could not launch a browser, do you have one?")
return
}
}
connect_instance_button.isEnabled = true
}
}

View File

@ -2,6 +2,7 @@ package com.h.pixeldroid.api
import com.h.pixeldroid.objects.Application
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Token
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
@ -31,7 +32,18 @@ interface PixelfedAPI {
@Field("website") website: String? = null
): Call<Application>
@GET("/api/v1/timelines/public")
@FormUrlEncoded
@POST("/oauth/token")
fun obtainToken(
@Field("client_id") client_id: String,
@Field("client_secret") client_secret: String,
@Field("redirect_uri") redirect_uri: String,
@Field("scope") scope: String? = "read",
@Field("code") code: String? = null,
@Field("grant_type") grant_type: String? = null
): Call<Token>
@GET("/api/v1/timelines/public")
fun timelinePublic(
@Query("local") local: Boolean? = null,
@Query("max_id") max_id: String? = null,

View File

@ -7,33 +7,33 @@
tools:context=".LoginActivity">
<Button
android:id="@+id/button"
android:id="@+id/connect_instance_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="162dp"
android:layout_marginEnd="161dp"
android:layout_marginBottom="425dp"
android:onClick="connexion"
android:layout_marginTop="36dp"
android:text="Connect"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/domainTextInputLayout" />
<EditText
android:id="@+id/editText"
android:layout_width="wrap_content"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/domainTextInputLayout"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginStart="99dp"
android:layout_marginTop="113dp"
android:layout_marginEnd="99dp"
android:layout_marginBottom="100dp"
android:ems="10"
android:inputType="textPersonName"
android:text="pixelfed.de"
app:layout_constraintBottom_toTopOf="@+id/button"
android:hint="Domain of your instance"
app:errorEnabled="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:autofillHints=""
tools:ignore="LabelFor" />
app:layout_constraintVertical_bias="0.40">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textUri" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>