Skip to content

Commit

Permalink
Merge pull request #5 from meik99/manage-student-data
Browse files Browse the repository at this point in the history
implemented managing student data
  • Loading branch information
meik99 authored Oct 22, 2021
2 parents 01910f5 + 1b8a1ee commit 03d8ded
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.rynkbit.jku.stuka.identity

import androidx.security.identity.AccessControlProfile
import androidx.security.identity.AccessControlProfileId

class AccessControlProfile {
val accessControlProfileId = AccessControlProfileId(1)
val accessControlProfile = AccessControlProfile.Builder(accessControlProfileId)
.setUserAuthenticationRequired(false)
.build()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.rynkbit.jku.stuka
package com.rynkbit.jku.stuka.identity

import android.content.Context
import androidx.security.identity.IdentityCredentialStore
Expand Down
40 changes: 40 additions & 0 deletions app/src/main/java/com/rynkbit/jku/stuka/identity/StudentProfile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.rynkbit.jku.stuka.identity

import androidx.security.identity.PersonalizationData

const val CREDENTIAL_NAMESPACE = "STUKA"
const val CREDENTIAL_STUDENT_FIRSTNAME = "FIRSTNAME"
const val CREDENTIAL_STUDENT_LASTNAME = "LASTNAME"
const val CREDENTIAL_STUDENT_BIRTHDATE = "BIRTHDATE"
const val CREDENTIAL_STUDENT_MATRICULATION_NUMBER = "MATRICULATION_NUMBER"
const val CREDENTIAL_STUDENT_STUDY_CODE = "STUDY_CODE"

class StudentProfile(
val firstname: String = "",
val lastname: String = "",
val birthdate: String = "",
val matriculationNumber: String = "",
val studyCode: String = "",
val studentAccessControlProfile: AccessControlProfile = AccessControlProfile(),
) {
fun toPersonalizationData() =
PersonalizationData.Builder()
.addAccessControlProfile(studentAccessControlProfile.accessControlProfile)
.putEntry(CREDENTIAL_STUDENT_FIRSTNAME, firstname)
.putEntry(CREDENTIAL_STUDENT_LASTNAME, lastname)
.putEntry(CREDENTIAL_STUDENT_BIRTHDATE, birthdate)
.putEntry(CREDENTIAL_STUDENT_MATRICULATION_NUMBER, matriculationNumber)
.putEntry(CREDENTIAL_STUDENT_STUDY_CODE, studyCode)
.build()

private fun PersonalizationData.Builder.putEntry(
key: String,
value: String
): PersonalizationData.Builder =
this.putEntryString(
CREDENTIAL_NAMESPACE,
key,
listOf(studentAccessControlProfile.accessControlProfileId),
value
)
}
101 changes: 101 additions & 0 deletions app/src/main/java/com/rynkbit/jku/stuka/identity/StudentStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.rynkbit.jku.stuka.identity

import android.content.Context
import androidx.security.identity.IdentityCredential
import androidx.security.identity.IdentityCredentialStore
import androidx.security.identity.ResultData
import java.lang.UnsupportedOperationException

const val CREDENTIAL_NAME = "STUDENT"
const val DOC_TYPE = "CARD"
const val IDENTITY_FEATURE = "android.hardware.identity_credential"

class StudentStore(
context: Context,
private val agnosticIdentityCredentialStore: AgnosticIdentityCredentialStore = AgnosticIdentityCredentialStore(
context
)
) {

init {

}

fun store(student: StudentProfile) {
val credentialStore = agnosticIdentityCredentialStore.credentialStore
val credentials = credentialStore.getCredentialByName(
CREDENTIAL_NAME,
IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256
)

if (credentials == null) {
createStudent(student)
} else {
updateStudent(student, credentials)
}
}

fun get(): StudentProfile {
val credentialStore = agnosticIdentityCredentialStore.credentialStore
val credentials = credentialStore.getCredentialByName(
CREDENTIAL_NAME,
IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256
)

if (credentials != null) {
val results = credentials.getEntries(
null,
mapOf(
Pair(
CREDENTIAL_NAMESPACE, listOf(
CREDENTIAL_STUDENT_FIRSTNAME,
CREDENTIAL_STUDENT_LASTNAME,
CREDENTIAL_STUDENT_BIRTHDATE,
CREDENTIAL_STUDENT_MATRICULATION_NUMBER,
CREDENTIAL_STUDENT_STUDY_CODE
)
)
),
null
)
return results.toStudent()
}

return StudentProfile()
}

private fun createStudent(student: StudentProfile) {
val credentialStore = agnosticIdentityCredentialStore.credentialStore
val credentials = credentialStore.createCredential(CREDENTIAL_NAME, DOC_TYPE)
credentials.personalize(student.toPersonalizationData())
}

private fun updateStudent(student: StudentProfile, credentials: IdentityCredential) {
val credentialStore = agnosticIdentityCredentialStore.credentialStore

when {
credentialStore.capabilities.isUpdateSupported -> {
credentials.update(student.toPersonalizationData())
}
credentialStore.capabilities.isDeleteSupported -> {
deleteStudent(credentials)
createStudent(student)
}
else -> {
throw UnsupportedOperationException()
}
}
}

private fun deleteStudent(credentials: IdentityCredential) {
credentials.delete(ByteArray(1))
}

private fun ResultData.toStudent(): StudentProfile = StudentProfile(
getEntryString(CREDENTIAL_NAMESPACE, CREDENTIAL_STUDENT_FIRSTNAME) ?: "",
getEntryString(CREDENTIAL_NAMESPACE, CREDENTIAL_STUDENT_LASTNAME) ?: "",
getEntryString(CREDENTIAL_NAMESPACE, CREDENTIAL_STUDENT_BIRTHDATE) ?: "",
getEntryString(CREDENTIAL_NAMESPACE, CREDENTIAL_STUDENT_MATRICULATION_NUMBER) ?: "",
getEntryString(CREDENTIAL_NAMESPACE, CREDENTIAL_STUDENT_STUDY_CODE) ?: ""
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import androidx.core.widget.addTextChangedListener
import androidx.navigation.findNavController
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.rynkbit.jku.stuka.R

const val CREDENTIAL_NAME = "STUDENT_6"
const val DOC_TYPE = "CARD"
const val CREDENTIAL_NAMESPACE = "StuKa"
const val STUDENT_NAME = "NAME"
const val IDENTITY_FEATURE = "android.hardware.identity_credential"
import com.rynkbit.jku.stuka.identity.StudentStore
import java.lang.UnsupportedOperationException

class EditDataFragment : Fragment() {
private lateinit var viewModel: EditDataViewModel
Expand All @@ -30,11 +29,64 @@ class EditDataFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(EditDataViewModel::class.java)

val context = requireContext()
val studentProfile = StudentStore(context).get()

viewModel.applyStudentProfile(studentProfile)

view.apply {
findViewById<FloatingActionButton>(R.id.fabSaveData)
.setOnClickListener {
saveData()
findNavController().navigate(R.id.action_editDataFragment_to_mainFragment)
}

val editFirstname = findViewById<EditText>(R.id.editFirstname)
val editLastname = findViewById<EditText>(R.id.editLastname)
val editBirthdate = findViewById<EditText>(R.id.editBirthdate)
val editMatriculationNumber = findViewById<EditText>(R.id.editMatriculationNumber)
val editStudyCode = findViewById<EditText>(R.id.editStudyCode)

editFirstname.setTextIfNotEmpty(studentProfile.firstname)
editLastname.setTextIfNotEmpty(studentProfile.lastname)
editBirthdate.setTextIfNotEmpty(studentProfile.birthdate)
editMatriculationNumber.setTextIfNotEmpty(studentProfile.matriculationNumber)
editStudyCode.setTextIfNotEmpty(studentProfile.studyCode)

editFirstname.addTextChangedListener {
viewModel.firstname = it.toString()
}
editLastname.addTextChangedListener {
viewModel.lastname = it.toString()
}
editBirthdate.addTextChangedListener {
viewModel.birthdate = it.toString()
}
editMatriculationNumber.addTextChangedListener {
viewModel.matriculationNumber = it.toString()
}
editStudyCode.addTextChangedListener {
viewModel.studyCode = it.toString()
}
}
}

private fun EditText.setTextIfNotEmpty(value: String) {
if (value.isNotEmpty()) {
setText(value)
}
}

private fun saveData() {
val context = requireContext()
try {
StudentStore(context)
.store(viewModel.studentProfile)
} catch (e: UnsupportedOperationException) {
Snackbar
.make(requireView(), R.string.updating_credentials_not_supported, Snackbar.LENGTH_LONG)
.show()
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
package com.rynkbit.jku.stuka.ui.edit

import androidx.lifecycle.ViewModel
import com.rynkbit.jku.stuka.identity.StudentProfile

class EditDataViewModel : ViewModel() {
var firstname: String = ""
var lastname: String = ""
var birthdate: String = ""
var matriculationNumber: String = ""
var studyCode: String = ""

val studentProfile
get() = StudentProfile(
firstname, lastname, birthdate, matriculationNumber, studyCode
)

fun applyStudentProfile(studentProfile: StudentProfile) {
firstname = studentProfile.firstname
lastname = studentProfile.lastname
birthdate = studentProfile.birthdate
matriculationNumber = studentProfile.matriculationNumber
studyCode = studentProfile.studyCode
}
}
69 changes: 20 additions & 49 deletions app/src/main/java/com/rynkbit/jku/stuka/ui/main/MainFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ package com.rynkbit.jku.stuka.ui.main

import androidx.lifecycle.ViewModelProvider
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import android.widget.TextView
import androidx.navigation.findNavController
import androidx.security.identity.*
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.rynkbit.jku.stuka.AgnosticIdentityCredentialStore
import com.rynkbit.jku.stuka.R
import java.lang.Exception
import com.rynkbit.jku.stuka.identity.StudentStore

class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
Expand All @@ -35,50 +31,25 @@ class MainFragment : Fragment() {
.setOnClickListener {
findNavController().navigate(R.id.action_mainFragment_to_editDataFragment)
}

val txtFirstname = findViewById<TextView>(R.id.txtFirstname)
val txtLastname = findViewById<TextView>(R.id.txtLastname)
val txtBirthdate = findViewById<TextView>(R.id.txtBirthdate)
val txtMatriculationNumber = findViewById<TextView>(R.id.txtMatriculationNumber)
val txtStudyCode = findViewById<TextView>(R.id.txtStudyCode)

viewModel.student.observe(this@MainFragment.viewLifecycleOwner) {
txtFirstname.text = it.firstname
txtLastname.text = it.lastname
txtBirthdate.text = it.birthdate
txtMatriculationNumber.text = it.matriculationNumber
txtStudyCode.text = it.studyCode
}
}
// val context = requireContext()
}

// // All of the code below was made by trial and error
// // The documentation only gives minimal insight and the error messages are close to useless
// val identityStore = AgnosticIdentityCredentialStore(context)
// val credentials = identityStore.credentialStore.createCredential(CREDENTIAL_NAME, DOC_TYPE)
//
// // For some reason an access control profile is needed, however
// // I could not find the reason why or what that does
// val accessControlProfileId = AccessControlProfileId(1)
// val accessControlProfile = AccessControlProfile.Builder(accessControlProfileId)
// .setUserAuthenticationRequired(false)
// .build()
//
// val data = PersonalizationData.Builder()
// .addAccessControlProfile(accessControlProfile)
// .putEntryString(
// CREDENTIAL_NAMESPACE,
// STUDENT_NAME,
// listOf(accessControlProfileId),
// "Test Name"
// )
// .build()
//
// try {
// credentials.personalize(data)
// val readCredentials = identityStore.credentialStore.getCredentialByName(
// CREDENTIAL_NAME,
// IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256
// )
//
// // If no access control profile is created or used
// // the result data contains all keys, but a null value will be returned
// val resultData = readCredentials?.getEntries(
// null,
// mapOf(Pair(CREDENTIAL_NAMESPACE, listOf(STUDENT_NAME))),
// null
// )
// val status = resultData?.getStatus(CREDENTIAL_NAMESPACE, STUDENT_NAME)
// val name = resultData?.getEntryString(CREDENTIAL_NAMESPACE, STUDENT_NAME)
// Snackbar.make(view, "Somehow this worked: $status: $name", Snackbar.LENGTH_SHORT).show()
// } catch (e: Exception) {
// Log.e(MainFragment::class.java.simpleName, e.message, e)
// }
override fun onResume() {
super.onResume()
viewModel.student.postValue(StudentStore(requireContext()).get())
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.rynkbit.jku.stuka.ui.main

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.rynkbit.jku.stuka.identity.StudentProfile

class MainViewModel : ViewModel() {
var student = MutableLiveData<StudentProfile>()
}
Loading

0 comments on commit 03d8ded

Please sign in to comment.