diff --git a/demos/jans-chip/android/README.md b/demos/jans-chip/android/README.md index 606d8c8186e..f119e878fdd 100644 --- a/demos/jans-chip/android/README.md +++ b/demos/jans-chip/android/README.md @@ -2,13 +2,33 @@ ## A first party android mobile application that leverages dynamic client registration (DCR), DPoP access tokens. -[Demo Video](https://youtu.be/rqPewmESJb0) +[Demo Video](https://www.loom.com/embed/66e145e3bba4406ebda53715168ca8f9?sid=e946f580-587e-4c55-8ea8-3845d6ae4ce9) + ### Steps followed in App for authentication -1. DCR with attestation -2. Execute Authorization Challenge Endpoint to get the Authorization Code -3. Generate DPoP Token using Authorization Code and DPoP header +#### Enrolment + +![](./docs/enrolment.png) + +#### Authentication + +![](./docs/authentication.png) + +### Prerequisite + +1. A Running Janssen Auth server and Janssen FIDO server. +2. SSA generated from the Janssen Auth server with `authorization_code` `grant_types`. + +### Auth Challenge Script + +Add following [Auth Challenge Script](./docs/authChallengeScript.java) in Jans Server (before using the App) with following details. + +- **Name of Script** : passkey_auth_challenge +- **Script Type** : Authorization Challenge +- **Programming Language** : Java +- **Location Type**: Database +- Add `fido2_server_uri` custom property with https://{fido_server_url} as value. Replace `{fido_server_url}` with fido server hostname. ### Workspace Setup @@ -18,16 +38,9 @@ ``` 2. Start Android Studio and open `{jans_monorep_path}\demos\jans-chip\android` of cloned jans monorepo. 3. Press `ctrl` key twice on Android Studio to open `Run Anything` dialog. -4. Enter `gradle wrapper --gradle-version 8.0` and press enter key. This will generate gradle wrapper at `{jans_monorep_path}\demos\jans-chip\gradle\wrapper`. +4. Enter `gradle wrapper --gradle-version 8.0` and press enter key. This will generate gradle wrapper at `{jans_monorep_path}\demos\jans-chip\gradle\wrapper`. +5. Add SSA from Janssen Auth server to SSA field in `demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.kt`. 5. Build and run project on an emmulator (in Android Studio). -6. After launch add configuration endpoint of Janssen Server (with a trusted - domain, not self-signed certificate) and desired scopes on the register screen - to start testing. - -## To-Dos - -- Add FIDO authentication in app. - **Reference:** diff --git a/demos/jans-chip/android/app/build.gradle.kts b/demos/jans-chip/android/app/build.gradle.kts index c4b14d31623..96489287631 100644 --- a/demos/jans-chip/android/app/build.gradle.kts +++ b/demos/jans-chip/android/app/build.gradle.kts @@ -1,52 +1,96 @@ plugins { id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") + kotlin("kapt") } android { - namespace = "io.jans.chip" - compileSdk = 33 + namespace = "io.jans.jans_chip" + compileSdk = 34 defaultConfig { - applicationId = "io.jans.chip" - minSdk = 24 - targetSdk = 33 + applicationId = "io.jans.jans_chip" + minSdk = 28 + targetSdk = 34 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } } buildTypes { release { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } } } dependencies { - val room_version = "2.5.2" - - implementation("androidx.room:room-runtime:$room_version") - annotationProcessor("androidx.room:room-compiler:$room_version") - - implementation("androidx.appcompat:appcompat:1.6.1") - implementation("com.google.android.material:material:1.9.0") - implementation("androidx.constraintlayout:constraintlayout:2.1.4") - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") - implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.11") - implementation("com.google.code.google-collections:google-collect:snapshot-20080530") + + implementation(project(mapOf("path" to ":webauthn"))) + implementation("io.coil-kt:coil-compose:2.6.0") + //implementation("androidx.credentials:credentials:1.3.0-alpha01") + val roomVersion = "2.5.2" + implementation("androidx.room:room-ktx:$roomVersion") + ksp("androidx.room:room-compiler:$roomVersion") + implementation("androidx.compose.material:material-icons-extended:1.5.4") + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") + implementation("androidx.activity:activity-compose:1.8.2") + implementation(platform("androidx.compose:compose-bom:2023.03.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3:1.1.1") + implementation("androidx.core:core-splashscreen:1.1.0-rc01") + implementation("androidx.navigation:navigation-compose:2.8.0-alpha06") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1-Beta") + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-gson:2.11.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1-Beta") + implementation("com.nimbusds:nimbus-jose-jwt:9.31") implementation("io.jsonwebtoken:jjwt:0.9.1") implementation("javax.xml.bind:jaxb-api:2.4.0-b180830.0359") - implementation("com.nimbusds:nimbus-jose-jwt:9.31") implementation("com.google.android.play:integrity:1.2.0") - implementation("android.arch.lifecycle:viewmodel:1.1.1") + implementation ("androidx.appcompat:appcompat:1.4.0") + implementation("androidx.biometric:biometric:1.2.0-alpha05") + implementation("com.github.MahboubehSeyedpour:jetpack-loading:1.1.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") +} + +kapt { + correctErrorTypes = true } \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/androidTest/java/io/jans/chip/ExampleInstrumentedTest.kt b/demos/jans-chip/android/app/src/androidTest/java/io/jans/chip/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..65bdca3e8db --- /dev/null +++ b/demos/jans-chip/android/app/src/androidTest/java/io/jans/chip/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.jans.chip + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.jans.jans_chip1", appContext.packageName) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/AndroidManifest.xml b/demos/jans-chip/android/app/src/main/AndroidManifest.xml index 506249df03d..678d4c3c070 100644 --- a/demos/jans-chip/android/app/src/main/AndroidManifest.xml +++ b/demos/jans-chip/android/app/src/main/AndroidManifest.xml @@ -13,23 +13,16 @@ android:theme="@style/Theme.Janschip" tools:targetApi="31"> - - + android:name="io.jans.chip.MainActivity" + android:exported="true" + android:label="@string/app_name" + android:theme="@style/Theme.AppCompat"> - \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/AfterLoginActivity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/AfterLoginActivity.java deleted file mode 100644 index 99e53a71930..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/AfterLoginActivity.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.jans.chip; - -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; - -import io.jans.chip.modal.LogoutResponse; -import io.jans.chip.modelview.LogoutViewModel; - -public class AfterLoginActivity extends AppCompatActivity { - TextView message; - Button logoutButton; - AlertDialog.Builder errorDialog; - LogoutViewModel logoutViewModel; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_after_login); - logoutViewModel = new LogoutViewModel(getApplicationContext()); - - errorDialog = new AlertDialog.Builder(this); - message = findViewById(R.id.userInfo); - // Retrieve user info from the intent passed from LoginActivity - Intent intent = getIntent(); - String userInfo = intent.getStringExtra(LoginActivity.USER_INFO); - message.setText("User Info is: " + userInfo); - - logoutButton = findViewById(R.id.logoutButton); - logoutButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - logoutViewModel.logout().observe(AfterLoginActivity.this, new Observer() { - - @Override - public void onChanged(LogoutResponse logoutResponse) { - if (logoutResponse.isSuccessful()) { - Intent intent = new Intent(AfterLoginActivity.this, LoginActivity.class); - startActivity(intent); - } else { - createErrorDialog(logoutResponse.getOperationError().getMessage()); - errorDialog.show(); - } - - } - }); - } - }); - } - - private void createErrorDialog(String message) { - errorDialog.setMessage(message) - .setTitle(R.string.error_title) - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.java deleted file mode 100644 index 3455e038cd4..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.jans.chip; - -import android.content.Context; -import android.util.Log; - -import androidx.room.Database; -import androidx.room.Room; -import androidx.room.RoomDatabase; - -import io.jans.chip.dao.AppIntegrityDao; -import io.jans.chip.dao.OPConfigurationDao; -import io.jans.chip.dao.OidcClientDao; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; -import io.jans.chip.utils.AppConfig; - -@Database( - entities = { - OIDCClient.class, - OPConfiguration.class, - AppIntegrityEntity.class - }, - version = AppConfig.ROOM_DATABASE_VERSION -) -public abstract class AppDatabase extends RoomDatabase { - private static final String LOG_TAG = AppDatabase.class.getSimpleName(); - private static final Object LOCK = new Object(); - private static final String DATABASE_NAME = AppConfig.SQLITE_DB_NAME; - private static AppDatabase instance; - - public static AppDatabase getInstance(Context context) { - if (instance == null) { - synchronized (LOCK) { - Log.d(LOG_TAG, "Creating new database instance"); - instance = Room.databaseBuilder(context.getApplicationContext(), - AppDatabase.class, AppDatabase.DATABASE_NAME) - .allowMainThreadQueries() - .fallbackToDestructiveMigration() - .build(); - } - } - Log.d(LOG_TAG, "Getting the database instance"); - return instance; - } - - public abstract OidcClientDao oidcClientDao(); - public abstract OPConfigurationDao opConfigurationDao(); - public abstract AppIntegrityDao appIntegrityDao(); -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.kt new file mode 100644 index 00000000000..6d60ee276a4 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/AppDatabase.kt @@ -0,0 +1,56 @@ +package io.jans.chip + +import android.content.Context +import android.util.Log +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +import io.jans.chip.model.OPConfiguration +import io.jans.chip.utils.AppConfig +import io.jans.chip.dao.AppIntegrityDao +import io.jans.chip.dao.FidoConfigurationDao +import io.jans.chip.dao.OPConfigurationDao +import io.jans.chip.dao.OidcClientDao +import io.jans.chip.model.OIDCClient +import io.jans.chip.model.appIntegrity.AppIntegrityEntity +import io.jans.chip.model.fido.config.FidoConfiguration + + +@Database( + entities = [OIDCClient::class, OPConfiguration::class, AppIntegrityEntity::class, FidoConfiguration::class], + version = AppConfig.ROOM_DATABASE_VERSION +) +abstract class AppDatabase : RoomDatabase() { + abstract fun oidcClientDao(): OidcClientDao + abstract fun opConfigurationDao(): OPConfigurationDao + abstract fun appIntegrityDao(): AppIntegrityDao + abstract fun fidoConfigurationDao(): FidoConfigurationDao + + companion object { + private val LOG_TAG = AppDatabase::class.java.simpleName + private val LOCK = Any() + private val DATABASE_NAME: String = AppConfig.SQLITE_DB_NAME + private var instance: AppDatabase? = null + fun getInstance(context: Context): AppDatabase { + if (instance == null) { + synchronized(LOCK) { + Log.d( + LOG_TAG, + "Creating new database instance" + ) + instance = Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, DATABASE_NAME + ) + .allowMainThreadQueries() + .fallbackToDestructiveMigration() + .build() + } + Log.d(LOG_TAG, "Getting the database instance") + } + return instance!! + } + } + +} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/LoginActivity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/LoginActivity.java deleted file mode 100644 index 68d200461c8..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/LoginActivity.java +++ /dev/null @@ -1,166 +0,0 @@ -package io.jans.chip; - -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; - -import java.util.List; - -import io.jans.chip.modal.LoginResponse; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.TokenResponse; -import io.jans.chip.modal.UserInfoResponse; -import io.jans.chip.modelview.LoginViewModel; -import io.jans.chip.modelview.TokenViewModel; -import io.jans.chip.modelview.UserInfoViewModel; - -public class LoginActivity extends AppCompatActivity { - - EditText username; - EditText password; - Button loginButton; - ProgressBar loginProgressBar; - AlertDialog.Builder errorDialog; - public static final String USER_INFO = "io.jans.DPoP.LoginActivity.USER_INFO"; - AppDatabase appDatabase; - LoginViewModel loginViewModel; - TokenViewModel tokenViewModel; - UserInfoViewModel userInfoViewModel; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - errorDialog = new AlertDialog.Builder(this); - appDatabase = AppDatabase.getInstance(this); - loginViewModel = new LoginViewModel(getApplicationContext()); - tokenViewModel = new TokenViewModel(getApplicationContext()); - userInfoViewModel = new UserInfoViewModel(getApplicationContext()); - - loginProgressBar = findViewById(R.id.loginProgressBar); - loginButton = findViewById(R.id.loginButton); - // Check if OIDCClient is available, if not, navigate to MainActivity - List oidcClientList = appDatabase.oidcClientDao().getAll(); - if (oidcClientList == null || oidcClientList.isEmpty()) { - createErrorDialog("OpenID client not found in database."); - errorDialog.show(); - loginButton.setEnabled(false); - return; - } - OIDCClient oidcClient = oidcClientList.get(0); - if (oidcClient == null || oidcClient.getClientId() == null) { - Intent intent = new Intent(LoginActivity.this, RegisterActivity.class); - startActivity(intent); - finish(); - } - // If a recent access token is available, automatically try to fetch user info - if (oidcClient.getRecentGeneratedAccessToken() != null) { - loginProgressBar.setVisibility(View.VISIBLE); - loginButton.setEnabled(false); - userInfoViewModel.getUserInfo(oidcClient.getRecentGeneratedAccessToken(), true) - .observe(this, new Observer() { - @Override - public void onChanged(UserInfoResponse userInfoResponse) { - if (userInfoResponse.isSuccessful()) { - Intent intent = new Intent(LoginActivity.this, AfterLoginActivity.class); - intent.putExtra(USER_INFO, userInfoResponse.getReponse().toString()); - startActivity(intent); - } else { - showErrorDialog(userInfoResponse.getOperationError().getMessage()); - } - } - }); - } - loginButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - - username = findViewById(R.id.username); - password = findViewById(R.id.password); - String usernameText = username.getText().toString(); - String passwordText = password.getText().toString(); - - if (validateInputs()) { - loginProgressBar.setVisibility(View.VISIBLE); - loginButton.setEnabled(false); - - loginViewModel.processlogin(usernameText, passwordText).observe(LoginActivity.this, new Observer() { - - @Override - public void onChanged(LoginResponse loginResponse) { - if (loginResponse.isSuccessful()) { - tokenViewModel.getToken(loginResponse.getAuthorizationCode(), usernameText, passwordText) - .observe(LoginActivity.this, new Observer() { - - @Override - public void onChanged(TokenResponse tokenResponse) { - if (tokenResponse.isSuccessful()) { - userInfoViewModel.getUserInfo(tokenResponse.getAccessToken(), false) - .observe(LoginActivity.this, new Observer() { - @Override - public void onChanged(UserInfoResponse userInfoResponse) { - if (userInfoResponse.isSuccessful()) { - Intent intent = new Intent(LoginActivity.this, AfterLoginActivity.class); - intent.putExtra(USER_INFO, userInfoResponse.getReponse().toString()); - startActivity(intent); - loginButton.setEnabled(true); - } else { - showErrorDialog(userInfoResponse.getOperationError().getMessage()); - } - } - }); - } else { - showErrorDialog(tokenResponse.getOperationError().getMessage()); - } - - } - }); - } else { - showErrorDialog(loginResponse.getOperationError().getMessage()); - } - } - }); - } - } - }); - } - - private void showErrorDialog(String message) { - createErrorDialog(message); - errorDialog.show(); - loginProgressBar.setVisibility(View.INVISIBLE); - loginButton.setEnabled(true); - } - - private boolean validateInputs() { - if (username == null || username.length() == 0) { - createErrorDialog("Username cannot be left empty."); - errorDialog.show(); - return false; - } - if (password == null || password.length() == 0) { - createErrorDialog("Password cannot be left empty."); - errorDialog.show(); - return false; - } - return true; - } - - private void createErrorDialog(String message) { - errorDialog.setMessage(message) - .setTitle(R.string.error_title) - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/MainActivity.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/MainActivity.kt new file mode 100644 index 00000000000..6a12ac8d4ee --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/MainActivity.kt @@ -0,0 +1,458 @@ +package io.jans.chip + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.annotation.DrawableRes +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.example.compose.AppTheme +import com.nimbusds.jwt.JWTClaimsSet +import com.spr.jetpack_loading.components.indicators.lineScaleIndicator.LineScaleIndicator +import com.spr.jetpack_loading.enums.PunchType +import io.jans.chip.factories.DPoPProofFactory +import io.jans.chip.model.OIDCClient +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.UserInfoResponse +import io.jans.chip.model.appIntegrity.AppIntegrityResponse +import io.jans.chip.ui.screens.NavigationRoutes +import io.jans.chip.ui.screens.authenticatedGraph +import io.jans.chip.ui.screens.unauthenticatedGraph +import io.jans.chip.utils.AppConfig +import io.jans.chip.viewmodel.MainViewModel +import io.jans.jans_chip.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val mainViewModel = MainViewModel.getInstance(this) + var loading = true + + CoroutineScope(Dispatchers.IO).launch { + + mainViewModel.opConfigurationPresent = false + mainViewModel.fidoConfigurationPresent = false + mainViewModel.attestationOptionSuccess = false + mainViewModel.attestationOptionResponse = false + mainViewModel.clientRegistered = false + mainViewModel.userIsAuthenticated = false + + mainViewModel.errorInLoading = false + + //get openid configuration + try { + val jwtClaimsSet: JWTClaimsSet = DPoPProofFactory.getClaimsFromSSA() + var opConfiguration: OPConfiguration? = + async { mainViewModel.getOPConfigurationInDatabase() }.await() + if (opConfiguration != null) { + mainViewModel.opConfigurationPresent = true + } else { + val issuer: String = jwtClaimsSet.getClaim("iss").toString() + mainViewModel.setOpConfigUrl(issuer + AppConfig.OP_CONFIG_URL) + opConfiguration = async { mainViewModel.fetchOPConfiguration() }.await() + if (opConfiguration == null || opConfiguration.isSuccessful == false) { + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = "Error in fetching OP Configuration" + throw Exception("Error in fetching OP Configuration") + } + } + + //get FIDO configuration + val fidoConfiguration = async { mainViewModel.getFidoConfigInDatabase() }.await() + if (fidoConfiguration != null) { + mainViewModel.fidoConfigurationPresent = true + } else { + val issuer: String = jwtClaimsSet.getClaim("iss").toString() + mainViewModel.setFidoConfigUrl(issuer + AppConfig.FIDO_CONFIG_URL) + val fidoConfigurationResponse = + async { mainViewModel.fetchFidoConfiguration() }.await() + + if (fidoConfigurationResponse == null || fidoConfigurationResponse.isSuccessful == false) { + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = "Error in fetching FIDO Configuration" + throw Exception("Error in fetching FIDO Configuration") + } + } + //check OIDC client + var oidcClient: OIDCClient? = async { mainViewModel.getClientInDatabase() }.await() + + if (oidcClient != null) { + mainViewModel.clientRegistered = true + } else { + oidcClient = mainViewModel.doDCRUsingSSA( + AppConfig.SSA, + AppConfig.ALLOWED_REGISTRATION_SCOPES + ) + mainViewModel.clientRegistered = oidcClient != null + if (oidcClient == null || oidcClient.isSuccessful == false) { + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = "Error in registering OIDC Client" + throw Exception("Error in registering OIDC Client") + } + } + val userInfoResponse: UserInfoResponse? = + mainViewModel.getUserInfoWithAccessToken(oidcClient.recentGeneratedAccessToken) + if (userInfoResponse?.isSuccessful == true) { + mainViewModel.setUserInfoResponse(userInfoResponse) + mainViewModel.userIsAuthenticated = true + } + + + var appIntegrityEntity: String? = + async { mainViewModel.checkAppIntegrityFromDatabase() }.await() + if (appIntegrityEntity == null) { + val appIntegrityResponse: AppIntegrityResponse? = + async { mainViewModel.checkAppIntegrity() }.await() + if (appIntegrityResponse != null) { + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = + appIntegrityResponse.appIntegrity?.appRecognitionVerdict + ?: "Unable to fetch App Integrity from Google Play Integrity" + } + } else { + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = "App Integrity: ${appIntegrityEntity}" + } + loading = false + } catch (e: Exception) { + //catching exception + loading = false + mainViewModel.errorInLoading = true + mainViewModel.loadingErrorMessage = "Error in loading app: ${e.message}" + e.printStackTrace() + } + + } + setContent { + AppTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val loadingApp = remember { mutableStateOf(loading) } + val shouldShowDialog = remember { mutableStateOf(false) } + val dialogContent = remember { mutableStateOf("") } + + shouldShowDialog.value = mainViewModel.errorInLoading + dialogContent.value = mainViewModel.loadingErrorMessage + AppAlertDialog( + shouldShowDialog = shouldShowDialog, + content = dialogContent + ) + if (!loading) { + MainApp() + } else { + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .height(400.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.height(40.dp)) + LineScaleIndicator( + color = Color(0xFF134520), + rectCount = 5, + distanceOnXAxis = 30f, + lineHeight = 100, + animationDuration = 500, + minScale = 0.3f, + maxScale = 1.5f, + punchType = PunchType.RANDOM_PUNCH, + penThickness = 15f + ) + } + } + } + } + } + } +} + +@Composable +fun MainApp() { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + MainAppNavHost() + } + +} + +@Composable +fun MainAppNavHost( + modifier: Modifier = Modifier, + navController: NavHostController = rememberNavController(), +) { + NavHost( + modifier = modifier, + navController = navController, + startDestination = NavigationRoutes.Unauthenticated.NavigationRoute.route + ) { + // Authenticated user flow screens + authenticatedGraph(navController = navController) + + // Unauthenticated user flow screens + unauthenticatedGraph(navController = navController) + } + +} + +@Composable +fun AppAlertDialog(shouldShowDialog: MutableState, content: MutableState) { + if (shouldShowDialog.value) { + AlertDialog( + onDismissRequest = { + shouldShowDialog.value = false + }, + + title = { Text(text = "Warning") }, + text = { Text(text = content.value) }, + confirmButton = { + Button( + onClick = { + shouldShowDialog.value = false + } + ) { + Text( + text = "Ok", + color = Color.White + ) + } + } + ) + } +} + +@Composable +fun AppLoaderDialog(shouldShowDialog: MutableState) { + if (shouldShowDialog.value) { + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .height(400.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Spacer(modifier = Modifier.height(40.dp)) + LineScaleIndicator( + color = Color(0xFF134520), + rectCount = 5, + distanceOnXAxis = 30f, + lineHeight = 100, + animationDuration = 500, + minScale = 0.3f, + maxScale = 1.5f, + punchType = PunchType.RANDOM_PUNCH, + penThickness = 15f + ) + } + } +} + +@Composable +fun Title( + // 1 + text: String, + fontFamily: FontFamily, + fontWeight: FontWeight, + fontSize: TextUnit, +) { + Text( // 2 + text = text, + style = TextStyle( + fontFamily = fontFamily, + fontWeight = fontWeight, + fontSize = fontSize, // 3 + ) + ) +} + +@Composable +fun LogButton( + // 1 + text: String, + isClickable: Boolean, + onClick: () -> Unit, +) { + Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) { + Button( + // 3 + enabled = isClickable, + onClick = { onClick() }, + modifier = Modifier + .width(200.dp) + .height(50.dp), + ) { + Text( + // 4 + text = text, + fontSize = 20.sp, + ) + } + } +} + +@Composable +fun UserInfoRow( + label: String, + value: String, +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(15.dp), + elevation = CardDefaults.cardElevation( + defaultElevation = 10.dp + ) + ) { + Row { // 1 + Text( + text = label, + style = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ), + modifier = Modifier + .padding(5.dp), + ) + } + Divider(color = Color.Gray, thickness = 1.dp) + Row { + Text( + text = value, + style = TextStyle( + fontFamily = FontFamily.Default, + fontSize = 20.sp, + + ), + modifier = Modifier + .padding(5.dp), + ) + } + } +} + +@Composable +fun ElevatedCardExample( + heading: String, + subheading: String, + @DrawableRes icon: Int, + onButtonClick: () -> Unit, +) { + ElevatedCard( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + ), + modifier = Modifier + .fillMaxWidth() + .padding(15.dp), + ) { + + Row { // 1 + Column( + modifier = Modifier + .width(64.dp) + .height(150.dp) + ) { + Image( + painterResource(icon), + contentDescription = "", + contentScale = ContentScale.Inside, + modifier = Modifier.fillMaxSize() + ) + } + Column { + Row { + Text( + text = heading, + style = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ), + modifier = Modifier + .padding(1.dp), + ) + } + Row { + Text( + text = subheading, + style = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 15.sp, + ), + modifier = Modifier + .padding(1.dp), + ) + } + Spacer(modifier = Modifier.height(40.dp)) + Row(horizontalArrangement = Arrangement.End) { + LogButton( + isClickable = true, + text = stringResource(R.string.continue_), + onClick = onButtonClick, + ) + } + } + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/PasswordTextField.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/PasswordTextField.kt new file mode 100644 index 00000000000..08c74e5d95f --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/PasswordTextField.kt @@ -0,0 +1,116 @@ +package io.jans.chip + + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import java.util.regex.Pattern + +/** + * Jetpack Compose component for a password text field with validation and visibility toggle. + * + * @param password The current value of the password text field. + * @param onPasswordChange The callback to be invoked when the password value changes. + * @param errorColor The color to be used for displaying error messages. + * @param textFieldLabel The label for the password text field. + * @param errorText The error message to be displayed when the password is not valid. + */ + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun PasswordTextField( + password: TextFieldValue, + onPasswordChange: (TextFieldValue) -> Unit, + errorColor: Color = MaterialTheme.colorScheme.error, + textFieldLabel: String = "Password", + errorText: String = "Password not valid" +) { + // State variables to manage password visibility and validity + var showPassword by remember { mutableStateOf(false) } + var isPasswordError by remember { mutableStateOf(true) } + + // TextField for entering user password + TextField( + value = password, + onValueChange = { + onPasswordChange(it) + isPasswordError = it.isValidPassword() + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(), + trailingIcon = { + // Password visibility toggle icon + PasswordVisibilityToggleIcon( + showPassword = showPassword, + onTogglePasswordVisibility = { showPassword = !showPassword }) + }, + isError = !isPasswordError, + supportingText = { + // Display error text if the password is not valid + if (!isPasswordError) { + Text( + modifier = Modifier.fillMaxWidth(), + text = errorText, + color = errorColor + ) + } + }, + label = { Text(textFieldLabel) }, + //modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp) + ) +} + +/** + * Jetpack Compose component for a password visibility toggle icon. + * + * @param showPassword Whether the password is currently visible. + * @param onTogglePasswordVisibility The callback to toggle password visibility. + */ +@Composable +fun PasswordVisibilityToggleIcon( + showPassword: Boolean, + onTogglePasswordVisibility: () -> Unit +) { + // Determine the icon based on password visibility + val image = if (showPassword) Icons.Filled.Visibility else Icons.Filled.VisibilityOff + val contentDescription = if (showPassword) "Hide password icon" else "Show password icon" + + // IconButton to toggle password visibility + IconButton(onClick = onTogglePasswordVisibility) { + Icon(imageVector = image, contentDescription = contentDescription) + } +} + +/** + * Extension function to check if the [TextFieldValue] represents a valid password. + */ +fun TextFieldValue.isValidPassword(): Boolean { + val password = text + val passwordRegex = + Pattern.compile("[a-zA-Z0-9\\-#\\.\\(\\)\\/%&\\s]{0,19}") + + return password.matches((passwordRegex).toRegex()) +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/RegisterActivity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/RegisterActivity.java deleted file mode 100644 index 426c1a3f04d..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/RegisterActivity.java +++ /dev/null @@ -1,249 +0,0 @@ -package io.jans.chip; - -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; -import io.jans.chip.modelview.DCRViewModel; -import io.jans.chip.modelview.OPConfigurationViewModel; -import io.jans.chip.utils.AppConfig; - -public class RegisterActivity extends AppCompatActivity { - - EditText issuer; - TextView scopes; - Button registerButton; - ProgressBar registerProgressBar; - AlertDialog.Builder errorDialog; - TextView appIntegrityText; - TextView deviceIntegrityText; - OPConfigurationViewModel opConfigurationViewModel; - DCRViewModel dcrViewModel; - AppDatabase appDatabase; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_register); - errorDialog = new AlertDialog.Builder(this); - appDatabase = AppDatabase.getInstance(this); - opConfigurationViewModel = new OPConfigurationViewModel(getApplicationContext()); - dcrViewModel = new DCRViewModel(getApplicationContext()); - - registerProgressBar = findViewById(R.id.registerProgressBar); - registerButton = findViewById(R.id.registerButton); - issuer = findViewById(R.id.issuer); - scopes = findViewById(R.id.scopes); - showItemSelectionPopup(scopes, AppConfig.scopeArray); - - //Display app integrity - displayAppIntegrity(appDatabase); - registerButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - selectScopesByDefault(scopes, AppConfig.defaultScopeArray); - String issuerText = issuer.getText().toString(); - String scopeText = scopes.getText().toString(); - if (validateInputs()) { - registerProgressBar.setVisibility(View.VISIBLE); - registerButton.setEnabled(false); - opConfigurationViewModel.fetchOPConfiguration(issuerText) - .observe(RegisterActivity.this, new Observer() { - @Override - public void onChanged(OPConfiguration opConfiguration) { - if (opConfiguration.isSuccessful()) { - dcrViewModel.doDCR(scopeText).observe(RegisterActivity.this, new Observer() { - - @Override - public void onChanged(OIDCClient oidcClient) { - if (opConfiguration.isSuccessful()) { - Intent intent = new Intent(RegisterActivity.this, LoginActivity.class); - startActivity(intent); - } else { - showErrorDialog(opConfiguration.getOperationError().getMessage()); - } - } - }); - } else { - showErrorDialog(opConfiguration.getOperationError().getMessage()); - } - - } - }); - } - } - }); - } - - private void showErrorDialog(String message) { - createErrorDialog(message); - errorDialog.show(); - registerProgressBar.setVisibility(View.INVISIBLE); - registerButton.setEnabled(true); - } - - private void selectScopesByDefault(TextView scopesEditText, String[] scopeArray) { - // Initialize string builder - StringBuilder stringBuilder = new StringBuilder(); - Set scopeSet = new HashSet<>(); - scopeSet.addAll(Arrays.asList(scopeArray)); - - String scopesString = scopesEditText.getText().toString(); - scopeSet.addAll(Arrays.asList(scopesString.split(" "))); - - scopeSet.stream().forEach(scope -> { - stringBuilder.append(scope); - stringBuilder.append(" "); - }); - - // set text on textView - scopesEditText.setText(stringBuilder.toString()); - } - - private void showItemSelectionPopup(TextView scopesEditText, String[] scopeArray) { - - boolean[] selectedScope = new boolean[scopeArray.length]; - List scopeList = new ArrayList<>(); - scopesEditText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - - // Initialize alert dialog - AlertDialog.Builder builder = new AlertDialog.Builder(RegisterActivity.this); - // set title - builder.setTitle("Select Scope"); - // set dialog non cancelable - builder.setCancelable(false); - builder.setMultiChoiceItems(scopeArray, selectedScope, new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i, boolean b) { - // check condition - if (b) { - // when checkbox selected - // Add position in scope list - scopeList.add(i); - // Sort array list - Collections.sort(scopeList); - } else { - // when checkbox unselected - // Remove position from scopeList - scopeList.remove(Integer.valueOf(i)); - } - } - }); - - builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // Initialize string builder - StringBuilder stringBuilder = new StringBuilder(); - // use for loop - for (int j = 0; j < scopeList.size(); j++) { - // concat array value - stringBuilder.append(scopeArray[scopeList.get(j)]); - // check condition - if (j != scopeList.size() - 1) { - // When j value not equal - // to scope list size - 1 - // add comma - stringBuilder.append(" "); - } - } - // set text on textView - scopesEditText.setText(stringBuilder.toString()); - } - }); - - builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // dismiss dialog - dialogInterface.dismiss(); - } - }); - builder.setNeutralButton("Clear All", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // use for loop - for (int j = 0; j < selectedScope.length; j++) { - // remove all selection - selectedScope[j] = false; - // clear scope list - scopeList.clear(); - // clear text view value - scopesEditText.setText(""); - } - } - }); - // show dialog - builder.show(); - } - }); - } - - private boolean validateInputs() { - if (issuer == null || issuer.length() == 0) { - createErrorDialog("Configuration endpoint cannot be left empty."); - errorDialog.show(); - return false; - } - return true; - } - - private void displayAppIntegrity(AppDatabase appDatabase) { - List appIntegrityEntityList = appDatabase.appIntegrityDao().getAll(); - if (appIntegrityEntityList == null || appIntegrityEntityList.isEmpty()) { - createErrorDialog("App integrity not found in database."); - errorDialog.show(); - return; - } - AppIntegrityEntity appIntegrityEntity = appIntegrityEntityList.get(0); - appIntegrityText = findViewById(R.id.appIntegrityText); - deviceIntegrityText = findViewById(R.id.deviceIntegrityText); - - if (appIntegrityEntity == null) { - appIntegrityText.setText("Unable to fetch App Integrity data frpm App server."); - registerButton.setEnabled(false); - } - if (appIntegrityEntity.getError() != null) { - appIntegrityText.setText(appIntegrityEntity.getError()); - registerButton.setEnabled(false); - } - if (appIntegrityEntity.getAppIntegrity() != null) { - appIntegrityText.setText(appIntegrityEntity.getAppLicensingVerdict()); - } - if (appIntegrityEntity.getDeviceIntegrity() != null) { - deviceIntegrityText.setText(appIntegrityEntity.getDeviceIntegrity()); - } - - } - - private void createErrorDialog(String message) { - errorDialog.setMessage(message) - .setTitle(R.string.error_title) - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - dialog.cancel(); - } - }); - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/SplashScreenActivity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/SplashScreenActivity.java deleted file mode 100644 index 4000b1c8cd8..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/SplashScreenActivity.java +++ /dev/null @@ -1,86 +0,0 @@ -package io.jans.chip; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.widget.Toast; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.lifecycle.Observer; - -import java.util.List; - -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; -import io.jans.chip.modal.appIntegrity.AppIntegrityResponse; -import io.jans.chip.modelview.PlayIntegrityViewModel; - -public class SplashScreenActivity extends AppCompatActivity { - public static final String TAG = "SplashScreenActivity"; - Handler handler; - int handlerDelay = 1000; - PlayIntegrityViewModel playIntegrityViewModel; - AppDatabase appDatabase; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_splash_screen); - // Initialize a Handler to manage the delay - handler = new Handler(); - playIntegrityViewModel = new PlayIntegrityViewModel(getApplicationContext()); - appDatabase = AppDatabase.getInstance(this); - //check app integrity - checkAppIntegrity(appDatabase); - - handler.postDelayed(new Runnable() { - @Override - public void run() { - - // Create a database handler to interact with your database - List oidcClientList = appDatabase.oidcClientDao().getAll(); - OIDCClient client = null; - if (oidcClientList != null && !oidcClientList.isEmpty()) { - client = oidcClientList.get(0); - } - - if (client == null || client.getClientId() == null) { - // If the client is not found or its client ID is null, navigate to the MainActivity - Intent intent = new Intent(SplashScreenActivity.this, RegisterActivity.class); - startActivity(intent); - finish(); - } else { - // If the client is found, log its details and navigate to the LoginActivity - Log.d(TAG, "client" + client.toString()); - Intent intent = new Intent(SplashScreenActivity.this, LoginActivity.class); - startActivity(intent); - finish(); - } - - } - }, handlerDelay); // Delay execution for 1000 milliseconds (1 second) - } - - private void checkAppIntegrity(AppDatabase appDatabase) { - List appIntegrityEntityList = appDatabase.appIntegrityDao().getAll(); - AppIntegrityEntity appIntegrityEntity = null; - if (appIntegrityEntityList != null && !appIntegrityEntityList.isEmpty()) { - appIntegrityEntity = appIntegrityEntityList.get(0); - } - if (appIntegrityEntity == null || appIntegrityEntity.getError() != null) { - appDatabase.appIntegrityDao().deleteAll(); - playIntegrityViewModel.checkAppIntegrity() - .observe(SplashScreenActivity.this, new Observer() { - - @Override - public void onChanged(AppIntegrityResponse appIntegrityResponse) { - if (!appIntegrityResponse.isSuccessful()) { - Toast.makeText(SplashScreenActivity.this, appIntegrityResponse.getOperationError().getMessage(), Toast.LENGTH_SHORT).show(); - } - } - }); - handlerDelay = 20000; - } - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/AuthAdaptor.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/AuthAdaptor.kt new file mode 100644 index 00000000000..654b333eab3 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/AuthAdaptor.kt @@ -0,0 +1,290 @@ +package io.jans.chip.common + +import android.content.Context +import android.util.Base64 +import android.util.Log +import android.util.Pair +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import io.jans.chip.model.fido.assertion.option.AssertionOptionResponse +import io.jans.chip.model.fido.assertion.result.AssertionResultRequest +import io.jans.chip.model.fido.assertion.result.Response +import io.jans.chip.model.fido.attestation.option.AttestationOptionResponse +import io.jans.chip.model.fido.attestation.result.AttestationResultRequest +import io.jans.webauthn.Authenticator +import io.jans.webauthn.models.AttestationObject +import io.jans.webauthn.models.AuthenticatorGetAssertionOptions +import io.jans.webauthn.models.AuthenticatorGetAssertionResult +import io.jans.webauthn.models.AuthenticatorMakeCredentialOptions +import io.jans.webauthn.models.PublicKeyCredentialDescriptor +import io.jans.webauthn.models.PublicKeyCredentialSource +import io.jans.webauthn.models.RpEntity +import io.jans.webauthn.models.UserEntity +import io.jans.webauthn.util.CredentialSafe +import io.jans.webauthn.util.CredentialSelector +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.security.Signature + +class AuthAdaptor(context: Context) { + + private var authenticator: Authenticator? = null + private val TAG: String = AuthAdaptor::class.java.name + var obtainedContext: Context = context + + init { + authenticator = Authenticator(context, false, false) + } + + fun getAllCredentials(): List? { + val credentialSafe: CredentialSafe? = authenticator?.credentialSafe + return credentialSafe?.allCredentialSource + } + + fun isCredentialsPresent(username: String): Boolean { + val allCredentials = getAllCredentials() + allCredentials?.forEach { ele -> + if(ele.userDisplayName == username) { + return true + } + } + return false + } + + private fun generateAuthenticatorMakeCredentialOptions(responseFromAPI: AttestationOptionResponse?, origin: String?): AuthenticatorMakeCredentialOptions { + val options = AuthenticatorMakeCredentialOptions() + options.rpEntity = RpEntity() + options.rpEntity.id = responseFromAPI?.rp?.id + options.rpEntity.name = responseFromAPI?.rp?.name + options.userEntity = UserEntity() + options.userEntity.id = + responseFromAPI?.user?.id?.toByteArray() //"vScQ9Aec2Z8RKNvfZhpg375RWVIN1QMf8x_q9houJnc".getBytes(); + options.userEntity.name = responseFromAPI?.user?.name //"admin"; + options.userEntity.displayName = responseFromAPI?.user?.displayName //"admin"; + options.clientDataHash = generateClientDataHash( + responseFromAPI?.challenge, + "webauthn.create", + origin + ) + options.requireResidentKey = false + options.requireUserPresence = true + options.requireUserVerification = false + options.excludeCredentialDescriptorList = + java.util.ArrayList() + val credTypesAndPubKeyAlgs: MutableList> = ArrayList() + val pair = Pair("public-key", -7L) + credTypesAndPubKeyAlgs.add(pair) + options.credTypesAndPubKeyAlgs = credTypesAndPubKeyAlgs + return options + } + suspend fun register( + responseFromAPI: AttestationOptionResponse?, + origin: String?, + credentialSource: PublicKeyCredentialSource? + ): AttestationResultRequest? { + val credentialSafe: CredentialSafe? = authenticator?.credentialSafe + val creds: List? = credentialSafe?.allCredentialSource + creds?.forEach { ele -> + Log.d("ele.id", ele.id.toString()) + Log.d("ele.rpId", ele.rpId) + Log.d("ele.userDisplayName", ele.userDisplayName) + } + try { + val options = generateAuthenticatorMakeCredentialOptions(responseFromAPI, origin) + val attestationObject: AttestationObject? = + authenticator?.makeCredential(options, credentialSource, obtainedContext, null) + val attestationObjectBytes: ByteArray? = attestationObject?.asCBOR() + Log.d(TAG + "attestationObjectBytes :", urlEncodeToString(attestationObjectBytes)) + Log.d(TAG, urlEncodeToString(attestationObject?.credentialId).replace("\n", "")) + Log.d(TAG, urlEncodeToString(attestationObject?.credentialId)) + + val clientDataJSON = generateClientDataJSON( + responseFromAPI?.challenge, + "webauthn.create", + origin + ) + val response = clientDataJSON?.let { + io.jans.chip.model.fido.attestation.result.Response( + urlEncodeToString(attestationObjectBytes).replace("\n", ""), + it + ) + } + + val attestationResultRequest = response?.let { + AttestationResultRequest( + urlEncodeToString(attestationObject?.credentialId).replace("\n", "") + .replace("=", ""), + "public-key", + it + ) + } + attestationResultRequest?.id = + urlEncodeToString(attestationObject?.credentialId).replace("\n", "") + .replace("=", "") + attestationResultRequest?.type = "public-key" + + if (response != null) { + attestationResultRequest?.response = response + } + return attestationResultRequest + } catch (e: Exception) { + val attestationResultRequest = AttestationResultRequest(null, null, null) + attestationResultRequest.isSuccessful = false + attestationResultRequest.errorMessage = + "Error in making credential by Authenticator : ${e.message}" + e.printStackTrace() + return attestationResultRequest + } + } + + suspend fun getPublicKeyCredentialSource(responseFromAPI: AttestationOptionResponse?, origin: String?): PublicKeyCredentialSource? { + val options = generateAuthenticatorMakeCredentialOptions(responseFromAPI, origin) + return authenticator?.getPublicKeyCredentialSource(options) + } + + suspend fun generateSignature(credentialSource: PublicKeyCredentialSource? ): Signature? { + return authenticator?.generateSignature(credentialSource) + } + + @Throws(JsonProcessingException::class, NoSuchAlgorithmException::class) + private fun generateClientDataHash( + challenge: String?, + type: String?, + origin: String? + ): ByteArray? { + // Convert clientDataJson to JSON string + val objectMapper = ObjectMapper() + val clientData = objectMapper.createObjectNode() + clientData.put("type", type) + clientData.put("challenge", challenge) + clientData.put("origin", origin) + objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + val serializedClientData = objectMapper.writeValueAsString(clientData) + + // Calculate SHA-256 hash + val digest = MessageDigest.getInstance("SHA-256") + return digest.digest(serializedClientData.toByteArray(StandardCharsets.UTF_8)) + } + + private fun urlEncodeToString(src: ByteArray?): String { + return Base64.encodeToString(src, Base64.URL_SAFE) + } + + private fun generateClientDataJSON( + challenge: String?, + type: String?, + origin: String? + ): String? { + + + // Convert clientDataJson to JSON string + val objectMapper = ObjectMapper() + val clientData = objectMapper.createObjectNode() + clientData.put("type", type) + clientData.put("challenge", challenge) + clientData.put("origin", origin) + Log.d( + TAG + "clientData.toString()", + clientData.toString() + ) + val clientDataJSON = + urlEncodeToString(clientData.toString().toByteArray(StandardCharsets.UTF_8)) + Log.d( + TAG + "clientDataJSON", + clientDataJSON.replace("\n", "") + ) + return clientDataJSON.replace("\n", "") + } + + private fun decode(src: String?): ByteArray? { + return Base64.decode(src, Base64.URL_SAFE) + } + + private fun generateAuthenticatorGetAssertionOptions(assertionOptionResponse: AssertionOptionResponse?, origin: String?): AuthenticatorGetAssertionOptions { + val options = AuthenticatorGetAssertionOptions() + options.rpId = assertionOptionResponse?.rpId + options.requireUserVerification = false + options.requireUserPresence = true + options.clientDataHash = generateClientDataHash( + assertionOptionResponse?.challenge, + "webauthn.get", + origin/*"https://admin-ui-test.gluu.org"*/ + ) + val allowCredentialDescriptorList: MutableList = + ArrayList() + assertionOptionResponse?.allowCredentials?.stream() + ?.forEach { cred -> + Log.d(TAG, cred.id) + val publicKeyCredentialDescriptor = + PublicKeyCredentialDescriptor( + cred.type, + decode(cred.id), + cred.transports + ) + allowCredentialDescriptorList.add(publicKeyCredentialDescriptor) + } + options.allowCredentialDescriptorList = allowCredentialDescriptorList + return options + } + + suspend fun authenticate( + assertionOptionResponse: AssertionOptionResponse, + origin: String?, + selectedCredential: PublicKeyCredentialSource? + ): AssertionResultRequest { + try { + + val options: AuthenticatorGetAssertionOptions = generateAuthenticatorGetAssertionOptions(assertionOptionResponse, origin) + val assertionObject: AuthenticatorGetAssertionResult? = + authenticator?.getAssertion(options, selectedCredential, LocalCredentialSelector()) + + val assertionResultRequest = AssertionResultRequest() + assertionResultRequest.id = + urlEncodeToString(assertionObject?.selectedCredentialId).replace( + "\n", + "" + ).replace("=", "") + + assertionResultRequest.type = "public-key" + assertionResultRequest.rawId = + urlEncodeToString(assertionObject?.selectedCredentialId).replace( + "\n", + "" + ) + + val response: Response = Response() + + response.clientDataJSON = generateClientDataJSON( + assertionOptionResponse.challenge, + "webauthn.get", + origin + ) + + response.authenticatorData = + urlEncodeToString(assertionObject?.authenticatorData).replace( + "\n", + "" + ) + response.signature = urlEncodeToString(assertionObject?.signature).replace("\n", "") + assertionResultRequest.response = response + return assertionResultRequest + } catch (e: Exception) { + val assertionResultRequest = AssertionResultRequest() + assertionResultRequest.isSuccessful = false + assertionResultRequest.errorMessage = + "Error in making credential by Authenticator : ${e.message}" + e.printStackTrace() + return assertionResultRequest + } + } + suspend fun selectPublicKeyCredentialSource( + credentialSelector: CredentialSelector, + assertionOptionResponse: AssertionOptionResponse?, + origin: String?, + ): PublicKeyCredentialSource? { + val options: AuthenticatorGetAssertionOptions = generateAuthenticatorGetAssertionOptions(assertionOptionResponse, origin) + return authenticator?.selectPublicKeyCredentialSource(credentialSelector, options) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/LocalCredentialSelector.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/LocalCredentialSelector.kt new file mode 100644 index 00000000000..02bc3a039f1 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/common/LocalCredentialSelector.kt @@ -0,0 +1,10 @@ +package io.jans.chip.common + +import io.jans.webauthn.models.PublicKeyCredentialSource +import io.jans.webauthn.util.CredentialSelector + +open class LocalCredentialSelector: CredentialSelector { + override fun selectFrom(credentialList: MutableList?): PublicKeyCredentialSource? { + return credentialList?.get(0) ?: null + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.java deleted file mode 100644 index 0fc043f0961..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.jans.chip.dao; - -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.Query; -import androidx.room.Update; - -import java.util.List; - -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; - -@Dao -public interface AppIntegrityDao { - @Insert - void insert(AppIntegrityEntity appIntegrityEntity); - - @Update - void update(AppIntegrityEntity appIntegrityEntity); - - @Query("SELECT * FROM APP_INTEGRITY") - List getAll(); - - @Query("DELETE FROM APP_INTEGRITY") - void deleteAll(); -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.kt new file mode 100644 index 00000000000..2b68c5d5060 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/AppIntegrityDao.kt @@ -0,0 +1,22 @@ +package io.jans.chip.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.jans.chip.model.appIntegrity.AppIntegrityEntity + +@Dao +interface AppIntegrityDao { + @Insert + fun insert(appIntegrityEntity: AppIntegrityEntity?) + + @Update + fun update(appIntegrityEntity: AppIntegrityEntity?) + + @Query("SELECT * FROM APP_INTEGRITY") + fun getAll(): List + + @Query("DELETE FROM APP_INTEGRITY") + fun deleteAll() +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/FidoConfigurationDao.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/FidoConfigurationDao.kt new file mode 100644 index 00000000000..b55226a4241 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/FidoConfigurationDao.kt @@ -0,0 +1,22 @@ +package io.jans.chip.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.jans.chip.model.fido.config.FidoConfiguration + +@Dao +interface FidoConfigurationDao { + @Insert + fun insert(opConfiguration: FidoConfiguration) + + @Update + fun update(opConfiguration: FidoConfiguration) + + @Query("SELECT * FROM FIDO_CONFIGURATION") + fun getAll(): List + + @Query("DELETE FROM FIDO_CONFIGURATION") + fun deleteAll() +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.java deleted file mode 100644 index 6a8da6b2d70..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.jans.chip.dao; - -import androidx.room.Dao; -import androidx.room.Delete; -import androidx.room.Insert; -import androidx.room.Query; -import androidx.room.Update; - -import java.util.List; - -import io.jans.chip.modal.OPConfiguration; -@Dao -public interface OPConfigurationDao { - @Insert - void insert(OPConfiguration opConfiguration); - - @Update - void update(OPConfiguration opConfiguration); - - @Query("SELECT * FROM OP_CONFIGURATION") - List getAll(); - - @Query("DELETE FROM OP_CONFIGURATION") - void deleteAll(); -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.kt new file mode 100644 index 00000000000..7acae91e284 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OPConfigurationDao.kt @@ -0,0 +1,22 @@ +package io.jans.chip.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.jans.chip.model.OPConfiguration + +@Dao +interface OPConfigurationDao { + @Insert + fun insert(opConfiguration: OPConfiguration?) + + @Update + fun update(opConfiguration: OPConfiguration?) + + @Query("SELECT * FROM OP_CONFIGURATION") + fun getAll(): List + + @Query("DELETE FROM OP_CONFIGURATION") + fun deleteAll() +} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.java deleted file mode 100644 index 33baa957a0b..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.jans.chip.dao; - -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.Query; -import androidx.room.Update; - -import java.util.List; - -import io.jans.chip.modal.OIDCClient; -@Dao -public interface OidcClientDao { - @Insert - void insert(OIDCClient oidcClient); - - @Update - void update(OIDCClient oidcClient); - - @Query("SELECT * FROM OIDC_CLIENT") - List getAll(); - @Query("DELETE FROM OIDC_CLIENT") - void deleteAll(); -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.kt new file mode 100644 index 00000000000..58af111bcf5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/dao/OidcClientDao.kt @@ -0,0 +1,21 @@ +package io.jans.chip.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import io.jans.chip.model.OIDCClient +@Dao +interface OidcClientDao { + @Insert + fun insert(oidcClient: OIDCClient) + + @Update + fun update(oidcClient: OIDCClient) + + @Query("SELECT * FROM OIDC_CLIENT") + fun getAll(): List + + @Query("DELETE FROM OIDC_CLIENT") + fun deleteAll() +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.java deleted file mode 100644 index 5ce8adcbaf2..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.jans.chip.factories; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; - -public class DPoPProofFactory { - - private static DPoPProofFactory single_instance = null; - - private DPoPProofFactory() { - } - - public static synchronized DPoPProofFactory getInstance() { - if (single_instance == null) { - single_instance = new DPoPProofFactory(); - } - - return single_instance; - } - - /** - * The function `issueDPoPJWTToken` generates a DPoP (Distributed Proof of Possession) JWT (JSON Web Token) with the - * specified HTTP method and request URL. - * - * @param httpMethod The `httpMethod` parameter represents the HTTP method used in the request, such as "GET", "POST", - * "PUT", etc. - * @param requestUrl The `requestUrl` parameter is the URL of the HTTP request that you want to issue a DPoP JWT token - * for. It represents the target resource or endpoint that you want to access. - * @return The method is returning a JWT (JSON Web Token) token. - */ - public String issueDPoPJWTToken(String httpMethod, String requestUrl) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - - Map headers = new HashMap<>(); - headers.put("typ", "dpop+jwt"); - headers.put("alg", "RS256"); - headers.put("jwk", KeyManager.getPublicKeyJWK(KeyManager.getInstance().getPublicKey()).getRequiredParams()); - - Map claims = new HashMap<>(); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - LocalDateTime now = LocalDateTime.now(); - Instant instant = now.atZone(ZoneId.systemDefault()).toInstant(); - Date iat = Date.from(instant); - claims.put("iat", iat); - } - - claims.put("jti", UUID.randomUUID().toString()); - claims.put("htm", httpMethod); //POST - claims.put("htu", requestUrl); //issuer - - String token = Jwts.builder() - .setHeaderParams(headers) - .setClaims(claims) - .signWith(SignatureAlgorithm.RS256, KeyManager.getInstance().getPrivateKey()) - .compact(); - return token; - } - - /** - * The function generates a JWT token with specified claims and signs it using an RSA256 algorithm. - * - * @param claims A map containing the claims to be included in the JWT token. Claims are key-value pairs that provide - * information about the token, such as the issuer, subject, expiration time, etc. - * @return The method returns a JWT (JSON Web Token) token as a String. - */ - public String issueJWTToken(Map claims) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { - - Map headers = new HashMap<>(); - headers.put("typ", "jwt"); - headers.put("alg", "RS256"); - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - LocalDateTime now = LocalDateTime.now(); - Instant instant = now.atZone(ZoneId.systemDefault()).toInstant(); - Date iat = Date.from(instant); - claims.put("iat", iat); - } - - claims.put("jti", UUID.randomUUID().toString()); - - String token = Jwts.builder() - .setHeaderParams(headers) - .setClaims(claims) - .signWith(SignatureAlgorithm.RS256, KeyManager.getInstance().getPrivateKey()) - .compact(); - return token; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.kt new file mode 100644 index 00000000000..d53b902aaba --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/DPoPProofFactory.kt @@ -0,0 +1,85 @@ +package io.jans.chip.factories + +import android.os.Build +import com.nimbusds.jose.JWSObject +import com.nimbusds.jwt.JWTClaimsSet +import io.jans.chip.factories.KeyManager.Companion.getPublicKeyJWK +import io.jans.chip.utils.AppConfig +import io.jsonwebtoken.Jwts +import io.jsonwebtoken.SignatureAlgorithm +import java.security.InvalidAlgorithmParameterException +import java.security.NoSuchAlgorithmException +import java.security.NoSuchProviderException +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.Date +import java.util.UUID + +class DPoPProofFactory { + companion object { + var keyManager: KeyManager = KeyManager() + + @Throws( + InvalidAlgorithmParameterException::class, + NoSuchAlgorithmException::class, + NoSuchProviderException::class + ) + fun issueDPoPJWTToken(httpMethod: String, requestUrl: String): String? { + + val headers: MutableMap = + mutableMapOf() + headers["typ"] = "dpop+jwt" + headers["alg"] = "RS256" + val requiredParams = getPublicKeyJWK(KeyManager.getPublicKey())?.requiredParams + headers["jwk"] = requiredParams!! + + val claims: MutableMap = HashMap() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val now = LocalDateTime.now() + val instant = + now.atZone(ZoneId.systemDefault()).toInstant() + val iat = Date.from(instant) + claims["iat"] = iat + } + claims["jti"] = UUID.randomUUID().toString() + claims["htm"] = httpMethod //POST + claims["htu"] = requestUrl //issuer + return Jwts.builder() + .setHeaderParams(headers) + .setClaims(claims) + .signWith(SignatureAlgorithm.RS256, KeyManager.getPrivateKey()) + .compact() + } + + @Throws( + InvalidAlgorithmParameterException::class, + NoSuchAlgorithmException::class, + NoSuchProviderException::class + ) + fun issueJWTToken(claims: MutableMap): String? { + val headers: MutableMap = + java.util.HashMap() + headers["typ"] = "jwt" + headers["alg"] = "RS256" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val now = LocalDateTime.now() + val instant = + now.atZone(ZoneId.systemDefault()).toInstant() + val iat = Date.from(instant) + claims["iat"] = iat + } + claims["jti"] = UUID.randomUUID().toString() + return Jwts.builder() + .setHeaderParams(headers) + .setClaims(claims) + .signWith(SignatureAlgorithm.RS256, KeyManager.getPrivateKey()) + .compact() + } + + fun getClaimsFromSSA(): JWTClaimsSet { + val jwsObject: JWSObject = JWSObject.parse(AppConfig.SSA) + val claims: JWTClaimsSet = JWTClaimsSet.parse(jwsObject.getPayload().toJSONObject()); + return claims + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.java deleted file mode 100644 index b5efa2d3f23..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.java +++ /dev/null @@ -1,202 +0,0 @@ -package io.jans.chip.factories; - -import android.icu.util.Calendar; -import android.icu.util.GregorianCalendar; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; -import android.util.Base64; -import android.util.Log; - -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.interfaces.RSAPublicKey; - -import javax.security.auth.x500.X500Principal; - -public class KeyManager { - private static final String KEY_ALIAS = "JansChipKeystore"; - private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; - private KeyPair kp; - private static KeyManager single_instance = null; - - private KeyManager() { - } - - public static synchronized KeyManager getInstance() { - if (single_instance == null) { - single_instance = new KeyManager(); - } - - return single_instance; - } - - /** - * The function retrieves the public key from the Android Keystore, generating a new key pair if necessary. - * - * @return The method is returning a PublicKey object. - */ - public PublicKey getPublicKey() { - try { - if (!checkKeyExists()) { - generateKeyPair(); - } - KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); - keyStore.load(null); - PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, null); - PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS).getPublicKey(); - return publicKey; - } catch ( - NoSuchAlgorithmException | - UnrecoverableKeyException | - CertificateException | - KeyStoreException | - IOException e) { - throw new RuntimeException(e); - } - } - - /** - * The function retrieves the private key from the Android Keystore, generating a new key pair if it doesn't exist. - * - * @return The method is returning a PrivateKey object. - */ - public PrivateKey getPrivateKey() { - try { - if (!checkKeyExists()) { - generateKeyPair(); - } - KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); - keyStore.load(null); - PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, null); - PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS).getPublicKey(); - - return privateKey; - } catch (NoSuchAlgorithmException | - UnrecoverableKeyException | CertificateException | - KeyStoreException | IOException e) { - throw new RuntimeException(e); - } - } - - /** - * The function generates a key pair using RSA algorithm with specified parameters and returns the public key. - * - * @return The method is returning a PublicKey object. - */ - private PublicKey generateKeyPair() { - - try { - - GregorianCalendar startDate = new GregorianCalendar(); - GregorianCalendar endDate = new GregorianCalendar(); - endDate.add(Calendar.YEAR, 1); - - KeyPairGenerator kpg = KeyPairGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE); - - kpg.initialize(new KeyGenParameterSpec.Builder( - KEY_ALIAS, - KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) - .setCertificateSerialNumber(BigInteger.valueOf(777)) //Serial number used for the self-signed certificate of the generated key pair, default is 1 - .setCertificateSubject(new X500Principal("CN=" + KEY_ALIAS)) //Subject used for the self-signed certificate of the generated key pair, default is CN=fake - .setDigests(KeyProperties.DIGEST_SHA256) //Set of digests algorithms with which the key can be used - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) - .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) //Set of padding schemes with which the key can be used when signing/verifying - .setCertificateNotBefore(startDate.getTime()) //Start of the validity period for the self-signed certificate of the generated, default Jan 1 1970 - .setCertificateNotAfter(endDate.getTime()) //End of the validity period for the self-signed certificate of the generated key, default Jan 1 2048 - //.setUserAuthenticationRequired(true) //Sets whether this key is authorized to be used only if the user has been authenticated, default false - .setKeySize(2048) - .setUserAuthenticationValidityDurationSeconds(30) - .build()); - - kp = kpg.generateKeyPair(); - Log.d("Key pair successfully generated:: Public Key", kp.getPublic().toString()); - Log.d("Key pair successfully generated:: Public Key", Base64.encodeToString(kp.getPublic().getEncoded(), Base64.NO_WRAP)); - - return kp.getPublic(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - /** - * The function checks if a private and public key pair exists in the Android Keystore. - * - * @return The method is returning a boolean value. It returns true if both the private key and public key exist in the - * keystore, and false otherwise. - */ - private boolean checkKeyExists() { - //We get the Keystore instance - KeyStore keyStore = null; - try { - keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); - keyStore.load(null); - //We get the private and public key from the keystore if they exists - PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, null); - PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS).getPublicKey(); - return privateKey != null && publicKey != null; - } catch (KeyStoreException | UnrecoverableKeyException | CertificateException | - IOException | NoSuchAlgorithmException | NullPointerException e) { - return false; - //throw new RuntimeException(e); - } - } - - /** - * The function generates a signature for the given data using a private key stored in the Android Keystore. - * - * @param data The "data" parameter is a byte array that represents the data that you want to sign. - * @return The method is returning a String representation of the signature generated from the provided data. - */ - public String signData(byte[] data) { - KeyStore keyStore = null; - try { - if (!checkKeyExists()) { - generateKeyPair(); - } - keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); - keyStore.load(null); - //We get the private and public key from the keystore if they exists - PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, null); - //PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS).getPublicKey(); - Signature s = Signature.getInstance("SHA256withRSA"); - s.initSign(privateKey); - s.update(data); - byte[] signature = s.sign(); - return new String(signature, StandardCharsets.UTF_8); - } catch (KeyStoreException | UnrecoverableKeyException | CertificateException | - IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - throw new RuntimeException(e); - } - } - - /** - * The function takes a public key and returns a JWK (JSON Web Key) representation of the key. - * - * @param publicKey The publicKey parameter is of type PublicKey and represents the public key that you want to convert - * to a JWK (JSON Web Key) format. - * @return The method is returning a JWK (JSON Web Key) object. - */ - public static JWK getPublicKeyJWK(PublicKey publicKey) { - JWK jwk = new RSAKey.Builder((RSAPublicKey) publicKey) - .build(); - return jwk; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.kt new file mode 100644 index 00000000000..0f4844bb302 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/factories/KeyManager.kt @@ -0,0 +1,226 @@ +package io.jans.chip.factories + +import android.icu.util.Calendar +import android.icu.util.GregorianCalendar +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import android.util.Log +import com.nimbusds.jose.jwk.JWK +import com.nimbusds.jose.jwk.RSAKey +import java.io.IOException +import java.math.BigInteger +import java.security.InvalidKeyException +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.KeyStoreException +import java.security.NoSuchAlgorithmException +import java.security.PrivateKey +import java.security.PublicKey +import java.security.Signature +import java.security.SignatureException +import java.security.UnrecoverableKeyException +import java.security.cert.CertificateException +import java.security.interfaces.RSAPublicKey +import javax.security.auth.x500.X500Principal + +class KeyManager { + + + + companion object { + private val KEY_ALIAS = "JansChipKeystore" + private val ANDROID_KEYSTORE = "AndroidKeyStore" + + /** + * The function retrieves the public key from the Android Keystore, generating a new key pair if necessary. + * + * @return The method is returning a PublicKey object. + */ + fun getPublicKey(): PublicKey? { + return try { + if (!checkKeyExists()) { + generateKeyPair() + } + val keyStore = + KeyStore.getInstance(ANDROID_KEYSTORE) + keyStore.load(null) + val privateKey = keyStore.getKey( + KEY_ALIAS, + null + ) as PrivateKey + keyStore.getCertificate(KEY_ALIAS).publicKey + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } catch (e: UnrecoverableKeyException) { + throw RuntimeException(e) + } catch (e: CertificateException) { + throw RuntimeException(e) + } catch (e: KeyStoreException) { + throw RuntimeException(e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + /** + * The function retrieves the private key from the Android Keystore, generating a new key pair if it doesn't exist. + * + * @return The method is returning a PrivateKey object. + */ + fun getPrivateKey(): PrivateKey? { + return try { + if (!checkKeyExists()) { + generateKeyPair() + } + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + keyStore.load(null) + val privateKey = + keyStore.getKey(KEY_ALIAS, null) as PrivateKey + val publicKey = + keyStore.getCertificate(KEY_ALIAS).publicKey + privateKey + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } catch (e: UnrecoverableKeyException) { + throw RuntimeException(e) + } catch (e: CertificateException) { + throw RuntimeException(e) + } catch (e: KeyStoreException) { + throw RuntimeException(e) + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + /** + * The function generates a key pair using RSA algorithm with specified parameters and returns the public key. + * + * @return The method is returning a PublicKey object. + */ + private fun generateKeyPair(): PublicKey? { + try { + val startDate = GregorianCalendar() + val endDate = GregorianCalendar() + endDate.add(Calendar.YEAR, 1) + val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEYSTORE + ) + kpg.initialize( + KeyGenParameterSpec.Builder( + KEY_ALIAS, + KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY + ) + .setCertificateSerialNumber(BigInteger.valueOf(777)) //Serial number used for the self-signed certificate of the generated key pair, default is 1 + .setCertificateSubject(X500Principal("CN=" + KEY_ALIAS)) //Subject used for the self-signed certificate of the generated key pair, default is CN=fake + .setDigests(KeyProperties.DIGEST_SHA256) //Set of digests algorithms with which the key can be used + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) + .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) //Set of padding schemes with which the key can be used when signing/verifying + .setCertificateNotBefore(startDate.time) //Start of the validity period for the self-signed certificate of the generated, default Jan 1 1970 + .setCertificateNotAfter(endDate.time) //End of the validity period for the self-signed certificate of the generated key, default Jan 1 2048 + //.setUserAuthenticationRequired(true) //Sets whether this key is authorized to be used only if the user has been authenticated, default false + .setKeySize(2048) + .setUserAuthenticationValidityDurationSeconds(30) + .build() + ) + var kp: KeyPair = kpg.generateKeyPair() + Log.d("Key pair successfully generated:: Public Key", kp.getPublic().toString()) + Log.d( + "Key pair successfully generated:: Public Key", + Base64.encodeToString(kp.getPublic().encoded, Base64.NO_WRAP) + ) + return kp.getPublic() + } catch (e: Exception) { + e.printStackTrace() + } + return null + } + + /** + * The function checks if a private and public key pair exists in the Android Keystore. + * + * @return The method is returning a boolean value. It returns true if both the private key and public key exist in the + * keystore, and false otherwise. + */ + private fun checkKeyExists(): Boolean { + //We get the Keystore instance + var keyStore: KeyStore? = null + return try { + keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + keyStore.load(null) + //We get the private and public key from the keystore if they exists + val privateKey = + keyStore.getKey(KEY_ALIAS, null) as PrivateKey + val publicKey = + keyStore.getCertificate(KEY_ALIAS).publicKey + privateKey != null && publicKey != null + } catch (e: KeyStoreException) { + false + //throw new RuntimeException(e); + } catch (e: UnrecoverableKeyException) { + false + } catch (e: CertificateException) { + false + } catch (e: IOException) { + false + } catch (e: NoSuchAlgorithmException) { + false + } catch (e: NullPointerException) { + false + } + } + + /** + * The function generates a signature for the given data using a private key stored in the Android Keystore. + * + * @param data The "data" parameter is a byte array that represents the data that you want to sign. + * @return The method is returning a String representation of the signature generated from the provided data. + */ + fun signData(data: ByteArray?): String? { + var keyStore: KeyStore? = null + return try { + if (!checkKeyExists()) { + generateKeyPair() + } + keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + keyStore.load(null) + //We get the private and public key from the keystore if they exists + val privateKey = + keyStore.getKey(KEY_ALIAS, null) as PrivateKey + //PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS).getPublicKey(); + val s = Signature.getInstance("SHA256withRSA") + s.initSign(privateKey) + s.update(data) + val signature = s.sign().toString(Charsets.UTF_8) + return signature + } catch (e: KeyStoreException) { + throw RuntimeException(e) + } catch (e: UnrecoverableKeyException) { + throw RuntimeException(e) + } catch (e: CertificateException) { + throw RuntimeException(e) + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } catch (e: SignatureException) { + throw RuntimeException(e) + } catch (e: InvalidKeyException) { + throw RuntimeException(e) + } + } + + /** + * The function takes a public key and returns a JWK (JSON Web Key) representation of the key. + * + * @param publicKey The publicKey parameter is of type PublicKey and represents the public key that you want to convert + * to a JWK (JSON Web Key) format. + * @return The method is returning a JWK (JSON Web Key) object. + */ + fun getPublicKeyJWK(publicKey: PublicKey?): JWK? { + return RSAKey.Builder(publicKey as RSAPublicKey?) + .build() + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCRequest.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCRequest.java deleted file mode 100644 index 2e01401d197..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCRequest.java +++ /dev/null @@ -1,161 +0,0 @@ -package io.jans.chip.modal; - -import com.google.gson.annotations.SerializedName; - -import java.util.List; - -public class DCRequest { - public DCRequest() { - } - - public DCRequest(String issuer, - List redirectUris, - String scope, - List responseTypes, - List postLogoutRedirectUris, - List grantTypes, - String applicationType, - String clientName, - String tokenEndpointAuthMethod, - String evidence, - String jwks) { - this.issuer = issuer; - this.redirectUris = redirectUris; - this.scope = scope; - this.responseTypes = responseTypes; - this.postLogoutRedirectUris = postLogoutRedirectUris; - this.grantTypes = grantTypes; - this.applicationType = applicationType; - this.clientName = clientName; - this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - this.evidence = evidence; - this.jwks = jwks; - } - - @SerializedName("issuer") - private String issuer; - @SerializedName("redirect_uris") - private List redirectUris; - @SerializedName("scope") - private String scope; - @SerializedName("response_types") - private List responseTypes; - @SerializedName("post_logout_redirect_uris") - private List postLogoutRedirectUris; - @SerializedName("grant_types") - private List grantTypes; - @SerializedName("application_type") - private String applicationType; - @SerializedName("client_name") - private String clientName; - @SerializedName("token_endpoint_auth_method") - private String tokenEndpointAuthMethod; - @SerializedName("evidence") - private String evidence; - @SerializedName("jwks") - private String jwks; - - public String getJwks() { - return jwks; - } - - public void setJwks(String jwks) { - this.jwks = jwks; - } - - public String getIssuer() { - return issuer; - } - - public void setIssuer(String issuer) { - this.issuer = issuer; - } - - public List getRedirectUris() { - return redirectUris; - } - - public void setRedirectUris(List redirectUris) { - this.redirectUris = redirectUris; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public List getResponseTypes() { - return responseTypes; - } - - public void setResponseTypes(List responseTypes) { - this.responseTypes = responseTypes; - } - - public List getPostLogoutRedirectUris() { - return postLogoutRedirectUris; - } - - public void setPostLogoutRedirectUris(List postLogoutRedirectUris) { - this.postLogoutRedirectUris = postLogoutRedirectUris; - } - - public List getGrantTypes() { - return grantTypes; - } - - public void setGrantTypes(List grantTypes) { - this.grantTypes = grantTypes; - } - - public String getApplicationType() { - return applicationType; - } - - public void setApplicationType(String applicationType) { - this.applicationType = applicationType; - } - - public String getClientName() { - return clientName; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getTokenEndpointAuthMethod() { - return tokenEndpointAuthMethod; - } - - public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { - this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; - } - - public String getEvidence() { - return evidence; - } - - public void setEvidence(String evidence) { - this.evidence = evidence; - } - - @Override - public String toString() { - return "DCRequest{" + - "issuer='" + issuer + '\'' + - ", redirectUris=" + redirectUris + - ", scope='" + scope + '\'' + - ", responseTypes=" + responseTypes + - ", postLogoutRedirectUris=" + postLogoutRedirectUris + - ", grantTypes=" + grantTypes + - ", applicationType='" + applicationType + '\'' + - ", clientName='" + clientName + '\'' + - ", tokenEndpointAuthMethod='" + tokenEndpointAuthMethod + '\'' + - ", evidence='" + evidence + '\'' + - '}'; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCResponse.java deleted file mode 100644 index 2246e39d957..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/DCResponse.java +++ /dev/null @@ -1,67 +0,0 @@ -package io.jans.chip.modal; - -import com.google.gson.annotations.SerializedName; - -public class DCResponse { - public DCResponse(String clientId, - String clientSecret, - String clientName, - String authorizationChallengeEndpoint, - String endSessionEndpoint) { - this.clientId = clientId; - this.clientSecret = clientSecret; - this.clientName = clientName; - this.authorizationChallengeEndpoint = authorizationChallengeEndpoint; - this.endSessionEndpoint = endSessionEndpoint; - } - - @SerializedName("client_id") - private String clientId; - @SerializedName("client_secret") - private String clientSecret; - @SerializedName("client_name") - private String clientName; - @SerializedName("authorization_challenge_endpoint") - private String authorizationChallengeEndpoint; - @SerializedName("end_session_endpoint") - private String endSessionEndpoint; - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public String getClientName() { - return clientName; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getAuthorizationChallengeEndpoint() { - return authorizationChallengeEndpoint; - } - - public void setAuthorizationChallengeEndpoint(String authorizationChallengeEndpoint) { - this.authorizationChallengeEndpoint = authorizationChallengeEndpoint; - } - - public String getEndSessionEndpoint() { - return endSessionEndpoint; - } - - public void setEndSessionEndpoint(String endSessionEndpoint) { - this.endSessionEndpoint = endSessionEndpoint; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/JSONWebKeySet.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/JSONWebKeySet.java deleted file mode 100644 index e719110faa0..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/JSONWebKeySet.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.jans.chip.modal; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class JSONWebKeySet { - - public JSONWebKeySet(List> keys) { - this.keys = keys; - } - - private List> keys; - - public JSONWebKeySet() { - keys = new ArrayList<>(); - } - - public List> getKeys() { - return keys; - } - - public void setKeys(List> keys) { - this.keys = keys; - } - - public void addKey(Map key){ - keys.add(key); - } - - public String toJsonString() { - ObjectMapper mapper = new ObjectMapper(); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LoginResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LoginResponse.java deleted file mode 100644 index 9ff357d946f..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LoginResponse.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.jans.chip.modal; - -import com.google.gson.annotations.SerializedName; - -public class LoginResponse { - public LoginResponse() { - } - - public LoginResponse(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - - public LoginResponse(String authorizationCode) { - this.authorizationCode = authorizationCode; - } - - @SerializedName("authorization_code") - private String authorizationCode; - private boolean isSuccessful; - private OperationError operationError; - - public String getAuthorizationCode() { - return authorizationCode; - } - - public void setAuthorizationCode(String authorizationCode) { - this.authorizationCode = authorizationCode; - } - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LogoutResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LogoutResponse.java deleted file mode 100644 index b4a338b469b..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/LogoutResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.jans.chip.modal; - -public class LogoutResponse { - private boolean isSuccessful; - private OperationError operationError; - - public LogoutResponse() { - } - - public LogoutResponse(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OIDCClient.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OIDCClient.java deleted file mode 100644 index 0a270e7ee0b..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OIDCClient.java +++ /dev/null @@ -1,147 +0,0 @@ -package io.jans.chip.modal; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.PrimaryKey; - -import com.google.gson.annotations.SerializedName; - -@Entity(tableName = "OIDC_CLIENT") -public class OIDCClient { - public OIDCClient() { - } - - public OIDCClient(String sno, - String clientName, - String clientId, - String clientSecret, - String recentGeneratedIdToken, - String recentGeneratedAccessToken, - String scope, - boolean isSuccessful) { - this.sno = sno; - this.clientName = clientName; - this.clientId = clientId; - this.clientSecret = clientSecret; - this.recentGeneratedIdToken = recentGeneratedIdToken; - this.recentGeneratedAccessToken = recentGeneratedAccessToken; - this.scope = scope; - this.isSuccessful = isSuccessful; - } - - public OIDCClient(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - @NonNull - @PrimaryKey - @SerializedName("SNO") - private String sno; - @ColumnInfo(name = "CLIENT_NAME") - @SerializedName("CLIENT_NAME") - private String clientName; - @ColumnInfo(name = "CLIENT_ID") - @SerializedName("CLIENT_ID") - private String clientId; - @ColumnInfo(name = "CLIENT_SECRET") - @SerializedName("CLIENT_SECRET") - private String clientSecret; - @ColumnInfo(name = "RECENT_GENERATED_ID_TOKEN") - @SerializedName("RECENT_GENERATED_ID_TOKEN") - private String recentGeneratedIdToken; - @ColumnInfo(name = "RECENT_GENERATED_ACCESS_TOKEN") - @SerializedName("RECENT_GENERATED_ACCESS_TOKEN") - private String recentGeneratedAccessToken; - @SerializedName("scope") - private String scope; - @Ignore - private boolean isSuccessful; - @Ignore - private OperationError operationError; - - public String getRecentGeneratedAccessToken() { - return recentGeneratedAccessToken; - } - - public void setRecentGeneratedAccessToken(String recentGeneratedAccessToken) { - this.recentGeneratedAccessToken = recentGeneratedAccessToken; - } - - public String getRecentGeneratedIdToken() { - return recentGeneratedIdToken; - } - - public void setRecentGeneratedIdToken(String recentGeneratedIdToken) { - this.recentGeneratedIdToken = recentGeneratedIdToken; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getSno() { - return sno; - } - - public void setSno(String sno) { - this.sno = sno; - } - - public String getClientName() { - return clientName; - } - - public void setClientName(String clientName) { - this.clientName = clientName; - } - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public void setClientSecret(String clientSecret) { - this.clientSecret = clientSecret; - } - - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } - - @Override - public String toString() { - return "OIDCClient{" + - "sno='" + sno + '\'' + - ", clientName='" + clientName + '\'' + - ", clientId='" + clientId + '\'' + - ", clientSecret='" + clientSecret + '\'' + - ", recentGeneratedIdToken='" + recentGeneratedIdToken + '\'' + - ", scope='" + scope + '\'' + - '}'; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OPConfiguration.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OPConfiguration.java deleted file mode 100644 index eaad9e50f92..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OPConfiguration.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.jans.chip.modal; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.PrimaryKey; - -import com.google.gson.annotations.SerializedName; - -import io.jans.chip.modal.OperationError; -@Entity(tableName = "OP_CONFIGURATION") -public class OPConfiguration { - public OPConfiguration() { - } - - public OPConfiguration(String sno, - String issuer, - String registrationEndpoint, - String tokenEndpoint, - String userinfoEndpoint, - String authorizationChallengeEndpoint, - String revocationEndpoint, - boolean isSuccessful) { - this.sno = sno; - this.issuer = issuer; - this.registrationEndpoint = registrationEndpoint; - this.tokenEndpoint = tokenEndpoint; - this.userinfoEndpoint = userinfoEndpoint; - this.authorizationChallengeEndpoint = authorizationChallengeEndpoint; - this.revocationEndpoint = revocationEndpoint; - this.isSuccessful = isSuccessful; - } - - public OPConfiguration(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - @NonNull - @PrimaryKey - @SerializedName("SNO") - private String sno; - @ColumnInfo(name = "ISSUER") - @SerializedName("issuer") - private String issuer; - @ColumnInfo(name = "REGISTRATION_ENDPOINT") - @SerializedName("registration_endpoint") - private String registrationEndpoint; - @ColumnInfo(name = "TOKEN_ENDPOINT") - @SerializedName("token_endpoint") - private String tokenEndpoint; - @ColumnInfo(name = "USERINFO_ENDPOINT") - @SerializedName("userinfo_endpoint") - private String userinfoEndpoint; - @ColumnInfo(name = "AUTHORIZATION_CHALLENGE_ENDPOINT") - @SerializedName("authorization_challenge_endpoint") - private String authorizationChallengeEndpoint; - @ColumnInfo(name = "REVOCATION_ENDPOINT") - @SerializedName("revocation_endpoint") - private String revocationEndpoint; - @Ignore - private boolean isSuccessful; - @Ignore - private OperationError operationError; - public String getRevocationEndpoint() { - return revocationEndpoint; - } - - public void setRevocationEndpoint(String revocationEndpoint) { - this.revocationEndpoint = revocationEndpoint; - } - - public String getIssuer() { - return issuer; - } - - public void setIssuer(String issuer) { - this.issuer = issuer; - } - - public String getAuthorizationChallengeEndpoint() { - return authorizationChallengeEndpoint; - } - public void setAuthorizationChallengeEndpoint(String authorizationChallengeEndpoint) { - this.authorizationChallengeEndpoint = authorizationChallengeEndpoint; - } - - public String getRegistrationEndpoint() { - return registrationEndpoint; - } - - public void setRegistrationEndpoint(String registrationEndpoint) { - this.registrationEndpoint = registrationEndpoint; - } - - public String getTokenEndpoint() { - return tokenEndpoint; - } - - public void setTokenEndpoint(String tokenEndpoint) { - this.tokenEndpoint = tokenEndpoint; - } - - public String getUserinfoEndpoint() { - return userinfoEndpoint; - } - - public void setUserinfoEndpoint(String userinfoEndpoint) { - this.userinfoEndpoint = userinfoEndpoint; - } - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } - - @NonNull - public String getSno() { - return sno; - } - - public void setSno(@NonNull String sno) { - this.sno = sno; - } - - @Override - public String toString() { - return "OPConfiguration{" + - "issuer='" + issuer + '\'' + - ", registrationEndpoint='" + registrationEndpoint + '\'' + - ", tokenEndpoint='" + tokenEndpoint + '\'' + - ", userinfoEndpoint='" + userinfoEndpoint + '\'' + - ", authorizationChallengeEndpoint='" + authorizationChallengeEndpoint + '\'' + - '}'; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OperationError.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OperationError.java deleted file mode 100644 index 4e3685729e7..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/OperationError.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.jans.chip.modal; - -import com.google.common.base.Preconditions; - -public class OperationError { - private String title; - private String message; - - public String getTitle() { - return title; - } - - public String getMessage() { - return message; - } - - public OperationError(Builder builder) { - this.title = builder.title; - this.message = builder.message; - } - public static class Builder { - private String title; - private String message; - public Builder() { - } - public Builder title(String title) { - this.title = title; - return this; - } - public Builder message(String message) { - this.message = message; - return this; - } - public OperationError build() { - return new OperationError(this); - } - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/SingleLiveEvent.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/SingleLiveEvent.java deleted file mode 100644 index 168f8fc7632..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/SingleLiveEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.jans.chip.modal; - -import android.support.annotation.MainThread; -import android.support.annotation.Nullable; -import android.util.Log; - -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A lifecycle-aware observable that sends only new updates after subscription, used for events like - * navigation and Snackbar messages. - *

