From 74601bc18d5c80634d538e81076cace505ca9282 Mon Sep 17 00:00:00 2001 From: Billy Brawner Date: Thu, 31 Oct 2019 09:40:54 -0600 Subject: [PATCH] Implement updated authentication --- app/build.gradle | 5 +- .../java/com/wbrawner/budget/di/Dagger.kt | 4 +- .../com/wbrawner/budget/ui/SplashViewModel.kt | 11 ++-- .../budget/auth/CredentialsProvider.kt | 25 -------- budgetlib/build.gradle | 2 +- .../budget/lib/network/BudgetApiService.kt | 3 + .../budget/lib/network/NetworkModule.kt | 58 ++++++++++++------- .../lib/repository/NetworkBudgetRepository.kt | 5 -- .../lib/repository/NetworkUserRepository.kt | 7 +++ build.gradle | 2 +- .../budget/common/budget/BudgetRepository.kt | 2 - .../budget/common/user/UserRepository.kt | 2 + settings.gradle | 2 +- {auth => storage}/.gitignore | 0 {auth => storage}/build.gradle | 0 {auth => storage}/proguard-rules.pro | 0 .../auth/ExampleInstrumentedTest.java | 0 .../src/main/AndroidManifest.xml | 0 .../wbrawner/budget/storage/StorageModule.kt | 9 +-- .../src/main/res/values/strings.xml | 0 .../com/wbrawner/auth/ExampleUnitTest.java | 0 21 files changed, 64 insertions(+), 73 deletions(-) delete mode 100644 auth/src/main/java/com/wbrawner/budget/auth/CredentialsProvider.kt rename {auth => storage}/.gitignore (100%) rename {auth => storage}/build.gradle (100%) rename {auth => storage}/proguard-rules.pro (100%) rename {auth => storage}/src/androidTest/java/com/wbrawner/auth/ExampleInstrumentedTest.java (100%) rename {auth => storage}/src/main/AndroidManifest.xml (100%) rename auth/src/main/java/com/wbrawner/budget/auth/AuthModule.kt => storage/src/main/java/com/wbrawner/budget/storage/StorageModule.kt (74%) rename {auth => storage}/src/main/res/values/strings.xml (100%) rename {auth => storage}/src/test/java/com/wbrawner/auth/ExampleUnitTest.java (100%) diff --git a/app/build.gradle b/app/build.gradle index b4ee76d..87fa3db 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,8 +20,9 @@ android { useSupportLibrary = true } testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - buildConfigField "String", "API_URL", "\"http://192.168.86.92:8080/\"" +// buildConfigField "String", "API_URL", "\"http://192.168.86.92:8080/\"" // buildConfigField "String", "API_URL", "\"http://10.0.2.2:8080/\"" + buildConfigField "String", "API_URL", "\"https://budget-api.intra.wbrawner.com/\"" } buildTypes { release { @@ -38,7 +39,7 @@ android { dependencies { implementation project(':common') implementation project(':budgetlib') - implementation project(':auth') + implementation project(':storage') implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/app/src/main/java/com/wbrawner/budget/di/Dagger.kt b/app/src/main/java/com/wbrawner/budget/di/Dagger.kt index 0696114..16e1a2f 100644 --- a/app/src/main/java/com/wbrawner/budget/di/Dagger.kt +++ b/app/src/main/java/com/wbrawner/budget/di/Dagger.kt @@ -1,8 +1,8 @@ package com.wbrawner.budget.di import android.content.Context -import com.wbrawner.budget.auth.AuthModule import com.wbrawner.budget.lib.network.NetworkModule +import com.wbrawner.budget.storage.StorageModule import com.wbrawner.budget.ui.MainActivity import com.wbrawner.budget.ui.SplashActivity import com.wbrawner.budget.ui.SplashViewModelMapper @@ -29,7 +29,7 @@ import javax.inject.Named import javax.inject.Singleton @Singleton -@Component(modules = [AuthModule::class, AppModule::class, NetworkModule::class]) +@Component(modules = [StorageModule::class, AppModule::class, NetworkModule::class]) interface AppComponent { fun inject(fragment: OverviewFragment) fun inject(fragment: LoginFragment) diff --git a/app/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt b/app/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt index d68c26c..d332ba1 100644 --- a/app/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt +++ b/app/src/main/java/com/wbrawner/budget/ui/SplashViewModel.kt @@ -2,9 +2,8 @@ package com.wbrawner.budget.ui import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.wbrawner.budget.auth.CredentialsProvider -import com.wbrawner.budget.common.budget.BudgetRepository import com.wbrawner.budget.common.user.User +import com.wbrawner.budget.common.user.UserRepository import com.wbrawner.budget.di.ViewModelKey import dagger.Binds import dagger.Module @@ -12,14 +11,13 @@ import dagger.multibindings.IntoMap import javax.inject.Inject class SplashViewModel @Inject constructor( - private val credentialsProvider: CredentialsProvider, - private val budgetRepository: BudgetRepository + private val userRepository: UserRepository ) : ViewModel() { val isLoading: MutableLiveData = MutableLiveData(false) suspend fun checkForExistingCredentials(): User? { return try { - login(credentialsProvider.username, credentialsProvider.password) + userRepository.getProfile() } catch (ignored: Exception) { null } @@ -27,9 +25,8 @@ class SplashViewModel @Inject constructor( suspend fun login(username: String, password: String): User { isLoading.value = true - credentialsProvider.saveCredentials(username, password) return try { - budgetRepository.login(username, password) + userRepository.login(username, password) } catch (e: java.lang.Exception) { isLoading.value = false throw e diff --git a/auth/src/main/java/com/wbrawner/budget/auth/CredentialsProvider.kt b/auth/src/main/java/com/wbrawner/budget/auth/CredentialsProvider.kt deleted file mode 100644 index 7c7c912..0000000 --- a/auth/src/main/java/com/wbrawner/budget/auth/CredentialsProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.wbrawner.budget.auth - -import android.content.SharedPreferences - -interface CredentialsProvider { - val username: String - val password: String - fun saveCredentials(username: String, password: String) -} - -private const val PREF_KEY_USERNAME = "PREF_KEY_USERNAME" -private const val PREF_KEY_PASSWORD = "PREF_KEY_PASSWORD" - -class SharedPreferencesCredentialsProvider(private val sharedPreferences: SharedPreferences) : CredentialsProvider { - override val username: String = sharedPreferences.getString(PREF_KEY_USERNAME, null) ?: "" - - override val password: String = sharedPreferences.getString(PREF_KEY_PASSWORD, null) ?: "" - - override fun saveCredentials(username: String, password: String) { - sharedPreferences.edit() - .putString(PREF_KEY_USERNAME, username) - .putString(PREF_KEY_PASSWORD, password) - .apply() - } -} \ No newline at end of file diff --git a/budgetlib/build.gradle b/budgetlib/build.gradle index 45e99b8..1c25203 100644 --- a/budgetlib/build.gradle +++ b/budgetlib/build.gradle @@ -25,7 +25,7 @@ android { dependencies { implementation project(':common') - implementation project(':auth') + implementation project(':storage') // Retrofit api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit" implementation "com.squareup.retrofit2:converter-moshi:$rootProject.ext.retrofit" diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt index 96a6457..2cd4dc6 100644 --- a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt +++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/BudgetApiService.kt @@ -95,6 +95,9 @@ interface BudgetApiService { @POST("users/login") suspend fun login(@Body request: LoginRequest): User + @GET("users/me") + suspend fun getProfile(): User + @GET("users/search") suspend fun searchUsers(@Query("query") query: String): List diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt index 211f8bc..1f37488 100644 --- a/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt +++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/network/NetworkModule.kt @@ -1,8 +1,9 @@ package com.wbrawner.budget.lib.network +import android.content.SharedPreferences +import android.util.Base64 import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter -import com.wbrawner.budget.auth.CredentialsProvider import com.wbrawner.budget.common.budget.BudgetRepository import com.wbrawner.budget.common.category.CategoryRepository import com.wbrawner.budget.common.transaction.TransactionRepository @@ -13,10 +14,13 @@ import com.wbrawner.budget.lib.repository.NetworkTransactionRepository import com.wbrawner.budget.lib.repository.NetworkUserRepository import dagger.Module import dagger.Provides -import okhttp3.Credentials +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory +import java.nio.charset.Charset import java.util.* import javax.inject.Named @@ -28,25 +32,39 @@ class NetworkModule { .build() @Provides - fun provideOkHttpClient(credentialsProvider: CredentialsProvider): OkHttpClient = OkHttpClient.Builder() - .addInterceptor { - if (it.request().headers().get("Authorization") != null) - return@addInterceptor it.proceed(it.request()) - val credentials = Credentials.basic( - credentialsProvider.username, - credentialsProvider.password - ) - val newHeaders = it.request() - .headers() - .newBuilder() - .add("Authorization: $credentials") - .build() - val newRequest = it.request() - .newBuilder() - .headers(newHeaders) - .build() - it.proceed(newRequest) + fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar { + return object : CookieJar { + override fun saveFromResponse(url: HttpUrl, cookies: MutableList) { + sharedPreferences.edit() + .putString( + url.host(), + cookies.joinToString(separator = ",") { + Base64.encode(it.toString().toByteArray(), 0) + .toString(charset = Charset.forName("UTF-8")) + } + ) + .apply() } + + override fun loadForRequest(url: HttpUrl): MutableList { + return sharedPreferences.getString(url.host(), "") + ?.split(",") + ?.mapNotNull { + Cookie.parse( + url, + Base64.decode(it, 0).toString(Charset.forName("UTF-8")) + ) + } + ?.toMutableList() + ?: mutableListOf() + } + } + } + + @Provides + fun provideOkHttpClient(cookieJar: CookieJar): OkHttpClient = OkHttpClient.Builder() + .cookieJar(cookieJar) + // TODO: Add Gander interceptor .build() @Provides diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkBudgetRepository.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkBudgetRepository.kt index 101033f..b43c2f6 100644 --- a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkBudgetRepository.kt +++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkBudgetRepository.kt @@ -2,8 +2,6 @@ package com.wbrawner.budget.lib.repository import com.wbrawner.budget.common.budget.Budget import com.wbrawner.budget.common.budget.BudgetRepository -import com.wbrawner.budget.common.user.LoginRequest -import com.wbrawner.budget.common.user.User import com.wbrawner.budget.lib.network.BudgetApiService import javax.inject.Inject @@ -21,9 +19,6 @@ class NetworkBudgetRepository @Inject constructor(private val apiService: Budget override suspend fun delete(id: Long) = apiService.deleteBudget(id) - override suspend fun login(username: String, password: String): User = - apiService.login(LoginRequest(username, password)) - override suspend fun getBalance(id: Long): Long = apiService.getBudgetBalance(id).balance } diff --git a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt index 6bddc79..42db4e8 100644 --- a/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt +++ b/budgetlib/src/main/java/com/wbrawner/budget/lib/repository/NetworkUserRepository.kt @@ -1,11 +1,18 @@ package com.wbrawner.budget.lib.repository +import com.wbrawner.budget.common.user.LoginRequest import com.wbrawner.budget.common.user.User import com.wbrawner.budget.common.user.UserRepository import com.wbrawner.budget.lib.network.BudgetApiService import javax.inject.Inject class NetworkUserRepository @Inject constructor(private val apiService: BudgetApiService) : UserRepository { + + override suspend fun login(username: String, password: String): User = + apiService.login(LoginRequest(username, password)) + + override suspend fun getProfile(): User = apiService.getProfile() + override suspend fun create(newItem: User): User = apiService.newUser(newItem) override suspend fun findAll(accountId: Long?): Collection = apiService.getUsers(accountId) diff --git a/build.gradle b/build.gradle index 2aabf53..1804bcd 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.3.2' classpath 'io.fabric.tools:gradle:1.31.0' diff --git a/common/src/main/java/com/wbrawner/budget/common/budget/BudgetRepository.kt b/common/src/main/java/com/wbrawner/budget/common/budget/BudgetRepository.kt index c030abd..d4b9281 100644 --- a/common/src/main/java/com/wbrawner/budget/common/budget/BudgetRepository.kt +++ b/common/src/main/java/com/wbrawner/budget/common/budget/BudgetRepository.kt @@ -1,9 +1,7 @@ package com.wbrawner.budget.common.budget import com.wbrawner.budget.common.Repository -import com.wbrawner.budget.common.user.User interface BudgetRepository : Repository { - suspend fun login(username: String, password: String): User suspend fun getBalance(id: Long): Long } \ No newline at end of file diff --git a/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt b/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt index d420160..83030a8 100644 --- a/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt +++ b/common/src/main/java/com/wbrawner/budget/common/user/UserRepository.kt @@ -3,6 +3,8 @@ package com.wbrawner.budget.common.user import com.wbrawner.budget.common.Repository interface UserRepository : Repository { + suspend fun login(username: String, password: String): User + suspend fun getProfile(): User suspend fun findAll(accountId: Long? = null): Collection suspend fun findAllByNameLike(query: String): Collection } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index d2b5e04..6c53892 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':budgetlib', ':common', ':auth' +include ':app', ':budgetlib', ':common', ':storage' diff --git a/auth/.gitignore b/storage/.gitignore similarity index 100% rename from auth/.gitignore rename to storage/.gitignore diff --git a/auth/build.gradle b/storage/build.gradle similarity index 100% rename from auth/build.gradle rename to storage/build.gradle diff --git a/auth/proguard-rules.pro b/storage/proguard-rules.pro similarity index 100% rename from auth/proguard-rules.pro rename to storage/proguard-rules.pro diff --git a/auth/src/androidTest/java/com/wbrawner/auth/ExampleInstrumentedTest.java b/storage/src/androidTest/java/com/wbrawner/auth/ExampleInstrumentedTest.java similarity index 100% rename from auth/src/androidTest/java/com/wbrawner/auth/ExampleInstrumentedTest.java rename to storage/src/androidTest/java/com/wbrawner/auth/ExampleInstrumentedTest.java diff --git a/auth/src/main/AndroidManifest.xml b/storage/src/main/AndroidManifest.xml similarity index 100% rename from auth/src/main/AndroidManifest.xml rename to storage/src/main/AndroidManifest.xml diff --git a/auth/src/main/java/com/wbrawner/budget/auth/AuthModule.kt b/storage/src/main/java/com/wbrawner/budget/storage/StorageModule.kt similarity index 74% rename from auth/src/main/java/com/wbrawner/budget/auth/AuthModule.kt rename to storage/src/main/java/com/wbrawner/budget/storage/StorageModule.kt index d0289a8..e71f260 100644 --- a/auth/src/main/java/com/wbrawner/budget/auth/AuthModule.kt +++ b/storage/src/main/java/com/wbrawner/budget/storage/StorageModule.kt @@ -1,4 +1,4 @@ -package com.wbrawner.budget.auth +package com.wbrawner.budget.storage import android.content.Context import android.content.SharedPreferences @@ -10,12 +10,7 @@ import javax.inject.Singleton const val ENCRYPTED_SHARED_PREFS_FILE_NAME = "shared-prefs.encrypted" @Module -class AuthModule { - @Singleton - @Provides - fun provideCredentialsProvider(sharedPreferences: SharedPreferences): CredentialsProvider = - SharedPreferencesCredentialsProvider(sharedPreferences) - +class StorageModule { @Provides @Singleton fun provideSharedPreferences(context: Context): SharedPreferences = diff --git a/auth/src/main/res/values/strings.xml b/storage/src/main/res/values/strings.xml similarity index 100% rename from auth/src/main/res/values/strings.xml rename to storage/src/main/res/values/strings.xml diff --git a/auth/src/test/java/com/wbrawner/auth/ExampleUnitTest.java b/storage/src/test/java/com/wbrawner/auth/ExampleUnitTest.java similarity index 100% rename from auth/src/test/java/com/wbrawner/auth/ExampleUnitTest.java rename to storage/src/test/java/com/wbrawner/auth/ExampleUnitTest.java