- * This avoids a common problem with events: on configuration change (like rotation) an update - * can be emitted if the observer is active. This LiveData only calls the observable if there's an - * explicit call to setValue() or call(). - *

- * Note that only one observer is going to be notified of changes. - */ -public class SingleLiveEvent extends MutableLiveData { - - private static final String TAG = "SingleLiveEvent"; - - private final AtomicBoolean mPending = new AtomicBoolean(false); - - @MainThread - public void observe(LifecycleOwner owner, final Observer observer) { - - if (hasActiveObservers()) { - Log.w(TAG, "Multiple observers registered but only one will be notified of changes."); - } - - // Observe the internal MutableLiveData - super.observe(owner, new Observer() { - @Override - public void onChanged(@Nullable T t) { - if (mPending.compareAndSet(true, false)) { - observer.onChanged(t); - } - } - }); - } - - @MainThread - public void setValue(@Nullable T t) { - mPending.set(true); - super.setValue(t); - } - - /** - * Used for cases where T is Void, to make calls cleaner. - */ - @MainThread - public void call() { - setValue(null); - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/TokenResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/TokenResponse.java deleted file mode 100644 index 4b5332d0af1..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/TokenResponse.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.jans.chip.modal; - -import com.google.gson.annotations.SerializedName; - -public class TokenResponse { - public TokenResponse(String accessToken, - String idToken, - String tokenType) { - this.accessToken = accessToken; - this.idToken = idToken; - this.tokenType = tokenType; - } - - public TokenResponse() { - } - - public TokenResponse(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - - @SerializedName("access_token") - private String accessToken; - @SerializedName("id_token") - private String idToken; - @SerializedName("token_type") - private String tokenType; - private boolean isSuccessful; - private OperationError operationError; - - public String getTokenType() { - return tokenType; - } - - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public String getIdToken() { - return idToken; - } - - public void setIdToken(String idToken) { - this.idToken = idToken; - } - - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/UserInfoResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/UserInfoResponse.java deleted file mode 100644 index d048f3d6b91..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/UserInfoResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.jans.chip.modal; - -import com.fasterxml.jackson.databind.JsonNode; - -import org.json.JSONObject; - -public class UserInfoResponse { - Object reponse; - private boolean isSuccessful; - private OperationError operationError; - - public UserInfoResponse(Object reponse) { - this.reponse = reponse; - } - - public UserInfoResponse(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - - public UserInfoResponse() { - } - - public Object getReponse() { - return reponse; - } - - public void setReponse(Object reponse) { - this.reponse = reponse; - } - - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AccountDetails.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AccountDetails.java deleted file mode 100644 index 430ad6fdadd..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AccountDetails.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -public class AccountDetails{ - public String appLicensingVerdict; - - public String getAppLicensingVerdict() { - return appLicensingVerdict; - } - - public void setAppLicensingVerdict(String appLicensingVerdict) { - this.appLicensingVerdict = appLicensingVerdict; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrity.java deleted file mode 100644 index ceab812c00a..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrity.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -public class AppIntegrity{ - private String appRecognitionVerdict; - - public String getAppRecognitionVerdict() { - return appRecognitionVerdict; - } - - public void setAppRecognitionVerdict(String appRecognitionVerdict) { - this.appRecognitionVerdict = appRecognitionVerdict; - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityEntity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityEntity.java deleted file mode 100644 index d29c2f74503..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityEntity.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -import androidx.annotation.NonNull; -import androidx.room.ColumnInfo; -import androidx.room.Entity; -import androidx.room.PrimaryKey; - -import com.google.gson.annotations.SerializedName; - -@Entity(tableName = "APP_INTEGRITY") -public class AppIntegrityEntity { - @NonNull - @PrimaryKey - @SerializedName("SNO") - private String sno; - @ColumnInfo(name = "APP_INTEGRITY") - private String appIntegrity; - @ColumnInfo(name = "DEVICE_INTEGRITY") - private String deviceIntegrity; - @ColumnInfo(name = "APP_LICENSING_VERDICT") - private String appLicensingVerdict; - @ColumnInfo(name = "REQUEST_PACKAGE_NAME") - private String requestPackageName; - @ColumnInfo(name = "NONCE") - private String nonce; - @ColumnInfo(name = "ERROR") - private String error; - - public AppIntegrityEntity(String sno, String appIntegrity, String deviceIntegrity, String appLicensingVerdict, String requestPackageName, String nonce, String error) { - this.sno = sno; - this.appIntegrity = appIntegrity; - this.deviceIntegrity = deviceIntegrity; - this.appLicensingVerdict = appLicensingVerdict; - this.requestPackageName = requestPackageName; - this.nonce = nonce; - this.error = error; - } - - public AppIntegrityEntity() { - } - - @NonNull - public String getSno() { - return sno; - } - - public void setSno(@NonNull String sno) { - this.sno = sno; - } - - public String getAppIntegrity() { - return appIntegrity; - } - - public void setAppIntegrity(String appIntegrity) { - this.appIntegrity = appIntegrity; - } - - public String getDeviceIntegrity() { - return deviceIntegrity; - } - - public void setDeviceIntegrity(String deviceIntegrity) { - this.deviceIntegrity = deviceIntegrity; - } - - public String getAppLicensingVerdict() { - return appLicensingVerdict; - } - - public void setAppLicensingVerdict(String appLicensingVerdict) { - this.appLicensingVerdict = appLicensingVerdict; - } - - public String getRequestPackageName() { - return requestPackageName; - } - - public void setRequestPackageName(String requestPackageName) { - this.requestPackageName = requestPackageName; - } - - public String getNonce() { - return nonce; - } - - public void setNonce(String nonce) { - this.nonce = nonce; - } - - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityResponse.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityResponse.java deleted file mode 100644 index 1e8520eda78..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/AppIntegrityResponse.java +++ /dev/null @@ -1,92 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -import com.google.gson.annotations.SerializedName; - -import io.jans.chip.modal.OperationError; - -public class AppIntegrityResponse { - @SerializedName("requestDetails") - private RequestDetails requestDetails; - @SerializedName("appIntegrity") - private AppIntegrity appIntegrity; - @SerializedName("deviceIntegrity") - private DeviceIntegrity deviceIntegrity; - @SerializedName("accountDetails") - private AccountDetails accountDetails; - @SerializedName("error") - private String error; - private boolean isSuccessful; - private OperationError operationError; - - public AppIntegrityResponse(RequestDetails requestDetails, AppIntegrity appIntegrity, DeviceIntegrity deviceIntegrity, AccountDetails accountDetails, String error) { - this.requestDetails = requestDetails; - this.appIntegrity = appIntegrity; - this.deviceIntegrity = deviceIntegrity; - this.accountDetails = accountDetails; - this.error = error; - } - - public AppIntegrityResponse(boolean isSuccessful, OperationError operationError) { - this.isSuccessful = isSuccessful; - this.operationError = operationError; - } - - public AppIntegrityResponse() { - } - - public RequestDetails getRequestDetails() { - return requestDetails; - } - - public void setRequestDetails(RequestDetails requestDetails) { - this.requestDetails = requestDetails; - } - - public AppIntegrity getAppIntegrity() { - return appIntegrity; - } - - public void setAppIntegrity(AppIntegrity appIntegrity) { - this.appIntegrity = appIntegrity; - } - - public DeviceIntegrity getDeviceIntegrity() { - return deviceIntegrity; - } - - public void setDeviceIntegrity(DeviceIntegrity deviceIntegrity) { - this.deviceIntegrity = deviceIntegrity; - } - - public AccountDetails getAccountDetails() { - return accountDetails; - } - - public void setAccountDetails(AccountDetails accountDetails) { - this.accountDetails = accountDetails; - } - - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - public boolean isSuccessful() { - return isSuccessful; - } - - public void setSuccessful(boolean successful) { - isSuccessful = successful; - } - - public OperationError getOperationError() { - return operationError; - } - - public void setOperationError(OperationError operationError) { - this.operationError = operationError; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/DeviceIntegrity.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/DeviceIntegrity.java deleted file mode 100644 index 3ffa2929b6b..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/DeviceIntegrity.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -import java.util.List; - -public class DeviceIntegrity{ - private List deviceRecognitionVerdict; - - public List getDeviceRecognitionVerdict() { - return deviceRecognitionVerdict; - } - - public void setDeviceRecognitionVerdict(List deviceRecognitionVerdict) { - this.deviceRecognitionVerdict = deviceRecognitionVerdict; - } - - public String commasSeparatedString(){ - if(this.deviceRecognitionVerdict != null) { - return String.join(",", this.deviceRecognitionVerdict); - } - return null; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/RequestDetails.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/RequestDetails.java deleted file mode 100644 index a6241051b90..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modal/appIntegrity/RequestDetails.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.jans.chip.modal.appIntegrity; - -public class RequestDetails{ - private String requestPackageName; - private String timestampMillis; - private String nonce; - - public String getRequestPackageName() { - return requestPackageName; - } - - public void setRequestPackageName(String requestPackageName) { - this.requestPackageName = requestPackageName; - } - - public String getTimestampMillis() { - return timestampMillis; - } - - public void setTimestampMillis(String timestampMillis) { - this.timestampMillis = timestampMillis; - } - - public String getNonce() { - return nonce; - } - - public void setNonce(String nonce) { - this.nonce = nonce; - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCRequest.kt new file mode 100644 index 00000000000..9ce6a20a5c1 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCRequest.kt @@ -0,0 +1,38 @@ +package io.jans.chip.model + +import com.google.gson.annotations.SerializedName + +data class DCRequest ( + @SerializedName("issuer") + var issuer: String? = null, + + @SerializedName("redirect_uris") + var redirectUris: List, + + @SerializedName("scope") + var scope: String? = null, + + @SerializedName("response_types") + var responseTypes: List, + + @SerializedName("post_logout_redirect_uris") + var postLogoutRedirectUris: List, + + @SerializedName("grant_types") + var grantTypes: List, + + @SerializedName("application_type") + var applicationType: String? = null, + + @SerializedName("client_name") + var clientName: String? = null, + + @SerializedName("token_endpoint_auth_method") + var tokenEndpointAuthMethod: String? = null, + + @SerializedName("evidence") + var evidence: String? = null, + + @SerializedName("jwks") + var jwks: String? = null, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCResponse.kt new file mode 100644 index 00000000000..cbb21c09dea --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/DCResponse.kt @@ -0,0 +1,21 @@ +package io.jans.chip.model + +import com.google.gson.annotations.SerializedName + +data class DCResponse ( + @SerializedName("client_id") + val clientId: String? = null, + + @SerializedName("client_secret") + val clientSecret: String? = null, + + @SerializedName("client_name") + val clientName: String? = null, + + @SerializedName("authorization_challenge_endpoint") + val authorizationChallengeEndpoint: String? = null, + + @SerializedName("end_session_endpoint") + val endSessionEndpoint: String? = null, + +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/FidoResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/FidoResponse.kt new file mode 100644 index 00000000000..b691906f281 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/FidoResponse.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model + +import androidx.room.Ignore + +class FidoResponse { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/ItemModel.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/ItemModel.kt new file mode 100644 index 00000000000..af7f7521e20 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/ItemModel.kt @@ -0,0 +1,6 @@ +package io.jans.chip.model + +data class ItemModel( + val title: String, + val selected: Boolean +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/JSONWebKeySet.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/JSONWebKeySet.kt new file mode 100644 index 00000000000..d812050f2bb --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/JSONWebKeySet.kt @@ -0,0 +1,19 @@ +package io.jans.chip.model + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.databind.ObjectMapper + +class JSONWebKeySet { + private var keys: MutableList?> = mutableListOf() + fun addKey(key: Map?) { + keys.add(key) + } + fun toJsonString(): String? { + val mapper = ObjectMapper() + return try { + mapper.writeValueAsString(this) + } catch (e: JsonProcessingException) { + throw RuntimeException(e) + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LoginResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LoginResponse.kt new file mode 100644 index 00000000000..2cc948d2a5d --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LoginResponse.kt @@ -0,0 +1,15 @@ +package io.jans.chip.model + +import androidx.room.Ignore +import com.google.gson.annotations.SerializedName + +data class LoginResponse ( + @SerializedName("authorization_code") + var authorizationCode: String? +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LogoutResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LogoutResponse.kt new file mode 100644 index 00000000000..1a1297a8ade --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/LogoutResponse.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model + +import androidx.room.Ignore + +class LogoutResponse() { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OIDCClient.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OIDCClient.kt new file mode 100644 index 00000000000..9fbcd22fcaf --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OIDCClient.kt @@ -0,0 +1,47 @@ +package io.jans.chip.model + +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "OIDC_CLIENT") +data class OIDCClient( + @NonNull + @PrimaryKey(autoGenerate = false) + @SerializedName("SNO") + var sno: String, + + @ColumnInfo(name = "CLIENT_NAME") + @SerializedName("CLIENT_NAME") + var clientName: String?, + + @ColumnInfo(name = "CLIENT_ID") + @SerializedName("CLIENT_ID") + var clientId: String?, + + @ColumnInfo(name = "CLIENT_SECRET") + @SerializedName("CLIENT_SECRET") + var clientSecret: String?, + + @ColumnInfo(name = "RECENT_GENERATED_ID_TOKEN") + @SerializedName("RECENT_GENERATED_ID_TOKEN") + var recentGeneratedIdToken: String?, + + @ColumnInfo(name = "RECENT_GENERATED_ACCESS_TOKEN") + @SerializedName("RECENT_GENERATED_ACCESS_TOKEN") + var recentGeneratedAccessToken: String?, + + @SerializedName("scope") + var scope: String?, + + +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OPConfiguration.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OPConfiguration.kt new file mode 100644 index 00000000000..f23c7d70146 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/OPConfiguration.kt @@ -0,0 +1,47 @@ +package io.jans.chip.model + +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "OP_CONFIGURATION") +data class OPConfiguration( + @NonNull + @PrimaryKey(autoGenerate = false) + @SerializedName("SNO") + var sno: String, + @ColumnInfo(name = "ISSUER") + @SerializedName("issuer") + var issuer: String?, + @ColumnInfo(name = "REGISTRATION_ENDPOINT") + @SerializedName("registration_endpoint") + var registrationEndpoint: String?, + @ColumnInfo(name = "TOKEN_ENDPOINT") + @SerializedName("token_endpoint") + var tokenEndpoint: String?, + @ColumnInfo(name = "USERINFO_ENDPOINT") + @SerializedName("userinfo_endpoint") + var userinfoEndpoint: String?, + @ColumnInfo(name = "AUTHORIZATION_CHALLENGE_ENDPOINT") + @SerializedName("authorization_challenge_endpoint") + var authorizationChallengeEndpoint: String?, + @ColumnInfo(name = "REVOCATION_ENDPOINT") + @SerializedName("revocation_endpoint") + var revocationEndpoint: String?, + @ColumnInfo(name = "BIOMETRIC_ENROLLED") + @SerializedName("biometric_enrolled") + var biometricEnrolled: Boolean = false, + +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null + + @ColumnInfo(name = "FIDO_URL") + var fidoUrl: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/SSARegRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/SSARegRequest.kt new file mode 100644 index 00000000000..a2c255403e4 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/SSARegRequest.kt @@ -0,0 +1,32 @@ +package io.jans.chip.model + +import com.google.gson.annotations.SerializedName + +data class SSARegRequest ( + @SerializedName("client_name") + var clientName: String? = null, + + @SerializedName("evidence") + var evidence: String? = null, + + @SerializedName("jwks") + var jwks: String? = null, + + @SerializedName("scope") + var scope: String? = null, + + @SerializedName("response_types") + var responseTypes: List, + + @SerializedName("grant_types") + var grantTypes: List, + + @SerializedName("software_statement") + var ssa: String? = null, + + @SerializedName("application_type") + var applicationType: String? = null, + + @SerializedName("redirect_uris") + var redirectUris: List, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/TokenResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/TokenResponse.kt new file mode 100644 index 00000000000..11f237657ee --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/TokenResponse.kt @@ -0,0 +1,20 @@ +package io.jans.chip.model + +import androidx.room.Ignore +import com.google.gson.annotations.SerializedName + +data class TokenResponse ( + + @SerializedName("access_token") + var accessToken: String? = null, + @SerializedName("id_token") + var idToken: String? = null, + @SerializedName("token_type") + var tokenType: String? = null +){ + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/UserInfoResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/UserInfoResponse.kt new file mode 100644 index 00000000000..890b1532a21 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/UserInfoResponse.kt @@ -0,0 +1,13 @@ +package io.jans.chip.model + +import androidx.room.Ignore + +class UserInfoResponse ( + var response: Any? = null +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AccountDetails.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AccountDetails.kt new file mode 100644 index 00000000000..724630eaad1 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AccountDetails.kt @@ -0,0 +1,5 @@ +package io.jans.chip.model.appIntegrity + +data class AccountDetails ( + var appLicensingVerdict: String? = null +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrity.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrity.kt new file mode 100644 index 00000000000..30b187663ba --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrity.kt @@ -0,0 +1,5 @@ +package io.jans.chip.model.appIntegrity + +data class AppIntegrity ( + var appRecognitionVerdict: String? = null +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityEntity.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityEntity.kt new file mode 100644 index 00000000000..81924bbfedc --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityEntity.kt @@ -0,0 +1,33 @@ +package io.jans.chip.model.appIntegrity + +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "APP_INTEGRITY") +data class AppIntegrityEntity( + @NonNull + @PrimaryKey + @SerializedName("SNO") + var sno: String, + + @ColumnInfo(name = "APP_INTEGRITY") + var appIntegrity: String? = null, + + @ColumnInfo(name = "DEVICE_INTEGRITY") + var deviceIntegrity: String? = null, + + @ColumnInfo(name = "APP_LICENSING_VERDICT") + var appLicensingVerdict: String? = null, + + @ColumnInfo(name = "REQUEST_PACKAGE_NAME") + var requestPackageName: String? = null, + + @ColumnInfo(name = "NONCE") + var nonce: String? = null, + + @ColumnInfo(name = "ERROR") + var error: String? = null, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityResponse.kt new file mode 100644 index 00000000000..27778d75b00 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/AppIntegrityResponse.kt @@ -0,0 +1,27 @@ +package io.jans.chip.model.appIntegrity + +import androidx.room.Ignore +import com.google.gson.annotations.SerializedName + +data class AppIntegrityResponse( + @SerializedName("requestDetails") + var requestDetails: RequestDetails? = null, + + @SerializedName("appIntegrity") + var appIntegrity: AppIntegrity? = null, + + @SerializedName("deviceIntegrity") + var deviceIntegrity: DeviceIntegrity? = null, + + @SerializedName("accountDetails") + var accountDetails: AccountDetails? = null, + + @SerializedName("error") + var error: String? = null +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/DeviceIntegrity.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/DeviceIntegrity.kt new file mode 100644 index 00000000000..283d573e0ad --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/DeviceIntegrity.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model.appIntegrity + +data class DeviceIntegrity ( + var deviceRecognitionVerdict: List? = null +) { + fun commasSeparatedString(): String? { + return if (deviceRecognitionVerdict != null) { + java.lang.String.join(",", deviceRecognitionVerdict) + } else null + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/RequestDetails.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/RequestDetails.kt new file mode 100644 index 00000000000..998c71c63d5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/appIntegrity/RequestDetails.kt @@ -0,0 +1,7 @@ +package io.jans.chip.model.appIntegrity + +data class RequestDetails( + var requestPackageName: String? = null, + var timestampMillis: String? = null, + var nonce: String? = null, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AllowCredentials.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AllowCredentials.kt new file mode 100644 index 00000000000..3c94d844a79 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AllowCredentials.kt @@ -0,0 +1,7 @@ +package io.jans.chip.model.fido.assertion.option + +data class AllowCredentials ( + val id: String, + val type: String, + val transports: List, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionRequest.kt new file mode 100644 index 00000000000..1d0597c4116 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionRequest.kt @@ -0,0 +1,10 @@ +package io.jans.chip.model.fido.assertion.option + +data class AssertionOptionRequest( + var username: String?, + var userVerification: String?, + var documentDomain: String?, + var extensions: String?, + var session_id: String?, + var description: String?, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionResponse.kt new file mode 100644 index 00000000000..14bbe1f4314 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/option/AssertionOptionResponse.kt @@ -0,0 +1,22 @@ +package io.jans.chip.model.fido.assertion.option + +import androidx.room.Ignore + +data class AssertionOptionResponse( + var challenge: String?, + + var user: String?, + + var userVerification: String?, + + var rpId: String?, + + var status: String?, + + var errorMessage: String?, + + var allowCredentials: List?, +) { + @Ignore + var isSuccessful: Boolean? = true +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultRequest.kt new file mode 100644 index 00000000000..98f2bd86587 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultRequest.kt @@ -0,0 +1,19 @@ +package io.jans.chip.model.fido.assertion.result + +import androidx.room.Ignore + +class AssertionResultRequest { + var id: String? = null + + var type: String? = null + + var rawId: String? = null + + var response: Response? = null + + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultResponse.kt new file mode 100644 index 00000000000..2c820236bd2 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/AssertionResultResponse.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model.fido.assertion.result + +import androidx.room.Ignore + +class AssertionResultResponse { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/ClientDataJSON.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/ClientDataJSON.kt new file mode 100644 index 00000000000..5963f308857 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/ClientDataJSON.kt @@ -0,0 +1,9 @@ +package io.jans.chip.model.fido.assertion.result + +data class ClientDataJSON( + var type: String, + + val origin: String, + + val challenge: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/Response.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/Response.kt new file mode 100644 index 00000000000..ddb0ffeaa84 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/assertion/result/Response.kt @@ -0,0 +1,18 @@ +package io.jans.chip.model.fido.assertion.result + +import androidx.room.Ignore + +class Response { + var authenticatorData: String? = null + // ClientDataJSON clientDataJSON; + + var clientDataJSON: String? = null + + var signature: String? = null + + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionRequest.kt new file mode 100644 index 00000000000..4d538a93974 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionRequest.kt @@ -0,0 +1,9 @@ +package io.jans.chip.model.fido.attestation.option + +data class AttestationOptionRequest( + var username: String, + + var displayName: String, + + var attestation: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionResponse.kt new file mode 100644 index 00000000000..e1690d8f861 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AttestationOptionResponse.kt @@ -0,0 +1,18 @@ +package io.jans.chip.model.fido.attestation.option + +import androidx.room.Ignore + +class AttestationOptionResponse ( + var attestation: String?, + var authenticatorSelection: AuthenticatorSelection?, + var challenge: String?, + var pubKeyCredParams: List?, + var rp: RP?, + var user: User?, +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AuthenticatorSelection.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AuthenticatorSelection.kt new file mode 100644 index 00000000000..5cd8ef94ea2 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/AuthenticatorSelection.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model.fido.attestation.option + +data class AuthenticatorSelection( + var authenticatorAttachment: String, + + var requireResidentKey: Boolean, + + var userVerification: String, + + var residentKey: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/PubKeyCredParam.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/PubKeyCredParam.kt new file mode 100644 index 00000000000..6c8844804db --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/PubKeyCredParam.kt @@ -0,0 +1,7 @@ +package io.jans.chip.model.fido.attestation.option + +data class PubKeyCredParam( + var type: String, + + val alg: Long = 0 +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/RP.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/RP.kt new file mode 100644 index 00000000000..a9ed6c0d441 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/RP.kt @@ -0,0 +1,7 @@ +package io.jans.chip.model.fido.attestation.option + +data class RP ( + var id: String, + + var name: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/User.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/User.kt new file mode 100644 index 00000000000..4df380bd177 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/option/User.kt @@ -0,0 +1,9 @@ +package io.jans.chip.model.fido.attestation.option + +data class User( + var id: String, + + val name: String, + + val displayName: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultRequest.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultRequest.kt new file mode 100644 index 00000000000..96a9e2f8e3e --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultRequest.kt @@ -0,0 +1,17 @@ +package io.jans.chip.model.fido.attestation.result + +import androidx.room.Ignore + +data class AttestationResultRequest( + var id: String?, + + var type: String?, + + var response: Response?, +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultResponse.kt new file mode 100644 index 00000000000..1bbad231db5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/AttestationResultResponse.kt @@ -0,0 +1,11 @@ +package io.jans.chip.model.fido.attestation.result + +import androidx.room.Ignore + +class AttestationResultResponse{ + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/ClientDataJSON.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/ClientDataJSON.kt new file mode 100644 index 00000000000..9acbfa3eedd --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/ClientDataJSON.kt @@ -0,0 +1,9 @@ +package io.jans.chip.model.fido.attestation.result + +data class ClientDataJSON( + var type: String, + + val origin: String, + + val challenge: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/CreatedCredentials.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/CreatedCredentials.kt new file mode 100644 index 00000000000..14330b7a176 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/CreatedCredentials.kt @@ -0,0 +1,21 @@ +package io.jans.chip.model.fido.attestation.result + +data class CreatedCredentials( + var createdDate: String, + + val updatedDate: String, + + val createdBy: String, + + val updatedBy: String, + + val username: String, + + val domain: String, + + val userId: String, + + val challenge: String, + + val attestationRequest: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/Response.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/Response.kt new file mode 100644 index 00000000000..f2eb5fe90ac --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/attestation/result/Response.kt @@ -0,0 +1,7 @@ +package io.jans.chip.model.fido.attestation.result + +data class Response( + var attestationObject: String, + + val clientDataJSON: String, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Assertion.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Assertion.kt new file mode 100644 index 00000000000..705d9a16454 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Assertion.kt @@ -0,0 +1,22 @@ +package io.jans.chip.model.fido.config + +import androidx.room.ColumnInfo +import com.google.gson.annotations.SerializedName + +data class Assertion( + @ColumnInfo(name = "BASE_PATH") + @SerializedName("base_path") + var basePath: String?, + + @ColumnInfo(name = "OPTIONS_ENDPOINT") + @SerializedName("options_endpoint") + var optionsEndpoint: String?, + + @ColumnInfo(name = "OPTIONS_GENERATE_ENDPOINT") + @SerializedName("options_generate_endpoint") + var optionsGenerateEndpoint: String?, + + @ColumnInfo(name = "RESULT_ENDPOINT") + @SerializedName("result_endpoint") + var resultEndpoint: String?, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Attestation.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Attestation.kt new file mode 100644 index 00000000000..b89f9200e01 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/Attestation.kt @@ -0,0 +1,18 @@ +package io.jans.chip.model.fido.config + +import androidx.room.ColumnInfo +import com.google.gson.annotations.SerializedName + +data class Attestation( + @ColumnInfo(name = "BASE_PATH") + @SerializedName("base_path") + var basePath: String?, + + @ColumnInfo(name = "OPTIONS_ENDPOINT") + @SerializedName("options_endpoint") + var optionsEndpoint: String?, + + @ColumnInfo(name = "RESULT_ENDPOINT") + @SerializedName("result_endpoint") + var resultEndpoint: String?, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfiguration.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfiguration.kt new file mode 100644 index 00000000000..1f683901bc7 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfiguration.kt @@ -0,0 +1,29 @@ +package io.jans.chip.model.fido.config + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.google.gson.annotations.SerializedName + +@Entity(tableName = "FIDO_CONFIGURATION") +data class FidoConfiguration( + @PrimaryKey + @SerializedName("SNO") + var sno: String , + + @ColumnInfo(name = "ISSUER") + @SerializedName("issuer") + var issuer: String? = null, + + @ColumnInfo(name = "ATTESTATION_OPTIONS_ENDPOINT") + var attestationOptionsEndpoint: String? = null, + + @ColumnInfo(name = "ATTESTATION_RESULT_ENDPOINT") + var attestationResultEndpoint: String? = null, + + @ColumnInfo(name = "ASSERTION_OPTIONS_ENDPOINT") + var assertionOptionsEndpoint: String? = null, + + @ColumnInfo(name = "ASSERTION_RESULT_ENDPOINT") + var assertionResultEndpoint: String? = null, +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfigurationResponse.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfigurationResponse.kt new file mode 100644 index 00000000000..d1853145fb5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/model/fido/config/FidoConfigurationResponse.kt @@ -0,0 +1,21 @@ +package io.jans.chip.model.fido.config + +import androidx.room.Ignore +import com.google.gson.annotations.SerializedName + +data class FidoConfigurationResponse( + @SerializedName("issuer") + var issuer: String? = null, + + @SerializedName("attestation") + var attestation: Attestation? = null, + + @SerializedName("assertion") + var assertion: Assertion? = null, +) { + @Ignore + var isSuccessful: Boolean? = true + + @Ignore + var errorMessage: String? = null +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/DCRViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/DCRViewModel.java deleted file mode 100644 index 8493fa6313f..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/DCRViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.repository.DCRRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class DCRViewModel extends ViewModel { - DCRRepository dcrRepository; - public DCRViewModel(Context context) { - dcrRepository = DCRRepository.getInstance(context); - } - public SingleLiveEvent doDCR(String scopeText) { - return dcrRepository.doDCR(scopeText); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LoginViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LoginViewModel.java deleted file mode 100644 index 2ac41c15f47..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LoginViewModel.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.LoginResponse; -import io.jans.chip.repository.LoginResponseRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class LoginViewModel extends ViewModel { - LoginResponseRepository loginResponseRepository; - - public LoginViewModel(Context context) { - loginResponseRepository = LoginResponseRepository.getInstance(context); - } - - public SingleLiveEvent processlogin(String usernameText, String passwordText) { - return loginResponseRepository.processlogin(usernameText, passwordText); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LogoutViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LogoutViewModel.java deleted file mode 100644 index cd820b6327d..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/LogoutViewModel.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.LogoutResponse; -import io.jans.chip.repository.LogoutRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class LogoutViewModel extends ViewModel { - LogoutRepository logoutRepository; - - public LogoutViewModel(Context context) { - logoutRepository = LogoutRepository.getInstance(context); - } - - public SingleLiveEvent logout() { - return logoutRepository.logout(); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/OPConfigurationViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/OPConfigurationViewModel.java deleted file mode 100644 index f4976d6705d..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/OPConfigurationViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.repository.OPConfigurationRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class OPConfigurationViewModel extends ViewModel { - OPConfigurationRepository opConfigurationRepository; - public OPConfigurationViewModel(Context context) { - opConfigurationRepository = OPConfigurationRepository.getInstance(context); - } - public SingleLiveEvent fetchOPConfiguration(String configurationUrl) { - return opConfigurationRepository.fetchOPConfiguration(configurationUrl); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/PlayIntegrityViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/PlayIntegrityViewModel.java deleted file mode 100644 index 7dfba61577e..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/PlayIntegrityViewModel.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.appIntegrity.AppIntegrityResponse; -import io.jans.chip.repository.PlayIntegrityRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class PlayIntegrityViewModel extends ViewModel { - PlayIntegrityRepository playIntegrityRepository; - - public PlayIntegrityViewModel(Context context) { - playIntegrityRepository = PlayIntegrityRepository.getInstance(context); - } - - public SingleLiveEvent checkAppIntegrity() { - return playIntegrityRepository.checkAppIntegrity(); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/TokenViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/TokenViewModel.java deleted file mode 100644 index c80d57eec58..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/TokenViewModel.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.TokenResponse; -import io.jans.chip.repository.TokenResponseRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class TokenViewModel extends ViewModel { - TokenResponseRepository tokenResponseRepository; - - public TokenViewModel(Context context) { - tokenResponseRepository = TokenResponseRepository.getInstance(context); - } - - public SingleLiveEvent getToken(String authorizationCode, String usernameText, String passwordText) { - return tokenResponseRepository.getToken(authorizationCode, usernameText, passwordText); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/UserInfoViewModel.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/UserInfoViewModel.java deleted file mode 100644 index 62c7e86bb7d..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/modelview/UserInfoViewModel.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.jans.chip.modelview; - -import android.content.Context; - -import androidx.lifecycle.ViewModel; - -import io.jans.chip.modal.UserInfoResponse; -import io.jans.chip.repository.UserInfoResponseRepository; -import io.jans.chip.modal.SingleLiveEvent; - -public class UserInfoViewModel extends ViewModel { - UserInfoResponseRepository userInfoResponseRepository; - - public UserInfoViewModel(Context context) { - userInfoResponseRepository = UserInfoResponseRepository.getInstance(context); - } - - public SingleLiveEvent getUserInfo(String accessToken, boolean silentOnError) { - return userInfoResponseRepository.getUserInfo(accessToken, silentOnError); - } - - public SingleLiveEvent getUserInfo(String accessToken) { - return userInfoResponseRepository.getUserInfo(accessToken); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.java deleted file mode 100644 index 0c66c56b2bc..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.java +++ /dev/null @@ -1,157 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.util.Log; - -import com.google.common.collect.Lists; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import io.jans.chip.AppDatabase; -import io.jans.chip.factories.DPoPProofFactory; -import io.jans.chip.factories.KeyManager; -import io.jans.chip.modal.DCRequest; -import io.jans.chip.modal.DCResponse; -import io.jans.chip.modal.JSONWebKeySet; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import io.jans.chip.utils.AppConfig; -import io.jans.chip.utils.AppUtil; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class DCRRepository { - public static final String TAG = "DCRRepository"; - private final SingleLiveEvent oidcClientLiveData = new SingleLiveEvent<>(); - Context context; - - private static DCRRepository dcrRepository; - - AppDatabase appDatabase; - - public static DCRRepository getInstance(Context context) { - if (dcrRepository == null) { - dcrRepository = new DCRRepository(context); - } - return dcrRepository; - } - - private DCRRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - public SingleLiveEvent doDCR(String scopeText) { - - List opConfigurationList = appDatabase.opConfigurationDao().getAll(); - if (opConfigurationList == null || opConfigurationList.isEmpty()) { - oidcClientLiveData.setValue(setErrorInLiveObject("OpenID configuration not found in database.")); - return oidcClientLiveData; - } - OPConfiguration opConfiguration = opConfigurationList.get(0); - String issuer = opConfiguration.getIssuer(); - String registrationUrl = opConfiguration.getRegistrationEndpoint(); - - DCRequest dcrRequest = new DCRequest(); - dcrRequest.setIssuer(issuer); - dcrRequest.setClientName(AppConfig.APP_NAME + UUID.randomUUID()); - dcrRequest.setApplicationType("web"); - dcrRequest.setGrantTypes(Lists.newArrayList("authorization_code", "client_credentials")); - dcrRequest.setScope(scopeText); - dcrRequest.setRedirectUris(Lists.newArrayList(issuer)); - dcrRequest.setResponseTypes(Lists.newArrayList("code")); - dcrRequest.setTokenEndpointAuthMethod("client_secret_basic"); - dcrRequest.setPostLogoutRedirectUris(Lists.newArrayList(issuer)); - - Map claims = new HashMap<>(); - claims.put("appName", AppConfig.APP_NAME); - claims.put("seq", UUID.randomUUID()); - claims.put("app_id", context.getPackageName()); - String checksum = null; - try { - checksum = AppUtil.getChecksum(context); - claims.put("app_checksum", checksum); - } catch (IOException | NoSuchAlgorithmException | PackageManager.NameNotFoundException e) { - Log.d(TAG, "Error in generating app checksum.\n" + e.getMessage()); - oidcClientLiveData.setValue(setErrorInLiveObject("Error in generating app checksum.\n" + e.getMessage())); - return oidcClientLiveData; - } - List appIntegrityList = appDatabase.appIntegrityDao().getAll(); - if (appIntegrityList == null || appIntegrityList.isEmpty()) { - oidcClientLiveData.setValue(setErrorInLiveObject("App Integrity not found in database.")); - return oidcClientLiveData; - } - claims.put("app_integrity_result", appIntegrityList.get(0)); - try { - String evidenceJwt = DPoPProofFactory.getInstance().issueJWTToken(claims); - dcrRequest.setEvidence(evidenceJwt); - Log.d(TAG, "Inside doDCR :: evidence :: " + evidenceJwt); - } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | - NoSuchProviderException e) { - Log.e(TAG, "Error in generating DPoP jwt.\n" + e.getMessage()); - oidcClientLiveData.setValue(setErrorInLiveObject("Error in generating DPoP jwt.\n" + e.getMessage())); - return oidcClientLiveData; - } - - JSONWebKeySet jwks = new JSONWebKeySet(); - jwks.addKey(KeyManager.getPublicKeyJWK(KeyManager.getInstance().getPublicKey()).getRequiredParams()); - - dcrRequest.setJwks(jwks.toJsonString()); - Log.d(TAG, "Inside doDCR :: jwks :: " + jwks.toJsonString()); - - Call call = RetrofitClient.getInstance(issuer).getAPIInterface().doDCR(dcrRequest, registrationUrl); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - - DCResponse responseFromAPI = response.body(); - if (response.code() == 200 || response.code() == 201) { - if (responseFromAPI.getClientId() != null && !responseFromAPI.getClientId().isEmpty()) { - OIDCClient client = new OIDCClient(); - client.setSno(AppConfig.DEFAULT_S_NO); - client.setClientName(responseFromAPI.getClientName()); - client.setClientId(responseFromAPI.getClientId()); - client.setClientSecret(responseFromAPI.getClientSecret()); - client.setScope(scopeText); - appDatabase.oidcClientDao().insert(client); - client.setSuccessful(true); - oidcClientLiveData.setValue(client); - } - } else { - Log.e(TAG, "Error in DCR.\n Error code: " + response.code() + "\n Error message: " + response.message()); - oidcClientLiveData.setValue(setErrorInLiveObject("Error in DCR.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Inside doDCR :: onFailure :: " + t.getMessage()); - oidcClientLiveData.setValue(setErrorInLiveObject("Error in DCR.\n" + t.getMessage())); - - } - }); - return oidcClientLiveData; - } - - private OIDCClient setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - OIDCClient oidcClient = new OIDCClient(false, operationError); - return oidcClient; - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.kt new file mode 100644 index 00000000000..26e77ae7953 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/DCRRepository.kt @@ -0,0 +1,296 @@ +package io.jans.chip.repository + +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import io.jans.chip.model.OPConfiguration +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.utils.AppConfig +import io.jans.chip.AppDatabase +import io.jans.chip.factories.DPoPProofFactory +import io.jans.chip.factories.KeyManager +import io.jans.chip.model.DCRequest +import io.jans.chip.model.DCResponse +import io.jans.chip.model.JSONWebKeySet +import io.jans.chip.model.OIDCClient +import io.jans.chip.model.SSARegRequest +import io.jans.chip.utils.AppUtil +import retrofit2.Response +import java.io.IOException +import java.security.InvalidAlgorithmParameterException +import java.security.NoSuchAlgorithmException +import java.security.NoSuchProviderException +import java.util.UUID + +class DCRRepository(context: Context) { + private val TAG = "DCRRepository" + private val appDatabase = AppDatabase.getInstance(context); + private var oidcClient: OIDCClient = + OIDCClient("", null, null, null, null, null, null) + var obtainedContext: Context = context + + suspend fun doDCRUsingSSA(ssaJwt: String?, scopeText: String?): OIDCClient? { + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList.isEmpty()) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = "OpenID configuration not found in database.." + return oidcClient + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val issuer: String? = opConfiguration.issuer + val registrationUrl: String? = opConfiguration.registrationEndpoint + + val dcrRequest = SSARegRequest( + AppConfig.APP_NAME + UUID.randomUUID(), + null, + null, + scopeText, + listOf("code"), + listOf("authorization_code", "client_credentials"), + ssaJwt, + "native", + listOf(issuer), + ) + + val claims: MutableMap = HashMap() + claims["appName"] = AppConfig.APP_NAME + claims["seq"] = UUID.randomUUID() + claims["app_id"] = obtainedContext.getPackageName() + var checksum: String? = null + try { + checksum = AppUtil.getChecksum(obtainedContext) + claims["app_checksum"] = checksum + } catch (e: IOException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum.${e.message}".trimIndent() + return oidcClient + } catch (e: NoSuchAlgorithmException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum. ${e.message}".trimIndent() + return oidcClient + } catch (e: PackageManager.NameNotFoundException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum. ${e.message}".trimIndent() + return oidcClient + } + + try { + val evidenceJwt: String? = DPoPProofFactory.issueJWTToken(claims) + dcrRequest.evidence = evidenceJwt + Log.d(TAG, "Inside doDCR :: evidence :: $evidenceJwt") + } catch (e: InvalidAlgorithmParameterException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } catch (e: NoSuchProviderException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } + val jwks = JSONWebKeySet() + jwks.addKey(KeyManager.getPublicKeyJWK(KeyManager.getPublicKey())?.getRequiredParams()) + dcrRequest.jwks = jwks.toJsonString() + Log.d(TAG, "Inside doDCR :: jwks :: " + jwks.toJsonString()) + + var response: Response = + ApiAdapter.getInstance(issuer).doDCR(dcrRequest, registrationUrl) + + if (response.code() != 200 && response.code() != 201) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + Log.e( + TAG, + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + ) + return oidcClient + } + val dcrResponse: DCResponse? = response.body() + if (!response.isSuccessful || dcrResponse == null) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + Log.e( + TAG, + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + ) + return oidcClient + } + + oidcClient.sno = AppConfig.DEFAULT_S_NO + oidcClient.clientName = dcrResponse.clientName + oidcClient.clientId = dcrResponse.clientId + oidcClient.clientSecret = dcrResponse.clientSecret + oidcClient.scope = scopeText + oidcClient.clientName = dcrResponse.clientName + oidcClient.isSuccessful = true + + appDatabase.oidcClientDao().deleteAll() + appDatabase.oidcClientDao().insert(oidcClient) + Log.d(TAG, "DCR is successful") + + return oidcClient + + } + suspend fun doDCR(scopeText: String?): OIDCClient? { + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList == null || opConfigurationList.isEmpty()) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = "OpenID configuration not found in database.." + return oidcClient + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val issuer: String? = opConfiguration.issuer + val registrationUrl: String? = opConfiguration.registrationEndpoint + + val dcrRequest = DCRequest( + issuer, + listOf(issuer), + scopeText, + listOf("code"), + listOf(issuer), + listOf("authorization_code", "client_credentials"), + "web", + AppConfig.APP_NAME + UUID.randomUUID(), + "client_secret_basic", + null, + null, + ) + + val claims: MutableMap = HashMap() + claims["appName"] = AppConfig.APP_NAME + claims["seq"] = UUID.randomUUID() + claims["app_id"] = obtainedContext.getPackageName() + var checksum: String? = null + try { + checksum = AppUtil.getChecksum(obtainedContext) + claims["app_checksum"] = checksum + } catch (e: IOException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum.${e.message}".trimIndent() + return oidcClient + } catch (e: NoSuchAlgorithmException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum. ${e.message}".trimIndent() + return oidcClient + } catch (e: PackageManager.NameNotFoundException) { + Log.d(TAG, "Error in generating app checksum. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating app checksum. ${e.message}".trimIndent() + return oidcClient + } + + /*val appIntegrityList: List = appDatabase.appIntegrityDao().getAll() + if (appIntegrityList == null || appIntegrityList.isEmpty()) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = "App Integrity not found in database" + return oidcClient + } + claims["app_integrity_result"] = appIntegrityList[0] + + */ + try { + val evidenceJwt: String? = DPoPProofFactory.issueJWTToken(claims) + dcrRequest.evidence = evidenceJwt + Log.d(TAG, "Inside doDCR :: evidence :: $evidenceJwt") + } catch (e: InvalidAlgorithmParameterException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } catch (e: NoSuchAlgorithmException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } catch (e: NoSuchProviderException) { + Log.e(TAG, "Error in generating DPoP jwt. ${e.message}".trimIndent()) + oidcClient.isSuccessful = false + oidcClient.errorMessage = "Error in generating DPoP jwt. ${e.message}".trimIndent() + + return oidcClient + } + val jwks = JSONWebKeySet() + jwks.addKey(KeyManager.getPublicKeyJWK(KeyManager.getPublicKey())?.getRequiredParams()) + dcrRequest.jwks = jwks.toJsonString() + Log.d(TAG, "Inside doDCR :: jwks :: " + jwks.toJsonString()) + + var response: Response = + ApiAdapter.getInstance(issuer).doDCR(dcrRequest, registrationUrl) + + if (response.code() != 200 && response.code() != 201) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + Log.e( + TAG, + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + ) + return oidcClient + } + val dcrResponse: DCResponse? = response.body() + if (!response.isSuccessful || dcrResponse == null) { + oidcClient.isSuccessful = false + oidcClient.errorMessage = + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + Log.e( + TAG, + "Error in DCR. Error code: ${response.code()} Error message: ${response.message()}" + ) + return oidcClient + } + + oidcClient.sno = AppConfig.DEFAULT_S_NO + oidcClient.clientName = dcrResponse.clientName + oidcClient.clientId = dcrResponse.clientId + oidcClient.clientSecret = dcrResponse.clientSecret + oidcClient.scope = scopeText + oidcClient.clientName = dcrResponse.clientName + oidcClient.isSuccessful = true + + appDatabase.oidcClientDao().deleteAll() + appDatabase.oidcClientDao().insert(oidcClient) + Log.d(TAG, "DCR is successful") + + return oidcClient + + } + + suspend fun isClientInDatabase(): Boolean { + var oidcClients: List? = appDatabase?.oidcClientDao()?.getAll() + var oidcClient: OIDCClient? = null + oidcClient = oidcClients?.let { it -> it[0] } + return oidcClient != null + } + + suspend fun getClientInDatabase(): OIDCClient? { + val oidcClients: List = appDatabase.oidcClientDao().getAll() + var oidcClient: OIDCClient? = null + if(oidcClients.isNotEmpty()) { + oidcClient = oidcClients?.let { it -> it.get(0) } + } + return oidcClient + } + + suspend fun deleteClientInDatabase() { + appDatabase.oidcClientDao().deleteAll() + } + +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAssertionRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAssertionRepository.kt new file mode 100644 index 00000000000..af50cecfe35 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAssertionRepository.kt @@ -0,0 +1,104 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.AppDatabase +import io.jans.chip.model.fido.assertion.option.AssertionOptionRequest +import io.jans.chip.model.fido.assertion.option.AssertionOptionResponse +import io.jans.chip.model.fido.assertion.result.AssertionResultRequest +import io.jans.chip.model.fido.assertion.result.AssertionResultResponse +import io.jans.chip.model.fido.config.FidoConfiguration +import io.jans.chip.retrofit.ApiAdapter +import okhttp3.ResponseBody +import retrofit2.Response + +class FidoAssertionRepository(context: Context) { + private val TAG = "FidoAssertionRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + private var assertionOptionResponse: AssertionOptionResponse? = + AssertionOptionResponse(null, null, null, null, null, null, null) + + suspend fun assertionOption(username: String): AssertionOptionResponse? { + try { + val fidoConfigurationList: List = + appDatabase.fidoConfigurationDao().getAll() + if (fidoConfigurationList == null || fidoConfigurationList.isEmpty()) { + assertionOptionResponse?.isSuccessful = false + assertionOptionResponse?.errorMessage = "Fido configuration not found in database." + return assertionOptionResponse + } + val fidoConfiguration: FidoConfiguration = fidoConfigurationList[0] + val req = AssertionOptionRequest(username, null, null, null, null, null) + + val response: Response = + ApiAdapter.getInstance(fidoConfiguration.issuer) + .assertionOption(req, fidoConfiguration.assertionOptionsEndpoint) + if (response.code() != 200 && response.code() != 201) { + assertionOptionResponse?.isSuccessful = false + assertionOptionResponse?.errorMessage = + "Error in assertion option. Error code ${response.code()}" + return assertionOptionResponse + } + assertionOptionResponse = response.body(); + if (assertionOptionResponse?.challenge == null) { + assertionOptionResponse?.isSuccessful = false + assertionOptionResponse?.errorMessage = + "Challenge field in assertion Option Response is null." + return assertionOptionResponse + } + assertionOptionResponse?.isSuccessful = true + return assertionOptionResponse + } catch (e: Exception) { + Log.e(TAG, "Error in fetching assertion Option Response : ${e.message}") + assertionOptionResponse?.isSuccessful = false + assertionOptionResponse?.errorMessage = + "Error in fetching assertion Option Response : ${e.message}" + return assertionOptionResponse + } + } + + suspend fun assertionResult(assertionResultRequest: AssertionResultRequest?): AssertionResultResponse { + val assertionResultResponse = AssertionResultResponse() + try { + val fidoConfigurationList: List = + appDatabase.fidoConfigurationDao().getAll() + if (fidoConfigurationList.isEmpty()) { + assertionResultResponse.isSuccessful = false + assertionResultResponse.errorMessage = "Fido configuration not found in database." + return assertionResultResponse + } + val fidoConfiguration: FidoConfiguration = fidoConfigurationList[0] + + val oidcClientList = appDatabase.oidcClientDao().getAll() + if (oidcClientList.isEmpty()) { + assertionResultResponse.isSuccessful = false + assertionResultResponse.errorMessage = "OpenID client not found in database." + return assertionResultResponse + } + //oidcClientList[0] + + val response: Response = + ApiAdapter.getInstance(fidoConfiguration.issuer) + .assertionResult( + assertionResultRequest, + fidoConfiguration.assertionResultEndpoint + ) + if (response.code() != 200 && response.code() != 201) { + assertionResultResponse.isSuccessful = false + assertionResultResponse.errorMessage = + "Error in assertion result. Error code ${response.code()}" + return assertionResultResponse + } + //val responseFromAPI = response.body(); + assertionResultResponse.isSuccessful = true + return assertionResultResponse + } catch (e: Exception) { + Log.e(TAG, "Error in fetching assertion result Response : ${e.message}") + assertionResultResponse.isSuccessful = false + assertionResultResponse.errorMessage = + "Error in fetching assertion result Response : ${e.message}" + return assertionResultResponse + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAttestationRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAttestationRepository.kt new file mode 100644 index 00000000000..40f98c71f0a --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoAttestationRepository.kt @@ -0,0 +1,108 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.AppDatabase +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.fido.attestation.option.AttestationOptionRequest +import io.jans.chip.model.fido.attestation.option.AttestationOptionResponse +import io.jans.chip.model.fido.attestation.result.AttestationResultRequest +import io.jans.chip.model.fido.attestation.result.AttestationResultResponse +import io.jans.chip.model.fido.config.FidoConfiguration +import okhttp3.ResponseBody +import retrofit2.Response + +class FidoAttestationRepository(context: Context) { + private val TAG = "FidoAttestationRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + + suspend fun attestationOption(username: String): AttestationOptionResponse? { + val req = AttestationOptionRequest(username, username, "none") + var attestationOptionResponse: AttestationOptionResponse? = + AttestationOptionResponse(null, null, null, null, null, null) + try { + val fidoConfigurationList: List = + appDatabase.fidoConfigurationDao().getAll() + if (fidoConfigurationList.isEmpty()) { + attestationOptionResponse?.isSuccessful = false + attestationOptionResponse?.errorMessage = "Fido configuration not found in database." + return attestationOptionResponse + } + val fidoConfiguration: FidoConfiguration = fidoConfigurationList[0] + + val response: Response = ApiAdapter.getInstance(fidoConfiguration.issuer) + .attestationOption(req, fidoConfiguration.attestationOptionsEndpoint) + if (response.code() != 200 && response.code() != 201) { + attestationOptionResponse?.isSuccessful = false + attestationOptionResponse?.errorMessage = + "Error in attestation Option Response. Error code ${response.code()}" + return attestationOptionResponse + } + attestationOptionResponse = response.body(); + if (attestationOptionResponse?.challenge == null) { + attestationOptionResponse?.isSuccessful = false + attestationOptionResponse?.errorMessage = + "Challenge field in attestationOptionResponse is null." + return attestationOptionResponse + } + attestationOptionResponse?.isSuccessful = true + return attestationOptionResponse + } catch(e: Exception) { + Log.e(TAG,"Error in fetching AttestationOptionResponse : ${e.message}") + attestationOptionResponse?.isSuccessful = false + attestationOptionResponse?.errorMessage = "Error in fetching AttestationOptionResponse : ${e.message}" + return attestationOptionResponse + } + } + + suspend fun attestationResult(attestationResultRequest: AttestationResultRequest?): AttestationResultResponse { + val fidoConfigurationList = appDatabase.fidoConfigurationDao().getAll() + val attestationResultResponse = AttestationResultResponse() + try { + if (fidoConfigurationList.isEmpty()) { + attestationResultResponse.isSuccessful = false + attestationResultResponse.errorMessage = "Fido configuration not found in database." + return attestationResultResponse + } + val fidoConfiguration = fidoConfigurationList[0] + + val response: Response = ApiAdapter.getInstance(fidoConfiguration.issuer) + .attestationResult( + attestationResultRequest, + fidoConfiguration.attestationResultEndpoint + ) + if (response.code() != 200 && response.code() != 201) { + attestationResultResponse.isSuccessful = false + attestationResultResponse.errorMessage = + "Error in assertion option. Error code ${response.code()}" + return attestationResultResponse + } + + val responseFromAPI = response.body() + + Log.d(TAG, responseFromAPI.toString()) + attestationResultResponse.isSuccessful = true + + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList.isEmpty()) { + attestationResultResponse.isSuccessful = false + attestationResultResponse.errorMessage = "OpenID configuration not found in database." + return attestationResultResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + opConfiguration.biometricEnrolled = true + appDatabase.opConfigurationDao().update(opConfiguration) + + return attestationResultResponse + } catch(e: Exception) { + Log.e(TAG,"Error in fetching AttestationResultRequest : ${e.message}") + e.printStackTrace() + attestationResultResponse.isSuccessful = false + attestationResultResponse.errorMessage = "Error in fetching AttestationResultRequest : ${e.message}" + return attestationResultResponse + } + + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoConfigurationRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoConfigurationRepository.kt new file mode 100644 index 00000000000..1e783e40082 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/FidoConfigurationRepository.kt @@ -0,0 +1,91 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.utils.AppConfig +import io.jans.chip.AppDatabase +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.fido.config.FidoConfiguration +import io.jans.chip.model.fido.config.FidoConfigurationResponse +import retrofit2.Response + +class FidoConfigurationRepository(context: Context) { + private val TAG = "FidoConfigurationRepository" + private val appDatabase = AppDatabase.getInstance(context); + private var fidoConfigurationResponse: FidoConfigurationResponse? = + FidoConfigurationResponse(null, null, null) + var obtainedContext: Context = context + + suspend fun fetchFidoConfiguration(configurationUrl: String): FidoConfigurationResponse? { + val issuer: String = configurationUrl.replace(AppConfig.FIDO_CONFIG_URL, "") + Log.d(TAG, "Inside fetchFIDOConfiguration :: configurationUrl ::$configurationUrl") + try { + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList.isEmpty()) { + fidoConfigurationResponse?.isSuccessful = false + fidoConfigurationResponse?.errorMessage = "OpenID configuration not found in database." + return fidoConfigurationResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + + val response: Response = + ApiAdapter.getInstance(issuer).getFidoConfiguration(configurationUrl) + + if (response.code() != 200) { + fidoConfigurationResponse?.isSuccessful = false + fidoConfigurationResponse?.errorMessage = + "Error in fetching FIDO Configuration. Error message: ${response.message()}" + return fidoConfigurationResponse + } + fidoConfigurationResponse = response.body() + if (!response.isSuccessful || fidoConfigurationResponse == null) { + fidoConfigurationResponse?.isSuccessful = false + fidoConfigurationResponse?.errorMessage = + "Error in fetching FIDO Configuration. Error message: ${response.message()}" + return fidoConfigurationResponse + } + fidoConfigurationResponse?.isSuccessful = true + val fidoConfigDB = FidoConfiguration( + AppConfig.DEFAULT_S_NO, + fidoConfigurationResponse?.issuer, + fidoConfigurationResponse?.attestation?.optionsEndpoint, + fidoConfigurationResponse?.attestation?.resultEndpoint, + fidoConfigurationResponse?.assertion?.optionsEndpoint, + fidoConfigurationResponse?.assertion?.resultEndpoint + ) + + Log.d( + TAG, + "Inside fetchOPConfiguration :: opConfiguration :: ${fidoConfigurationResponse?.issuer}" + ) + appDatabase.fidoConfigurationDao().deleteAll() + appDatabase.fidoConfigurationDao().insert(fidoConfigDB) + + opConfiguration.fidoUrl = configurationUrl + appDatabase.opConfigurationDao().update(opConfiguration) + + return fidoConfigurationResponse + } catch (e: Exception) { + Log.e(TAG, "Error in fetching OP Configuration. ${e.message}".trimIndent()) + fidoConfigurationResponse?.isSuccessful = false + fidoConfigurationResponse?.errorMessage = + "Error in fetching FIDO Configuration. Error message: ${e.message}" + return fidoConfigurationResponse + } + } + + suspend fun getFidoConfigInDatabase(): FidoConfiguration? { + val fidoConfigurationList: List = appDatabase.fidoConfigurationDao() + .getAll() + var fidoConfiguration: FidoConfiguration? = null + if(fidoConfigurationList.isNotEmpty()) { + fidoConfiguration = fidoConfigurationList.let { it -> it[0] } + } + return fidoConfiguration + } + + suspend fun deleteFidoConfigurationInDatabase() { + appDatabase.fidoConfigurationDao().deleteAll() + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.java deleted file mode 100644 index 4f5f16df562..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Log; - -import java.util.List; -import java.util.UUID; - -import io.jans.chip.AppDatabase; -import io.jans.chip.modal.LoginResponse; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class LoginResponseRepository { - public static final String TAG = "LoginRepository"; - private final SingleLiveEvent loginResponseLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - - private LoginResponseRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static LoginResponseRepository dcrRepository; - - public static LoginResponseRepository getInstance(Context context) { - if (dcrRepository == null) { - dcrRepository = new LoginResponseRepository(context); - } - return dcrRepository; - } - - public SingleLiveEvent processlogin(String usernameText, String passwordText) { - // Get OPConfiguration and OIDCClient - List opConfigurationList = appDatabase.opConfigurationDao().getAll(); - if (opConfigurationList == null || opConfigurationList.isEmpty()) { - loginResponseLiveData.setValue(setErrorInLiveObject("OpenID configuration not found in database.")); - return loginResponseLiveData; - } - List oidcClientList = appDatabase.oidcClientDao().getAll(); - if (oidcClientList == null || oidcClientList.isEmpty()) { - loginResponseLiveData.setValue(setErrorInLiveObject("OpenID client not found in database.")); - return loginResponseLiveData; - } - OPConfiguration opConfiguration = opConfigurationList.get(0); - OIDCClient oidcClient = oidcClientList.get(0); - Log.d(TAG,"Authorization Challenge Endpoint :: "+ opConfiguration.getAuthorizationChallengeEndpoint()); - // Create a call to request an authorization challenge - Call call = RetrofitClient.getInstance(opConfiguration.getIssuer()).getAPIInterface(). - getAuthorizationChallenge(oidcClient.getClientId(), - usernameText, - passwordText, - UUID.randomUUID().toString(), - UUID.randomUUID().toString(), - true, - opConfiguration.getAuthorizationChallengeEndpoint()); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.code() == 200) { - LoginResponse responseFromAPI = response.body(); - Log.d(TAG, "processlogin Response :: getAuthorizationCode ::" + responseFromAPI.getAuthorizationCode()); - - if (responseFromAPI.getAuthorizationCode() != null && !responseFromAPI.getAuthorizationCode().isEmpty()) { - responseFromAPI.setSuccessful(true); - loginResponseLiveData.setValue(responseFromAPI); - } else { - loginResponseLiveData.setValue(setErrorInLiveObject("Error in fetching OP Configuration. Authorization code is empty")); - } - } else { - loginResponseLiveData.setValue(setErrorInLiveObject("Error in generating authorization code.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Inside processlogin :: onFailure :: " + t.getMessage()); - loginResponseLiveData.setValue(setErrorInLiveObject("Inside processlogin :: onFailure :: " + t.getMessage())); - } - }); - return loginResponseLiveData; - } - - private LoginResponse setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - LoginResponse loginResponse = new LoginResponse(false, operationError); - return loginResponse; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.kt new file mode 100644 index 00000000000..1c8dfe834be --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LoginResponseRepository.kt @@ -0,0 +1,107 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.AppDatabase +import io.jans.chip.model.LoginResponse +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.UserInfoResponse +import io.jans.chip.retrofit.ApiAdapter +import retrofit2.Response +import java.util.UUID + +class LoginResponseRepository(context: Context) { + private val TAG = "LoginResponseRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + private var loginResponse: LoginResponse? = LoginResponse(null) + suspend fun processlogin( + usernameText: String, + passwordText: String?, + authMethod: String, + assertionResultRequest: String? + ): LoginResponse? { + // Get OPConfiguration and OIDCClient + try { + val opConfigurationList: List? = + appDatabase.opConfigurationDao().getAll() + if (opConfigurationList == null || opConfigurationList.isEmpty()) { + loginResponse?.isSuccessful = false + loginResponse?.errorMessage = "OpenID configuration not found in database." + return loginResponse + } + val oidcClientList = appDatabase.oidcClientDao().getAll() + if (oidcClientList == null || oidcClientList.isEmpty()) { + loginResponse?.isSuccessful = false + loginResponse?.errorMessage = "OpenID client not found in database." + return loginResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val oidcClient = oidcClientList[0] + Log.d( + TAG, + "Authorization Challenge Endpoint :: " + opConfiguration.authorizationChallengeEndpoint + ) + // Create a call to request an authorization challenge + val response: Response = + ApiAdapter.getInstance(opConfiguration.issuer).getAuthorizationChallenge( + oidcClient.clientId, + usernameText, + passwordText, + UUID.randomUUID().toString(), + UUID.randomUUID().toString(), + true, + "passkey", + authMethod, + assertionResultRequest, + opConfiguration.authorizationChallengeEndpoint + ) + + if (response.code() != 200) { + loginResponse?.isSuccessful = false + loginResponse?.errorMessage = + "Error in login. Check Username/ Password." + Log.d( + TAG, + "Error in login. Check Username/ Password. ErrorCode : ${response.code()} and ErrorMessage: ${response.message()}" + ) + return loginResponse + } + loginResponse = response.body() + if (!response.isSuccessful || loginResponse == null) { + loginResponse?.isSuccessful = false + loginResponse?.errorMessage = + "Error in login. ErrorCode : ${response.code()} and ErrorMessage: ${response.message()}" + return loginResponse + } + + Log.d( + TAG, + "processlogin Response :: getAuthorizationCode :: ${loginResponse?.authorizationCode}" + ) + loginResponse?.isSuccessful = true + return loginResponse + } catch (e: Exception) { + Log.e(TAG, "Error in login. ${e.message}".trimIndent()) + loginResponse?.isSuccessful = false + loginResponse?.errorMessage = + "Error in login. Error message: ${e.message}" + return loginResponse + } + } + + suspend fun isAuthenticated(accessToken: String?): Boolean { + var userInfoResponseRepository: UserInfoResponseRepository = + UserInfoResponseRepository(obtainedContext) + var userInfoResponse: UserInfoResponse? = + userInfoResponseRepository.getUserInfo(accessToken) + return userInfoResponse?.let { it -> it?.isSuccessful } ?: false + } + + suspend fun getUserInfoWithAccessToken(accessToken: String?): UserInfoResponse? { + var userInfoResponseRepository: UserInfoResponseRepository = + UserInfoResponseRepository(obtainedContext) + return userInfoResponseRepository.getUserInfo(accessToken) + + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.java deleted file mode 100644 index 355eccc782f..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Base64; -import android.util.Log; - -import java.util.List; - -import io.jans.chip.AppDatabase; -import io.jans.chip.modal.LogoutResponse; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class LogoutRepository { - public static final String TAG = "LogoutRepository"; - private final SingleLiveEvent logoutResponseLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - private LogoutRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static LogoutRepository logoutRepository; - - public static LogoutRepository getInstance(Context context) { - if (logoutRepository == null) { - logoutRepository = new LogoutRepository(context); - } - return logoutRepository; - } - public SingleLiveEvent logout() { - List opConfigurationList = appDatabase.opConfigurationDao().getAll(); - if (opConfigurationList == null || opConfigurationList.isEmpty()) { - logoutResponseLiveData.setValue(setErrorInLiveObject("OpenID configuration not found in database.")); - return logoutResponseLiveData; - } - List oidcClientList = appDatabase.oidcClientDao().getAll(); - if (oidcClientList == null || oidcClientList.isEmpty()) { - logoutResponseLiveData.setValue(setErrorInLiveObject("OpenID client not found in database.")); - return logoutResponseLiveData; - } - OPConfiguration opConfiguration = opConfigurationList.get(0); - OIDCClient oidcClient = oidcClientList.get(0); - Log.d(TAG,"Logout access token :: "+ oidcClient.getRecentGeneratedAccessToken()); - // Create a call to perform the logout - Call call = RetrofitClient.getInstance(opConfiguration.getIssuer()).getAPIInterface().logout(oidcClient.getRecentGeneratedAccessToken(), - "access_token", - "Basic " + Base64.encodeToString((oidcClient.getClientId() + ":" + oidcClient.getClientSecret()).getBytes(), Base64.NO_WRAP), - opConfiguration.getRevocationEndpoint()); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - Log.d("Logout Response code :: ", String.valueOf(response.code())); - if (response.code() == 200) { - oidcClient.setRecentGeneratedAccessToken(null); - oidcClient.setRecentGeneratedAccessToken(null); - appDatabase.oidcClientDao().update(oidcClient); - LogoutResponse logoutResponse = new LogoutResponse(); - logoutResponse.setSuccessful(true); - logoutResponseLiveData.setValue(logoutResponse); - } else { - Log.e(TAG, "Error in logout.\n Error code: " + response.code() + "\n Error message: " + response.message()); - logoutResponseLiveData.setValue(setErrorInLiveObject("Error in logout.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG,"Error in logout. " + t.getMessage()); - logoutResponseLiveData.setValue(setErrorInLiveObject("Error in logout. " + t.getMessage())); - } - }); - return logoutResponseLiveData; - } - private LogoutResponse setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - LogoutResponse logoutResponse = new LogoutResponse(false, operationError); - return logoutResponse; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.kt new file mode 100644 index 00000000000..cb1a778f1ad --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/LogoutRepository.kt @@ -0,0 +1,60 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Base64 +import android.util.Log +import io.jans.chip.model.OPConfiguration +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.AppDatabase +import io.jans.chip.model.LogoutResponse +import io.jans.chip.model.OIDCClient +import retrofit2.Response + +class LogoutRepository(context: Context) { + val TAG = "LogoutRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + private var logoutResponse: LogoutResponse = LogoutResponse() + + suspend fun logout(): LogoutResponse { + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList == null || opConfigurationList.isEmpty()) { + logoutResponse.isSuccessful = false + logoutResponse.errorMessage = "OpenID configuration not found in database." + return logoutResponse + } + val oidcClientList: List = appDatabase.oidcClientDao().getAll() + if (oidcClientList == null || oidcClientList.isEmpty()) { + logoutResponse.isSuccessful = false + logoutResponse.errorMessage = "OpenID client not found in database." + return logoutResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val oidcClient: OIDCClient = oidcClientList[0] + Log.d("oidcClient.recentGeneratedAccessToken", oidcClient.recentGeneratedAccessToken.toString()) + // Create a call to perform the logout + val response: Response = + ApiAdapter.getInstance(opConfiguration.issuer).logout( + oidcClient.recentGeneratedAccessToken, + "access_token", + "Basic " + Base64.encodeToString( + (oidcClient.clientId + ":" + oidcClient.clientSecret).toByteArray(), + Base64.NO_WRAP + ), + opConfiguration.revocationEndpoint + ) + + if (response.code() != 200) { + logoutResponse.isSuccessful = false + logoutResponse.errorMessage = "Error in logout. Error code: ${response.code()} Error message: ${response.message()}" + return logoutResponse + } + + oidcClient.recentGeneratedAccessToken = null + appDatabase.oidcClientDao().update(oidcClient) + val logoutResponse = LogoutResponse() + logoutResponse.isSuccessful = true + + return logoutResponse + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.java deleted file mode 100644 index fb4812cfb6e..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Log; - -import io.jans.chip.AppDatabase; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import io.jans.chip.utils.AppConfig; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class OPConfigurationRepository { - public static final String TAG = "OPConfigurationRepository"; - private final SingleLiveEvent opConfigurationLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - private OPConfigurationRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static OPConfigurationRepository opConfigurationRepository; - - public static OPConfigurationRepository getInstance(Context context) { - if (opConfigurationRepository == null) { - opConfigurationRepository = new OPConfigurationRepository(context); - } - return opConfigurationRepository; - } - - public SingleLiveEvent fetchOPConfiguration(String configurationUrl) { - - String issuer = configurationUrl.replace("/.well-known/openid-configuration", ""); - Log.d(TAG, "Inside fetchOPConfiguration :: configurationUrl ::" + configurationUrl); - try { - Call call = RetrofitClient.getInstance(issuer).getAPIInterface().getOPConfiguration(configurationUrl); - - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.code() == 200) { - OPConfiguration opConfiguration = response.body(); - opConfiguration.setSuccessful(true); - opConfiguration.setSno(AppConfig.DEFAULT_S_NO); - Log.d(TAG, "Inside fetchOPConfiguration :: opConfiguration :: " + opConfiguration.toString()); - appDatabase.opConfigurationDao().deleteAll(); - appDatabase.opConfigurationDao().insert(opConfiguration); - opConfigurationLiveData.setValue(opConfiguration); - } else { - opConfigurationLiveData.setValue(setErrorInLiveObject("Error in fetching OP Configuration.\n Error code: " + response.code() + "\n Error message: " + response.message())); - - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Inside fetchOPConfiguration :: onFailure :: " + t.getMessage()); - opConfigurationLiveData.setValue(setErrorInLiveObject("Error in fetching OP Configuration.\n" + t.getMessage())); - - } - }); - } catch (Exception e) { - Log.e(TAG, "Error in fetching OP Configuration.\n" + e.getMessage()); - opConfigurationLiveData.setValue(setErrorInLiveObject("Error in fetching OP Configuration.\n" + e.getMessage())); - } - return opConfigurationLiveData; - - } - private OPConfiguration setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - OPConfiguration opConfiguration = new OPConfiguration(false, operationError); - return opConfiguration; - } -} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.kt new file mode 100644 index 00000000000..349f108e5c4 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/OPConfigurationRepository.kt @@ -0,0 +1,73 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.model.OPConfiguration +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.utils.AppConfig +import io.jans.chip.AppDatabase +import retrofit2.Response + +class OPConfigurationRepository (context: Context){ + private val TAG = "OPConfigurationRepository" + private val appDatabase = AppDatabase.getInstance(context); + private var opConfiguration: OPConfiguration? = + OPConfiguration("", null, null, null, null, null, null) + suspend fun fetchOPConfiguration(configurationUrl: String): OPConfiguration? { + try { + val issuer: String = configurationUrl.replace(AppConfig.OP_CONFIG_URL, "") + var response: Response = + ApiAdapter.getInstance(issuer).getOPConfiguration(configurationUrl) + if (response.code() != 200) { + opConfiguration?.isSuccessful = false + opConfiguration?.errorMessage = "Error in fetching OP configuration." + return opConfiguration + } + opConfiguration = response.body() + + if (!response.isSuccessful || opConfiguration == null) { + opConfiguration?.isSuccessful = false + opConfiguration?.errorMessage = "Error in fetching OP configuration." + return opConfiguration + } + + opConfiguration?.isSuccessful = true + opConfiguration?.sno = AppConfig.DEFAULT_S_NO + appDatabase.opConfigurationDao().deleteAll() + appDatabase.opConfigurationDao().insert(opConfiguration) + Log.d(TAG,"Inside fetchOPConfiguration :: opConfiguration :: " + opConfiguration.toString()) + + return opConfiguration + } catch(e: Exception) { + Log.e(TAG,"Error in fetching OP configuration :: " + e.message) + opConfiguration?.isSuccessful = false + opConfiguration?.errorMessage = "Error in fetching OP configuration :: " + e.message + return opConfiguration + //e.printStackTrace() + } + } + + suspend fun isOPConfigurationInDatabase(): Boolean { + var opConfigurations: List? = appDatabase?.opConfigurationDao()?.getAll() + var opConfiguration: OPConfiguration? = null + if(opConfigurations != null && !opConfigurations.isEmpty()) { + opConfiguration = opConfigurations?.let { it -> it.get(0) } + return opConfiguration != null + } + return false + } + + suspend fun getOPConfigurationInDatabase(): OPConfiguration? { + var opConfigurations: List? = appDatabase.opConfigurationDao().getAll() + var opConfiguration: OPConfiguration? = null + if(opConfigurations != null && !opConfigurations.isEmpty()) { + opConfiguration = opConfigurations?.let { it -> it.get(0) } + } + return opConfiguration + } + + suspend fun deleteOPConfigurationInDatabase() { + appDatabase.opConfigurationDao().deleteAll() + } + +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.java deleted file mode 100644 index bd49f00b944..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.java +++ /dev/null @@ -1,196 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Log; - -import com.google.android.gms.tasks.Task; -import com.google.android.play.core.integrity.IntegrityManager; -import com.google.android.play.core.integrity.IntegrityManagerFactory; -import com.google.android.play.core.integrity.IntegrityTokenRequest; -import com.google.android.play.core.integrity.IntegrityTokenResponse; -import com.google.android.play.core.integrity.model.IntegrityErrorCode; - -import java.util.UUID; - -import io.jans.chip.AppDatabase; -import io.jans.chip.modal.OperationError; -import io.jans.chip.modal.appIntegrity.AppIntegrityEntity; -import io.jans.chip.modal.appIntegrity.AppIntegrityResponse; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import io.jans.chip.utils.AppConfig; -import retrofit2.Call; -import retrofit2.Callback; - -public class PlayIntegrityRepository { - public static final String TAG = "PlayIntegrityRepository"; - private SingleLiveEvent appIntegrityResponseLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - - private PlayIntegrityRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static PlayIntegrityRepository playIntegrityRepository; - - public static PlayIntegrityRepository getInstance(Context context) { - if (playIntegrityRepository == null) { - playIntegrityRepository = new PlayIntegrityRepository(context); - } - return playIntegrityRepository; - } - - public SingleLiveEvent checkAppIntegrity() { - // Create an instance of a manager. - IntegrityManager integrityManager = IntegrityManagerFactory.create(this.context); - - // Request the integrity token by providing a nonce. - Task integrityTokenResponse = integrityManager - .requestIntegrityToken(IntegrityTokenRequest.builder().setNonce(UUID.randomUUID().toString()).setCloudProjectNumber(AppConfig.GOOGLE_CLOUD_PROJECT_ID).build()); - integrityTokenResponse.addOnSuccessListener(integrityTokenResponse1 -> { - String integrityToken = integrityTokenResponse1.token(); - Log.d("Integrity token Obtained result", "success"); - appIntegrityResponseLiveData = getTokenResponse(integrityToken); - }); - - integrityTokenResponse.addOnFailureListener(e -> { - Log.e("Integrity token Obtained result", "failure"); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Error in obtaining integrity token :: " + getErrorText(e))); - OperationError operationError = new OperationError.Builder() - .title("Error") - .message("Error in obtaining integrity token :: " + getErrorText(e)) - .build(); - AppIntegrityResponse appIntegrityResponse = new AppIntegrityResponse(false, operationError); - appIntegrityResponseLiveData.setValue(appIntegrityResponse); - }); - return appIntegrityResponseLiveData; - } - - private SingleLiveEvent getTokenResponse(String integrityToken) { - Log.d("INTEGRITY_APP_SERVER_URL", AppConfig.INTEGRITY_APP_SERVER_URL + "/api/check?token=" + integrityToken); - Call call = RetrofitClient.getInstance(AppConfig.INTEGRITY_APP_SERVER_URL).getAPIInterface().verifyIntegrityTokenOnAppServer(AppConfig.INTEGRITY_APP_SERVER_URL + "/api/check?token=" + integrityToken); - - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - if (response.code() == 200) { - AppIntegrityResponse appIntegrityResponse = response.body(); - - if (appIntegrityResponse == null) { - Log.e("Response from App server :: ", "Response body is empty"); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Empty response obtained from App Server.")); - appIntegrityResponseLiveData.setValue(setErrorInLiveObject("Empty response obtained from App Server.")); - } else if (appIntegrityResponse.getError() != null) { - Log.e("Response from App server :: ", "Response body has error"); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Response from App server has error :: " + appIntegrityResponse.getError())); - appIntegrityResponseLiveData.setValue(setErrorInLiveObject("Response from App server has error :: " + appIntegrityResponse.getError())); - } else if (appIntegrityResponse.getAppIntegrity() == null || appIntegrityResponse.getAppIntegrity().getAppRecognitionVerdict() == null) { - Log.e("Response from App server :: ", "Response body do not have appIntegrity"); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Response body do not have appIntegrity.")); - appIntegrityResponseLiveData.setValue(setErrorInLiveObject("Response body do not have appIntegrity.")); - } else { - Log.d("Inside getTokenResponse :: appIntegrityResponse ::", appIntegrityResponse.getAppIntegrity().getAppRecognitionVerdict()); - AppIntegrityEntity appIntegrityEntity = new AppIntegrityEntity(AppConfig.DEFAULT_S_NO, appIntegrityResponse.getAppIntegrity().getAppRecognitionVerdict(), - appIntegrityResponse.getDeviceIntegrity().commasSeparatedString(), - appIntegrityResponse.getAccountDetails().getAppLicensingVerdict(), - appIntegrityResponse.getRequestDetails().getRequestPackageName(), - appIntegrityResponse.getRequestDetails().getNonce(), - appIntegrityResponse.getError()); - appDatabase.appIntegrityDao().insert(appIntegrityEntity); - appIntegrityResponse.setSuccessful(true); - appIntegrityResponseLiveData.setValue(appIntegrityResponse); - } - - } else { - Log.e("Response from App server :: Unsuccessful, Response code :: ", String.valueOf(response.code())); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Error in obtaining response from aap server :: Response code :: " + response.code())); - appIntegrityResponseLiveData.setValue(setErrorInLiveObject("Error in obtaining response from aap server :: Response code :: " + response.code())); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e("Error in fetching AppIntegrity :: ", t.getMessage()); - appDatabase.appIntegrityDao().insert(setErrorInAppIntegrityEntity("Error in fetching AppIntegrity :: " + t.getMessage())); - appIntegrityResponseLiveData.setValue(setErrorInLiveObject("Error in fetching AppIntegrity :: " + t.getMessage())); - - } - }); - return appIntegrityResponseLiveData; - } - - private String getErrorText(Exception e) { - String msg = e.getMessage(); - if (msg == null) { - return "Unknown Error"; - } - - int errorCode = Integer.parseInt(msg.replaceAll("\n", "").replaceAll(":(.*)", "")); - switch (errorCode) { - case IntegrityErrorCode.API_NOT_AVAILABLE: - return "Integrity API is not available.\n\n" + - "The Play Store version might be old, try updating it."; - case IntegrityErrorCode.APP_NOT_INSTALLED: - return "The calling app is not installed.\n\n" + - "This shouldn't happen. If it does please open an issue on Github."; - case IntegrityErrorCode.APP_UID_MISMATCH: - return "The calling app UID (user id) does not match the one from Package Manager.\n\n" + - "This shouldn't happen. If it does please open an issue on Github."; - case IntegrityErrorCode.CANNOT_BIND_TO_SERVICE: - return "Binding to the service in the Play Store has failed.\n\n" + - "This can be due to having an old Play Store version installed on the device."; - case IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE: - return "Unknown internal Google server error."; - case IntegrityErrorCode.INTERNAL_ERROR: - return "Unknown internal error."; - case IntegrityErrorCode.NETWORK_ERROR: - return "No available network is found.\n\n" + - "Please check your connection."; - case IntegrityErrorCode.NO_ERROR: - return "No error has occurred.\n\n" + - "If you ever get this, congrats, I have no idea what it means."; - case IntegrityErrorCode.NONCE_IS_NOT_BASE64: - return "Nonce is not encoded as a base64 web-safe no-wrap string.\n\n" + - "This shouldn't happen. If it does please open an issue on Github."; - case IntegrityErrorCode.NONCE_TOO_LONG: - return "Nonce length is too long.\n" + - "This shouldn't happen. If it does please open an issue on Github."; - case IntegrityErrorCode.NONCE_TOO_SHORT: - return "Nonce length is too short.\n" + - "This shouldn't happen. If it does please open an issue on Github."; - case IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND: - return "Play Services is not available or version is too old.\n\n" + - "Try updating Google Play Services."; - case IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND: - return "No Play Store account is found on device.\n\n" + - "Try logging into Play Store."; - case IntegrityErrorCode.PLAY_STORE_NOT_FOUND: - return "No Play Store app is found on device or not official version is installed.\n\n" + - "This app can't work without Play Store."; - case IntegrityErrorCode.TOO_MANY_REQUESTS: - return "The calling app is making too many requests to the API and hence is throttled.\n\n" + - "This shouldn't happen. If it does please open an issue on Github."; - default: - return "Unknown Error"; - } - } - - private AppIntegrityEntity setErrorInAppIntegrityEntity(String error) { - AppIntegrityEntity appIntegrityEntity = new AppIntegrityEntity(); - appIntegrityEntity.setSno(AppConfig.DEFAULT_S_NO); - appIntegrityEntity.setError(error); - return appIntegrityEntity; - - } - - private AppIntegrityResponse setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - AppIntegrityResponse appIntegrityResponse = new AppIntegrityResponse(false, operationError); - return appIntegrityResponse; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.kt new file mode 100644 index 00000000000..5df44d66f8f --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/PlayIntegrityRepository.kt @@ -0,0 +1,240 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import com.google.android.play.core.integrity.IntegrityManagerFactory +import com.google.android.play.core.integrity.IntegrityTokenRequest +import com.google.android.play.core.integrity.IntegrityTokenResponse +import com.google.android.play.core.integrity.model.IntegrityErrorCode +import io.jans.chip.AppDatabase +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.appIntegrity.AppIntegrityEntity +import io.jans.chip.model.appIntegrity.AppIntegrityResponse +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.utils.AppConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import retrofit2.Response +import java.util.UUID + +class PlayIntegrityRepository(context: Context) { + val TAG = "PlayIntegrityRepository" + private var appIntegrityResponse: AppIntegrityResponse? = + AppIntegrityResponse(null, null, null, null) + var obtainedContext: Context = context + private val appDatabase = AppDatabase.getInstance(context); + + suspend fun checkAppIntegrity(): AppIntegrityResponse? { + // Create an instance of a manager. + val integrityManager = IntegrityManagerFactory.create(obtainedContext) + + // Request the integrity token by providing a nonce. + val integrityTokenResponse = integrityManager + .requestIntegrityToken( + IntegrityTokenRequest.builder().setNonce(UUID.randomUUID().toString()) + .setCloudProjectNumber(AppConfig.GOOGLE_CLOUD_PROJECT_ID).build() + ) + integrityTokenResponse.addOnSuccessListener { integrityTokenResponse1: IntegrityTokenResponse -> + CoroutineScope(Dispatchers.Main).launch { + val integrityToken = integrityTokenResponse1.token() + Log.d("Integrity token Obtained result", "success") + appIntegrityResponse = getTokenResponse(integrityToken) + } + } + integrityTokenResponse.addOnFailureListener { e: Exception -> + Log.e("Integrity token Obtained result", "failure") + appDatabase.appIntegrityDao().insert( + setErrorInAppIntegrityEntity( + "Error in obtaining integrity token :: " + getErrorText(e) + ) + ) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = + "Error in obtaining integrity token :: " + getErrorText(e) + + } + return appIntegrityResponse + } + + private suspend fun getTokenResponse(integrityToken: String): AppIntegrityResponse? { + Log.d( + "INTEGRITY_APP_SERVER_URL", + AppConfig.INTEGRITY_APP_SERVER_URL + "/api/check?token=" + integrityToken + ) + + val response: Response? = + ApiAdapter.getInstance(AppConfig.INTEGRITY_APP_SERVER_URL) + .verifyIntegrityTokenOnAppServer(AppConfig.INTEGRITY_APP_SERVER_URL + "/api/check?token=" + integrityToken) + + if (response?.code() != 200) { + appDatabase.appIntegrityDao() + .insert(setErrorInAppIntegrityEntity("AppIntegrity response is unsuccessful. Error code: ${response?.code()}, Error message: ${response?.message()}")) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = + "AppIntegrity response is unsuccessful. Error code: ${response?.code()}, Error message: ${response?.message()}" + return appIntegrityResponse + } + appIntegrityResponse = response.body() + + if (appIntegrityResponse == null) { + Log.e("Response from App server :: ", "Response body is empty") + appDatabase.appIntegrityDao() + .insert(setErrorInAppIntegrityEntity("Empty response obtained from App Server.")) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = "Empty response obtained from App Server." + return appIntegrityResponse + } + + if (!response.isSuccessful) { + appDatabase.appIntegrityDao() + .insert(setErrorInAppIntegrityEntity("AppIntegrity response is unsuccessful.")) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = "AppIntegrity response is unsuccessful." + return appIntegrityResponse + } + + if (appIntegrityResponse?.error != null) { + Log.e("Response from App server :: ", "Response body has error") + appDatabase.appIntegrityDao() + .insert(setErrorInAppIntegrityEntity("Response from App server has error :: " + appIntegrityResponse?.error)) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = + "Response from App server has error :: " + appIntegrityResponse?.error + return appIntegrityResponse + } + if (appIntegrityResponse?.appIntegrity == null || appIntegrityResponse?.appIntegrity + ?.appRecognitionVerdict == null + ) { + Log.e( + "Response from App server :: ", + "Response body do not have appIntegrity" + ) + + appDatabase.appIntegrityDao() + .insert(setErrorInAppIntegrityEntity("Response body do not have appIntegrity.")) + appIntegrityResponse?.isSuccessful = false + appIntegrityResponse?.errorMessage = "Response body do not have appIntegrity." + return appIntegrityResponse + + } + Log.d( + "Inside getTokenResponse :: appIntegrityResponse ::", + appIntegrityResponse?.appIntegrity?.appRecognitionVerdict.toString() + ) + val appIntegrityEntity = AppIntegrityEntity( + AppConfig.DEFAULT_S_NO, + appIntegrityResponse?.appIntegrity?.appRecognitionVerdict, + appIntegrityResponse?.deviceIntegrity?.commasSeparatedString(), + appIntegrityResponse?.accountDetails?.appLicensingVerdict, + appIntegrityResponse?.requestDetails?.requestPackageName, + appIntegrityResponse?.requestDetails?.nonce, + appIntegrityResponse?.error + ) + appDatabase.appIntegrityDao().insert(appIntegrityEntity) + appIntegrityResponse?.isSuccessful = true + + return appIntegrityResponse + } + + + private fun getErrorText(e: Exception): String { + val msg = e.message ?: return "Unknown Error" + val errorCode = msg.replace("\n".toRegex(), "").replace(":(.*)".toRegex(), "").toInt() + return when (errorCode) { + IntegrityErrorCode.API_NOT_AVAILABLE -> """ + Integrity API is not available. + + The Play Store version might be old, try updating it. + """.trimIndent() + + IntegrityErrorCode.APP_NOT_INSTALLED -> """ + The calling app is not installed. + + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + IntegrityErrorCode.APP_UID_MISMATCH -> """ + The calling app UID (user id) does not match the one from Package Manager. + + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + IntegrityErrorCode.CANNOT_BIND_TO_SERVICE -> """ + Binding to the service in the Play Store has failed. + + This can be due to having an old Play Store version installed on the device. + """.trimIndent() + + IntegrityErrorCode.GOOGLE_SERVER_UNAVAILABLE -> "Unknown internal Google server error." + IntegrityErrorCode.INTERNAL_ERROR -> "Unknown internal error." + IntegrityErrorCode.NETWORK_ERROR -> """ + No available network is found. + + Please check your connection. + """.trimIndent() + + IntegrityErrorCode.NO_ERROR -> """ + No error has occurred. + + If you ever get this, congrats, I have no idea what it means. + """.trimIndent() + + IntegrityErrorCode.NONCE_IS_NOT_BASE64 -> """ + Nonce is not encoded as a base64 web-safe no-wrap string. + + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + IntegrityErrorCode.NONCE_TOO_LONG -> """ + Nonce length is too long. + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + IntegrityErrorCode.NONCE_TOO_SHORT -> """ + Nonce length is too short. + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + IntegrityErrorCode.PLAY_SERVICES_NOT_FOUND -> """ + Play Services is not available or version is too old. + + Try updating Google Play Services. + """.trimIndent() + + IntegrityErrorCode.PLAY_STORE_ACCOUNT_NOT_FOUND -> """ + No Play Store account is found on device. + + Try logging into Play Store. + """.trimIndent() + + IntegrityErrorCode.PLAY_STORE_NOT_FOUND -> """ + No Play Store app is found on device or not official version is installed. + + This app can't work without Play Store. + """.trimIndent() + + IntegrityErrorCode.TOO_MANY_REQUESTS -> """ + The calling app is making too many requests to the API and hence is throttled. + + This shouldn't happen. If it does please open an issue on Github. + """.trimIndent() + + else -> "Unknown Error" + } + } + + private fun setErrorInAppIntegrityEntity(error: String): AppIntegrityEntity? { + return AppIntegrityEntity(AppConfig.DEFAULT_S_NO, null, null, null, null, null, error) + } + + suspend fun getAppIntegrityEntityInDatabase(): AppIntegrityEntity? { + var appIntegrityEntities: List? = appDatabase.appIntegrityDao().getAll() + var appIntegrityEntity: AppIntegrityEntity? = null + if(appIntegrityEntities != null && !appIntegrityEntities.isEmpty()) { + appIntegrityEntity = appIntegrityEntities?.let { it -> it.get(0) } + } + return appIntegrityEntity + } + +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.java deleted file mode 100644 index c9cbccc9f10..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Base64; -import android.util.Log; - -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.util.List; - -import io.jans.chip.AppDatabase; -import io.jans.chip.factories.DPoPProofFactory; -import io.jans.chip.modal.OIDCClient; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.modal.TokenResponse; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import io.jans.chip.utils.AppConfig; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class TokenResponseRepository { - public static final String TAG = "TokenResponseRepository"; - private final SingleLiveEvent tokenResponseLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - private TokenResponseRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static TokenResponseRepository tokenResponseRepository; - - public static TokenResponseRepository getInstance(Context context) { - if (tokenResponseRepository == null) { - tokenResponseRepository = new TokenResponseRepository(context); - } - return tokenResponseRepository; - } - - public SingleLiveEvent getToken(String authorizationCode, String usernameText, String passwordText) { - // Get OPConfiguration and OIDCClient - List opConfigurationList = appDatabase.opConfigurationDao().getAll(); - if (opConfigurationList == null || opConfigurationList.isEmpty()) { - tokenResponseLiveData.setValue(setErrorInLiveObject("OpenID configuration not found in database.")); - return tokenResponseLiveData; - } - List oidcClientList = appDatabase.oidcClientDao().getAll(); - if (oidcClientList == null || oidcClientList.isEmpty()) { - tokenResponseLiveData.setValue(setErrorInLiveObject("OpenID client not found in database.")); - return tokenResponseLiveData; - } - OPConfiguration opConfiguration = opConfigurationList.get(0); - OIDCClient oidcClient = oidcClientList.get(0); - - try { - Log.d(TAG, "dpop token" + DPoPProofFactory.getInstance().issueDPoPJWTToken("POST", opConfiguration.getIssuer())); - // Create a call to request a token - Call call = RetrofitClient.getInstance(opConfiguration.getIssuer()).getAPIInterface() - .getToken(oidcClient.getClientId(), - authorizationCode, - "authorization_code", - opConfiguration.getIssuer(), - oidcClient.getScope(), - "Basic " + Base64.encodeToString((oidcClient.getClientId() + ":" + oidcClient.getClientSecret()).getBytes(), Base64.NO_WRAP), - DPoPProofFactory.getInstance().issueDPoPJWTToken("POST", opConfiguration.getIssuer()), - opConfiguration.getTokenEndpoint()); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - // this method is called when we get response from our api. - if (response.code() == 200) { - TokenResponse responseFromAPI = response.body(); - - if (responseFromAPI.getAccessToken() != null && !responseFromAPI.getAccessToken().isEmpty()) { - Log.d(TAG, "getToken Response :: getIdToken ::" + responseFromAPI.getIdToken()); - Log.d(TAG, "getToken Response :: getTokenType ::" + responseFromAPI.getTokenType()); - oidcClient.setRecentGeneratedIdToken(responseFromAPI.getIdToken()); - oidcClient.setRecentGeneratedAccessToken(responseFromAPI.getAccessToken()); - appDatabase.oidcClientDao().update(oidcClient); - responseFromAPI.setSuccessful(true); - tokenResponseLiveData.setValue(responseFromAPI); - } - } else { - Log.e(TAG, "Error in Token generation.\n Error code: " + response.code() + "\n Error message: " + response.message()); - tokenResponseLiveData.setValue(setErrorInLiveObject("Error in Token generation.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG,"Inside getToken :: onFailure :: "+ t.getMessage()); - tokenResponseLiveData.setValue(setErrorInLiveObject("Error in Token generation.\n" + t.getMessage())); - } - }); - } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException e) { - Log.e(TAG, "Error in Token generation.\n" + e.getMessage()); - tokenResponseLiveData.setValue(setErrorInLiveObject("Error in Token generation.\n" + e.getMessage())); - } - return tokenResponseLiveData; - } - private TokenResponse setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - TokenResponse tokenResponse = new TokenResponse(false, operationError); - return tokenResponse; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.kt new file mode 100644 index 00000000000..31907d88863 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/TokenResponseRepository.kt @@ -0,0 +1,89 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Base64 +import android.util.Log +import io.jans.chip.model.OPConfiguration +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.AppDatabase +import io.jans.chip.factories.DPoPProofFactory +import io.jans.chip.model.OIDCClient +import io.jans.chip.model.TokenResponse +import retrofit2.Response + +class TokenResponseRepository(context: Context) { + val TAG = "TokenResponseRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + private var tokenResponse: TokenResponse = TokenResponse(null, null, null) + + suspend fun getToken( + authorizationCode: String?, + ): TokenResponse? { + // Get OPConfiguration and OIDCClient + val opConfigurationList: List = appDatabase.opConfigurationDao().getAll() + if (opConfigurationList.isEmpty()) { + tokenResponse.isSuccessful = false + tokenResponse.errorMessage = "OpenID configuration not found in database." + return tokenResponse + } + val oidcClientList: List = appDatabase.oidcClientDao().getAll() + if (oidcClientList.isEmpty()) { + tokenResponse.isSuccessful = false + tokenResponse.errorMessage = "OpenID client not found in database." + return tokenResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val oidcClient: OIDCClient = oidcClientList[0] + + Log.d( + TAG, + "dpop token" + opConfiguration.issuer?.let { + DPoPProofFactory.issueDPoPJWTToken("POST", + it + ) + } + ) + + // Create a call to request a token + val response: Response = + ApiAdapter.getInstance(opConfiguration.issuer).getToken( + oidcClient.clientId, + authorizationCode, + "authorization_code", + opConfiguration.issuer, + oidcClient.scope, + "Basic " + Base64.encodeToString( + (oidcClient.clientId + ":" + oidcClient.clientSecret).toByteArray(), + Base64.NO_WRAP + ), + opConfiguration.issuer?.let { DPoPProofFactory.issueDPoPJWTToken("POST", it) }, + opConfiguration.tokenEndpoint + ) + + if (response.code() != 200) { + tokenResponse.isSuccessful = false + tokenResponse.errorMessage = + "Error in Token generation. Error code: ${response.code()} Error message: ${response.message()}" + return tokenResponse + } + + val tokenResponse: TokenResponse? = response.body() + Log.d(TAG, "getToken Response :: getIdToken ::" + tokenResponse?.idToken) + Log.d(TAG, "getToken Response :: getTokenType ::" + tokenResponse?.tokenType) + + if (!response.isSuccessful || tokenResponse == null) { + tokenResponse?.isSuccessful = false + tokenResponse?.errorMessage = + "Error in Token generation. Error code: ${response.code()} Error message: ${response.message()}" + return tokenResponse + } + + oidcClient.recentGeneratedIdToken = tokenResponse.idToken + oidcClient.recentGeneratedAccessToken = tokenResponse.accessToken + appDatabase.oidcClientDao().update(oidcClient) + tokenResponse.isSuccessful = true + + return tokenResponse + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.java deleted file mode 100644 index 56ff3c97afd..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.java +++ /dev/null @@ -1,99 +0,0 @@ -package io.jans.chip.repository; - -import android.content.Context; -import android.util.Log; - -import java.util.List; - -import io.jans.chip.AppDatabase; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.OperationError; -import io.jans.chip.modal.UserInfoResponse; -import io.jans.chip.retrofit.RetrofitClient; -import io.jans.chip.modal.SingleLiveEvent; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class UserInfoResponseRepository { - public static final String TAG = "UserInfoResponseRepository"; - private final SingleLiveEvent userInfoResponseLiveData = new SingleLiveEvent<>(); - Context context; - AppDatabase appDatabase; - private UserInfoResponseRepository(Context context) { - this.context = context; - appDatabase = AppDatabase.getInstance(context); - } - - private static UserInfoResponseRepository userInfoResponseRepository; - - public static UserInfoResponseRepository getInstance(Context context) { - if (userInfoResponseRepository == null) { - userInfoResponseRepository = new UserInfoResponseRepository(context); - } - return userInfoResponseRepository; - } - - public SingleLiveEvent getUserInfo(String accessToken) { - return getUserInfo(accessToken, false); - } - - // Overloaded function to get user information with silentOnError flag - public SingleLiveEvent getUserInfo(String accessToken, boolean silentOnError) { - List opConfigurationList = appDatabase.opConfigurationDao().getAll(); - if (opConfigurationList == null || opConfigurationList.isEmpty()) { - userInfoResponseLiveData.setValue(setErrorInLiveObject("OpenID configuration not found in database.")); - return userInfoResponseLiveData; - } - OPConfiguration opConfiguration = opConfigurationList.get(0); - // Create a call to fetch user information - Call call = RetrofitClient.getInstance(opConfiguration.getIssuer()).getAPIInterface().getUserInfo(accessToken, "Bearer " + accessToken, opConfiguration.getUserinfoEndpoint()); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - // this method is called when we get response from our api. - - if (response.code() == 200) { - Object responseFromAPI = response.body(); - Log.d("getUserInfo Response :: getUserInfo ::", responseFromAPI.toString()); - - if (responseFromAPI != null) { - UserInfoResponse userInfoResponse = new UserInfoResponse(); - userInfoResponse.setReponse(responseFromAPI); - userInfoResponse.setSuccessful(true); - userInfoResponseLiveData.setValue(userInfoResponse); - } else { - Log.e(TAG, "User-Info is null"); - userInfoResponseLiveData.setValue(setErrorInLiveObject("User-Info is null")); - } - } else { - Log.e(TAG, "Error in fetching getUserInfo.\n Error code: " + response.code() + "\n Error message: " + response.message()); - if (!silentOnError) { - userInfoResponseLiveData.setValue(setErrorInLiveObject("Error in fetching getUserInfo.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } else { - userInfoResponseLiveData.setValue(setErrorInLiveObject("Error in fetching getUserInfo. The previous session has expired.\n Error code: " + response.code() + "\n Error message: " + response.message())); - } - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e(TAG, "Error in fetching getUserInfo :: " + t.getMessage()); - if (!silentOnError) { - userInfoResponseLiveData.setValue(setErrorInLiveObject("Error in fetching getUserInfo :: " + t.getMessage())); - } else { - userInfoResponseLiveData.setValue(setErrorInLiveObject("Error in fetching getUserInfo :: " + t.getMessage())); - } - } - }); - return userInfoResponseLiveData; - } - private UserInfoResponse setErrorInLiveObject(String errorMessage) { - OperationError operationError = new OperationError.Builder() - .title("Error") - .message(errorMessage) - .build(); - UserInfoResponse userInfoResponse = new UserInfoResponse(false, operationError); - return userInfoResponse; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.kt new file mode 100644 index 00000000000..a970ca6c246 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/repository/UserInfoResponseRepository.kt @@ -0,0 +1,61 @@ +package io.jans.chip.repository + +import android.content.Context +import android.util.Log +import io.jans.chip.model.OPConfiguration +import io.jans.chip.retrofit.ApiAdapter +import io.jans.chip.AppDatabase +import io.jans.chip.model.UserInfoResponse +import retrofit2.Response + +class UserInfoResponseRepository(context: Context) { + private val TAG = "UserInfoResponseRepository" + private val appDatabase = AppDatabase.getInstance(context); + var obtainedContext: Context = context + private var userInfoResponse: UserInfoResponse = UserInfoResponse(null) + suspend fun getUserInfo(accessToken: String?): UserInfoResponse { + try { + val opConfigurationList: List = + appDatabase.opConfigurationDao().getAll() + if (opConfigurationList.isEmpty()) { + userInfoResponse.isSuccessful = false + userInfoResponse.errorMessage = "OpenID configuration not found in database." + return userInfoResponse + } + val opConfiguration: OPConfiguration = opConfigurationList[0] + val response: Response = ApiAdapter.getInstance(opConfiguration.issuer) + .getUserInfo( + accessToken, + "Bearer $accessToken", + opConfiguration.userinfoEndpoint + ) + + if (response.code() != 200) { + userInfoResponse.isSuccessful = false + userInfoResponse.errorMessage = + "Error in fetching getUserInfo. Error code: ${response.code()}, Error Message: ${response.message()} " + return userInfoResponse + } + val responseFromAPI = response.body() + Log.d("getUserInfo Response :: getUserInfo ::", responseFromAPI.toString()) + if (!response.isSuccessful || responseFromAPI == null) { + userInfoResponse.isSuccessful = true + userInfoResponse.errorMessage = + "Error in fetching getUserInfo. Error code: ${response.code()}, Error Message: ${response.message()} " + return userInfoResponse + } + + val userInfoResponse = UserInfoResponse() + userInfoResponse.response = responseFromAPI + userInfoResponse.isSuccessful = true + + return userInfoResponse + } catch (e: Exception) { + Log.e(TAG, "Error in fetching getUserInfo :: " + e.message) + userInfoResponse.isSuccessful = false + userInfoResponse.errorMessage = "Error in fetching getUserInfo :: " + e.message + return userInfoResponse + //e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/APIInterface.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/APIInterface.kt new file mode 100644 index 00000000000..6abf39502ec --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/APIInterface.kt @@ -0,0 +1,175 @@ +package io.jans.chip.retrofit + +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.DCRequest +import io.jans.chip.model.DCResponse +import io.jans.chip.model.LoginResponse +import io.jans.chip.model.SSARegRequest +import io.jans.chip.model.TokenResponse +import io.jans.chip.model.appIntegrity.AppIntegrityResponse +import io.jans.chip.model.fido.assertion.option.AssertionOptionRequest +import io.jans.chip.model.fido.assertion.option.AssertionOptionResponse +import io.jans.chip.model.fido.assertion.result.AssertionResultRequest +import io.jans.chip.model.fido.attestation.option.AttestationOptionRequest +import io.jans.chip.model.fido.attestation.option.AttestationOptionResponse +import io.jans.chip.model.fido.attestation.result.AttestationResultRequest +import io.jans.chip.model.fido.config.FidoConfigurationResponse +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.Url + +interface APIInterface { + @GET + suspend fun getOPConfiguration(@Url url: String): Response + + @POST + suspend fun doDCR(@Body dcrRequest: DCRequest, @Url url: String?): Response + + @POST + suspend fun doDCR(@Body dcrRequest: SSARegRequest, @Url url: String?): Response + + @FormUrlEncoded + @POST + suspend fun getAuthorizationChallenge( + @Field("client_id") clientId: String?, + @Field("username") username: String?, + @Field("password") password: String?, + @Field("state") state: String?, + @Field("nonce") nonce: String?, + @Field("use_device_session") useDeviceSession: Boolean, + @Field("acr_values") acrValues: String?, + @Field("auth_method") authMethod: String?, + @Field("assertion_result_request") assertionResultRequest: String?, + @Url url: String? + ): Response + + @FormUrlEncoded + @POST + suspend fun getUserInfo( + @Field("access_token") accessToken: String?, + @Header("Authorization") authHeader: String?, + @Url url: String? + ): Response + + @FormUrlEncoded + @POST + suspend fun getToken( + @Field("client_id") clientId: String?, + @Field("code") code: String?, + @Field("grant_type") grantType: String?, + @Field("redirect_uri") redirectUri: String?, + @Field("scope") scope: String?, + @Header("Authorization") authHeader: String?, + @Header("DPoP") dpopJwt: String?, + @Url url: String? + ): Response + + @FormUrlEncoded + @POST + suspend fun logout( + @Field("token") token: String?, + @Field("token_type_hint") tokenTypeHint: String?, + @Header("Authorization") authHeader: String?, + @Url url: String? + ): Response + + @GET + suspend fun getFidoConfiguration(@Url url: String?): Response + + @POST + suspend fun attestationOption(@Body request: AttestationOptionRequest?, @Url url: String?): Response + + @POST + suspend fun attestationResult(@Body request: AttestationResultRequest?, @Url url: String?): Response + + @POST + suspend fun assertionOption(@Body request: AssertionOptionRequest?, @Url url: String?): Response + + @POST + suspend fun assertionResult(@Body request: AssertionResultRequest?, @Url url: String?): Response + + @GET + suspend fun verifyIntegrityTokenOnAppServer(@Url url: String?): Response? + + /*@GET + fun getFidoConfiguration(@Url url: String?): Call? + + @POST + fun doDCR(@Body dcrRequest: DCRequest?, @Url url: String?): Call? + + @FormUrlEncoded + @POST + fun getAuthorizationChallenge( + @Field("client_id") clientId: String?, + @Field("username") username: String?, + @Field("password") password: String?, + @Field("state") state: String?, + @Field("nonce") nonce: String?, + @Field("use_device_session") useDeviceSession: Boolean, + @Url url: String? + ): Call? + + @FormUrlEncoded + @POST + fun getToken( + @Field("client_id") clientId: String?, + @Field("code") code: String?, + @Field("grant_type") grantType: String?, + @Field("redirect_uri") redirectUri: String?, + @Field("scope") scope: String?, + @Header("Authorization") authHeader: String?, + @Header("DPoP") dpopJwt: String?, + @Url url: String? + ): Call? + + @FormUrlEncoded + @POST + fun getUserInfo( + @Field("access_token") accessToken: String?, + @Header("Authorization") authHeader: String?, + @Url url: String? + ): Call? + + @FormUrlEncoded + @POST + fun logout( + @Field("token") token: String?, + @Field("token_type_hint") tokenTypeHint: String?, + @Header("Authorization") authHeader: String?, + @Url url: String? + ): Call? + + // This API for verifying integrity token + @GET + fun verifyIntegrityTokenOnAppServer(@Url url: String?): Call? + + @POST + fun attestationOption( + @Body request: AttestationOptionRequest?, + @Url url: String? + ): Call? + + @POST + fun attestationResult( + @Body request: AttestationResultRequest?, + @Url url: String? + ): POST?>? + + @POST + fun assertionOption( + @Body request: AssertionOptionRequest?, + @Url url: String? + ): Call? + + @POST + fun assertionResult( + @Body request: AssertionResultRequest?, + @Url url: String? + ): Call?>?*/ +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/ApiAdapter.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/ApiAdapter.kt new file mode 100644 index 00000000000..73401e1ca82 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/ApiAdapter.kt @@ -0,0 +1,15 @@ +package io.jans.chip.retrofit + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object ApiAdapter { + fun getInstance(baseUrl: String?): APIInterface { + var apiInterface: APIInterface? = null + val retrofit = Retrofit.Builder().baseUrl(baseUrl) + .addConverterFactory(GsonConverterFactory.create()) + .build() + apiInterface = retrofit.create(APIInterface::class.java) + return apiInterface + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/RetrofitClient.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/RetrofitClient.java deleted file mode 100644 index 2007aaeadc5..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/retrofit/RetrofitClient.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.jans.chip.retrofit; - -import io.jans.chip.services.APIInterface; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class RetrofitClient { - private static RetrofitClient instance = null; - private APIInterface apiInterface; - - private RetrofitClient(String baseUrl) { - Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - apiInterface = retrofit.create(APIInterface.class); - } - - public static synchronized RetrofitClient getInstance(String baseUrl) { - if (instance == null) { - instance = new RetrofitClient(baseUrl); - } - return instance; - } - - public APIInterface getAPIInterface() { - return apiInterface; - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/services/APIInterface.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/services/APIInterface.java deleted file mode 100644 index 3b7627a8444..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/services/APIInterface.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.jans.chip.services; - -import io.jans.chip.modal.appIntegrity.AppIntegrityResponse; -import io.jans.chip.modal.DCRequest; -import io.jans.chip.modal.DCResponse; -import io.jans.chip.modal.LoginResponse; -import io.jans.chip.modal.OPConfiguration; -import io.jans.chip.modal.TokenResponse; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.Field; -import retrofit2.http.FormUrlEncoded; -import retrofit2.http.GET; -import retrofit2.http.Header; -import retrofit2.http.POST; -import retrofit2.http.Url; - -public interface APIInterface { - @GET - Call getOPConfiguration(@Url String url); - - @POST - Call doDCR(@Body DCRequest dcrRequest, @Url String url); - - @FormUrlEncoded - @POST - Call getAuthorizationChallenge (@Field("client_id") String clientId, - @Field("username") String username, - @Field("password") String password, - @Field("state") String state, - @Field("nonce") String nonce, - @Field("use_device_session") boolean useDeviceSession, - @Url String url); - @FormUrlEncoded - @POST - Call getToken (@Field("client_id") String clientId, - @Field("code") String code, - @Field("grant_type") String grantType, - @Field("redirect_uri") String redirectUri, - @Field("scope") String scope, - @Header("Authorization") String authHeader, - @Header("DPoP") String dpopJwt, - @Url String url); - @FormUrlEncoded - @POST - Call getUserInfo (@Field("access_token") String accessToken, @Header("Authorization") String authHeader, @Url String url); - - @FormUrlEncoded - @POST - Call logout (@Field("token") String token, @Field("token_type_hint") String tokenTypeHint, @Header("Authorization") String authHeader, @Url String url); - - // This API for verifying integrity token - @GET - Call verifyIntegrityTokenOnAppServer(@Url String url); -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Color.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Color.kt new file mode 100644 index 00000000000..0c0000d8c08 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Color.kt @@ -0,0 +1,67 @@ +package com.example.compose +import androidx.compose.ui.graphics.Color + +val md_theme_light_primary = Color(0xFF006D3D) +val md_theme_light_onPrimary = Color(0xFFFFFFFF) +val md_theme_light_primaryContainer = Color(0xFF97F7B7) +val md_theme_light_onPrimaryContainer = Color(0xFF00210F) +val md_theme_light_secondary = Color(0xFF4F6354) +val md_theme_light_onSecondary = Color(0xFFFFFFFF) +val md_theme_light_secondaryContainer = Color(0xFFD2E8D4) +val md_theme_light_onSecondaryContainer = Color(0xFF0D1F13) +val md_theme_light_tertiary = Color(0xFF3B6470) +val md_theme_light_onTertiary = Color(0xFFFFFFFF) +val md_theme_light_tertiaryContainer = Color(0xFFBEEAF7) +val md_theme_light_onTertiaryContainer = Color(0xFF001F26) +val md_theme_light_error = Color(0xFFBA1A1A) +val md_theme_light_errorContainer = Color(0xFFFFDAD6) +val md_theme_light_onError = Color(0xFFFFFFFF) +val md_theme_light_onErrorContainer = Color(0xFF410002) +val md_theme_light_background = Color(0xFFFBFDF8) +val md_theme_light_onBackground = Color(0xFF191C1A) +val md_theme_light_surface = Color(0xFFFBFDF8) +val md_theme_light_onSurface = Color(0xFF191C1A) +val md_theme_light_surfaceVariant = Color(0xFFDCE5DB) +val md_theme_light_onSurfaceVariant = Color(0xFF414942) +val md_theme_light_outline = Color(0xFF717971) +val md_theme_light_inverseOnSurface = Color(0xFFF0F1EC) +val md_theme_light_inverseSurface = Color(0xFF2E312E) +val md_theme_light_inversePrimary = Color(0xFF7BDA9C) +val md_theme_light_shadow = Color(0xFF000000) +val md_theme_light_surfaceTint = Color(0xFF006D3D) +val md_theme_light_outlineVariant = Color(0xFFC0C9BF) +val md_theme_light_scrim = Color(0xFF000000) + +val md_theme_dark_primary = Color(0xFF7BDA9C) +val md_theme_dark_onPrimary = Color(0xFF00391D) +val md_theme_dark_primaryContainer = Color(0xFF00522D) +val md_theme_dark_onPrimaryContainer = Color(0xFF97F7B7) +val md_theme_dark_secondary = Color(0xFFB6CCB9) +val md_theme_dark_onSecondary = Color(0xFF223527) +val md_theme_dark_secondaryContainer = Color(0xFF384B3D) +val md_theme_dark_onSecondaryContainer = Color(0xFFD2E8D4) +val md_theme_dark_tertiary = Color(0xFFA3CDDA) +val md_theme_dark_onTertiary = Color(0xFF023640) +val md_theme_dark_tertiaryContainer = Color(0xFF214C57) +val md_theme_dark_onTertiaryContainer = Color(0xFFBEEAF7) +val md_theme_dark_error = Color(0xFFFFB4AB) +val md_theme_dark_errorContainer = Color(0xFF93000A) +val md_theme_dark_onError = Color(0xFF690005) +val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) +val md_theme_dark_background = Color(0xFF191C1A) +val md_theme_dark_onBackground = Color(0xFFE1E3DE) +val md_theme_dark_surface = Color(0xFF191C1A) +val md_theme_dark_onSurface = Color(0xFFE1E3DE) +val md_theme_dark_surfaceVariant = Color(0xFF414942) +val md_theme_dark_onSurfaceVariant = Color(0xFFC0C9BF) +val md_theme_dark_outline = Color(0xFF8B938A) +val md_theme_dark_inverseOnSurface = Color(0xFF191C1A) +val md_theme_dark_inverseSurface = Color(0xFFE1E3DE) +val md_theme_dark_inversePrimary = Color(0xFF006D3D) +val md_theme_dark_shadow = Color(0xFF000000) +val md_theme_dark_surfaceTint = Color(0xFF7BDA9C) +val md_theme_dark_outlineVariant = Color(0xFF414942) +val md_theme_dark_scrim = Color(0xFF000000) + + +val seed = Color(0xFF006D3D) diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Theme.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Theme.kt new file mode 100644 index 00000000000..473f7588267 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Theme.kt @@ -0,0 +1,90 @@ +package com.example.compose + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable + + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary, + surfaceTint = md_theme_light_surfaceTint, + outlineVariant = md_theme_light_outlineVariant, + scrim = md_theme_light_scrim, +) + + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary, + surfaceTint = md_theme_dark_surfaceTint, + outlineVariant = md_theme_dark_outlineVariant, + scrim = md_theme_dark_scrim, +) + +@Composable +fun AppTheme( + useDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable() () -> Unit +) { + val colors = if (!useDarkTheme) { + LightColors + } else { + DarkColors + } + + MaterialTheme( + colorScheme = colors, + content = content + ) +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Type.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Type.kt new file mode 100644 index 00000000000..2ca555e7947 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/theme/Type.kt @@ -0,0 +1,34 @@ +package io.jans.fidokot.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/ButtonComposableViews.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/ButtonComposableViews.kt new file mode 100644 index 00000000000..31ffa596346 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/ButtonComposableViews.kt @@ -0,0 +1,62 @@ +package io.jans.chip.ui.common.customComposableViews + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.QuestionMark +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import io.jans.chip.ui.theme.AppTheme + +@Composable +fun NormalButton( + modifier: Modifier = Modifier, + text: String, + onClick: () -> Unit +) { + Button( + modifier = modifier + .height(AppTheme.dimens.normalButtonHeight) + .requiredWidth(AppTheme.dimens.minButtonWidth), + onClick = onClick + ) { + Text(text = text, style = MaterialTheme.typography.titleMedium) + } +} + +@Composable +fun SmallClickableWithIconAndText( + modifier: Modifier = Modifier, + iconVector: ImageVector = Icons.Outlined.QuestionMark, + iconContentDescription: String = "", + text: String = "", + onClick: () -> Unit +) { + Row( + modifier = modifier.clickable { + onClick.invoke() + }, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = iconVector, + contentDescription = iconContentDescription, + tint = MaterialTheme.colorScheme.primary + ) + Text( + modifier = Modifier.padding(start = AppTheme.dimens.paddingSmall), + text = text, + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary + ) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextComposableViews.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextComposableViews.kt new file mode 100644 index 00000000000..98daccf58de --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextComposableViews.kt @@ -0,0 +1,50 @@ +package io.jans.chip.ui.common.customComposableViews + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign + +@Composable +fun TitleText( + modifier: Modifier = Modifier, + text: String, + textAlign: TextAlign = TextAlign.Start +) { + Text( + modifier = modifier, + text = text, + textAlign = textAlign, + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.secondary + ) +} + +@Composable +fun MediumTitleText( + modifier: Modifier = Modifier, + text: String, + textAlign: TextAlign = TextAlign.Start +) { + Text( + modifier = modifier, + text = text, + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = textAlign + ) +} + +@Composable +fun ErrorTextInputField( + modifier: Modifier = Modifier, + text: String +) { + Text( + modifier = modifier, + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.error + ) +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextFieldComposables.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextFieldComposables.kt new file mode 100644 index 00000000000..167ef37f1e4 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/customComposableViews/TextFieldComposables.kt @@ -0,0 +1,162 @@ +package io.jans.chip.ui.common.customComposableViews + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.outlined.VisibilityOff +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import io.jans.jans_chip.R + +/** + * Password Text Field + */ +@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) +@Composable +fun PasswordTextField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + label: String, + isError: Boolean = false, + errorText: String = "", + imeAction: ImeAction = ImeAction.Done +) { + val keyboardController = LocalSoftwareKeyboardController.current + + var isPasswordVisible by remember { + mutableStateOf(false) + } + + OutlinedTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + label = { + Text(text = label) + }, + trailingIcon = { + IconButton(onClick = { + isPasswordVisible = !isPasswordVisible + }) { + + val visibleIconAndText = Pair( + first = Icons.Outlined.Visibility, + second = stringResource(id = R.string.icon_password_visible) + ) + + val hiddenIconAndText = Pair( + first = Icons.Outlined.VisibilityOff, + second = stringResource(id = R.string.icon_password_hidden) + ) + + val passwordVisibilityIconAndText = + if (isPasswordVisible) visibleIconAndText else hiddenIconAndText + + // Render Icon + Icon( + imageVector = passwordVisibilityIconAndText.first, + contentDescription = passwordVisibilityIconAndText.second + ) + } + }, + singleLine = true, + visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = imeAction + ), + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + }), + isError = isError, + supportingText = { + if (isError) { + ErrorTextInputField(text = errorText) + } + } + ) +} + +/** + * Email Text Field + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EmailTextField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + label: String, + isError: Boolean = false, + errorText: String = "", + imeAction: ImeAction = ImeAction.Next +) { + + OutlinedTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + label = { + Text(text = label) + }, + maxLines = 1, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = imeAction + ), + isError = isError, + supportingText = { + if (isError) { + ErrorTextInputField(text = errorText) + } + } + ) + +} + +/** + * Mobile Number Text Field + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MobileNumberTextField( + modifier: Modifier = Modifier, + value: String, + onValueChange: (String) -> Unit, + label: String, + isError: Boolean = false, + errorText: String = "", + imeAction: ImeAction = ImeAction.Next +) { + + OutlinedTextField( + modifier = modifier, + value = value, + onValueChange = onValueChange, + label = { + Text(text = label) + }, + maxLines = 1, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone, + imeAction = imeAction + ), + isError = isError, + supportingText = { + if (isError) { + ErrorTextInputField(text = errorText) + } + } + ) + +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/state/ErrorState.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/state/ErrorState.kt new file mode 100644 index 00000000000..f70dfcce4a8 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/common/state/ErrorState.kt @@ -0,0 +1,12 @@ +package io.jans.chip.ui.common.state + +import androidx.annotation.StringRes +import io.jans.jans_chip.R + +/** + * Error state holding values for error ui + */ +data class ErrorState( + val hasError: Boolean = false, + @StringRes val errorMessageStringResource: Int = R.string.empty_string +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NavigationRoutes.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NavigationRoutes.kt new file mode 100644 index 00000000000..fe65d1fefb0 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NavigationRoutes.kt @@ -0,0 +1,17 @@ +package io.jans.chip.ui.screens + +sealed class NavigationRoutes { + + // Unauthenticated Routes + sealed class Unauthenticated(val route: String) : NavigationRoutes() { + object NavigationRoute : Unauthenticated(route = "unauthenticated") + object Login : Unauthenticated(route = "login") + object Registration : Unauthenticated(route = "registration") + } + + // Authenticated Routes + sealed class Authenticated(val route: String) : NavigationRoutes() { + object NavigationRoute : Authenticated(route = "authenticated") + object Dashboard : Authenticated(route = "Dashboard") + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NestedNavigations.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NestedNavigations.kt new file mode 100644 index 00000000000..7832e7188f2 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/NestedNavigations.kt @@ -0,0 +1,77 @@ +package io.jans.chip.ui.screens + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.navigation +import io.jans.chip.ui.screens.dashboard.DashboardScreen +import io.jans.chip.ui.screens.unauthenticated.login.LoginScreen +import io.jans.chip.ui.screens.unauthenticated.registration.RegistrationScreen + +/** + * Login, registration, forgot password screens nav graph builder + * (Unauthenticated user) + */ +fun NavGraphBuilder.unauthenticatedGraph(navController: NavController) { + + navigation( + route = NavigationRoutes.Unauthenticated.NavigationRoute.route, + startDestination = NavigationRoutes.Unauthenticated.Registration.route + ) { + + // Login + composable(route = NavigationRoutes.Unauthenticated.Login.route) { + LoginScreen( + onNavigateToRegistration = { + navController.navigate(route = NavigationRoutes.Unauthenticated.Registration.route) + }, + onNavigateToAuthenticatedRoute = { + navController.navigate(route = NavigationRoutes.Authenticated.NavigationRoute.route) { + popUpTo(route = NavigationRoutes.Unauthenticated.NavigationRoute.route) { + inclusive = true + } + } + }, + ) + } + // Registration + composable(route = NavigationRoutes.Unauthenticated.Registration.route) { + RegistrationScreen( + onNavigateBack = { + //navController.navigateUp() + navController.navigate(route = NavigationRoutes.Unauthenticated.Login.route) + }, + onNavigateToAuthenticatedRoute = { + navController.navigate(route = NavigationRoutes.Authenticated.NavigationRoute.route) { + popUpTo(route = NavigationRoutes.Unauthenticated.NavigationRoute.route) { + inclusive = true + } + } + } + ) + } + } +} + +/** + * Authenticated screens nav graph builder + */ +fun NavGraphBuilder.authenticatedGraph(navController: NavController) { + navigation( + route = NavigationRoutes.Authenticated.NavigationRoute.route, + startDestination = NavigationRoutes.Authenticated.Dashboard.route + ) { + // Dashboard + composable(route = NavigationRoutes.Authenticated.Dashboard.route) { + DashboardScreen( + onNavigateToUnAuthenticatedRoute = { + navController.navigate(route = NavigationRoutes.Unauthenticated.NavigationRoute.route) { + popUpTo(route = NavigationRoutes.Authenticated.NavigationRoute.route) { + inclusive = true + } + } + } + ) + } + } +} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/dashboard/DashboardScreen.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/dashboard/DashboardScreen.kt new file mode 100644 index 00000000000..bbe1f7b32c4 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/dashboard/DashboardScreen.kt @@ -0,0 +1,107 @@ +package io.jans.chip.ui.screens.dashboard + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.viewmodel.compose.viewModel +import com.spr.jetpack_loading.components.indicators.lineScaleIndicator.LineScaleIndicator +import com.spr.jetpack_loading.enums.PunchType +import io.jans.chip.AppAlertDialog +import io.jans.chip.LogButton +import io.jans.chip.UserInfoRow +import io.jans.chip.model.LogoutResponse +import io.jans.chip.ui.common.customComposableViews.TitleText +import io.jans.chip.ui.screens.unauthenticated.login.LoginViewModel +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginUiEvent +import io.jans.chip.viewmodel.MainViewModel +import io.jans.jans_chip.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import org.json.JSONObject + +@Composable +fun DashboardScreen( + loginViewModel: LoginViewModel = viewModel(), + onNavigateToUnAuthenticatedRoute: () -> Unit +) { + val context = LocalContext.current as FragmentActivity + var loading by remember { mutableStateOf(false) } + val shouldShowDialog = remember { mutableStateOf(false) } + val dialogContent = remember { mutableStateOf("") } + val loginState by remember { + loginViewModel.loginState + } + val mainViewModel = MainViewModel.getInstance(context) + AppAlertDialog( + shouldShowDialog = shouldShowDialog, + content = dialogContent + ) + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + + if (mainViewModel.clientRegistered && (mainViewModel.attestationOptionResponse || mainViewModel.assertionOptionResponse)) { + if (mainViewModel.userIsAuthenticated) { + TitleText(text = stringResource(id = R.string.dashboard_title_welcome) + " " + mainViewModel.getUsername()) + if (mainViewModel.getUserInfoResponse().response != null) { + val userInfo = JSONObject(mainViewModel.getUserInfoResponse().response.toString()) + val keys = userInfo.keys() + while (keys.hasNext()) { + val key: String = keys.next() + UserInfoRow(key, userInfo.get(key).toString()) + } + } + LogButton( + isClickable = true, + text = stringResource(R.string.logout), + onClick = { + CoroutineScope(Dispatchers.Main).launch { + loading = true + val logoutResponse: LogoutResponse = + async { mainViewModel.logout() }.await() + if (logoutResponse.isSuccessful != true) { + shouldShowDialog.value = true + dialogContent.value = logoutResponse.errorMessage.toString() + } + loginViewModel.onUiEvent(loginUiEvent = LoginUiEvent.Logout) + onNavigateToUnAuthenticatedRoute.invoke() + loading = false + } + }, + ) + } + } + if (loading) { + Spacer(modifier = Modifier.height(40.dp)) + LineScaleIndicator( + color = Color(0xFF134520), + rectCount = 5, + distanceOnXAxis = 30f, + lineHeight = 100, + animationDuration = 500, + minScale = 0.3f, + maxScale = 1.5f, + punchType = PunchType.RANDOM_PUNCH, + penThickness = 15f + ) + } + } +} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginInputs.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginInputs.kt new file mode 100644 index 00000000000..1493664fa16 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginInputs.kt @@ -0,0 +1,24 @@ +package io.jans.chip.ui.screens.unauthenticated.login + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import io.jans.chip.ElevatedCardExample +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginState + +@Composable +fun LoginInputs( + loginState: LoginState, + heading: String, + subheading: String, + @DrawableRes icon: Int, + onContinueClick: () -> Unit, +) { + + // Login Inputs Section + ElevatedCardExample( + heading = heading, + subheading = subheading, + icon = icon, + onButtonClick = onContinueClick + ) +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginScreen.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginScreen.kt new file mode 100644 index 00000000000..572716bcd02 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginScreen.kt @@ -0,0 +1,368 @@ +package io.jans.chip.ui.screens.unauthenticated.login + +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import coil.request.ImageRequest +import coil.size.Scale +import com.google.gson.Gson +import com.spr.jetpack_loading.components.indicators.lineScaleIndicator.LineScaleIndicator +import com.spr.jetpack_loading.enums.PunchType +import io.jans.chip.AppAlertDialog +import io.jans.chip.common.AuthAdaptor +import io.jans.chip.common.LocalCredentialSelector +import io.jans.chip.model.LoginResponse +import io.jans.chip.model.TokenResponse +import io.jans.chip.model.UserInfoResponse +import io.jans.chip.model.fido.assertion.option.AssertionOptionResponse +import io.jans.chip.model.fido.assertion.result.AssertionResultRequest +import io.jans.chip.ui.common.customComposableViews.MediumTitleText +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginUiEvent +import io.jans.chip.ui.theme.AppTheme +import io.jans.chip.ui.theme.Janschip1Theme +import io.jans.chip.utils.biometric.BiometricHelper +import io.jans.chip.viewmodel.MainViewModel +import io.jans.jans_chip.R +import io.jans.webauthn.models.PublicKeyCredentialSource +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch + +@Composable +fun LoginScreen( + loginViewModel: LoginViewModel = viewModel(), + onNavigateToRegistration: () -> Unit, + onNavigateToAuthenticatedRoute: () -> Unit +) { + val context = LocalContext.current as FragmentActivity + val mainViewModel = MainViewModel.getInstance(context) + val isBiometricAvailable = remember { BiometricHelper.isBiometricAvailable(context) } + val authAdaptor = AuthAdaptor(context) + val shouldShowDialog = remember { mutableStateOf(false) } + val dialogContent = remember { mutableStateOf("") } + val creds: List? = + authAdaptor.getAllCredentials() + var loginState by remember { + loginViewModel.loginState + } + + if (loginState.isLoginSuccessful) { + /** + * Navigate to Authenticated navigation route + * once login is successful + */ + LaunchedEffect(key1 = true) { + onNavigateToAuthenticatedRoute.invoke() + } + } else { + AppAlertDialog( + shouldShowDialog = shouldShowDialog, + content = dialogContent + ) + if (loginState.isLoading) { + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .height(400.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + LineScaleIndicator( + color = Color(0xFF134520), + rectCount = 5, + distanceOnXAxis = 30f, + lineHeight = 100, + animationDuration = 500, + minScale = 0.3f, + maxScale = 1.5f, + punchType = PunchType.RANDOM_PUNCH, + penThickness = 15f + ) + } + } else { + // Full Screen Content + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Register Section + Row( + modifier = Modifier.padding(AppTheme.dimens.paddingNormal), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + // Don't have an account? + Text(text = stringResource(id = R.string.do_not_have_account)) + + //Register + Text( + modifier = Modifier + .padding(start = AppTheme.dimens.paddingExtraSmall) + .clickable { + onNavigateToRegistration.invoke() + }, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.enrol), + color = MaterialTheme.colorScheme.primary + ) + } + + // Main card Content for Login + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .padding(AppTheme.dimens.paddingLarge) + ) { + Column( + modifier = Modifier + .padding(horizontal = AppTheme.dimens.paddingLarge) + .padding(bottom = AppTheme.dimens.paddingExtraLarge) + ) { + + // Heading Jetpack Compose + MediumTitleText( + modifier = Modifier + .padding(top = AppTheme.dimens.paddingLarge) + .fillMaxWidth(), + text = stringResource(id = R.string.janssen), + textAlign = TextAlign.Center + ) + + // Login Logo + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .height(128.dp) + .padding(top = AppTheme.dimens.paddingSmall), + model = ImageRequest.Builder(LocalContext.current) + .data(data = R.drawable.janssen_logo) + .crossfade(enable = true) + .scale(Scale.FILL) + .build(), + contentDescription = stringResource(id = R.string.use_saved_passkey) + ) + // Heading Login + // Login Inputs Composable + if (creds == null || creds.isEmpty()) { + MediumTitleText( + modifier = Modifier.padding(top = AppTheme.dimens.paddingLarge), + text = stringResource(id = R.string.no_passkey_enrolled) + ) + } else { + MediumTitleText( + modifier = Modifier.padding(top = AppTheme.dimens.paddingLarge), + text = stringResource(id = R.string.use_saved_passkey) + ) + } + Column( + modifier = Modifier + .fillMaxSize() + .wrapContentSize(unbounded = true) + + ) { + creds?.forEach { ele -> + LoginInputs( + loginState = loginState, + heading = ele.userDisplayName, + subheading = ele.rpId, + icon = R.drawable.passkey_icon, + onContinueClick = { + + if (isBiometricAvailable) { + CoroutineScope(Dispatchers.Main).launch { + loginState = loginState.copy(isLoading = true) + val fidoConfiguration = + async { mainViewModel.fetchFidoConfiguration() }.await() + if (fidoConfiguration?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + fidoConfiguration.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + val assertionOptionResponse: AssertionOptionResponse? = + async { mainViewModel.assertionOption(ele.userDisplayName) }.await() + if (assertionOptionResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + assertionOptionResponse.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + val authAdaptor = AuthAdaptor(context) + + val selectedPublicKeyCredentialSource = async { + authAdaptor.selectPublicKeyCredentialSource( + LocalCredentialSelector(), + assertionOptionResponse, + fidoConfiguration?.issuer, + ) + }.await() + + val signature = + async { + authAdaptor.generateSignature( + selectedPublicKeyCredentialSource + ) + }.await() + + BiometricHelper.authenticateUser(context, + signature!!, + onSuccess = { plainText -> + CoroutineScope(Dispatchers.Main).launch { + mainViewModel.setUsername(ele.userDisplayName) + if (assertionOptionResponse != null) { + val assertionResultRequest: AssertionResultRequest = + async { + authAdaptor.authenticate( + assertionOptionResponse, + fidoConfiguration?.issuer, + selectedPublicKeyCredentialSource + ) + }.await() + if (assertionResultRequest.isSuccessful == false) { + shouldShowDialog.value = + true + dialogContent.value = + assertionResultRequest.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + + + val loginResponse: LoginResponse? = + async { + mainViewModel.processlogin( + ele.userDisplayName, + null, + "authenticate", + Gson().toJson( + assertionResultRequest + ) + ) + }.await() + + if (loginResponse?.isSuccessful == false) { + shouldShowDialog.value = + true + dialogContent.value = + loginResponse.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + val tokenResponse: TokenResponse? = + async { + mainViewModel.getToken( + loginResponse?.authorizationCode, + ) + }.await() + if (tokenResponse?.isSuccessful == false) { + shouldShowDialog.value = + true + dialogContent.value = + tokenResponse.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + val userInfoResponse: UserInfoResponse? = + async { + mainViewModel.getUserInfo( + tokenResponse?.accessToken + ) + }.await() + if (userInfoResponse != null) { + mainViewModel.setUserInfoResponse( + userInfoResponse + ) + } + if (userInfoResponse?.isSuccessful == false) { + shouldShowDialog.value = + true + dialogContent.value = + userInfoResponse.errorMessage.toString() + loginState = loginState.copy(isLoading = false) + return@launch + } + } + mainViewModel.assertionOptionResponse = + true + loginViewModel.onUiEvent( + loginUiEvent = LoginUiEvent.Submit + ) + loginState = loginState.copy(isLoading = false) + //Toast.makeText(context,"Biometric authentication successful!$plainText", Toast.LENGTH_SHORT).show() + } + }) + } + //end + } else { + Toast.makeText( + context, + "Biometric authentication is not available!", + Toast.LENGTH_SHORT + ).show() + loginState = loginState.copy(isLoading = false) + } + }) + } + } + } + } + } + } + + } + +} + +@Preview(showBackground = true) +@Composable +fun PreviewLoginScreen() { + Janschip1Theme { + LoginScreen( + onNavigateToRegistration = {}, + onNavigateToAuthenticatedRoute = {} + ) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginViewModel.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginViewModel.kt new file mode 100644 index 00000000000..92e21089444 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/LoginViewModel.kt @@ -0,0 +1,112 @@ +package io.jans.chip.ui.screens.unauthenticated.login + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginErrorState +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginState +import io.jans.chip.ui.screens.unauthenticated.login.state.LoginUiEvent +import io.jans.chip.ui.screens.unauthenticated.login.state.emailOrMobileEmptyErrorState +import io.jans.chip.ui.screens.unauthenticated.login.state.passwordEmptyErrorState + +/** + * ViewModel for Login Screen + */ +class LoginViewModel : ViewModel() { + + var loginState = mutableStateOf(LoginState()) + private set + + /** + * Function called on any login event [LoginUiEvent] + */ + fun onUiEvent(loginUiEvent: LoginUiEvent) { + when (loginUiEvent) { + + // Email/Mobile changed + /*is LoginUiEvent.EmailOrMobileChanged -> { + loginState.value = loginState.value.copy( + emailOrMobile = loginUiEvent.inputValue, + errorState = loginState.value.errorState.copy( + emailOrMobileErrorState = if (loginUiEvent.inputValue.trim().isNotEmpty()) + ErrorState() + else + emailOrMobileEmptyErrorState + ) + ) + } + + // Password changed + is LoginUiEvent.PasswordChanged -> { + loginState.value = loginState.value.copy( + password = loginUiEvent.inputValue, + errorState = loginState.value.errorState.copy( + passwordErrorState = if (loginUiEvent.inputValue.trim().isNotEmpty()) + ErrorState() + else + passwordEmptyErrorState + ) + ) + }*/ + + // Submit Login + is LoginUiEvent.Submit -> { + //val inputsValidated = validateInputs() + //if (inputsValidated) { + // TODO Trigger login in authentication flow + loginState.value = loginState.value.copy(isLoginSuccessful = true) + //} + } + + is LoginUiEvent.Logout -> { + //val inputsValidated = validateInputs() + //if (inputsValidated) { + // TODO Trigger login in authentication flow + loginState.value = loginState.value.copy(isLoginSuccessful = false) + //} + } + + else -> {} + } + } + + /** + * Function to validate inputs + * Ideally it should be on domain layer (usecase) + * @return true -> inputs are valid + * @return false -> inputs are invalid + */ + private fun validateInputs(): Boolean { + val username = loginState.value.username.trim() + val passwordString = loginState.value.password + return when { + + // Email/Mobile empty + username.isEmpty() -> { + loginState.value = loginState.value.copy( + errorState = LoginErrorState( + emailOrMobileErrorState = emailOrMobileEmptyErrorState + ) + ) + false + } + + //Password Empty + passwordString.isEmpty() -> { + loginState.value = loginState.value.copy( + errorState = LoginErrorState( + passwordErrorState = passwordEmptyErrorState + ) + ) + false + } + + // No errors + else -> { + // Set default error state + loginState.value = loginState.value.copy(errorState = LoginErrorState()) + true + } + } + } + +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginState.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginState.kt new file mode 100644 index 00000000000..dec4062df3d --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginState.kt @@ -0,0 +1,24 @@ +package io.jans.chip.ui.screens.unauthenticated.login.state + +import io.jans.chip.ui.common.state.ErrorState + +/** + * Login State holding ui input values + */ +data class LoginState( + val username: String = "", + val password: String = "", + val errorState: LoginErrorState = LoginErrorState(), + val isLoginSuccessful: Boolean = false, + val isLoading: Boolean = false +) + +/** + * Error state in login holding respective + * text field validation errors + */ +data class LoginErrorState( + val emailOrMobileErrorState: ErrorState = ErrorState(), + val passwordErrorState: ErrorState = ErrorState() +) + diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUiEvent.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUiEvent.kt new file mode 100644 index 00000000000..0142953b52c --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUiEvent.kt @@ -0,0 +1,9 @@ +package io.jans.chip.ui.screens.unauthenticated.login.state + +/** + * Login Screen Events + */ +sealed class LoginUiEvent { + object Submit : LoginUiEvent() + object Logout : LoginUiEvent() +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUtils.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUtils.kt new file mode 100644 index 00000000000..7335e1c43e8 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/login/state/LoginUtils.kt @@ -0,0 +1,14 @@ +package io.jans.chip.ui.screens.unauthenticated.login.state + +import io.jans.jans_chip.R +import io.jans.chip.ui.common.state.ErrorState + +val emailOrMobileEmptyErrorState = ErrorState( + hasError = true, + errorMessageStringResource = R.string.login_error_msg_empty_email_mobile +) + +val passwordEmptyErrorState = ErrorState( + hasError = true, + errorMessageStringResource = R.string.login_error_msg_empty_password +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationInputs.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationInputs.kt new file mode 100644 index 00000000000..69e2951cb88 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationInputs.kt @@ -0,0 +1,64 @@ +package io.jans.chip.ui.screens.unauthenticated.registration + +import android.util.Log +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import io.jans.chip.ui.common.customComposableViews.EmailTextField +import io.jans.chip.ui.common.customComposableViews.NormalButton +import io.jans.chip.ui.common.customComposableViews.PasswordTextField +import io.jans.chip.ui.screens.unauthenticated.registration.state.RegistrationState +import io.jans.chip.ui.theme.AppTheme +import io.jans.jans_chip.R + +@Composable +fun RegistrationInputs( + registrationState: RegistrationState, + onEmailIdChange: (String) -> Unit, + //onMobileNumberChange: (String) -> Unit, + onPasswordChange: (String) -> Unit, + //onConfirmPasswordChange: (String) -> Unit, + onSubmit: () -> Unit, +) { + // Login Inputs Section + Column(modifier = Modifier.fillMaxWidth()) { + // Email ID + EmailTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = AppTheme.dimens.paddingLarge), + value = registrationState.username, + onValueChange = onEmailIdChange, + label = stringResource(id = R.string.username), + isError = registrationState.errorState.emailIdErrorState.hasError, + errorText = stringResource(id = registrationState.errorState.emailIdErrorState.errorMessageStringResource), + imeAction = ImeAction.Next + ) + + // Password + PasswordTextField( + modifier = Modifier + .fillMaxWidth() + .padding(top = AppTheme.dimens.paddingLarge), + value = registrationState.password, + onValueChange = onPasswordChange, + label = stringResource(id = R.string.registration_password_label), + isError = registrationState.errorState.passwordErrorState.hasError, + errorText = stringResource(id = registrationState.errorState.passwordErrorState.errorMessageStringResource), + imeAction = ImeAction.Next + ) + + // Registration Submit Button + NormalButton( + modifier = Modifier.padding(top = AppTheme.dimens.paddingExtraLarge), + text = stringResource(id = R.string.login_button_text), + onClick = onSubmit + ) + + + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationScreen.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationScreen.kt new file mode 100644 index 00000000000..19cfcfe272f --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationScreen.kt @@ -0,0 +1,391 @@ +package io.jans.chip.ui.screens.unauthenticated.registration + +import android.widget.Toast +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowBack +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import coil.request.ImageRequest +import coil.size.Scale +import com.spr.jetpack_loading.components.indicators.lineScaleIndicator.LineScaleIndicator +import com.spr.jetpack_loading.enums.PunchType +import io.jans.chip.AppAlertDialog +import io.jans.chip.common.AuthAdaptor +import io.jans.chip.model.LoginResponse +import io.jans.chip.model.TokenResponse +import io.jans.chip.model.UserInfoResponse +import io.jans.chip.model.fido.attestation.option.AttestationOptionResponse +import io.jans.chip.model.fido.attestation.result.AttestationResultRequest +import io.jans.chip.model.fido.config.FidoConfigurationResponse +import io.jans.chip.ui.common.customComposableViews.MediumTitleText +import io.jans.chip.ui.common.customComposableViews.SmallClickableWithIconAndText +import io.jans.chip.ui.common.customComposableViews.TitleText +import io.jans.chip.ui.screens.unauthenticated.registration.state.RegistrationUiEvent +import io.jans.chip.ui.theme.AppTheme +import io.jans.chip.ui.theme.Janschip1Theme +import io.jans.chip.utils.biometric.BiometricHelper +import io.jans.chip.viewmodel.MainViewModel +import io.jans.jans_chip.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch + +@Composable +fun RegistrationScreen( + registrationViewModel: RegistrationViewModel = viewModel(), + onNavigateBack: () -> Unit, + onNavigateToAuthenticatedRoute: () -> Unit +) { + val context = LocalContext.current as FragmentActivity + val isBiometricAvailable = remember { BiometricHelper.isBiometricAvailable(context) } + val shouldShowDialog = remember { mutableStateOf(false) } + val dialogContent = remember { mutableStateOf("") } + + val mainViewModel = MainViewModel.getInstance(context) + var registrationState by remember { + registrationViewModel.registrationState + } + + if (registrationState.isRegistrationSuccessful) { + LaunchedEffect(key1 = true) { + onNavigateToAuthenticatedRoute.invoke() + } + } else { + // Full Screen Content + AppAlertDialog( + shouldShowDialog = shouldShowDialog, + content = dialogContent + ) + if (registrationState.isLoading) { + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .height(400.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + LineScaleIndicator( + color = Color(0xFF134520), + rectCount = 5, + distanceOnXAxis = 30f, + lineHeight = 100, + animationDuration = 500, + minScale = 0.3f, + maxScale = 1.5f, + punchType = PunchType.RANDOM_PUNCH, + penThickness = 15f + ) + } + } else { + Column( + modifier = Modifier + .fillMaxSize() + .navigationBarsPadding() + .imePadding() + .verticalScroll(rememberScrollState()) + ) { + + // Back Button Icon + SmallClickableWithIconAndText( + modifier = Modifier + .padding(horizontal = AppTheme.dimens.paddingLarge) + .padding(top = AppTheme.dimens.paddingLarge), + iconContentDescription = stringResource(id = R.string.navigate_back), + iconVector = Icons.Outlined.ArrowBack, + text = stringResource(id = R.string.back_to_login), + onClick = onNavigateBack + ) + + // Main card Content for Registration + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .padding(AppTheme.dimens.paddingLarge) + ) { + + Column( + modifier = Modifier + .padding(horizontal = AppTheme.dimens.paddingLarge) + .padding(bottom = AppTheme.dimens.paddingExtraLarge) + ) { + // Heading Jetpack Compose + MediumTitleText( + modifier = Modifier + .padding(top = AppTheme.dimens.paddingLarge) + .fillMaxWidth(), + text = stringResource(id = R.string.janssen), + textAlign = TextAlign.Center + ) + + // Logo + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .height(128.dp) + .padding(top = AppTheme.dimens.paddingSmall), + model = ImageRequest.Builder(LocalContext.current) + .data(data = R.drawable.janssen_logo) + .crossfade(enable = true) + .scale(Scale.FILL) + .build(), + contentDescription = stringResource(id = R.string.enrol_account) + ) + // Heading Registration + TitleText( + modifier = Modifier.padding(top = AppTheme.dimens.paddingLarge), + text = stringResource(id = R.string.enrol_account) + ) + + /** + * Registration Inputs Composable + */ + RegistrationInputs( + registrationState = registrationState, + + onEmailIdChange = { inputString -> + val result = registrationViewModel.onUiEvent( + registrationUiEvent = RegistrationUiEvent.UsernameChanged( + inputValue = inputString + ) + ) + if (result) { + mainViewModel.setUsername(inputString) + } + }, + + onPasswordChange = { inputString -> + val result = registrationViewModel.onUiEvent( + registrationUiEvent = RegistrationUiEvent.PasswordChanged( + inputValue = inputString + ) + ) + if (result) { + mainViewModel.setPassword(inputString) + } + }, + + onSubmit = { + val authAdaptor = AuthAdaptor(context) + if(authAdaptor.isCredentialsPresent(mainViewModel.getUsername())) { + shouldShowDialog.value = true + dialogContent.value = "Username already enrolled!" + return@RegistrationInputs + } + if (isBiometricAvailable) { + CoroutineScope(Dispatchers.Main).launch { + registrationState = registrationState.copy(isLoading = true) + val loginResponse: LoginResponse? = async { + mainViewModel.processlogin( + mainViewModel.getUsername(), + mainViewModel.getPassword(), + "enroll", + null + ) + }.await() + + if (loginResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + loginResponse.errorMessage.toString() + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + val tokenResponse: TokenResponse? = async { + mainViewModel.getToken( + loginResponse?.authorizationCode, + ) + }.await() + if (tokenResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + tokenResponse.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + val userInfoResponse: UserInfoResponse? = + async { mainViewModel.getUserInfo(tokenResponse?.accessToken) }.await() + if (userInfoResponse != null) { + mainViewModel.setUserInfoResponse( + userInfoResponse + ) + } + if (userInfoResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + userInfoResponse.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + + val fidoConfiguration: FidoConfigurationResponse? = + async { mainViewModel.fetchFidoConfiguration() }.await() + if (fidoConfiguration?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + fidoConfiguration?.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + val attestationOptionResponse: AttestationOptionResponse? = + async { + mainViewModel.attestationOption( + mainViewModel.getUsername() + ) + }.await() + if (attestationOptionResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + attestationOptionResponse?.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + + val publicKeyCredentialSource = async { + authAdaptor.getPublicKeyCredentialSource( + attestationOptionResponse, + fidoConfiguration?.issuer + ) + }.await() + val signature = + async { + authAdaptor.generateSignature( + publicKeyCredentialSource + ) + }.await() + + BiometricHelper.registerUserBiometrics(context, + signature!!, + onSuccess = { plainText -> + CoroutineScope(Dispatchers.Main).launch { + + val attestationResultRequest: AttestationResultRequest? = + async { + authAdaptor.register( + attestationOptionResponse, + fidoConfiguration?.issuer, + publicKeyCredentialSource + ) + }.await() + if (attestationResultRequest?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + attestationResultRequest.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + + val attestationResultResponse = async { + mainViewModel.attestationResult( + attestationResultRequest + ) + }.await() + if (attestationResultResponse?.isSuccessful == false) { + shouldShowDialog.value = true + dialogContent.value = + attestationResultResponse.errorMessage.toString() + + registrationState = + registrationState.copy(isLoading = false) + return@launch + } + mainViewModel.attestationOptionResponse = true + + registrationViewModel.onUiEvent( + registrationUiEvent = RegistrationUiEvent.Submit + ) + + registrationState = + registrationState.copy(isLoading = false) + } + }) + } + } else { + Toast.makeText( + context, + "Biometric authentication is not available!", + Toast.LENGTH_SHORT + ).show() + registrationState = + registrationState.copy(isLoading = false) + } + } + ) + Row( + modifier = Modifier.padding(AppTheme.dimens.paddingNormal), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + // Don't have an account? + Text(text = stringResource(id = R.string.already_have_login)) + + //Register + Text( + modifier = Modifier + .padding(start = AppTheme.dimens.paddingExtraSmall) + .clickable { onNavigateBack.invoke() }, + fontWeight = FontWeight.Bold, + text = stringResource(id = R.string.login), + color = MaterialTheme.colorScheme.primary + ) + } + } + } + } + } + } + +} + +@Preview(showBackground = true) +@Composable +fun PreviewRegistrationScreen() { + Janschip1Theme { + RegistrationScreen(onNavigateBack = {}, onNavigateToAuthenticatedRoute = {}) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationViewModel.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationViewModel.kt new file mode 100644 index 00000000000..6cd32c54142 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/RegistrationViewModel.kt @@ -0,0 +1,117 @@ +package io.jans.chip.ui.screens.unauthenticated.registration + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import io.jans.chip.ui.common.state.ErrorState +import io.jans.chip.ui.screens.unauthenticated.registration.state.RegistrationErrorState +import io.jans.chip.ui.screens.unauthenticated.registration.state.RegistrationState +import io.jans.chip.ui.screens.unauthenticated.registration.state.RegistrationUiEvent +import io.jans.chip.ui.screens.unauthenticated.registration.state.emailEmptyErrorState +import io.jans.chip.ui.screens.unauthenticated.registration.state.passwordEmptyErrorState + +class RegistrationViewModel : ViewModel() { + + var registrationState = mutableStateOf(RegistrationState()) + private set + + /** + * Function called on any login event [RegistrationUiEvent] + */ + fun onUiEvent(registrationUiEvent: RegistrationUiEvent): Boolean { + when (registrationUiEvent) { + + // Email id changed event + is RegistrationUiEvent.UsernameChanged -> { + registrationState.value = registrationState.value.copy( + username = registrationUiEvent.inputValue, + errorState = registrationState.value.errorState.copy( + emailIdErrorState = if (registrationUiEvent.inputValue.trim().isEmpty()) { + // Email id empty state + emailEmptyErrorState + } else { + // Valid state + ErrorState() + } + + ) + ) + } + + // Password changed event + is RegistrationUiEvent.PasswordChanged -> { + registrationState.value = registrationState.value.copy( + password = registrationUiEvent.inputValue, + errorState = registrationState.value.errorState.copy( + passwordErrorState = if (registrationUiEvent.inputValue.trim().isEmpty()) { + // Password Empty state + passwordEmptyErrorState + } else { + // Valid state + ErrorState() + } + + ) + ) + } + + // Submit Registration event + is RegistrationUiEvent.Submit -> { + val inputsValidated = validateInputs() + if (inputsValidated) { + // TODO Trigger registration in authentication flow + registrationState.value = + registrationState.value.copy(isRegistrationSuccessful = true) + } else { + return false + } + } + + else -> { return false} + } + return true + } + + /** + * Function to validate inputs + * Ideally it should be on domain layer (usecase) + * @return true -> inputs are valid + * @return false -> inputs are invalid + */ + private fun validateInputs(): Boolean { + val username = registrationState.value.username.trim() + //val mobileNumberString = registrationState.value.mobileNumber.trim() + val passwordString = registrationState.value.password.trim() + //val confirmPasswordString = registrationState.value.confirmPassword.trim() + + return when { + + // Email empty + username.isEmpty() -> { + registrationState.value = registrationState.value.copy( + errorState = RegistrationErrorState( + emailIdErrorState = emailEmptyErrorState + ) + ) + false + } + + //Password Empty + passwordString.isEmpty() -> { + registrationState.value = registrationState.value.copy( + errorState = RegistrationErrorState( + passwordErrorState = passwordEmptyErrorState + ) + ) + false + } + + // No errors + else -> { + // Set default error state + registrationState.value = + registrationState.value.copy(errorState = RegistrationErrorState()) + true + } + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationState.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationState.kt new file mode 100644 index 00000000000..ab10eb09d5e --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationState.kt @@ -0,0 +1,23 @@ +package io.jans.chip.ui.screens.unauthenticated.registration.state + +import io.jans.chip.ui.common.state.ErrorState + +/** + * Registration State holding ui input values + */ +data class RegistrationState( + val username: String = "", + val password: String = "", + val errorState: RegistrationErrorState = RegistrationErrorState(), + val isRegistrationSuccessful: Boolean = false, + val isLoading: Boolean = false +) + +/** + * Error state in registration holding respective + * text field validation errors + */ +data class RegistrationErrorState( + val emailIdErrorState: ErrorState = ErrorState(), + val passwordErrorState: ErrorState = ErrorState(), +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUiEvent.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUiEvent.kt new file mode 100644 index 00000000000..142d93f3a69 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUiEvent.kt @@ -0,0 +1,11 @@ +package io.jans.chip.ui.screens.unauthenticated.registration.state + +/** + * Registration Screen Events + */ +sealed class RegistrationUiEvent { + data class UsernameChanged(val inputValue: String) : RegistrationUiEvent() + data class PasswordChanged(val inputValue: String) : RegistrationUiEvent() + object Submit : RegistrationUiEvent() + object Loading : RegistrationUiEvent() +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUtils.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUtils.kt new file mode 100644 index 00000000000..0be53037488 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/screens/unauthenticated/registration/state/RegistrationUtils.kt @@ -0,0 +1,19 @@ +package io.jans.chip.ui.screens.unauthenticated.registration.state + +import io.jans.jans_chip.R +import io.jans.chip.ui.common.state.ErrorState + +val emailEmptyErrorState = ErrorState( + hasError = true, + errorMessageStringResource = R.string.registration_error_msg_empty_username +) + +val passwordEmptyErrorState = ErrorState( + hasError = true, + errorMessageStringResource = R.string.registration_error_msg_empty_password +) + +val passwordMismatchErrorState = ErrorState( + hasError = true, + errorMessageStringResource = R.string.registration_error_msg_password_mismatch +) diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Color.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Color.kt new file mode 100644 index 00000000000..8e7dbc992fb --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package io.jans.chip.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Dimensions.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Dimensions.kt new file mode 100644 index 00000000000..3742b844693 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Dimensions.kt @@ -0,0 +1,34 @@ +package io.jans.chip.ui.theme + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +interface Dimensions { + val paddingTooSmall: Dp + val paddingExtraSmall: Dp + val paddingSmall: Dp + val paddingNormal: Dp + val paddingLarge: Dp + val paddingExtraLarge: Dp + val normalButtonHeight: Dp + val minButtonWidth: Dp +} + +val normalDimensions: Dimensions = object : Dimensions { + override val paddingTooSmall: Dp + get() = 2.dp + override val paddingExtraSmall: Dp + get() = 4.dp + override val paddingSmall: Dp + get() = 8.dp + override val paddingNormal: Dp + get() = 16.dp + override val paddingLarge: Dp + get() = 24.dp + override val paddingExtraLarge: Dp + get() = 32.dp + override val normalButtonHeight: Dp + get() = 56.dp + override val minButtonWidth: Dp + get() = 120.dp +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Theme.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Theme.kt new file mode 100644 index 00000000000..fbf71c637e7 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Theme.kt @@ -0,0 +1,127 @@ +package io.jans.chip.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.ViewCompat +import com.example.compose.* + +private val LightColors = lightColorScheme( + primary = md_theme_light_primary, + onPrimary = md_theme_light_onPrimary, + primaryContainer = md_theme_light_primaryContainer, + onPrimaryContainer = md_theme_light_onPrimaryContainer, + secondary = md_theme_light_secondary, + onSecondary = md_theme_light_onSecondary, + secondaryContainer = md_theme_light_secondaryContainer, + onSecondaryContainer = md_theme_light_onSecondaryContainer, + tertiary = md_theme_light_tertiary, + onTertiary = md_theme_light_onTertiary, + tertiaryContainer = md_theme_light_tertiaryContainer, + onTertiaryContainer = md_theme_light_onTertiaryContainer, + error = md_theme_light_error, + errorContainer = md_theme_light_errorContainer, + onError = md_theme_light_onError, + onErrorContainer = md_theme_light_onErrorContainer, + background = md_theme_light_background, + onBackground = md_theme_light_onBackground, + surface = md_theme_light_surface, + onSurface = md_theme_light_onSurface, + surfaceVariant = md_theme_light_surfaceVariant, + onSurfaceVariant = md_theme_light_onSurfaceVariant, + outline = md_theme_light_outline, + inverseOnSurface = md_theme_light_inverseOnSurface, + inverseSurface = md_theme_light_inverseSurface, + inversePrimary = md_theme_light_inversePrimary +) + +private val DarkColors = darkColorScheme( + primary = md_theme_dark_primary, + onPrimary = md_theme_dark_onPrimary, + primaryContainer = md_theme_dark_primaryContainer, + onPrimaryContainer = md_theme_dark_onPrimaryContainer, + secondary = md_theme_dark_secondary, + onSecondary = md_theme_dark_onSecondary, + secondaryContainer = md_theme_dark_secondaryContainer, + onSecondaryContainer = md_theme_dark_onSecondaryContainer, + tertiary = md_theme_dark_tertiary, + onTertiary = md_theme_dark_onTertiary, + tertiaryContainer = md_theme_dark_tertiaryContainer, + onTertiaryContainer = md_theme_dark_onTertiaryContainer, + error = md_theme_dark_error, + errorContainer = md_theme_dark_errorContainer, + onError = md_theme_dark_onError, + onErrorContainer = md_theme_dark_onErrorContainer, + background = md_theme_dark_background, + onBackground = md_theme_dark_onBackground, + surface = md_theme_dark_surface, + onSurface = md_theme_dark_onSurface, + surfaceVariant = md_theme_dark_surfaceVariant, + onSurfaceVariant = md_theme_dark_onSurfaceVariant, + outline = md_theme_dark_outline, + inverseOnSurface = md_theme_dark_inverseOnSurface, + inverseSurface = md_theme_dark_inverseSurface, + inversePrimary = md_theme_dark_inversePrimary +) + +private val LocalAppDimens = staticCompositionLocalOf { + normalDimensions +} + +@Composable +fun ProvideDimens( + dimensions: Dimensions, + content: @Composable () -> Unit +) { + val dimensionSet = remember { dimensions } + CompositionLocalProvider(LocalAppDimens provides dimensionSet, content = content) +} +@Composable +fun Janschip1Theme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + darkTheme -> DarkColors + else -> LightColors + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb() + ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme + } + } + + //Dimensions (calculate dimens here based on screen size) + val dimensions = normalDimensions + + ProvideDimens(dimensions = dimensions) { + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) + } +} + +object AppTheme { + val dimens: Dimensions + @Composable + get() = LocalAppDimens.current +} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Type.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Type.kt new file mode 100644 index 00000000000..0fe35e499a0 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/ui/theme/Type.kt @@ -0,0 +1,18 @@ +package io.jans.chip.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.java deleted file mode 100644 index 7bcc479d54a..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.jans.chip.utils; - -public enum AppConfig { - INSTANCE; - public static final String INTEGRITY_APP_SERVER_URL = "https://play-integrity-checker-server-2eua-3ndysngub.vercel.app"; - public static final long GOOGLE_CLOUD_PROJECT_ID = 618764598105L; - public static final String SQLITE_DB_NAME = "chipDB"; - public static final String APP_NAME = "jans-chip"; - public static final String DEFAULT_S_NO = "1"; - public static final int ROOM_DATABASE_VERSION = 1; - public static final String[] scopeArray = {"openid", "authorization_challenge", "profile"}; - public static final String[] defaultScopeArray = {"openid", "authorization_challenge"}; -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.kt new file mode 100644 index 00000000000..b39306ad7f5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppConfig.kt @@ -0,0 +1,21 @@ +package io.jans.chip.utils + +class AppConfig { + companion object { + const val INTEGRITY_APP_SERVER_URL = "https://play-integrity-checker-server-2eua-3ndysngub.vercel.app" + const val GOOGLE_CLOUD_PROJECT_ID = 618764598105L + const val SQLITE_DB_NAME = "chipDB" + const val APP_NAME = "jans-chip" + const val DEFAULT_S_NO = "1" + const val ROOM_DATABASE_VERSION = 1 + const val FIDO_CONFIG_URL = "/jans-fido2/restv1/configuration" + const val OP_CONFIG_URL = "/.well-known/openid-configuration" + + const val KEY_NAME = "thumbSecrxet" + const val FIDO_ENROLMENT = "FIDO_ENROLMENT" + const val FIDO_AUTHENTICATION = "FIDO_AUTHENTICATION" + + const val ALLOWED_REGISTRATION_SCOPES = "openid authorization_challenge profile" + const val SSA = "eyJraWQiOiJjb25uZWN0X2UyYjdhZDBjLTRjY2ItNDY4MC1hNzA1LWU3YjRjOWRkZDg2ZV9zaWdfcnMyNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzb2Z0d2FyZV9pZCI6ImphbnMtY2hpcC1zc2EiLCJncmFudF90eXBlcyI6WyJhdXRob3JpemF0aW9uX2NvZGUiXSwib3JnX2lkIjoiR2x1dSIsImlzcyI6Imh0dHBzOi8vYWRtaW4tdWktdGVzdC5nbHV1Lm9yZyIsInNvZnR3YXJlX3JvbGVzIjpbImFkbWluIl0sImV4cCI6MTcyMjQ5OTkzNSwiaWF0IjoxNzE5OTA3OTM1LCJqdGkiOiJiMjM5M2NhNC1kOWIzLTRkYzgtOGVkNS0wYjQ5OTg4ZDJiNDUifQ.jnjy2Yam6xaf9lqAAubnTeiN5Ly2muy8wqkJ4AXzaOEMR_Md0WYHRA9bz2LvtVrno9cf0wUw27qDqGA99Y5J-guJjSc7ttnUceDSOGypoEdHsmCxN1-nsHgNcNcKN01FxAq4SPfkOeDj7un5GeKm82bBlO8Reyih5uNg3GYb8sNWVzHH3msSZuR2ExGvoJZlCbon1I5AdiZ-CnpSVywhHHwlJUQ0AN-zyns0JGIf8anLr13TPdp1vJiBLa_YOVJgxA-xT76Rlv0NB57SFoDMuyiSaaUySUyZo9sSIZIV0-U8XNYZ1HdOyJhdc5Kn3tpaiHbHOAc5NbLIodfMMZ2afg" + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.java b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.java deleted file mode 100644 index a3d57534e15..00000000000 --- a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.jans.chip.utils; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.util.Log; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class AppUtil { - public static String getChecksum(Context context) throws IOException, NoSuchAlgorithmException, PackageManager.NameNotFoundException { - // Get the Android package file path. - String apkPath = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.sourceDir; - Log.d("apkPath :::::::", apkPath); - // Get the checksum of the Android package file. - return getChecksum(new File(apkPath)); - } - - public static String getChecksum(File file) throws IOException, NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("MD5"); - FileInputStream fis = new FileInputStream(file); - byte[] data = new byte[1024]; - int nRead; - while ((nRead = fis.read(data)) != -1) { - md.update(data, 0, nRead); - } - byte[] mdbytes = md.digest(); - - // Convert the byte array to a hexadecimal string. - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mdbytes.length; i++) { - sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1)); - } - return sb.toString(); - } -} diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.kt new file mode 100644 index 00000000000..7a1515b8589 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/AppUtil.kt @@ -0,0 +1,50 @@ +package io.jans.chip.utils + +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +class AppUtil { + companion object { + @Throws( + IOException::class, + NoSuchAlgorithmException::class, + PackageManager.NameNotFoundException::class + ) + fun getChecksum(context: Context): String? { + // Get the Android package file path. + val apkPath = + context.packageManager.getPackageInfo( + context.packageName, + 0 + ).applicationInfo.sourceDir + Log.d("apkPath :::::::", apkPath) + // Get the checksum of the Android package file. + return getChecksum(File(apkPath)) + } + + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun getChecksum(file: File?): String? { + val md = MessageDigest.getInstance("MD5") + val fis = FileInputStream(file) + val data = ByteArray(1024) + var nRead: Int + while (fis.read(data).also { nRead = it } != -1) { + md.update(data, 0, nRead) + } + val mdbytes = md.digest() + + // Convert the byte array to a hexadecimal string. + val sb = StringBuilder() + for (i in mdbytes.indices) { + sb.append(Integer.toString((mdbytes[i].toInt() and 0xff) + 0x100, 16).substring(1)) + } + return sb.toString() + } + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/BiometricHelper.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/BiometricHelper.kt new file mode 100644 index 00000000000..fd59b0d6007 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/BiometricHelper.kt @@ -0,0 +1,175 @@ +package io.jans.chip.utils.biometric + + +import android.util.Log +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity +import io.jans.jans_chip.R +import java.security.Signature +/* + * BiometricHelper is a utility object that simplifies the implementation of biometric authentication + * functionalities in Android apps. It provides methods to check biometric availability, register user + * biometrics, and authenticate users using biometric authentication. + * + * This object encapsulates the logic for interacting with the BiometricPrompt API and integrates + * seamlessly with the CryptoManager to encrypt and decrypt sensitive data for secure storage. + */ +object BiometricHelper { + + // Check if biometric authentication is available on the device + fun isBiometricAvailable(context: FragmentActivity): Boolean { + val biometricManager = BiometricManager.from(context) + return when (biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK)) { + BiometricManager.BIOMETRIC_SUCCESS -> true + else -> { + Log.e("TAG", "Biometric authentication not available") + false + } + } + } + + // Retrieve a BiometricPrompt instance with a predefined callback + private fun getBiometricPrompt( + context: FragmentActivity, + onAuthSucceed: (BiometricPrompt.AuthenticationResult) -> Unit + ): BiometricPrompt { + val biometricPrompt = + BiometricPrompt( + context, + ContextCompat.getMainExecutor(context), + object : BiometricPrompt.AuthenticationCallback() { + // Handle successful authentication + override fun onAuthenticationSucceeded( + result: BiometricPrompt.AuthenticationResult + ) { + Log.e("TAG", "Authentication Succeeded: ${result.cryptoObject}") + // Execute custom action on successful authentication + onAuthSucceed(result) + } + + // Handle authentication errors + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + Log.e("TAG", "onAuthenticationError") + // TODO: Implement error handling + } + + // Handle authentication failures + override fun onAuthenticationFailed() { + Log.e("TAG", "onAuthenticationFailed") + // TODO: Implement failure handling + } + } + ) + return biometricPrompt + } + + // Create BiometricPrompt.PromptInfo with customized display text + private fun getPromptInfo( + context: FragmentActivity, + title: String, + subTitle: String, + description: String + ): BiometricPrompt.PromptInfo { + return BiometricPrompt.PromptInfo.Builder() + .setTitle(title) + .setSubtitle(subTitle) + .setDescription(description) + .setConfirmationRequired(false) + .setNegativeButtonText(context.getString(R.string.enable_biometric_dialog_dismiss_btn_text)) + .build() + } + + // Register user biometrics by encrypting a randomly generated token + /*fun registerUserBiometrics( + context: FragmentActivity, + prefBiometric: String, + onSuccess: (authResult: BiometricPrompt.AuthenticationResult) -> Unit = {}, + ) { + val cryptoManager = CryptoManager() + val cipher = cryptoManager.initEncryptionCipher(SECRET_KEY) + val biometricPrompt = getBiometricPrompt(context) { authResult -> + authResult.cryptoObject?.cipher?.let { cipher -> + // Dummy token for now(in production app, generate a unique and genuine token + // for each user registration or consider using token received from authentication server) + val token = UUID.randomUUID().toString() + val encryptedToken = cryptoManager.encrypt(token, cipher) + cryptoManager.saveToPrefs( + encryptedToken, + context, + ENCRYPTED_FILE_NAME, + Context.MODE_PRIVATE, + prefBiometric, + *//*PREF_BIOMETRIC*//* + ) + // Execute custom action on successful registration + onSuccess(authResult) + } + } + biometricPrompt.authenticate( + getPromptInfo( + context, + "Fido Enrolment", + "Enrol using your biometric credential", + "Touch the fingerprint sensor" + ), + BiometricPrompt.CryptoObject(cipher) + ) + }*/ + fun registerUserBiometrics( + context: FragmentActivity, + signature: Signature, + onSuccess: (authResult: BiometricPrompt.AuthenticationResult) -> Unit = {}, + ) { + //val cryptoManager = CryptoManager() + //val cipher = cryptoManager.initEncryptionCipher(SECRET_KEY) + val biometricPrompt = getBiometricPrompt(context) { authResult -> + + onSuccess(authResult) + } + biometricPrompt.authenticate( + getPromptInfo( + context, + "Fido Enrolment", + "Enrol using your biometric credential", + "Touch the fingerprint sensor" + ), + BiometricPrompt.CryptoObject(signature) + ) + } + + // Authenticate user using biometrics by decrypting stored token + fun authenticateUser( + context: FragmentActivity, + signature: Signature, + onSuccess: (authResult: BiometricPrompt.AuthenticationResult) -> Unit, + ) { + /*val cryptoManager = CryptoManager() + val encryptedData = cryptoManager.getFromPrefs( + context, + ENCRYPTED_FILE_NAME, + Context.MODE_PRIVATE, + prefBiometric*/ + /*PREF_BIOMETRIC*/ + //) + //encryptedData?.let { data -> + // val cipher = cryptoManager.initDecryptionCipher(SECRET_KEY, data.initializationVector) + val biometricPrompt = getBiometricPrompt(context) { authResult -> + //authResult.cryptoObject?.cipher?.let { cipher -> + //val plainText = cryptoManager.decrypt(data.ciphertext, cipher) + // Execute custom action on successful authentication + + //} + onSuccess(authResult) + } + val promptInfo = getPromptInfo( + context, + "Fido Authentication", + "Authenticate using your biometric credential", + "Touch the fingerprint sensor" + ) + biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(signature)) + //} + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/Constants.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/Constants.kt new file mode 100644 index 00000000000..7e4a74891e6 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/Constants.kt @@ -0,0 +1,10 @@ +package io.jans.chip.utils.biometric + +const val PREF_BIOMETRIC = "biometric_preferences" +const val ENCRYPTED_FILE_NAME = "encrypted_data_store" +const val SECRET_KEY = "biometric_secret_key" + +object NavigationRoutes { + const val SIGN_IN = "SignIn" + const val SIGN_UP = "SignUp" +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/CryptoManager.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/CryptoManager.kt new file mode 100644 index 00000000000..f85a12600d5 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/CryptoManager.kt @@ -0,0 +1,141 @@ +package io.jans.chip.utils.biometric + +import android.content.Context +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import com.google.gson.Gson +import java.nio.charset.Charset +import java.security.KeyStore +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.SecretKey +import javax.crypto.spec.GCMParameterSpec + +interface CryptoManager { + + fun initEncryptionCipher(keyName: String): Cipher + + fun initDecryptionCipher(keyName: String, initializationVector: ByteArray): Cipher + + fun encrypt(plaintext: String, cipher: Cipher): EncryptedData + + fun decrypt(ciphertext: ByteArray, cipher: Cipher): String + + fun saveToPrefs( + encryptedData: EncryptedData, + context: Context, + filename: String, + mode: Int, + prefKey: String + ) + + fun getFromPrefs( + context: Context, + filename: String, + mode: Int, + prefKey: String + ): EncryptedData? +} + +fun CryptoManager(): CryptoManager = CryptoManagerImpl() + +class CryptoManagerImpl : CryptoManager { + + private val ENCRYPTION_TRANSFORMATION = "AES/GCM/NoPadding" + private val ANDROID_KEYSTORE = "AndroidKeyStore" + private val KEY_ALIAS = "MyKeyAlias" + + private val keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + + init { + keyStore.load(null) + if (!keyStore.containsAlias(KEY_ALIAS)) { + createSecretKey() + } + } + + override fun initEncryptionCipher(keyName: String): Cipher { + val cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, getSecretKey()) + return cipher + } + + override fun initDecryptionCipher(keyName: String, initializationVector: ByteArray): Cipher { + val cipher = Cipher.getInstance(ENCRYPTION_TRANSFORMATION) + val spec = GCMParameterSpec(128, initializationVector) + cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec) + return cipher + } + + override fun encrypt(plaintext: String, cipher: Cipher): EncryptedData { + val encryptedBytes = cipher.doFinal(plaintext.toByteArray(Charset.forName("UTF-8"))) + return EncryptedData(encryptedBytes, cipher.iv) + } + + override fun decrypt(ciphertext: ByteArray, cipher: Cipher): String { + val decryptedBytes = cipher.doFinal(ciphertext) + return String(decryptedBytes, Charset.forName("UTF-8")) + } + + override fun saveToPrefs( + encryptedData: EncryptedData, + context: Context, + filename: String, + mode: Int, + prefKey: String + ) { + val json = Gson().toJson(encryptedData) + with(context.getSharedPreferences(filename, mode).edit()) { + putString(prefKey, json) + apply() + } + } + + override fun getFromPrefs( + context: Context, + filename: String, + mode: Int, + prefKey: String + ): EncryptedData? { + val json = context.getSharedPreferences(filename, mode).getString(prefKey, null) + return Gson().fromJson(json, EncryptedData::class.java) + } + + private fun createSecretKey() { + val keyGenParams = KeyGenParameterSpec.Builder( + KEY_ALIAS, + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ).apply { + setBlockModes(KeyProperties.BLOCK_MODE_GCM) + setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + setUserAuthenticationRequired(true) + }.build() + + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) + keyGenerator.init(keyGenParams) + keyGenerator.generateKey() + } + + private fun getSecretKey(): SecretKey { + return keyStore.getKey(KEY_ALIAS, null) as SecretKey + } +} + +data class EncryptedData(val ciphertext: ByteArray, val initializationVector: ByteArray) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as EncryptedData + + if (!ciphertext.contentEquals(other.ciphertext)) return false + return initializationVector.contentEquals(other.initializationVector) + } + + override fun hashCode(): Int { + var result = ciphertext.contentHashCode() + result = 31 * result + initializationVector.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/EnableBiometricDialog.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/EnableBiometricDialog.kt new file mode 100644 index 00000000000..5d0ba6f851d --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/utils/biometric/EnableBiometricDialog.kt @@ -0,0 +1,34 @@ +package io.jans.chip.utils.biometric + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import io.jans.jans_chip.R + +@Composable +fun EnableBiometricDialog( + onEnable: () -> Unit, + onDismiss: () -> Unit +) { + AlertDialog( + onDismissRequest = onDismiss, + title = { }, + text = { + Text(text = stringResource(R.string.enable_biometric_dialog_title_text)) + }, + confirmButton = { + Button(onClick = { + onEnable() + }) { + Text(text = stringResource(R.string.enable_biometric_dialog_confirm_btn_text)) + } + }, + dismissButton = { + Button(onClick = onDismiss) { + Text(text = stringResource(R.string.enable_biometric_dialog_dismiss_btn_text)) + } + } + ) +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/java/io/jans/chip/viewmodel/MainViewModel.kt b/demos/jans-chip/android/app/src/main/java/io/jans/chip/viewmodel/MainViewModel.kt new file mode 100644 index 00000000000..6cd0151b0c2 --- /dev/null +++ b/demos/jans-chip/android/app/src/main/java/io/jans/chip/viewmodel/MainViewModel.kt @@ -0,0 +1,321 @@ +package io.jans.chip.viewmodel + +import android.content.Context +import android.util.Log +import androidx.annotation.MainThread +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import io.jans.chip.model.LoginResponse +import io.jans.chip.model.LogoutResponse +import io.jans.chip.model.OIDCClient +import io.jans.chip.model.OPConfiguration +import io.jans.chip.model.TokenResponse +import io.jans.chip.model.UserInfoResponse +import io.jans.chip.model.appIntegrity.AppIntegrityEntity +import io.jans.chip.model.appIntegrity.AppIntegrityResponse +import io.jans.chip.model.fido.assertion.option.AssertionOptionResponse +import io.jans.chip.model.fido.assertion.result.AssertionResultRequest +import io.jans.chip.model.fido.assertion.result.AssertionResultResponse +import io.jans.chip.model.fido.attestation.option.AttestationOptionResponse +import io.jans.chip.model.fido.attestation.result.AttestationResultRequest +import io.jans.chip.model.fido.attestation.result.AttestationResultResponse +import io.jans.chip.model.fido.config.FidoConfiguration +import io.jans.chip.model.fido.config.FidoConfigurationResponse +import io.jans.chip.repository.DCRRepository +import io.jans.chip.repository.FidoAssertionRepository +import io.jans.chip.repository.FidoAttestationRepository +import io.jans.chip.repository.FidoConfigurationRepository +import io.jans.chip.repository.LoginResponseRepository +import io.jans.chip.repository.LogoutRepository +import io.jans.chip.repository.OPConfigurationRepository +import io.jans.chip.repository.PlayIntegrityRepository +import io.jans.chip.repository.TokenResponseRepository +import io.jans.chip.repository.UserInfoResponseRepository + +class MainViewModel : ViewModel() { + + private val TAG = "MainViewModel" + private lateinit var context: Context + private lateinit var opConfigUrl: String + private lateinit var fidoConfigUrl: String + private lateinit var username: String + private lateinit var password: String + private lateinit var userInfoResponse: UserInfoResponse + + var opConfigurationPresent by mutableStateOf(false) + var fidoConfigurationPresent by mutableStateOf(false) + var attestationOptionSuccess by mutableStateOf(false) + var attestationOptionResponse by mutableStateOf(false) + var clientRegistered by mutableStateOf(false) + var userIsAuthenticated by mutableStateOf(false) + var assertionOptionResponse by mutableStateOf(false) + var errorInLoading by mutableStateOf(false) + var loadingErrorMessage by mutableStateOf("") + + + lateinit var opConfigurationRepository: OPConfigurationRepository + lateinit var dcrRepository: DCRRepository + lateinit var fidoConfigurationRepository: FidoConfigurationRepository + lateinit var loginResponseRepository: LoginResponseRepository + lateinit var tokenResponseRepository: TokenResponseRepository + lateinit var userInfoResponseRepository: UserInfoResponseRepository + lateinit var logoutRepository: LogoutRepository + lateinit var fidoAttestationRepository: FidoAttestationRepository + lateinit var fidoAssertionRepository: FidoAssertionRepository + lateinit var playIntegrityRepository: PlayIntegrityRepository + + companion object { + + private lateinit var instance: MainViewModel + + @MainThread + fun getInstance(activityContext: Context): MainViewModel { + instance = if (::instance.isInitialized) { + instance + } else { + instance = MainViewModel() + instance.initModel(activityContext) + instance + } + return instance + } + } + + fun initModel(activityContext: Context) { + context = activityContext + opConfigurationRepository = OPConfigurationRepository(context) + dcrRepository = DCRRepository(context) + fidoConfigurationRepository = FidoConfigurationRepository(context) + loginResponseRepository = LoginResponseRepository(context) + tokenResponseRepository = TokenResponseRepository(context) + userInfoResponseRepository = UserInfoResponseRepository(context) + logoutRepository = LogoutRepository(context) + fidoAttestationRepository = FidoAttestationRepository(context) + fidoAssertionRepository = FidoAssertionRepository(context) + playIntegrityRepository = PlayIntegrityRepository(context) + } + + fun setOpConfigUrl(opConfigUrl: String) { + this.opConfigUrl = opConfigUrl + Log.d(TAG, opConfigUrl) + } + + fun getOpConfigUrl(): String { + return opConfigUrl + } + + fun getFidoConfigUrl(): String { + return fidoConfigUrl + } + + fun setFidoConfigUrl(fidoConfigUrl: String) { + this.fidoConfigUrl = fidoConfigUrl + Log.d(TAG, fidoConfigUrl) + } + + fun setUsername(username: String) { + this.username = username + Log.d(TAG, username) + } + + fun getUsername(): String { + if (!(this::username.isInitialized)) { + return "" + } + return username + } + + fun setPassword(password: String) { + this.password = password + } + + fun getPassword(): String { + if (!(this::password.isInitialized)) { + return "" + } + return password + } + + fun setUserInfoResponse(userInfoResponse: UserInfoResponse) { + this.userInfoResponse = userInfoResponse + } + + fun getUserInfoResponse(): UserInfoResponse { + return userInfoResponse + } + + suspend fun fetchOPConfiguration(): OPConfiguration? { + + val opConfiguration: OPConfiguration? = + opConfigurationRepository.fetchOPConfiguration(opConfigUrl) + if (opConfiguration?.isSuccessful == true) { + opConfigurationPresent = true + } + return opConfiguration + } + + suspend fun doDCR(scopeText: String): OIDCClient? { + val oidcClient: OIDCClient? = dcrRepository.doDCR(scopeText) + clientRegistered = true + return oidcClient + } + + suspend fun doDCRUsingSSA(ssa: String, scopeText: String): OIDCClient? { + val oidcClient: OIDCClient? = dcrRepository.doDCRUsingSSA(ssa, scopeText) + clientRegistered = true + return oidcClient + } + + suspend fun fetchFidoConfiguration(): FidoConfigurationResponse? { + if (!(this::fidoConfigUrl.isInitialized)) { + val opConfiguration: OPConfiguration? = + opConfigurationRepository.getOPConfigurationInDatabase() + opConfiguration?.fidoUrl?.let { setFidoConfigUrl(it) } + } + + val fidoConfigurationResponse: FidoConfigurationResponse? = + fidoConfigurationRepository.fetchFidoConfiguration(fidoConfigUrl) + if (fidoConfigurationResponse?.isSuccessful == true) { + fidoConfigurationPresent = true + } + return fidoConfigurationResponse + } + + suspend fun getFidoConfigInDatabase(): FidoConfiguration? { + return fidoConfigurationRepository.getFidoConfigInDatabase() + } + + suspend fun deleteOPConfigurationInDatabase() { + opConfigurationRepository.deleteOPConfigurationInDatabase() + opConfigurationPresent = false + } + + suspend fun deleteClientInDatabase() { + dcrRepository.deleteClientInDatabase() + clientRegistered = false + userIsAuthenticated = false + } + + suspend fun deleteFidoConfigurationInDatabase() { + fidoConfigurationRepository.deleteFidoConfigurationInDatabase() + fidoConfigurationPresent = false + attestationOptionSuccess = false + attestationOptionResponse = false + } + + suspend fun attestationOption(username: String): AttestationOptionResponse? { + val attestationOptionResponse: AttestationOptionResponse? = + fidoAttestationRepository.attestationOption(username) + if (attestationOptionResponse?.isSuccessful == true) { + attestationOptionSuccess = true + } + return attestationOptionResponse + } + + suspend fun attestationResult(attestationResultRequest: AttestationResultRequest?): AttestationResultResponse? { + val attestationResultResponse: AttestationResultResponse? = + fidoAttestationRepository.attestationResult(attestationResultRequest) + if (attestationResultResponse?.isSuccessful == true) { + attestationOptionResponse = true + } + return attestationResultResponse + } + + suspend fun checkAppIntegrity(): AppIntegrityResponse? { + return playIntegrityRepository.checkAppIntegrity() + } + + suspend fun checkAppIntegrityFromDatabase(): String? { + val appIntegrityEntity: AppIntegrityEntity = playIntegrityRepository.getAppIntegrityEntityInDatabase() + ?: return null + + if (appIntegrityEntity.error != null) { + return appIntegrityEntity.error + } + if (appIntegrityEntity.appIntegrity != null) { + return appIntegrityEntity.appLicensingVerdict + } + if (appIntegrityEntity.deviceIntegrity != null) { + return appIntegrityEntity.deviceIntegrity + } + return null + } + + suspend fun assertionOption(username: String): AssertionOptionResponse? { + + return fidoAssertionRepository.assertionOption(username) + } + + suspend fun assertionResult(assertionResultRequest: AssertionResultRequest?): AssertionResultResponse { + val assertionResultResponse: AssertionResultResponse = + fidoAssertionRepository.assertionResult(assertionResultRequest) + if (assertionResultResponse.isSuccessful == true) { + assertionOptionResponse = true + } + return assertionResultResponse + } + + suspend fun processlogin( + usernameText: String, + passwordText: String?, + authMethod: String, + assertionResultRequest: String? + ): LoginResponse? { + //userIsAuthenticated = true + return loginResponseRepository.processlogin( + usernameText, + passwordText, + authMethod, + assertionResultRequest + ) + } + + suspend fun getToken( + authorizationCode: String?, + ): TokenResponse? { + return tokenResponseRepository.getToken(authorizationCode) + } + + suspend fun getUserInfo(accessToken: String?): UserInfoResponse { + val userInfoResponse: UserInfoResponse = + userInfoResponseRepository.getUserInfo(accessToken) + if (userInfoResponse?.isSuccessful == true) { + userIsAuthenticated = true + } + return userInfoResponse + } + + suspend fun logout(): LogoutResponse { + val logoutResponse: LogoutResponse = logoutRepository.logout() + if (logoutResponse.isSuccessful == true) { + userIsAuthenticated = false + } + return logoutResponse + } + + suspend fun isOPConfigurationInDatabase(): Boolean { + return opConfigurationRepository.isOPConfigurationInDatabase() + } + + suspend fun getOPConfigurationInDatabase(): OPConfiguration? { + return opConfigurationRepository.getOPConfigurationInDatabase() + } + + suspend fun isClientInDatabase(): Boolean { + return dcrRepository.isClientInDatabase() + } + + suspend fun getClientInDatabase(): OIDCClient? { + return dcrRepository.getClientInDatabase() + } + + suspend fun isAuthenticated(accessToken: String?): Boolean { + return loginResponseRepository.isAuthenticated(accessToken) + } + + suspend fun getUserInfoWithAccessToken(accessToken: String?): UserInfoResponse? { + return userInfoResponseRepository.getUserInfo(accessToken) + } +} \ No newline at end of file diff --git a/demos/jans-chip/android/app/src/main/res/drawable/janssen_logo.png b/demos/jans-chip/android/app/src/main/res/drawable/janssen_logo.png index 203c19e37d4..6513e302c14 100644 Binary files a/demos/jans-chip/android/app/src/main/res/drawable/janssen_logo.png and b/demos/jans-chip/android/app/src/main/res/drawable/janssen_logo.png differ diff --git a/demos/jans-chip/android/app/src/main/res/drawable/passkey_icon.png b/demos/jans-chip/android/app/src/main/res/drawable/passkey_icon.png new file mode 100644 index 00000000000..abf43021b16 Binary files /dev/null and b/demos/jans-chip/android/app/src/main/res/drawable/passkey_icon.png differ diff --git a/demos/jans-chip/android/app/src/main/res/layout/activity_after_login.xml b/demos/jans-chip/android/app/src/main/res/layout/activity_after_login.xml deleted file mode 100644 index 7f1e7f2f183..00000000000 --- a/demos/jans-chip/android/app/src/main/res/layout/activity_after_login.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - -