25 Commits

Author SHA1 Message Date
TanelOrumaa
b565f6846d MOB-55 Demo website 2021-12-07 00:05:06 +02:00
Henrik Lepson
60207319b7 Merge pull request #16 from TanelOrumaa/newapproach
Improved code
2021-12-06 20:27:00 +02:00
Henrik Lepson
e5300dfa5e got rid of git status syntax 2021-12-04 17:21:07 +02:00
Henrik Lepson
d4c2a11521 added more error messages 2021-12-04 17:08:58 +02:00
Henrik Lepson
09c4fa6be3 fixed small issue in testmobileapp 2021-12-04 12:46:40 +02:00
Henrik Lepson
63bc89b0e4 Merge pull request #14 from TanelOrumaa/iter4UI
UI/UX improvements for iteration 4
2021-12-03 16:24:00 +02:00
Henrik Lepson
152fd16162 MOB-40 finished home fragment 2021-12-03 16:11:48 +02:00
Henrik Lepson
716b983389 MOB-40 made user fragment scrollable, changed app logo 2021-11-29 20:22:20 +02:00
Henrik Lepson
94fad95364 MOB-40 added listening to NFC adapter changes 2021-11-28 16:54:26 +02:00
Henrik Lepson
c33fba1a14 MOB-40 pin toggle status saved, added informational snackbars 2021-11-28 16:24:08 +02:00
Henrik Lepson
825335ea5f MOB-40 added save can to home fragment, fixed language resources 2021-11-27 21:16:50 +02:00
Henrik Lepson
0f6f31c995 MOB-40 improved authentication UX 2021-11-25 18:09:45 +02:00
Henrik Lepson
762a8c8cc2 MOB-40 changed the settings view and fixed menu notifications 2021-11-25 16:13:35 +02:00
Henrik Lepson
1138abcb11 MOB-40 home fragment logic changed 2021-11-25 14:17:00 +02:00
Henrik Lepson
f085076631 MOB-40 improved CAN and PIN views 2021-11-24 20:44:40 +02:00
Henrik Lepson
edc444c027 MOB-40 added xml res files for styles and dimensions 2021-11-24 18:24:21 +02:00
Henrik Lepson
df5febabb7 deleted unused code 2021-11-24 18:20:22 +02:00
Henrik Lepson
1b9a59d4eb Removed irrelevant information from the readme
The main readme contained information that was not up-to-date. Mobile Auth App capabilities clarified as well.
2021-11-23 11:40:07 +02:00
TanelOrumaa
bbd5039a0b Merge pull request #11 from TanelOrumaa/MOB-42
Backend + frontend + MOB-21 JWT creation.
2021-11-17 09:58:06 +02:00
Henrik Lepson
2c5430977d Updated main readme 2021-11-17 09:31:30 +02:00
Henrik Lepson
68a7db2e77 Created a readme for TestMobileApp 2021-11-17 09:26:21 +02:00
Henrik Lepson
a4caf24a35 MOB-41 fixed some remaining issues 2021-11-17 09:15:29 +02:00
TanelOrumaa
5b70a8f997 MOB-42 Added log out button to backend, fixed issue with challenge for test app 2021-11-16 21:30:58 +02:00
Henrik Lepson
168c9be010 fixed app not closing bug, when started from website 2021-11-14 10:13:40 +02:00
TanelOrumaa
636beeb7f3 MOB-42 Fixed token authentication issues (wrong library version, cache getting recreated every request, origin in wrong form) 2021-11-11 21:47:27 +02:00
58 changed files with 1225 additions and 871 deletions

View File

@@ -68,8 +68,4 @@ dependencies {
'io.jsonwebtoken:jjwt-gson:0.11.2' 'io.jsonwebtoken:jjwt-gson:0.11.2'
implementation 'com.koushikdutta.ion:ion:3.1.0' implementation 'com.koushikdutta.ion:ion:3.1.0'
// Retrofit + Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
} }

View File

@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.nfc.NfcAdapter import android.nfc.NfcAdapter
import android.nfc.TagLostException
import android.nfc.tech.IsoDep import android.nfc.tech.IsoDep
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
@@ -33,9 +34,10 @@ class AuthFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private val intentParameters: ParametersViewModel by activityViewModels() private val paramsModel: ParametersViewModel by activityViewModels()
private var binding: FragmentAuthBinding? = null private var _binding: FragmentAuthBinding? = null
private val binding get() = _binding!!
private val args: CanFragmentArgs by navArgs() private val args: CanFragmentArgs by navArgs()
@@ -48,8 +50,8 @@ class AuthFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentAuthBinding.inflate(inflater, container, false) _binding = FragmentAuthBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -58,63 +60,87 @@ class AuthFragment : Fragment() {
override fun onTick(p0: Long) { override fun onTick(p0: Long) {
timeRemaining-- timeRemaining--
if (timeRemaining == 0) { if (timeRemaining == 0) {
binding?.timeCounter?.text = getString(R.string.no_time) binding.timeCounter.text = getString(R.string.no_time)
} else { } else {
binding?.timeCounter?.text = getString(R.string.time_left, timeRemaining) binding.timeCounter.text = getString(R.string.time_left, timeRemaining)
} }
} }
override fun onFinish() { override fun onFinish() {
Thread.sleep(750) Thread.sleep(750)
goToTheStart() cancelAuth()
} }
}.start() }.start()
binding!!.nextButton.setOnClickListener { goToNextFragment() } // The button exists in code for testing reasons, but not visible to the user anymore unless visibility is changed in the code.
binding!!.cancelButton.setOnClickListener { goToTheStart() } binding.nextButton.visibility = View.GONE
binding.nextButton.setOnClickListener { goToNextFragment() }
binding.cancelButton.setOnClickListener { cancelAuth() }
val adapter = NfcAdapter.getDefaultAdapter(activity) val adapter = NfcAdapter.getDefaultAdapter(activity)
if (adapter != null) if (adapter != null)
getInfoFromIdCard(adapter) getInfoFromIdCard(adapter)
else { // If NFC adapter can not be detected then end the auth process as it is not possible to read an ID card
cancelAuth() // It would be a good idea to show user some notification as it might be confusing if the app suddenly closes
}
}
private fun goToNextFragment() {
timer.cancel()
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
findNavController().navigate(action)
}
private fun cancelAuth() {
viewModel.clearUserInfo()
timer.cancel()
if (args.mobile) {
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
} else {
requireActivity().finishAndRemoveTask()
}
} }
private fun getInfoFromIdCard(adapter: NfcAdapter) { private fun getInfoFromIdCard(adapter: NfcAdapter) {
adapter.enableReaderMode(activity, { tag -> adapter.enableReaderMode(activity, { tag ->
timer.cancel() timer.cancel()
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
binding!!.timeCounter.text = getString(R.string.card_detected) binding.timeCounter.text = getString(R.string.card_detected)
} }
val card = IsoDep.get(tag) val card = IsoDep.get(tag)
card.timeout = 32768 card.timeout = 32768
card.use { card.use {
try { try {
val comms = Comms(it, viewModel.userCan) val comms = Comms(it, viewModel.userCan)
if (args.auth) {
val jws = Authenticator(comms).authenticate( val jws = Authenticator(comms).authenticate(
intentParameters.challenge, paramsModel.challenge,
intentParameters.authUrl, paramsModel.origin,
viewModel.userPin viewModel.userPin
) )
intentParameters.setToken(jws) paramsModel.setToken(jws)
} else {
val response = comms.readPersonalData(byteArrayOf(1, 2, 6, 3, 4, 8))
viewModel.setUserFirstName(response[1])
viewModel.setUserLastName(response[0])
viewModel.setUserIdentificationNumber(response[2])
viewModel.setGender(response[3])
viewModel.setCitizenship(response[4])
viewModel.setExpiration(response[5])
}
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
binding!!.timeCounter.text = getString(R.string.data_read) goToNextFragment()
} }
} catch (e: Exception) { } catch (e: Exception) {
requireActivity().runOnUiThread { when(e) {
binding!!.timeCounter.text = getString(R.string.no_success) is TagLostException -> requireActivity().runOnUiThread { binding!!.timeCounter.text = getString(R.string.id_card_removed_early) }
else -> {
when ("invalid pin") {
in e.message.toString().lowercase() -> requireActivity().runOnUiThread {
val messagePieces = e.message.toString().split(" ")
binding.timeCounter.text = getString(R.string.wrong_pin, messagePieces[messagePieces.size - 1])
viewModel.deletePin(requireContext())
} }
// If the CAN is wrong we will also delete the saved CAN so that the user won't use it again. else -> requireActivity().runOnUiThread {
binding.timeCounter.text = getString(R.string.wrong_can_text)
viewModel.deleteCan(requireContext()) viewModel.deleteCan(requireContext())
// Gives user some time to read the error message }
Thread.sleep(1000) }
goToTheStart() }
}
// Give user some time to read the error message
Thread.sleep(2000)
cancelAuth()
} finally { } finally {
adapter.disableReaderMode(activity) adapter.disableReaderMode(activity)
} }
@@ -122,36 +148,8 @@ class AuthFragment : Fragment() {
}, NfcAdapter.FLAG_READER_NFC_A, null) }, NfcAdapter.FLAG_READER_NFC_A, null)
} }
private fun goToNextFragment() {
timer.cancel()
if (args.auth) {
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
findNavController().navigate(action)
} else {
findNavController().navigate(R.id.action_authFragment_to_userFragment)
}
}
private fun goToTheStart() {
viewModel.clearUserInfo()
timer.cancel()
if (args.reading) {
findNavController().navigate(R.id.action_authFragment_to_homeFragment)
} else {
if (!args.mobile) {
//Currently for some reason the activity is not killed entirely. Must be looked into further.
requireActivity().finish()
exitProcess(0)
} else {
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
}
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -3,17 +3,22 @@ package com.tarkvaraprojekt.mobileauthapp
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentCanBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentCanBinding
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import org.w3c.dom.Text
/** /**
* Fragment that deals with asking the user for a six digit CAN. If the CAN is already saved * Fragment that deals with asking the user for a six digit CAN. If the CAN is already saved
@@ -24,12 +29,11 @@ class CanFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private var binding: FragmentCanBinding? = null private var _binding: FragmentCanBinding? = null
private val binding get() = _binding!!
// Navigation arguments: // Navigation arguments:
// saving = true means that we are navigating here from the settings menu and must return to the settings menu. // saving = true means that we are navigating here from the settings menu and must return to the settings menu.
// reading = true means that we are only reading the information from the ID card that does not need PIN 1,
// this information is passed on to the next PinFragment.
private val args: CanFragmentArgs by navArgs() private val args: CanFragmentArgs by navArgs()
override fun onCreateView( override fun onCreateView(
@@ -37,20 +41,17 @@ class CanFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentCanBinding.inflate(inflater, container, false) _binding = FragmentCanBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
checkIfSkip() checkIfSkip()
// If the user arrives from the settings menu then the button should say binding.canTextField.editText?.addTextChangedListener {
// save instead of continue. checkEnteredCan()
if (args.saving) {
binding!!.nextButton.text = getString(R.string.save_text)
} }
binding!!.nextButton.setOnClickListener { checkEnteredCan() } binding.buttonCancel.setOnClickListener { goToTheStart() }
binding!!.cancelButton.setOnClickListener { goToTheStart() }
} }
/** /**
@@ -67,77 +68,66 @@ class CanFragment : Fragment() {
* Takes user to the next fragment, which is PinFragment. * Takes user to the next fragment, which is PinFragment.
*/ */
private fun goToTheNextFragment() { private fun goToTheNextFragment() {
val action = CanFragmentDirections.actionCanFragmentToPinFragment(reading = args.reading, auth = args.auth, mobile = args.mobile) val action = CanFragmentDirections.actionCanFragmentToPinFragment(auth = args.auth, mobile = args.mobile)
findNavController().navigate(action) findNavController().navigate(action)
} }
/**
* Checks whether the user has entered a 6 digit can to the input field.
* If yes then the user is allowed to continue otherwise the user is
* allowed to modify the entered can.
*/
private fun checkEnteredCan() {
val enteredCan = binding!!.canEditText.editText?.text.toString()
if (enteredCan.length == 6) {
viewModel.setUserCan(enteredCan)
if (args.saving) {
viewModel.storeCan(requireContext())
goToTheStart()
} else {
val storeCanQuestion = getDialog()
storeCanQuestion?.show()
}
} else {
Toast.makeText(requireContext(), getString(R.string.length_can), Toast.LENGTH_SHORT)
.show()
}
}
/**
* Builds a dialog that asks the user whether the entered CAN should be saved
* on the device or not.
*/
private fun getDialog(): AlertDialog? {
return activity?.let { frag ->
val builder = AlertDialog.Builder(frag)
builder.apply {
// If response is positive then save the CAN on the device.
setPositiveButton(R.string.save_text) { _, _ ->
viewModel.storeCan(
requireContext()
)
goToTheNextFragment()
}
setNegativeButton(R.string.deny_text) { _, _ ->
goToTheNextFragment()
}
}
builder.setMessage(R.string.can_save_request)
builder.setTitle(R.string.save_can_title)
builder.create()
}
}
/** /**
* Navigates the user back to the start depending on where the user arrived. * Navigates the user back to the start depending on where the user arrived.
* If the user arrived from the settings menu then the start is the settings menu * If the user arrived from the settings menu then the start is the settings menu
* not the HomeFragment. * not the HomeFragment.
*/ */
private fun goToTheStart() { private fun goToTheStart() {
// TODO: Needs special handling when the app is launched with intent. Temporary solution at the moment.
if (args.saving) { if (args.saving) {
if (args.fromhome) {
findNavController().navigate(R.id.action_canFragment_to_homeFragment)
} else {
findNavController().navigate(R.id.action_canFragment_to_settingsFragment) findNavController().navigate(R.id.action_canFragment_to_settingsFragment)
} else if (args.auth) { }
} else if (args.auth || args.mobile) {
if (args.mobile) {
val resultIntent = Intent() val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish() requireActivity().finish()
} else {
requireActivity().finishAndRemoveTask()
}
} else { } else {
findNavController().navigate(R.id.action_canFragment_to_homeFragment) findNavController().navigate(R.id.action_canFragment_to_homeFragment)
} }
} }
/**
* Method that creates and shows a snackbar that tells the user that CAN has been saved
*/
private fun showSnackbar() {
val snackbar = Snackbar.make(requireView(), R.string.can_status_saved, Snackbar.LENGTH_SHORT)
val snackbarText: TextView = snackbar.view.findViewById(R.id.snackbar_text)
snackbarText.setTextSize(TypedValue.COMPLEX_UNIT_SP, resources.getDimension(R.dimen.small_text))
snackbar.show()
}
/**
* Checks whether the user has entered a 6 digit can to the input field.
* If yes then the user is allowed to continue otherwise the user is
* allowed to modify the entered can.
*/
private fun checkEnteredCan() {
val enteredCan = binding.canTextField.editText?.text.toString()
if (enteredCan.length == 6) {
viewModel.setUserCan(enteredCan)
viewModel.storeCan(requireContext()) //Maybe storeCan should always automatically call setUserCan method as well because these methods usually are used together
showSnackbar()
if (args.saving) {
goToTheStart()
} else {
goToTheNextFragment()
}
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -1,15 +1,26 @@
package com.tarkvaraprojekt.mobileauthapp package com.tarkvaraprojekt.mobileauthapp
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.nfc.NfcAdapter
import android.nfc.TagLostException
import android.nfc.tech.IsoDep
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
@@ -28,75 +39,129 @@ class HomeFragment : Fragment() {
private val intentParams: ParametersViewModel by activityViewModels() private val intentParams: ParametersViewModel by activityViewModels()
private var binding: FragmentHomeBinding? = null private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
// The ID card reader mode is enabled on the home fragment when can is saved.
private var canSaved: Boolean = false
// Is the app used for authentication
private var auth: Boolean = false
private var receiver: BroadcastReceiver? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false) _binding = FragmentHomeBinding.inflate(inflater, container, false)
// Making settings menu active again // Making settings menu active again
(activity as MainActivity).menuAvailable = true (activity as MainActivity).menuAvailable = true
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initialChecks() initialChecks()
var auth = false
if (requireActivity().intent.data?.getQueryParameter("action") != null) { if (requireActivity().intent.data?.getQueryParameter("action") != null) {
// Currently we only support authentication not signing. // Currently we only support authentication not signing.
auth = true auth = true
} }
val mobile = requireActivity().intent.getBooleanExtra("mobile", false) val mobile = requireActivity().intent.getBooleanExtra("mobile", false)
if (auth || mobile) { if (auth || mobile) {
try { startAuthentication(mobile)
if (mobile) { } else {
// We use !! because we want an exception when something is not right. receiver = object : BroadcastReceiver() {
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!) override fun onReceive(p0: Context?, p1: Intent?) {
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!) updateAction(canSaved)
} else { //Website
// Currently the test website won't send the authUrl parameter
//Log.i("intentDebugging", requireActivity().intent.data.toString())
intentParams.setChallenge(requireActivity().intent.data!!.getQueryParameter("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
} }
} catch (e: Exception) {
// There was a problem with parameters, which means that authentication is not possible.
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
} }
goToTheNextFragment(true, mobile) val filter = IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
requireActivity().registerReceiver(receiver, filter)
updateAction(canSaved)
} }
binding!!.beginButton.setOnClickListener { goToTheNextFragment() }
}
/**
* Method where all the initial checks that should be done before any user input is accepted should be added.
*/
private fun initialChecks() {
viewModel.checkCan(requireContext())
viewModel.checkPin(requireContext())
displayStates()
} }
/** /**
* Starts the process of interacting with the ID card by sending user to the CAN fragment. * Starts the process of interacting with the ID card by sending user to the CAN fragment.
*/ */
private fun goToTheNextFragment(auth: Boolean = false, mobile: Boolean = false) { private fun goToTheNextFragment(mobile: Boolean = false) {
// Making settings menu inactive
(activity as MainActivity).menuAvailable = false (activity as MainActivity).menuAvailable = false
// Currently saving is true because the application is not yet integrated with val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(auth = true, mobile = mobile)
// other applications or websites.
// TODO: Check the navigation action default values. Not everything has to be declared explicitly.
if (auth) {
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = false, auth = true, mobile = mobile)
findNavController().navigate(action) findNavController().navigate(action)
}
/**
* Method that starts the authentication use case.
*
* NOTE: Comment out try-catch block when testing without backend
*/
private fun startAuthentication(mobile: Boolean) {
try {
if (mobile) {
// We use !! to get extras because we want an exception to be thrown when something is missing.
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.getStringExtra("originUrl")!!)
} else { //Website
var challenge = requireActivity().intent.data!!.getQueryParameter("challenge")!!
// TODO: Since due to encoding plus gets converted to space, temporary solution is to replace it back.
challenge = challenge.replace(" ", "+")
intentParams.setChallenge(challenge)
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
intentParams.setOrigin(requireActivity().intent.data!!.getQueryParameter("originUrl")!!)
}
goToTheNextFragment(mobile)
} catch (e: Exception) {
// There was a problem with parameters, which means that authentication is not possible.
// In that case we will cancel the authentication immediately as it would be waste of the user's time to carry on
// before getting an inevitable error.
val message = MaterialAlertDialogBuilder(requireContext())
message.setTitle(getString(R.string.problem_parameters))
if (intentParams.challenge == "") {
message.setMessage(getString(R.string.problem_challenge))
} else if (intentParams.authUrl == "") {
message.setMessage(getString(R.string.problem_authurl))
} else if (intentParams.origin == "") {
message.setMessage(getString(R.string.problem_originurl))
} else { } else {
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = true, auth = false, mobile = mobile) message.setMessage(getString(R.string.problem_other))
findNavController().navigate(action) }
message.setPositiveButton(getString(R.string.continue_button)) {_, _ ->
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
}
message.show()
}
}
/**
* Checks the state of the CAN, saved or not saved. Updates the text and logo.
*/
private fun canState() {
if (viewModel.userCan.length == 6) {
binding.canStatusText.text = getString(R.string.can_status_saved)
binding.canStatusLogo.setImageResource(R.drawable.ic_check_logo)
canSaved = true
} else {
binding.canStatusText.text = getString(R.string.can_status_negative)
binding.canStatusLogo.setImageResource(R.drawable.ic_info_logo)
canSaved = false
}
}
/**
* Checks the state of the PIN 1, saved or not saved. Updates the text and logo.
*/
private fun pinState() {
if (viewModel.userPin.length in 4..12) {
binding.pinStatusText.text = getString(R.string.pin_status_saved)
binding.pinStatusLogo.setImageResource(R.drawable.ic_check_logo)
} else {
binding.pinStatusText.text = getString(R.string.pin_status_negative)
binding.pinStatusLogo.setImageResource(R.drawable.ic_info_logo)
} }
} }
@@ -111,33 +176,117 @@ class HomeFragment : Fragment() {
} }
/** /**
* Checks the state of the CAN, saved or not saved. Updates the text and logo. * Method where all the initial checks that should be completed before any user input is accepted should be conducted.
*/ */
private fun canState() { private fun initialChecks() {
if (viewModel.userCan.length == 6) { viewModel.checkCan(requireContext())
binding!!.canStatusText.text = getString(R.string.can_status_saved) viewModel.checkPin(requireContext())
binding!!.canStatusLogo.setImageResource(R.drawable.ic_check_logo) displayStates()
}
/**
* Displays a help message to the user explaining what the CAN is
*/
private fun displayMessage(title: String, message: String) {
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.return_text){_, _ -> }
.show()
val title = dialog.findViewById<TextView>(R.id.alertTitle)
title?.textSize = 24F
}
/**
* Informs user whether the ID card can be detected or not.
*/
private fun updateAction(canIsSaved: Boolean) {
if (canIsSaved) {
binding.detectionActionText.text = getString(R.string.action_detect)
enableReaderMode()
binding.homeActionButton.visibility = View.GONE
binding.homeHelpButton.visibility = View.GONE
} else { } else {
binding!!.canStatusText.text = getString(R.string.can_status_negative) binding.detectionActionText.text = getString(R.string.action_detect_unavailable)
binding!!.canStatusLogo.setImageResource(R.drawable.ic_info_logo) binding.homeActionButton.text = getString(R.string.add_can_text)
binding.homeActionButton.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(saving = true, fromhome = true)
findNavController().navigate(action)
}
binding.homeHelpButton.setOnClickListener {
displayMessage(getString(R.string.can_question), getString(R.string.can_explanation))
}
binding.homeActionButton.visibility = View.VISIBLE
binding.homeHelpButton.visibility = View.VISIBLE
} }
} }
/** /**
* Checks the state of the PIN 1, saved or not saved. Updates the text and logo. * Resets the error message and allows the user to try again
*/ */
private fun pinState() { private fun reset() {
if (viewModel.userPin.length in 4..12) { binding.homeActionButton.text = getString(R.string.try_again_text)
binding!!.pinStatusText.text = getString(R.string.pin_status_saved) binding.homeActionButton.setOnClickListener {
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_check_logo) updateAction(canSaved)
}
binding.homeActionButton.visibility = View.VISIBLE
}
/**
* Method that enables the NFC reader mode, which allows the app to communicate with the ID card and retrieve information.
*/
private fun enableReaderMode() {
val adapter = NfcAdapter.getDefaultAdapter(activity)
if (adapter == null || !adapter.isEnabled) {
binding.detectionActionText.text = getString(R.string.nfc_not_available)
} else { } else {
binding!!.pinStatusText.text = getString(R.string.pin_status_negative) adapter.enableReaderMode(activity, { tag ->
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_info_logo) requireActivity().runOnUiThread {
binding.detectionActionText.text = getString(R.string.card_detected)
}
val card = IsoDep.get(tag)
card.timeout = 32768
card.use {
try {
val comms = Comms(it, viewModel.userCan)
val response = comms.readPersonalData(byteArrayOf(1, 2, 6, 3, 4, 8))
viewModel.setUserFirstName(response[1])
viewModel.setUserLastName(response[0])
viewModel.setUserIdentificationNumber(response[2])
viewModel.setGender(response[3])
viewModel.setCitizenship(response[4])
viewModel.setExpiration(response[5])
requireActivity().runOnUiThread {
val action = HomeFragmentDirections.actionHomeFragmentToUserFragment()
findNavController().navigate(action)
}
} catch (e: Exception) {
when(e) {
is TagLostException -> requireActivity().runOnUiThread {
binding.detectionActionText.text = getString(R.string.id_card_removed_early)
reset()
}
else -> requireActivity().runOnUiThread {
binding.detectionActionText.text = getString(R.string.nfc_reading_error)
viewModel.deleteCan(requireContext())
canState()
reset()
}
}
} finally {
adapter.disableReaderMode(activity)
}
}
}, NfcAdapter.FLAG_READER_NFC_A, null)
} }
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
binding = null if (receiver != null) {
requireActivity().unregisterReceiver(receiver)
}
_binding = null
} }
} }

View File

@@ -20,6 +20,8 @@ class MainActivity : AppCompatActivity() {
// If true the settings menu can be accessed from the toolbar in the upper part of the screen. // If true the settings menu can be accessed from the toolbar in the upper part of the screen.
var menuAvailable: Boolean = true var menuAvailable: Boolean = true
var inMenu: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater) val binding = ActivityMainBinding.inflate(layoutInflater)
@@ -40,9 +42,13 @@ class MainActivity : AppCompatActivity() {
R.id.menu_settings_option -> { R.id.menu_settings_option -> {
if (menuAvailable) { if (menuAvailable) {
navigationController.navigate(R.id.action_homeFragment_to_settingsFragment) navigationController.navigate(R.id.action_homeFragment_to_settingsFragment)
menuAvailable = false
inMenu = true
true true
} else { } else {
Toast.makeText(this, getString(R.string.unavailable), Toast.LENGTH_SHORT).show() if (!inMenu) {
Toast.makeText(this, getString(R.string.menu_unavailable_message), Toast.LENGTH_SHORT).show()
}
false false
} }
} }

View File

@@ -19,23 +19,24 @@ class Pin2Fragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private var binding: FragmentPin2Binding? = null private var _binding: FragmentPin2Binding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentPin2Binding.inflate(inflater, container, false) _binding = FragmentPin2Binding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding!!.nextButton.setOnClickListener { binding.nextButton.setOnClickListener {
checkPin2Length() checkPin2Length()
} }
binding!!.cancelButton.setOnClickListener { binding.cancelButton.setOnClickListener {
cancel() cancel()
} }
} }
@@ -45,7 +46,7 @@ class Pin2Fragment : Fragment() {
* then it is saved to the viewModel. * then it is saved to the viewModel.
*/ */
private fun checkPin2Length() { private fun checkPin2Length() {
val enteredPin2 = binding!!.pin2EditText.editText?.text.toString() val enteredPin2 = binding.pin2EditText.editText?.text.toString()
if (enteredPin2.length in 5..12) { if (enteredPin2.length in 5..12) {
viewModel.setUserPin2(enteredPin2) viewModel.setUserPin2(enteredPin2)
} else { } else {
@@ -66,7 +67,7 @@ class Pin2Fragment : Fragment() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -1,17 +1,22 @@
package com.tarkvaraprojekt.mobileauthapp package com.tarkvaraprojekt.mobileauthapp
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.google.android.material.snackbar.Snackbar
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentPinBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentPinBinding
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
@@ -24,121 +29,129 @@ class PinFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private var binding: FragmentPinBinding? = null private var _binding: FragmentPinBinding? = null
private val binding get() = _binding!!
// Navigation arguments: // Navigation arguments:
// saving = true means that the user must be returned to the settings menu // saving = true means that the user must be returned to the settings menu
// reading = true means that we are reading information from the ID card that does
// not require PIN 1 so it is not necessary to ask it.
private val args: PinFragmentArgs by navArgs() private val args: PinFragmentArgs by navArgs()
private var saveToggle = true
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentPinBinding.inflate(inflater, container, false) _binding = FragmentPinBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
checkIfSkip() checkIfSkip()
// If the user arrives from the settings menu then the button says // Switch should be not visible when user is in savings mode
// save instead of continue.
if (args.saving) { if (args.saving) {
binding!!.nextButton.text = getString(R.string.save_text) binding.savePinQuestion.visibility = View.GONE
binding.saveLayout.visibility = View.GONE
} else {
saveToggle =
activity?.getPreferences(Context.MODE_PRIVATE)?.getBoolean("saveToggle", true) == true //Android Studio recommendation to get rid of Boolean?.
if (!saveToggle) {
binding.saveSwitch.isChecked = false
} }
binding!!.nextButton.setOnClickListener { checkEnteredPin() } binding.saveSwitch.setOnCheckedChangeListener { _, isChecked ->
binding!!.cancelButton.setOnClickListener { goToTheStart() } if (isChecked) {
binding.saveStatus.text = getString(R.string.pin_save_on)
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", true)?.apply()
} else {
binding.saveStatus.text = getString(R.string.pin_save_off)
activity?.getPreferences(Context.MODE_PRIVATE)?.edit()?.putBoolean("saveToggle", false)?.apply()
} }
saveToggle = !saveToggle
/**
* Checks if the current fragment can be skipped or not.
* If the user has PIN 1 saved on the device or PIN 1 is not required
* then the PIN 1 won't be asked.
*/
private fun checkIfSkip() {
if (args.reading) {
goToTheNextFragment()
} else if (viewModel.userPin.length in 4..12) {
goToTheNextFragment()
} }
} }
binding.buttonContinue.setOnClickListener { checkEnteredPin() }
binding.buttonCancel.setOnClickListener { goToTheStart() }
}
/** /**
* Takes user to the next fragment, which is AuthFragment. * Takes user to the next fragment, which is AuthFragment.
*/ */
private fun goToTheNextFragment() { private fun goToTheNextFragment() {
val action = PinFragmentDirections.actionPinFragmentToAuthFragment(reading = args.reading, auth = args.auth, mobile = args.mobile) val action = PinFragmentDirections.actionPinFragmentToAuthFragment(auth = args.auth, mobile = args.mobile)
findNavController().navigate(action) findNavController().navigate(action)
} }
/**
* Checks whether the user has entered a PIN 1 with length between [4, 12] in the
* input field. If yes then the user is allowed to continue otherwise the user is
* allowed to modify the entered PIN 1.
*/
private fun checkEnteredPin() {
val enteredPin = binding!!.pinEditText.editText?.text.toString()
if (enteredPin.length in 4..12) {
viewModel.setUserPin(enteredPin)
if (args.saving) {
viewModel.storePin(requireContext())
goToTheStart()
} else {
val storePinQuestion = getDialog()
storePinQuestion?.show()
}
} else {
Toast.makeText(requireContext(), getString(R.string.length_pin), Toast.LENGTH_SHORT)
.show()
}
}
/**
* Builds a dialog that asks the user whether the entered PIN 1 should be saved
* on the device or not.
*/
private fun getDialog(): AlertDialog? {
return activity?.let { frag ->
val builder = AlertDialog.Builder(frag)
builder.apply {
// If response is positive save the PIN 1 on the device.
setPositiveButton(R.string.save_text) { _, _ ->
viewModel.storePin(
requireContext()
)
goToTheNextFragment()
}
setNegativeButton(R.string.deny_text) { _, _ ->
goToTheNextFragment()
}
}
builder.setMessage(R.string.pin_save_request)
builder.setTitle(R.string.save_pin_title)
builder.create()
}
}
/** /**
* Returns user to the start. If the user arrived from the settings menu then the start is * Returns user to the start. If the user arrived from the settings menu then the start is
* settings menu not the HomeFragment. * settings menu not the HomeFragment.
*/ */
private fun goToTheStart() { private fun goToTheStart() {
if (args.saving) { if (args.saving) {
findNavController().navigate(R.id.action_canFragment_to_settingsFragment) findNavController().navigate(R.id.action_pinFragment_to_settingsFragment)
} else if (args.auth) { } else if (args.auth || args.mobile) {
if (args.mobile) {
val resultIntent = Intent() val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish() requireActivity().finish()
} else {
requireActivity().finishAndRemoveTask()
}
} else { } else {
findNavController().navigate(R.id.action_canFragment_to_homeFragment) findNavController().navigate(R.id.action_canFragment_to_homeFragment)
} }
} }
/**
* Checks if the current fragment can be skipped or not.
* If the user has PIN 1 saved on the device or PIN 1 is not required
* then the PIN 1 won't be asked.
*/
private fun checkIfSkip() {
if (viewModel.userPin.length in 4..12) {
goToTheNextFragment()
}
}
/**
* Method that creates and shows a snackbar that tells the user that PIN 1 has been saved
*/
private fun showSnackbar() {
val snackbar = Snackbar.make(requireView(), R.string.pin_status_saved, Snackbar.LENGTH_SHORT)
val snackbarText: TextView = snackbar.view.findViewById(R.id.snackbar_text)
snackbarText.setTextSize(TypedValue.COMPLEX_UNIT_SP, resources.getDimension(R.dimen.small_text))
snackbar.show()
}
/**
* Checks whether the user has entered a PIN 1 with length between [4, 12] in the
* input field. If yes then the user is allowed to continue otherwise the user is
* allowed to modify the entered PIN 1.
*/
private fun checkEnteredPin() {
val enteredPin = binding.pinTextField.editText?.text.toString()
if (enteredPin.length in 4..12) {
viewModel.setUserPin(enteredPin)
if (args.saving) {
viewModel.storePin(requireContext())
showSnackbar()
goToTheStart()
} else {
if (saveToggle) {
viewModel.storePin(requireContext())
showSnackbar()
}
goToTheNextFragment()
}
} else {
Toast.makeText(requireContext(), getString(R.string.pin_helper_text), Toast.LENGTH_SHORT)
.show()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -14,15 +14,6 @@ import com.google.gson.JsonObject
import com.koushikdutta.ion.Ion import com.koushikdutta.ion.Ion
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import com.tarkvaraprojekt.mobileauthapp.network.BASE_URL
import com.tarkvaraprojekt.mobileauthapp.network.TokenApi
import com.tarkvaraprojekt.mobileauthapp.network.TokenApiService
import com.tarkvaraprojekt.mobileauthapp.network.TokenItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.system.exitProcess
/** /**
* ResultFragment is used to create a JWT and to send response to the website/application * ResultFragment is used to create a JWT and to send response to the website/application
@@ -33,7 +24,8 @@ class ResultFragment : Fragment() {
private val paramsModel: ParametersViewModel by activityViewModels() private val paramsModel: ParametersViewModel by activityViewModels()
private var binding: FragmentResultBinding? = null private var _binding: FragmentResultBinding? = null
private val binding get() = _binding!!
private val args: ResultFragmentArgs by navArgs() private val args: ResultFragmentArgs by navArgs()
@@ -42,18 +34,25 @@ class ResultFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentResultBinding.inflate(inflater, container, false) _binding = FragmentResultBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding!!.resultBackButton.setOnClickListener {
// if (args.mobile) {
// createResponse()
// }
postToken() postToken()
} }
/**
* Only used when the MobileAuthApp was launched by an app. Not for website use.
*/
private fun createResponse(success: Boolean = true, result: String = "noResult", token: String = "noToken") {
val responseCode = if (success) AppCompatActivity.RESULT_OK else AppCompatActivity.RESULT_CANCELED
val resultIntent = Intent()
resultIntent.putExtra("result", result)
resultIntent.putExtra("token", token)
requireActivity().setResult(responseCode, resultIntent)
requireActivity().finish()
} }
/** /**
@@ -64,47 +63,31 @@ class ResultFragment : Fragment() {
json.addProperty("token", paramsModel.token) json.addProperty("token", paramsModel.token)
json.addProperty("challenge", paramsModel.challenge) json.addProperty("challenge", paramsModel.challenge)
Ion.getDefault(activity).getConscryptMiddleware().enable(false) Ion.getDefault(activity).conscryptMiddleware.enable(false)
Ion.with(activity) Ion.with(activity)
.load("https://6bb0-85-253-195-252.ngrok.io/auth/authentication") .load(paramsModel.origin + paramsModel.authUrl)
.setJsonObjectBody(json) .setJsonObjectBody(json)
.asJsonObject() .asJsonObject()
.setCallback { e, result -> .setCallback { e, result ->
// do stuff with the result or error if (result == null) {
Log.i("Log thingy", result.toString()) if (args.mobile) {
createResponse(false)
} else {
requireActivity().finishAndRemoveTask()
}
} else {
if (args.mobile) {
createResponse(true, result.toString(), paramsModel.token)
} else {
requireActivity().finishAndRemoveTask()
}
} }
// CoroutineScope(Dispatchers.Default).launch {
// val response = TokenApi.retrofitService.postToken(jsonBody)
// Log.v("Response", response.message())
// if (response.isSuccessful) {
// //Success scenario here
// } else {
// //Failure scenario here
// if (args.mobile) {
// createResponse(false)
// } else {
// //Currently for some reason the activity is not killed entirely. Must be looked into further.
// requireActivity().finish()
// exitProcess(0)
// }
// }
// }
} }
/**
* Only used when the MobileAuthApp was launched by an app. Not for website use.
*/
private fun createResponse(success: Boolean = true) {
val responseCode = if (success) AppCompatActivity.RESULT_OK else AppCompatActivity.RESULT_CANCELED
val resultIntent = Intent()
requireActivity().setResult(responseCode, resultIntent)
requireActivity().finish()
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -20,33 +20,34 @@ class UserFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private var binding: FragmentUserBinding? = null private var _binding: FragmentUserBinding? = null
private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentUserBinding.inflate(inflater, container, false) _binding = FragmentUserBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
displayInformation() displayInformation()
binding!!.clearButton.setOnClickListener { goToTheStart() } binding.clearButton.setOnClickListener { goToTheStart() }
} }
/** /**
* Assigns text values to the fields in order to display user information. * Assigns text values to the fields in order to display user information.
*/ */
private fun displayInformation() { private fun displayInformation() {
binding!!.userName.text = binding.userName.text =
getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName) getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName)
binding!!.identificationNumber.text = viewModel.userIdentificationNumber binding.identificationNumber.text = viewModel.userIdentificationNumber
binding!!.gender.text = viewModel.gender binding.gender.text = viewModel.gender
binding!!.expiration.text = viewModel.expiration.replace(" ", "/") binding.expiration.text = viewModel.expiration.replace(" ", "/")
binding!!.citizenship.text = viewModel.citizenship binding.citizenship.text = viewModel.citizenship
} }
/** /**
@@ -59,6 +60,6 @@ class UserFragment : Fragment() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -36,7 +36,7 @@ class Authenticator(val comms : Comms) {
// Get header and claims. // Get header and claims.
val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}""" val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}"""
val claims = val claims =
"""{"iat":"$epoch","exp":"$exp","aud":"$originUrl","iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}""" """{"iat":"$epoch","exp":"$exp","aud":["$originUrl"],"iss":"$iss","sub":"$sub","nonce":"$challenge","cnf":{"tbh":""}}"""
val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode( val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode(
claims.toByteArray(Charsets.UTF_8) claims.toByteArray(Charsets.UTF_8)

View File

@@ -1,13 +1,17 @@
package com.tarkvaraprojekt.mobileauthapp.menu package com.tarkvaraprojekt.mobileauthapp.menu
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import com.tarkvaraprojekt.mobileauthapp.MainActivity
import com.tarkvaraprojekt.mobileauthapp.R import com.tarkvaraprojekt.mobileauthapp.R
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentSettingsBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentSettingsBinding
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
@@ -21,7 +25,8 @@ class SettingsFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private var binding: FragmentSettingsBinding? = null private var _binding: FragmentSettingsBinding? = null
private val binding get() = _binding!!
private var showPin: Boolean = false private var showPin: Boolean = false
@@ -30,8 +35,8 @@ class SettingsFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = FragmentSettingsBinding.inflate(inflater, container, false) _binding = FragmentSettingsBinding.inflate(inflater, container, false)
return binding!!.root return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -39,10 +44,20 @@ class SettingsFragment : Fragment() {
showCanField() showCanField()
showPinField() showPinField()
togglePinButton() togglePinButton()
binding!!.canMenuAction.setOnClickListener { canAction() } binding.canMenuAction.setOnClickListener { canAction() }
binding!!.pinMenuAction.setOnClickListener { pinAction() } binding.pinMenuAction.setOnClickListener { pinAction() }
binding!!.pinMenuShow.setOnClickListener { togglePin() } binding.pinMenuShow.setOnClickListener { togglePin() }
binding!!.returnButton.setOnClickListener { backToHome() } binding.returnButton.setOnClickListener { backToHome() }
}
/**
* Method for showing a snackbar with a message that is given as a parameter
*/
private fun showSnackbar(message: String) {
val snackbar = Snackbar.make(requireView(), message, Snackbar.LENGTH_SHORT)
val snackbarText: TextView = snackbar.view.findViewById(R.id.snackbar_text)
snackbarText.setTextSize(TypedValue.COMPLEX_UNIT_SP, resources.getDimension(R.dimen.small_text))
snackbar.show()
} }
/** /**
@@ -50,11 +65,11 @@ class SettingsFragment : Fragment() {
*/ */
private fun showCanField() { private fun showCanField() {
if (viewModel.userCan.length == 6) { if (viewModel.userCan.length == 6) {
binding!!.canSaved.text = getString(R.string.saved_can, viewModel.userCan) binding.canSaved.text = getString(R.string.saved_can, viewModel.userCan)
binding!!.canMenuAction.text = getString(R.string.can_delete) binding.canMenuAction.text = getString(R.string.can_delete)
} else { } else {
binding!!.canSaved.text = getString(R.string.saved_can, getString(R.string.missing)) binding.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
binding!!.canMenuAction.text = getString(R.string.can_add) binding.canMenuAction.text = getString(R.string.add_can_text)
} }
} }
@@ -66,7 +81,9 @@ class SettingsFragment : Fragment() {
if (viewModel.userCan.length == 6) { if (viewModel.userCan.length == 6) {
viewModel.deleteCan(requireContext()) viewModel.deleteCan(requireContext())
showCanField() showCanField()
showSnackbar(getString(R.string.can_deleted))
} else { } else {
(activity as MainActivity).inMenu = false
val action = SettingsFragmentDirections.actionSettingsFragmentToCanFragment(saving = true) val action = SettingsFragmentDirections.actionSettingsFragmentToCanFragment(saving = true)
findNavController().navigate(action) findNavController().navigate(action)
} }
@@ -79,16 +96,16 @@ class SettingsFragment : Fragment() {
*/ */
private fun showPinField() { private fun showPinField() {
if (viewModel.userPin.length in 4..12) { if (viewModel.userPin.length in 4..12) {
binding!!.pinMenuShow.visibility = Button.VISIBLE binding.pinMenuShow.visibility = Button.VISIBLE
if (showPin) if (showPin)
binding!!.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin) binding.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin)
else else
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin)) binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin))
binding!!.pinMenuAction.text = getString(R.string.pin1_delete) binding.pinMenuAction.text = getString(R.string.pin1_delete)
} else { } else {
binding!!.pinMenuShow.visibility = Button.GONE binding.pinMenuShow.visibility = Button.GONE
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing)) binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing))
binding!!.pinMenuAction.text = getString(R.string.pin1_add) binding.pinMenuAction.text = getString(R.string.pin1_add)
} }
} }
@@ -100,7 +117,9 @@ class SettingsFragment : Fragment() {
if (viewModel.userPin.length in 4..12) { if (viewModel.userPin.length in 4..12) {
viewModel.deletePin(requireContext()) viewModel.deletePin(requireContext())
showPinField() showPinField()
showSnackbar(getString(R.string.pin_deleted))
} else { } else {
(activity as MainActivity).inMenu = false
val action = SettingsFragmentDirections.actionSettingsFragmentToPinFragment(saving = true) val action = SettingsFragmentDirections.actionSettingsFragmentToPinFragment(saving = true)
findNavController().navigate(action) findNavController().navigate(action)
} }
@@ -120,9 +139,9 @@ class SettingsFragment : Fragment() {
*/ */
private fun togglePinButton() { private fun togglePinButton() {
if (showPin) { if (showPin) {
binding!!.pinMenuShow.text = getString(R.string.hide) binding.pinMenuShow.text = getString(R.string.hide)
} else { } else {
binding!!.pinMenuShow.text = getString(R.string.show) binding.pinMenuShow.text = getString(R.string.show)
} }
} }
@@ -130,12 +149,13 @@ class SettingsFragment : Fragment() {
* Navigates back to home fragment. * Navigates back to home fragment.
*/ */
private fun backToHome() { private fun backToHome() {
(activity as MainActivity).inMenu = false
findNavController().navigate(R.id.action_settingsFragment_to_homeFragment) findNavController().navigate(R.id.action_settingsFragment_to_homeFragment)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
binding = null _binding = null
} }
} }

View File

@@ -13,6 +13,9 @@ class ParametersViewModel: ViewModel() {
private var _token: String = "" private var _token: String = ""
val token get() = _token val token get() = _token
private var _origin: String = ""
val origin get() = _origin
fun setChallenge(newChallenge: String) { fun setChallenge(newChallenge: String) {
_challenge = newChallenge _challenge = newChallenge
} }
@@ -24,4 +27,8 @@ class ParametersViewModel: ViewModel() {
fun setToken(newToken: String) { fun setToken(newToken: String) {
_token = newToken _token = newToken
} }
fun setOrigin(newOrigin: String) {
_origin = newOrigin
}
} }

View File

@@ -1,34 +0,0 @@
package com.tarkvaraprojekt.mobileauthapp.network
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
/**
* Class for making HTTP requests
* Based on https://developer.android.com/courses/pathways/android-basics-kotlin-unit-4-pathway-2
*/
const val BASE_URL =
"https://6bb0-85-253-195-252.ngrok.io"
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val retrofit = Retrofit.Builder().addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL).build()
interface TokenApiService {
@Headers("Content-Type: application/json")
@POST("/auth/authentication")
suspend fun postToken(@Body data: String): Response<TokenItem>
}
object TokenApi {
val retrofitService : TokenApiService by lazy {
retrofit.create(TokenApiService::class.java)
}
}

View File

@@ -1,9 +0,0 @@
package com.tarkvaraprojekt.mobileauthapp.network
/**
* TokenItem for making POST request.
*/
data class TokenItem (
val token: String,
val challenge: String,
)

View File

@@ -5,7 +5,7 @@
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path <path
android:fillColor="#3DDC84" android:fillColor="#001970"
android:pathData="M0,0h108v108h-108z" /> android:pathData="M0,0h108v108h-108z" />
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"

View File

@@ -4,14 +4,14 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".AuthFragment"> tools:context=".AuthFragment">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/card_view" android:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="@dimen/margin"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@@ -23,31 +23,32 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="20sp"> android:padding="@dimen/padding_small">
<TextView <TextView
android:id="@+id/auth_fragment_instruction" android:id="@+id/auth_fragment_instruction"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="@dimen/margin"
android:gravity="center" android:gravity="left"
android:text="@string/auth_instruction_text" android:text="@string/auth_instruction_text"
android:textSize="20sp" /> android:textSize="@dimen/regular_text" />
<ImageView <ImageView
android:id="@+id/nfc_logo" android:id="@+id/nfc_logo"
android:layout_width="wrap_content" android:layout_width="@dimen/logo_big"
android:layout_height="wrap_content" android:layout_height="@dimen/logo_big"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_margin="6dp" android:layout_margin="@dimen/margin"
android:padding="@dimen/margin_huge"
android:src="@drawable/nfc_logo" /> android:src="@drawable/nfc_logo" />
<TextView <TextView
android:id="@+id/time_counter" android:id="@+id/time_counter"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="@dimen/margin"
android:textSize="14sp" android:textSize="@dimen/regular_text"
app:layout_constraintTop_toBottomOf="@id/auth_fragment_instruction" app:layout_constraintTop_toBottomOf="@id/auth_fragment_instruction"
tools:text="@string/time_left" /> tools:text="@string/time_left" />
@@ -60,9 +61,9 @@
android:id="@+id/next_button" android:id="@+id/next_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/next_text" android:text="@string/continue_button"
android:textSize="15sp" android:textSize="@dimen/regular_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cancel_button" app:layout_constraintStart_toEndOf="@id/cancel_button"
app:layout_constraintTop_toBottomOf="@id/card_view" /> app:layout_constraintTop_toBottomOf="@id/card_view" />
@@ -71,10 +72,10 @@
android:id="@+id/cancel_button" android:id="@+id/cancel_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:layout_marginStart="@dimen/padding_tiny"
android:text="@string/cancel_text" android:text="@string/cancel_text"
android:textSize="15sp" android:textSize="@dimen/regular_text"
app:layout_constraintEnd_toStartOf="@id/next_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_view" /> app:layout_constraintTop_toBottomOf="@id/card_view" />

View File

@@ -4,82 +4,57 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".CanFragment"> tools:context=".MainActivity">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="1dp"
app:strokeColor="@color/stroke_color"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20sp">
<TextView <TextView
android:id="@+id/enter_can" android:id="@+id/title_text"
android:text="@string/can_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:textSize="@dimen/headline_text"
android:text="@string/enter_can" android:layout_margin="@dimen/margin_big"
android:textSize="20sp" /> android:fontFamily="sans-serif"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/can_edit_text" android:id="@+id/canTextField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="@dimen/margin_big"
android:hint="@string/text_can" android:hint="@string/can_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_text"
app:helperTextEnabled="true"
app:helperText="@string/can_helper_text"
app:helperTextTextAppearance="@style/helper"
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="6" app:counterMaxLength="6"
app:endIconMode="password_toggle" app:counterTextAppearance="@style/helper"
app:errorEnabled="true" app:counterOverflowTextAppearance="@style/helper"
app:helperText="@string/example_can" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
app:helperTextEnabled="true"
app:startIconDrawable="@drawable/can_logo">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="numberPassword" android:textSize="@dimen/regular_text"
android:textSize="14sp" /> android:fontFamily="sans-serif"
android:inputType="number"
android:singleLine="true"
/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<Button <Button
android:id="@+id/next_button" android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/next_text"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cancel_button"
app:layout_constraintTop_toBottomOf="@id/card_view" />
<Button
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/cancel_text" android:text="@string/cancel_text"
android:textSize="15sp" android:textSize="@dimen/regular_text"
app:layout_constraintEnd_toStartOf="@id/next_button" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_big"
android:fontFamily="sans-serif"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_view" /> app:layout_constraintTop_toBottomOf="@id/canTextField" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".HomeFragment"> tools:context=".HomeFragment">
<LinearLayout <LinearLayout
@@ -20,7 +20,7 @@
android:id="@+id/can_status" android:id="@+id/can_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="@dimen/margin_big"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="@color/stroke_color" app:strokeColor="@color/stroke_color"
app:cardElevation="0dp"> app:cardElevation="0dp">
@@ -32,14 +32,14 @@
<ImageView <ImageView
android:id="@+id/can_status_logo" android:id="@+id/can_status_logo"
android:layout_marginStart="12dp" android:layout_marginStart="@dimen/margin"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<TextView <TextView
android:id="@+id/can_status_text" android:id="@+id/can_status_text"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:padding="12dp" android:padding="@dimen/margin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@@ -51,7 +51,7 @@
android:id="@+id/pin_status" android:id="@+id/pin_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="@dimen/margin_big"
app:strokeWidth="1dp" app:strokeWidth="1dp"
app:strokeColor="@color/stroke_color" app:strokeColor="@color/stroke_color"
app:cardElevation="0dp"> app:cardElevation="0dp">
@@ -63,14 +63,14 @@
<ImageView <ImageView
android:id="@+id/pin_status_logo" android:id="@+id/pin_status_logo"
android:layout_marginStart="12dp" android:layout_marginStart="@dimen/margin"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
<TextView <TextView
android:id="@+id/pin_status_text" android:id="@+id/pin_status_text"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:padding="12dp" android:padding="@dimen/margin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
@@ -80,15 +80,49 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/id_card_detection"
android:layout_margin="@dimen/margin_big"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/saved_states"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/detection_action_text"
android:layout_margin="@dimen/margin_big"
android:textSize="@dimen/regular_text"
android:text="@string/action_detect"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<Button <Button
android:id="@+id/begin_button" android:id="@+id/home_action_button"
android:textSize="@dimen/regular_text"
android:text="@string/try_again_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/begin_text" android:layout_marginTop="@dimen/margin_small"
android:layout_marginTop="24dp" android:layout_marginStart="@dimen/margin_huge"
android:textSize="15sp" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/saved_states"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintTop_toBottomOf="@id/id_card_detection"/>
<Button
android:id="@+id/home_help_button"
android:textSize="@dimen/regular_text"
android:text="@string/help_text"
android:layout_marginTop="@dimen/margin_small"
android:layout_marginStart="@dimen/margin_huge"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="?attr/materialButtonOutlinedStyle"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/home_action_button"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,81 +4,111 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".PinFragment"> tools:context=".MainActivity">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeWidth="1dp"
app:strokeColor="@color/stroke_color"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView <TextView
android:id="@+id/pin_fragment_text" android:id="@+id/title_text"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="@dimen/margin_big"
android:text="@string/pin_fragment" /> android:fontFamily="sans-serif"
android:text="@string/pin_view"
android:textSize="@dimen/headline_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/pin_edit_text" android:id="@+id/pinTextField"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="6dp" android:layout_margin="@dimen/margin_big"
android:hint="@string/enter_pin" android:hint="@string/hint_pin"
app:counterEnabled="true" app:counterEnabled="true"
app:counterMaxLength="12" app:counterMaxLength="12"
app:counterOverflowTextAppearance="@style/helper"
app:counterTextAppearance="@style/helper"
app:endIconMode="password_toggle" app:endIconMode="password_toggle"
app:errorEnabled="true" app:helperText="@string/pin_helper_text"
app:helperText="@string/example_pin"
app:helperTextEnabled="true" app:helperTextEnabled="true"
app:startIconDrawable="@drawable/can_logo"> app:helperTextTextAppearance="@style/helper"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_text">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:inputType="numberPassword" android:inputType="numberPassword"
android:textSize="14sp" /> android:singleLine="true"
android:textSize="@dimen/regular_text" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/save_pin_question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_big"
android:paddingTop="@dimen/padding"
android:fontFamily="sans-serif"
android:text="@string/save_pin"
android:textSize="@dimen/regular_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/pinTextField" />
<LinearLayout
android:id="@+id/save_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/save_pin_question">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/save_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_big"
android:checked="true"
android:minWidth="48dp"
android:minHeight="48dp"
android:layout_gravity="center_vertical"/>
<TextView
android:id="@+id/save_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_big"
android:fontFamily="sans-serif"
android:text="@string/pin_save_on"
android:textSize="@dimen/regular_text"
android:layout_gravity="center_vertical"/>
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView>
<Button <Button
android:id="@+id/next_button" android:id="@+id/button_continue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_margin="@dimen/margin_big"
android:text="@string/next_text" android:fontFamily="sans-serif"
android:textSize="15sp" android:text="@string/continue_button"
app:layout_constraintEnd_toEndOf="parent" android:textSize="@dimen/regular_text"
app:layout_constraintStart_toEndOf="@id/cancel_button"
app:layout_constraintTop_toBottomOf="@id/card_view" />
<Button
android:id="@+id/cancel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/cancel_text"
android:textSize="15sp"
app:layout_constraintEnd_toStartOf="@id/next_button"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_view" /> app:layout_constraintTop_toBottomOf="@id/save_layout" />
<Button
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_big"
android:fontFamily="sans-serif"
android:text="@string/cancel_text"
android:textSize="@dimen/regular_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_continue" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -64,7 +64,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="@string/next_text" android:text="@string/continue_button"
android:textSize="15sp" android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/cancel_button" app:layout_constraintStart_toEndOf="@id/cancel_button"

View File

@@ -4,14 +4,14 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".ResultFragment"> tools:context=".ResultFragment">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/can_status" android:id="@+id/can_status"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="@dimen/margin"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -27,33 +27,23 @@
<TextView <TextView
android:id="@+id/result_text" android:id="@+id/result_text"
android:text="@string/result_text" android:text="@string/result_text"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:padding="12dp" android:padding="@dimen/padding_small"
android:layout_marginVertical="6dp" android:layout_marginVertical="@dimen/margin_small"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<TextView <TextView
android:id="@+id/result_info_text" android:id="@+id/result_info_text"
android:text="@string/result_info" android:text="@string/result_info"
android:padding="12dp" android:padding="@dimen/padding_small"
android:textSize="16sp" android:textSize="@dimen/regular_text"
android:layout_marginVertical="6dp" android:layout_marginVertical="@dimen/margin_small"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Button
android:id="@+id/result_back_button"
android:text="@string/return_text"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="12dp" android:layout_margin="@dimen/padding"
tools:context=".menu.SettingsFragment"> tools:context=".menu.SettingsFragment">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
@@ -22,44 +22,46 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="12dp"> android:padding="@dimen/padding_small">
<TextView <TextView
android:id="@+id/can_saved" android:id="@+id/can_saved"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:padding="12dp" android:padding="@dimen/margin_small"
android:text="@string/saved_can" /> android:text="@string/saved_can" />
<Button <Button
android:id="@+id/can_menu_action" android:id="@+id/can_menu_action"
android:layout_margin="12dp" android:layout_margin="@dimen/margin_small"
android:textSize="15sp" android:textSize="@dimen/regular_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"/>
<TextView <TextView
android:id="@+id/pin_saved" android:id="@+id/pin_saved"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:padding="12dp" android:padding="@dimen/margin_small"
android:text="@string/saved_pin"/> android:text="@string/saved_pin"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="vertical">
<Button
android:id="@+id/pin_menu_action"
android:layout_margin="12dp"
android:textSize="15sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button <Button
android:id="@+id/pin_menu_show" android:id="@+id/pin_menu_show"
android:layout_margin="12dp" android:layout_marginHorizontal="@dimen/margin"
android:textSize="15sp" android:layout_marginVertical="@dimen/margin_small"
android:textSize="@dimen/regular_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"/> android:visibility="gone"/>
<Button
android:id="@+id/pin_menu_action"
android:layout_marginHorizontal="@dimen/margin"
android:layout_marginVertical="@dimen/margin_small"
android:textSize="@dimen/regular_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@@ -69,8 +71,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/return_text" android:text="@string/return_text"
android:layout_margin="24dp" android:layout_marginVertical="@dimen/margin"
android:textSize="15sp" android:layout_marginStart="@dimen/padding"
android:textSize="@dimen/regular_text"
app:layout_constraintTop_toBottomOf="@id/settings_card" app:layout_constraintTop_toBottomOf="@id/settings_card"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />

View File

@@ -1,17 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="24dp" android:padding="@dimen/padding"
tools:context=".UserFragment"> tools:context=".UserFragment">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/card_view" android:id="@+id/card_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="@dimen/margin"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@@ -23,86 +26,86 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20sp"> android:padding="@dimen/padding_tiny">
<TextView <TextView
android:id="@+id/user_name_label" android:id="@+id/user_name_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/user_name_label" android:text="@string/user_name_label"
android:textSize="14sp" /> android:textSize="@dimen/regular_text" />
<TextView <TextView
android:id="@+id/user_name" android:id="@+id/user_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="@dimen/margin_small"
android:text="@string/user_name" android:text="@string/user_name"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/identification_number_label" android:id="@+id/identification_number_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/identification_number_label" android:text="@string/identification_number_label"
android:textSize="14sp" /> android:textSize="@dimen/regular_text" />
<TextView <TextView
android:id="@+id/identification_number" android:id="@+id/identification_number"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="@dimen/margin_small"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/gender_label" android:id="@+id/gender_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/gender_label" android:text="@string/gender_label"
android:textSize="14sp" /> android:textSize="@dimen/regular_text" />
<TextView <TextView
android:id="@+id/gender" android:id="@+id/gender"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="@dimen/margin_small"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/expiration_label" android:id="@+id/expiration_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/expiration_label" android:text="@string/expiration_label"
android:textSize="14sp" /> android:textSize="@dimen/regular_text" />
<TextView <TextView
android:id="@+id/expiration" android:id="@+id/expiration"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="@dimen/margin_small"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:textStyle="bold" /> android:textStyle="bold" />
<TextView <TextView
android:id="@+id/citizenship_label" android:id="@+id/citizenship_label"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/citizenship_label" android:text="@string/citizenship_label"
android:textSize="14sp" /> android:textSize="@dimen/regular_text" />
<TextView <TextView
android:id="@+id/citizenship" android:id="@+id/citizenship"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="@dimen/margin_small"
android:textSize="20sp" android:textSize="@dimen/regular_text"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -113,11 +116,12 @@
android:id="@+id/clear_button" android:id="@+id/clear_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="@dimen/margin_big"
android:text="@string/clear_button" android:text="@string/return_text"
android:textSize="18sp" android:textSize="@dimen/regular_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_view" /> app:layout_constraintTop_toBottomOf="@id/card_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_check_logo" />
</adaptive-icon> </adaptive-icon>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" /> <background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" /> <foreground android:drawable="@drawable/ic_check_logo" />
</adaptive-icon> </adaptive-icon>

View File

@@ -18,6 +18,9 @@
android:id="@+id/action_homeFragment_to_canFragment" android:id="@+id/action_homeFragment_to_canFragment"
app:destination="@id/canFragment" app:destination="@id/canFragment"
app:popUpTo="@id/homeFragment" /> app:popUpTo="@id/homeFragment" />
<action
android:id="@+id/action_homeFragment_to_userFragment"
app:destination="@id/userFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/pinFragment" android:id="@+id/pinFragment"
@@ -42,10 +45,6 @@
android:id="@+id/action_pinFragment_to_authFragment" android:id="@+id/action_pinFragment_to_authFragment"
app:destination="@id/authFragment" app:destination="@id/authFragment"
app:popUpTo="@id/homeFragment" /> app:popUpTo="@id/homeFragment" />
<argument
android:name="reading"
app:argType="boolean"
android:defaultValue="false" />
<argument <argument
android:name="auth" android:name="auth"
app:argType="boolean" app:argType="boolean"
@@ -78,10 +77,6 @@
android:id="@+id/action_canFragment_to_pinFragment" android:id="@+id/action_canFragment_to_pinFragment"
app:destination="@id/pinFragment" app:destination="@id/pinFragment"
app:popUpTo="@id/homeFragment" /> app:popUpTo="@id/homeFragment" />
<argument
android:name="reading"
app:argType="boolean"
android:defaultValue="false" />
<argument <argument
android:name="auth" android:name="auth"
app:argType="boolean" app:argType="boolean"
@@ -90,6 +85,10 @@
android:name="mobile" android:name="mobile"
app:argType="boolean" app:argType="boolean"
android:defaultValue="false" /> android:defaultValue="false" />
<argument
android:name="fromhome"
app:argType="boolean"
android:defaultValue="false" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/authFragment" android:id="@+id/authFragment"
@@ -113,10 +112,6 @@
android:name="auth" android:name="auth"
app:argType="boolean" app:argType="boolean"
android:defaultValue="false" /> android:defaultValue="false" />
<argument
android:name="reading"
app:argType="boolean"
android:defaultValue="false" />
<argument <argument
android:name="mobile" android:name="mobile"
app:argType="boolean" app:argType="boolean"

View File

@@ -1,29 +1,46 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Must translate to English, but should work now --> <!-- Must translate to English, but should work now -->
<string name="app_name">NFC authentication</string> <string name="app_name">NFC authenticator</string>
<string name="home_fragment">Work in progress</string>
<!-- BUTTONS -->
<string name="cancel_text">CANCEL</string>
<string name="return_text">BACK</string>
<string name="add_can_text">ADD CAN</string>
<string name="try_again_text">TRY AGAIN</string>
<string name="continue_button">CONTINUE</string>
<!-- Card Detection related -->
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="wrong_can_text">Wrong CAN</string>
<string name="action_detect">Put the ID card against the phone to detect it</string>
<string name="action_detect_unavailable">CAN must be added before ID card can be detected</string>
<string name="nfc_not_available">NFC is not turned on or is not supported by the phone</string>
<string name="nfc_reading_error">The provided CAN does not match the ID card</string>
<string name="id_card_removed_early">ID card was removed too early</string>
<string name="wrong_pin">Wrong PIN 1. Tries on the card left %s</string>
<!-- string resources for HomeFragment --> <!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 saved</string> <string name="pin_status_saved">PIN 1 saved</string>
<string name="pin_status_negative">PIN 1 not saved</string> <string name="pin_status_negative">PIN 1 not saved</string>
<string name="can_status_saved">CAN saved</string> <string name="can_status_saved">CAN saved</string>
<string name="can_status_negative">CAN not saved</string> <string name="can_status_negative">CAN not saved</string>
<string name="help_text">HELP</string>
<string name="begin_text">READ ID CARD</string> <string name="can_question">What is CAN?</string>
<string name="next_text">NEXT</string> <string name="can_explanation">CAN is a 6 digit code that is needed to communicate with an ID card. It can be found on the ID card under the card holder\'s picture with a title KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE.</string>
<string name="cancel_text">CANCEL</string> <string name="problem_parameters">Problem with parameters</string>
<string name="save_text">SAVE</string> <string name="problem_challenge">Challenge is missing</string>
<string name="deny_text">NO</string> <string name="problem_authurl">AuthUrl is missing</string>
<string name="return_text">BACK</string> <string name="problem_originurl">OriginUrl is missing</string>
<string name="problem_other">Unspecified problem with parameters</string>
<!-- string resources for PinFragment --> <!-- string resources for PinFragment -->
<string name="pin_fragment">Please enter PIN 1</string> <string name="pin_view">Please enter PIN 1</string>
<string name="enter_pin">PIN 1</string> <string name="hint_pin">PIN 1</string>
<string name="example_pin">Example. 1234</string> <string name="pin_helper_text">PIN 1 must be 412 digits long</string>
<string name="length_pin">Allowed length for PIN 1 is 4..12</string> <string name="save_pin">Save PIN 1</string>
<string name="pin_save_request">PIN 1 is currently not saved. Do you wish to save the entered PIN 1? Saved PIN 1 will be entered automatically in the future. Saved PIN 1 can be changed and deleted in the settings menu.</string> <string name="pin_save_on">On</string>
<string name="save_pin_title">Save PIN 1</string> <string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment --> <!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Please enter PIN 2</string> <string name="pin2_fragment">Please enter PIN 2</string>
@@ -32,19 +49,14 @@
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string> <string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
<!-- string resources for CanFragment --> <!-- string resources for CanFragment -->
<string name="example_can">Example. 123456</string> <string name="can_view">Please enter CAN</string>
<string name="text_can">CAN</string> <string name="can_text">CAN</string>
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</string> <string name="can_helper_text">CAN must be 6 digits long</string>
<string name="length_can">Length of the CAN is wrong</string>
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="save_can_title">Save CAN</string>
<!-- string resources for AuthFragment layout --> <!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string> <string name="auth_instruction_text">Put the ID card against the phone</string>
<string name="time_left">Time left %d sek</string> <string name="time_left">Time left %d sek</string>
<string name="no_time">No time left</string> <string name="no_time">No time left</string>
<string name="no_success">Wrong CAN</string>
<!-- string resources for UserFragment layout --> <!-- string resources for UserFragment layout -->
<string name="user_name_label">NAME</string> <string name="user_name_label">NAME</string>
@@ -53,18 +65,14 @@
<string name="expiration_label">DATE OF EXPIRY</string> <string name="expiration_label">DATE OF EXPIRY</string>
<string name="citizenship_label">CITIZENSHIP</string> <string name="citizenship_label">CITIZENSHIP</string>
<string name="gender_label">SEX</string> <string name="gender_label">SEX</string>
<string name="clear_button">FORGET</string>
<!-- string resources for ResultFragment layout--> <!-- string resources for ResultFragment layout-->
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string> <string name="result_text">Controlling the created token</string>
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string> <string name="result_info">The app will close automatically</string>
<!-- menu --> <!-- menu -->
<string name="menu_settings_title">Settings</string> <string name="menu_settings_title">Settings</string>
<string name="menu_language_title">Language</string>
<string name="menu_action_unavailable">Currently unavailable</string>
<string name="saved_can">CAN: %s</string> <string name="saved_can">CAN: %s</string>
<string name="can_add">Add CAN</string>
<string name="can_delete">Delete CAN</string> <string name="can_delete">Delete CAN</string>
<string name="saved_pin">PIN1: %s</string> <string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Add PIN1</string> <string name="pin1_add">Add PIN1</string>
@@ -73,6 +81,7 @@
<string name="show">SHOW</string> <string name="show">SHOW</string>
<string name="hide">HIDE</string> <string name="hide">HIDE</string>
<string name="hidden_pin">****</string> <string name="hidden_pin">****</string>
<string name="unavailable">Settings currently unavailabe</string> <string name="menu_unavailable_message">Settings are currently unavailable</string>
<string name="can_save_request">CAN is currently not saved. Do you wish to save the CAN? Saved CAN will be entered automatically in the future. Saved CAN can be changed and deleted in the settings menu.</string> <string name="can_deleted">CAN deleted</string>
<string name="pin_deleted">PIN 1 deleted</string>
</resources> </resources>

View File

@@ -1,28 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">NFC authentication</string> <string name="app_name">NFC autentija</string>
<string name="home_fragment">Work in progress</string>
<string name="begin_text">LOE ID KAARTI</string> <!-- Buttons -->
<string name="next_text">EDASI</string>
<string name="cancel_text">KATKESTA</string> <string name="cancel_text">KATKESTA</string>
<string name="save_text">SALVESTA</string>
<string name="deny_text">EI</string>
<string name="return_text">TAGASI</string> <string name="return_text">TAGASI</string>
<string name="add_can_text">LISA CAN</string>
<string name="try_again_text">ÜRITA UUESTI</string>
<string name="continue_button">JÄTKA</string>
<!-- Card Detection related -->
<string name="card_detected">Kaart tuvastatud. Hoia kaarti vastu telefoni.</string>
<string name="data_read">Andmed loetud, võid jätkata.</string>
<string name="wrong_can_text">Vale CAN</string>
<string name="action_detect">ID kaardi tuvastamiseks pane kaart vastu telefoni</string>
<string name="action_detect_unavailable">ID kaardi tuvastamiseks peab olema CAN lisatud</string>
<string name="nfc_not_available">NFC ei ole sisse lülitatud või puudub telefonil NFC võimekus</string>
<string name="nfc_reading_error">Sisestatud CAN ei ole vastavuses ID kaardiga</string>
<string name="id_card_removed_early">ID kaart eemaldati liiga vara</string>
<string name="wrong_pin">Vale PIN 1. ID kaardil PIN 1 sisetamise kordi alles: %s</string>
<!-- string resources for HomeFragment --> <!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 on salvestatud</string> <string name="pin_status_saved">PIN 1 on salvestatud</string>
<string name="pin_status_negative">PIN 1 ei ole salvestatud</string> <string name="pin_status_negative">PIN 1 ei ole salvestatud</string>
<string name="can_status_saved">CAN on salvestatud</string> <string name="can_status_saved">CAN on salvestatud</string>
<string name="can_status_negative">CAN ei ole salvestatud</string> <string name="can_status_negative">CAN ei ole salvestatud</string>
<string name="help_text">INFO</string>
<string name="can_question">Mis on CAN?</string>
<string name="can_explanation">CAN on 6 kohaline numbritest koosnev kood, mida on vaja ID kaardiga suhtlemiseks. CAN-i leiab ID kaardilt omaniku pildi alt pealkirjaga KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE.</string>
<string name="problem_parameters">Probleem parameetritega</string>
<string name="problem_challenge">Puudub challenge parameeter</string>
<string name="problem_authurl">Puudub AuthUrl parameeter</string>
<string name="problem_originurl">Puudub OriginUrl parameeter</string>
<string name="problem_other">Täpsustamata probleem parameetritega</string>
<!-- string resources for PinFragment --> <!-- string resources for PinFragment -->
<string name="pin_fragment">Palun sisesta PIN 1</string> <string name="pin_view">Palun sisesta PIN 1</string>
<string name="enter_pin">PIN 1</string> <string name="hint_pin">PIN 1</string>
<string name="example_pin">Näide. 1234</string> <string name="pin_helper_text">PIN 1 lubatud pikkus on 4..12</string>
<string name="length_pin">PIN 1 lubatud pikkus on 4..12</string> <string name="save_pin">Save PIN 1</string>
<string name="pin_save_request">Praegu ei ole rakenduses PIN 1 salvestatud. Kas sa soovid sisestatud PIN 1-te salvestada? Sellisel juhul sisestatakse see järgmisel korral automaatselt. Salvestatud PIN 1-te saab alati menüüs muuta ja kustutada.</string> <string name="pin_save_on">On</string>
<string name="save_pin_title">Salvesta PIN 1</string> <string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment --> <!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Palun sisesta PIN 2</string> <string name="pin2_fragment">Palun sisesta PIN 2</string>
@@ -31,39 +48,30 @@
<string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string> <string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string>
<!-- string resources for CanFragment --> <!-- string resources for CanFragment -->
<string name="example_can">Näide. 123456</string> <string name="can_view">Please enter CAN</string>
<string name="text_can">CAN</string> <string name="can_text">CAN</string>
<string name="enter_can">Sisesta ID kaardi CAN (Card Access Number)</string> <string name="can_helper_text">CAN must be 6 digits long</string>
<string name="length_can">CANi pikkus on vale</string>
<string name="card_detected">Kaart on tuvastatud. Hoia kaarti vastu telefoni.</string>
<string name="data_read">Andmed loetud. Võid edasi minna.</string>
<string name="can_save_request">Praegu ei ole rakenduses CAN salvestatud. Kas sa soovid sisestatud CANi salvestada? Sellisel juhul sisestatakse see järgmisel korral automaatselt. Salvestatud CANi saab alati menüüs muuta ja kustutada.</string> <string name="save_can_title">Salvesta CAN</string>
<!-- string resources for AuthFragment layout --> <!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">ID kaardiga ühenduse loomiseks pane kaart vastu telefoni</string> <string name="auth_instruction_text">Pane ID kaart vastu telefoni</string>
<string name="time_left">Aega on jäänud %d sek</string> <string name="time_left">Aega on jäänud %d sek</string>
<string name="no_time">Aeg on otsas</string> <string name="no_time">Aeg on otsas</string>
<string name="no_success">Vale CAN</string>
<!-- string resources for UserFragment layout --> <!-- string resources for UserFragment layout -->
<string name="user_name_label">NIMI</string> <string name="user_name_label">NIMI</string>
<string name="user_name">%1$s %2$s</string> <string name="user_name">%1$s %2$s</string>
<string name="identification_number_label">ISIKUKOOD</string> <string name="identification_number_label">ISIKUKOOD</string>
<string name="clear_button">UNUSTA</string>
<string name="expiration_label">KEHTIV KUNI</string> <string name="expiration_label">KEHTIV KUNI</string>
<string name="citizenship_label">KODAKONDSUS</string> <string name="citizenship_label">KODAKONDSUS</string>
<string name="gender_label">SUGU</string> <string name="gender_label">SUGU</string>
<!-- string resources for ResultFragment layout--> <!-- string resources for ResultFragment layout-->
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string> <string name="result_text">Tulemust kontrollitakse</string>
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string> <string name="result_info">Rakendus sulgeb ennast ise</string>
<!-- menu --> <!-- menu -->
<string name="menu_settings_title">Seaded</string> <string name="menu_settings_title">Seaded</string>
<string name="menu_language_title">Keel</string>
<string name="menu_action_unavailable">Toiming pole hetkel saadaval</string>
<string name="saved_can">CAN: %s</string> <string name="saved_can">CAN: %s</string>
<string name="can_add">Lisa CAN</string>
<string name="can_delete">Kustuta CAN</string> <string name="can_delete">Kustuta CAN</string>
<string name="saved_pin">PIN1: %s</string> <string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Lisa PIN1</string> <string name="pin1_add">Lisa PIN1</string>
@@ -72,5 +80,7 @@
<string name="show">NÄITA</string> <string name="show">NÄITA</string>
<string name="hide">PEIDA</string> <string name="hide">PEIDA</string>
<string name="hidden_pin">****</string> <string name="hidden_pin">****</string>
<string name="unavailable">Seaded pole hetkel saadaval</string> <string name="menu_unavailable_message">Seaded pole hetkel saadaval</string>
<string name="can_deleted">CAN kustatud</string>
<string name="pin_deleted">PIN 1 kustatud</string>
</resources> </resources>

View File

@@ -8,9 +8,9 @@
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="blue_200">#90caf9</color> <color name="blue_200">#d1d9ff</color>
<color name="blue_500">#2196f3</color> <color name="blue_500">#002984</color>
<color name="blue_700">#1976d2</color> <color name="blue_700">#001970</color>
<color name="orange_200">#ffcc80</color> <color name="orange_200">#ffab91</color>
<color name="orange_700">#f57c00</color> <color name="orange_700">#f57c00</color>
</resources> </resources>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="margin_small">4dp</dimen>
<dimen name="margin">8dp</dimen>
<dimen name="margin_big">16dp</dimen>
<dimen name="margin_huge">32dp</dimen>
<dimen name="padding_tiny">8dp</dimen>
<dimen name="padding_small">16dp</dimen>
<dimen name="padding">24dp</dimen>
<dimen name="regular_text">24sp</dimen>
<dimen name="headline_text">32sp</dimen>
<dimen name="helper_text">16sp</dimen>
<dimen name="small_text">8sp</dimen>
<dimen name="logo_big">128dp</dimen>
</resources>

View File

@@ -1,27 +1,44 @@
<resources> <resources>
<string name="app_name">NFC authentication</string> <string name="app_name">NFC authenticator</string>
<string name="home_fragment">Work in progress</string>
<string name="begin_text">READ ID CARD</string> <!-- BUTTONS -->
<string name="next_text">NEXT</string>
<string name="cancel_text">CANCEL</string> <string name="cancel_text">CANCEL</string>
<string name="save_text">SAVE</string>
<string name="deny_text">NO</string>
<string name="return_text">BACK</string> <string name="return_text">BACK</string>
<string name="add_can_text">ADD CAN</string>
<string name="try_again_text">TRY AGAIN</string>
<string name="continue_button">CONTINUE</string>
<!-- Card Detection related -->
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="wrong_can_text">Wrong CAN</string>
<string name="action_detect">Put the ID card against the phone to detect it</string>
<string name="action_detect_unavailable">CAN must be added before ID card can be detected</string>
<string name="nfc_not_available">NFC is not turned on or is not supported by the phone</string>
<string name="nfc_reading_error">The provided CAN does not match the ID card</string>
<string name="id_card_removed_early">ID card was removed too early</string>
<string name="wrong_pin">Wrong PIN 1. Tries on the card left %s</string>
<!-- string resources for HomeFragment --> <!-- string resources for HomeFragment -->
<string name="pin_status_saved">PIN 1 saved</string> <string name="pin_status_saved">PIN 1 saved</string>
<string name="pin_status_negative">PIN 1 not saved</string> <string name="pin_status_negative">PIN 1 not saved</string>
<string name="can_status_saved">CAN saved</string> <string name="can_status_saved">CAN saved</string>
<string name="can_status_negative">CAN not saved</string> <string name="can_status_negative">CAN not saved</string>
<string name="help_text">HELP</string>
<string name="can_question">What is CAN?</string>
<string name="can_explanation">CAN is a 6 digit code that is needed to communicate with an ID card. It can be found on the ID card under the card holder\'s picture with a title KASUTAJA ALLKIRI/HOLDER\'S SIGNATURE.</string>
<string name="problem_parameters">Problem with parameters</string>
<string name="problem_challenge">Challenge is missing</string>
<string name="problem_authurl">AuthUrl is missing</string>
<string name="problem_originurl">OriginUrl is missing</string>
<string name="problem_other">Unspecified problem with parameters</string>
<!-- string resources for PinFragment --> <!-- string resources for PinFragment -->
<string name="pin_fragment">Please enter PIN 1</string> <string name="pin_view">Please enter PIN 1</string>
<string name="enter_pin">PIN 1</string> <string name="hint_pin">PIN 1</string>
<string name="example_pin">Example. 1234</string> <string name="pin_helper_text">PIN 1 must be 412 digits long</string>
<string name="length_pin">Allowed length for PIN 1 is 4..12</string> <string name="save_pin">Save PIN 1</string>
<string name="pin_save_request">PIN 1 is currently not saved. Do you wish to save the entered PIN 1? Saved PIN 1 will be entered automatically in the future. Saved PIN 1 can be changed and deleted in the settings menu.</string> <string name="pin_save_on">On</string>
<string name="save_pin_title">Save PIN 1</string> <string name="pin_save_off">Off</string>
<!-- string resources for Pin2Fragment --> <!-- string resources for Pin2Fragment -->
<string name="pin2_fragment">Please enter PIN 2</string> <string name="pin2_fragment">Please enter PIN 2</string>
@@ -30,20 +47,14 @@
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string> <string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
<!-- string resources for CanFragment --> <!-- string resources for CanFragment -->
<string name="example_can">Example. 123456</string> <string name="can_view">Please enter CAN</string>
<string name="text_can">CAN</string> <string name="can_text">CAN</string>
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</string> <string name="can_helper_text">CAN must be 6 digits long</string>
<string name="length_can">Length of the CAN is wrong</string>
<string name="card_detected">Card detected. Hold it against the phone.</string>
<string name="data_read">Data read. You can continue.</string>
<string name="can_save_request">CAN is currently not saved. Do you wish to save the CAN? Saved CAN will be entered automatically in the future. Saved CAN can be changed and deleted in the settings menu.</string>
<string name="save_can_title">Save CAN</string>
<!-- string resources for AuthFragment layout --> <!-- string resources for AuthFragment layout -->
<string name="auth_instruction_text">Put the ID card against the phone to establish connection</string> <string name="auth_instruction_text">Put the ID card against the phone</string>
<string name="time_left">Time left %d sek</string> <string name="time_left">Time left %d sek</string>
<string name="no_time">No time left</string> <string name="no_time">No time left</string>
<string name="no_success">Wrong CAN</string>
<!-- string resources for UserFragment layout --> <!-- string resources for UserFragment layout -->
<string name="user_name_label">NAME</string> <string name="user_name_label">NAME</string>
@@ -52,18 +63,14 @@
<string name="expiration_label">DATE OF EXPIRY</string> <string name="expiration_label">DATE OF EXPIRY</string>
<string name="citizenship_label">CITIZENSHIP</string> <string name="citizenship_label">CITIZENSHIP</string>
<string name="gender_label">SEX</string> <string name="gender_label">SEX</string>
<string name="clear_button">FORGET</string>
<!-- string resources for ResultFragment layout--> <!-- string resources for ResultFragment layout-->
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string> <string name="result_text">Controlling the created token</string>
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string> <string name="result_info">The app will close automatically</string>
<!-- menu --> <!-- menu -->
<string name="menu_settings_title">Settings</string> <string name="menu_settings_title">Settings</string>
<string name="menu_language_title">Language</string>
<string name="menu_action_unavailable">Currently unavailable</string>
<string name="saved_can">CAN: %s</string> <string name="saved_can">CAN: %s</string>
<string name="can_add">Add CAN</string>
<string name="can_delete">Delete CAN</string> <string name="can_delete">Delete CAN</string>
<string name="saved_pin">PIN1: %s</string> <string name="saved_pin">PIN1: %s</string>
<string name="pin1_add">Add PIN 1</string> <string name="pin1_add">Add PIN 1</string>
@@ -72,5 +79,7 @@
<string name="show">SHOW</string> <string name="show">SHOW</string>
<string name="hide">HIDE</string> <string name="hide">HIDE</string>
<string name="hidden_pin">****</string> <string name="hidden_pin">****</string>
<string name="unavailable">Settings currently unavailable</string> <string name="menu_unavailable_message">Settings are currently unavailable</string>
<string name="can_deleted">CAN deleted</string>
<string name="pin_deleted">PIN 1 deleted</string>
</resources> </resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="helper">
<item name="android:fontFamily">sans-serif</item>
<item name="android:textSize">@dimen/helper_text</item>
</style>
</resources>

View File

@@ -7,8 +7,6 @@ This is a proof-of-concept project for creating an Android app for authenticatin
* The smartphone must support NFC technology and it must be enabled * The smartphone must support NFC technology and it must be enabled
* The user must have an Estonian ID card with NFC capability (issued since 2018) * The user must have an Estonian ID card with NFC capability (issued since 2018)
_Knowledge of Estonian might be useful as the application is in Estonian language at the moment. The English language support will be added during the iteration 3. If one does not understand Estonian then the guide at the bottom of the release notes might be useful._
### Installing the application on the phone ### Installing the application on the phone
The first option is to open the MobileAuthApp folder of the project on the Android Studio and use the smartphone instead of an emulator (the application does not work with emulators because real ID card has to be scanned, which an emulator can not do) to run the application. This way the application gets installed on the phone automatically. The first option is to open the MobileAuthApp folder of the project on the Android Studio and use the smartphone instead of an emulator (the application does not work with emulators because real ID card has to be scanned, which an emulator can not do) to run the application. This way the application gets installed on the phone automatically.
@@ -20,11 +18,15 @@ More info about installing third party applications on the Android phones: https
**NB! Before using the application make sure that the NFC is enabled on the phone, otherwise information can not be read from the ID card.** **NB! Before using the application make sure that the NFC is enabled on the phone, otherwise information can not be read from the ID card.**
### Testing the application
The project comes with a test mobile application and a test web application that can be used to try the MobileAuthApp authentication feature even if you don't have any web applications or mobile applications that require user authentication. Both projects come with a README file that help with a setup.
The mobile authentication application, when launched by the user not a website or some other application, can also read card holder's information, which can be used to verify whether the application reads the information from the ID card correctly.
### Wiki pages relevant for the "Software project" subject ### Wiki pages relevant for the "Software project" subject
* [Project Vision](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-Vision) *last updated on 10.10* * [Project Vision](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-Vision)
* [Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes) *last updated for iteration3 on 08.11* * [Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes)
* [Project tasks](https://tvp-mobile-authentication.atlassian.net/jira/software/projects/MOB/boards/1/backlog) (Ask Tanel for JIRA permissions if needed). * [Project tasks](https://tvp-mobile-authentication.atlassian.net/jira/software/projects/MOB/boards/1/backlog) (Ask Tanel for JIRA permissions if needed).
* [Project plan](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-plan) *last updated on 10.10* * [Project plan](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-plan)
* [Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases) *last updated on 10.10* * [Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases)
* [User stories](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/User-stories) *last updated on 10.10* * [User stories](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/User-stories)
* [Use Case Tests](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Case-Tests) *WIP* * [Use Case Tests](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Case-Tests)

10
TestMobileApp/README.md Normal file
View File

@@ -0,0 +1,10 @@
# TestMobileApp overview
### The purpose
The TestMobileApp was created in order to demonstrate how a different application on the Android smartphone could use the MobileAuthApp for user authentication purposes.
### Installing the application
The application installation process is the same as with the MobileAuthApp. Check the guide in the project's [main readme file](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC#installing-the-application-on-the-phone).
### Using the application
In order to use this application a backend server must be running that can issue challenges and verify the token created by the MobileAuthApp.
Use demoBackend application that is included in the project. Follow the demoBackend setup guide and once you have a backend running take the https address of the backend
and add it in the TestMobileApp's MainActivty.kt file as the new value for the constant variable BASE_URL (this is easly noticeable in the class as it is pointed out with a comment).
Now the app can be used.

View File

@@ -5,10 +5,17 @@ import android.content.Intent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import com.example.testmobileapp.databinding.ActivityMainBinding import com.example.testmobileapp.databinding.ActivityMainBinding
import com.koushikdutta.ion.Ion import com.koushikdutta.ion.Ion
import org.json.JSONObject
/**
* Base url where the requests should be made. Add yours here. It must use https.
*/
private const val BASE_URL = "https-base-url-here"
/** /**
* Test mobile app to demonstrate how other applications can use MobileAuthApp. * Test mobile app to demonstrate how other applications can use MobileAuthApp.
@@ -18,35 +25,48 @@ class MainActivity : AppCompatActivity() {
private lateinit var authLauncher: ActivityResultLauncher<Intent> private lateinit var authLauncher: ActivityResultLauncher<Intent>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response -> authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response ->
if (response.resultCode == Activity.RESULT_OK) { if (response.resultCode == Activity.RESULT_OK) {
// Currently we are not actually checking whether we get a valid token.
// For testing purposes only, to make sure that we are able to get a response at all.
binding.loginTextView.text = getString(R.string.auth_success) binding.loginTextView.text = getString(R.string.auth_success)
// Logs are used to show what information can be retrieved from the mobileauthapp.
Log.i("getResult", response.data?.getStringExtra("token").toString())
Log.i("getResult", response.data?.getStringExtra("result").toString())
var user = ""
try {
val resultObject = JSONObject(response.data?.getStringExtra("result").toString())
user = resultObject.getString("principal")
} catch (e: Exception) {
Log.i("getResult", "unable to retrieve name from principal")
}
showResult(user)
} }
if (response.resultCode == Activity.RESULT_CANCELED) { if (response.resultCode == Activity.RESULT_CANCELED) {
binding.loginTextView.text = getString(R.string.auth_failure) binding.loginTextView.text = getString(R.string.auth_failure)
} }
} }
binding.loginOptionNfcButton.setOnClickListener { launchAuth() } showLogin()
//binding.loginOptionNfcButton.setOnClickListener { getData() }
binding.loginOptionNfcButton.setOnClickListener { getData() }
} }
/** /**
* Method that creates an intent to launch the MobileAuthApp * Method that creates an intent to launch the MobileAuthApp
*/ */
private fun launchAuth(challenge: String = "challenge", authUrl: String = "authUrl") { private fun launchAuth(challenge: String = "challenge", originUrl: String = "baseUrl", authUrl: String = "authUrl") {
val launchIntent = Intent() val launchIntent = Intent()
launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity") launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity")
launchIntent.putExtra("action", "auth") launchIntent.putExtra("action", "auth")
launchIntent.putExtra("challenge", challenge) launchIntent.putExtra("challenge", challenge)
launchIntent.putExtra("originUrl", originUrl)
launchIntent.putExtra("authUrl", authUrl) launchIntent.putExtra("authUrl", authUrl)
launchIntent.putExtra("mobile", true) launchIntent.putExtra("mobile", true)
authLauncher.launch(launchIntent) authLauncher.launch(launchIntent)
@@ -58,19 +78,36 @@ class MainActivity : AppCompatActivity() {
*/ */
private fun getData() { private fun getData() {
// Enter the server endpoint address to here // Enter the server endpoint address to here
val baseUrl = "enter-base-url-here" val url = "$BASE_URL/auth/challenge"
val url = "$baseUrl/auth/challenge" Ion.getDefault(this).conscryptMiddleware.enable(false)
Ion.with(applicationContext) Ion.with(applicationContext)
.load(url) .load(url)
.asJsonObject() .asJsonObject()
.setCallback { _, result -> .setCallback { _, result ->
try { try {
// Get data from the result and call launchAuth method // Get data from the result and call launchAuth method
val challenge = result.asJsonObject["nonce"].toString() val challenge = result.asJsonObject["nonce"].toString().replace("\"", "")
launchAuth(challenge, baseUrl) Log.v("Challenge", challenge)
launchAuth(challenge, BASE_URL, "/auth/authentication")
} catch (e: Exception) { } catch (e: Exception) {
Log.i("GETrequest", "was unsuccessful") Log.i("GETrequest", "was unsuccessful")
} }
} }
} }
private fun showLogin() {
binding.loginOptions.visibility = View.VISIBLE
}
private fun showResult(user: String) {
binding.loginOptions.visibility = View.GONE
binding.resultLayout.visibility = View.VISIBLE
binding.resultObject.text = getString(R.string.hello, user)
binding.buttonForget.setOnClickListener {
binding.loginTextView.text = getString(R.string.login_text)
binding.resultObject.text = ""
binding.resultLayout.visibility = View.GONE
binding.loginOptions.visibility = View.VISIBLE
}
}
} }

View File

@@ -26,7 +26,8 @@
android:layout_margin="12dp" android:layout_margin="12dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/login_text_view" app:layout_constraintTop_toBottomOf="@id/login_text_view"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone">
<TextView <TextView
android:id="@+id/choose_method_text_view" android:id="@+id/choose_method_text_view"
@@ -46,4 +47,30 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/result_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/login_text_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone">
<TextView
android:id="@+id/result_object"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:textSize="18sp"/>
<Button
android:id="@+id/button_forget"
android:text="@string/forget_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,6 +4,8 @@
<string name="login_text">Login</string> <string name="login_text">Login</string>
<string name="choose_login_method">Choose login method</string> <string name="choose_login_method">Choose login method</string>
<string name="method_nfc">NFC auth</string> <string name="method_nfc">NFC auth</string>
<string name="auth_success">Successful response</string> <string name="auth_success">Logged in</string>
<string name="auth_failure">Response failed</string> <string name="auth_failure">Response failed</string>
<string name="forget_button">Forget</string>
<string name="hello">Hello, %s!</string>
</resources> </resources>

View File

@@ -4,6 +4,8 @@
<string name="login_text">Logi sisse</string> <string name="login_text">Logi sisse</string>
<string name="choose_login_method">Vali sobiv meetod</string> <string name="choose_login_method">Vali sobiv meetod</string>
<string name="method_nfc">NFC auth</string> <string name="method_nfc">NFC auth</string>
<string name="auth_success">Vastus kätte saadud</string> <string name="auth_success">Sisse logimine õnnestus</string>
<string name="auth_failure">Vastust ei õnnestunud kätte saada</string> <string name="auth_failure">Vastust ei õnnestunud kätte saada</string>
<string name="forget_button">Unusta</string>
<string name="hello">Tere, %s!</string>
</resources> </resources>

View File

@@ -3,6 +3,8 @@
<string name="login_text">Login</string> <string name="login_text">Login</string>
<string name="choose_login_method">Choose login method</string> <string name="choose_login_method">Choose login method</string>
<string name="method_nfc">NFC auth</string> <string name="method_nfc">NFC auth</string>
<string name="auth_success">Successful response</string> <string name="auth_success">Logged in</string>
<string name="auth_failure">Response failed</string> <string name="auth_failure">Response failed</string>
<string name="forget_button">Forget</string>
<string name="hello">Hello, %s!</string>
</resources> </resources>

View File

@@ -42,6 +42,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.webeid.security</groupId> <groupId>org.webeid.security</groupId>
<artifactId>authtoken-validation</artifactId> <artifactId>authtoken-validation</artifactId>

View File

@@ -0,0 +1 @@
#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}#nav{padding:30px}#nav a{font-weight:700;color:#2c3e50}#nav a.router-link-exact-active{color:#42b983}.container>div[data-v-2dcb24ca]{margin-top:2vh}.loginButton[data-v-2dcb24ca]{height:4vh;width:20vh;line-height:3vh}.loginButton>p[data-v-2dcb24ca]{font-size:3vh;text-align:center}#canvas[data-v-2dcb24ca]{height:30vh;width:30vh}nav[data-v-21165a6a]{height:5vh}div[data-v-cd8fea1a]{margin-top:2vh}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>demo-website</title><link href="/css/app.eb039c1f.css" rel="preload" as="style"><link href="/css/chunk-vendors.a251e031.css" rel="preload" as="style"><link href="/js/app.c2a68e49.js" rel="preload" as="script"><link href="/js/chunk-vendors.22b03028.js" rel="preload" as="script"><link href="/css/chunk-vendors.a251e031.css" rel="stylesheet"><link href="/css/app.eb039c1f.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but demo-website doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.22b03028.js"></script><script src="/js/app.c2a68e49.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@ class ApplicationConfiguration {
// Endpoint for authentication // Endpoint for authentication
val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication" val AUTHENTICATION_ENDPOINT_URL = "/auth/authentication"
// URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here. // URL for application. Use ngrok for HTTPS (or a tool of your own choice) and put the HTTPS link here.
val WEBSITE_ORIGIN_URL = "https://6bb0-85-253-195-252.ngrok.io" val WEBSITE_ORIGIN_URL = "https://5d0c-85-253-195-195.ngrok.io"
} }
} }

View File

@@ -1,6 +1,8 @@
package com.tarkvaratehnika.demobackend.config package com.tarkvaratehnika.demobackend.config
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.core.io.support.PathMatchingResourcePatternResolver import org.springframework.core.io.support.PathMatchingResourcePatternResolver
@@ -28,14 +30,25 @@ import javax.cache.configuration.MutableConfiguration
import javax.cache.expiry.CreatedExpiryPolicy import javax.cache.expiry.CreatedExpiryPolicy
import javax.cache.expiry.Duration import javax.cache.expiry.Duration
import javax.cache.configuration.FactoryBuilder.factoryOf
@Configuration @Configuration
class ValidationConfiguration { class ValidationConfiguration {
private val LOG: Logger = LoggerFactory.getLogger(ValidationConfiguration::class.java)
private val NONCE_TTL_MINUTES: Long = 5 private val NONCE_TTL_MINUTES: Long = 5
private val CACHE_NAME = "nonceCache" private val CACHE_NAME = "nonceCache"
private val CERTS_RESOURCE_PATH = "/certs/" private val CERTS_RESOURCE_PATH = "/certs/"
private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks" private val TRUSTED_CERTIFICATES_JKS = "trusted_certificates.jks"
private val TRUSTSTORE_PASSWORD = "changeit" private val TRUSTSTORE_PASSWORD = "changeit"
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
init {
LOG.warn("Creating new ValidationConfiguration.")
}
@Bean @Bean
fun cacheManager(): CacheManager { fun cacheManager(): CacheManager {
@@ -47,7 +60,9 @@ class ValidationConfiguration {
val cacheManager: CacheManager = cacheManager() val cacheManager: CacheManager = cacheManager()
var cache = var cache =
cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME) cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME)
if (cache == null) { if (cache == null) {
LOG.warn("Creating new cache.")
cache = createNonceCache(cacheManager) cache = createNonceCache(cacheManager)
} }
return cache return cache
@@ -65,7 +80,7 @@ class ValidationConfiguration {
val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>() val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>()
.setTypes(String::class.java, ZonedDateTime::class.java) .setTypes(String::class.java, ZonedDateTime::class.java)
.setExpiryPolicyFactory( .setExpiryPolicyFactory(
FactoryBuilder.factoryOf( factoryOf(
CreatedExpiryPolicy( CreatedExpiryPolicy(
Duration( Duration(
TimeUnit.MINUTES, TimeUnit.MINUTES,

View File

@@ -23,6 +23,9 @@
package com.tarkvaratehnika.demobackend.security package com.tarkvaratehnika.demobackend.security
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration import com.tarkvaratehnika.demobackend.config.ValidationConfiguration
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import com.tarkvaratehnika.demobackend.web.rest.AuthenticationController
import org.slf4j.LoggerFactory
import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.authentication.AuthenticationServiceException
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException import org.springframework.security.core.AuthenticationException
@@ -37,11 +40,11 @@ import java.security.cert.X509Certificate
@Component @Component
class AuthTokenDTOAuthenticationProvider { object AuthTokenDTOAuthenticationProvider {
private val LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider::class.java)
companion object {
const val ROLE_USER : String = "ROLE_USER"
}
private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER) private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
@@ -52,7 +55,6 @@ class AuthTokenDTOAuthenticationProvider {
val authentication = auth as PreAuthenticatedAuthenticationToken val authentication = auth as PreAuthenticatedAuthenticationToken
val token = (authentication.credentials as AuthTokenDTO).token val token = (authentication.credentials as AuthTokenDTO).token
val challenge = (authentication.credentials as AuthTokenDTO).challenge val challenge = (authentication.credentials as AuthTokenDTO).challenge
val authorities = arrayListOf<GrantedAuthority>() val authorities = arrayListOf<GrantedAuthority>()
authorities.add(USER_ROLE) authorities.add(USER_ROLE)

View File

@@ -1,6 +1,6 @@
package com.tarkvaratehnika.demobackend.web package com.tarkvaratehnika.demobackend.web
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider.Companion.ROLE_USER import com.tarkvaratehnika.demobackend.config.ValidationConfiguration.Companion.ROLE_USER
import org.springframework.security.access.prepost.PreAuthorize import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
import org.springframework.ui.Model import org.springframework.ui.Model

View File

@@ -26,7 +26,7 @@ class AuthenticationController {
val auth = PreAuthenticatedAuthenticationToken(null, authToken) val auth = PreAuthenticatedAuthenticationToken(null, authToken)
// Return authentication object if success. // Return authentication object if success.
return AuthTokenDTOAuthenticationProvider().authenticate(auth) return AuthTokenDTOAuthenticationProvider.authenticate(auth)
} }
@GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE])

View File

@@ -24,6 +24,7 @@ package com.tarkvaratehnika.demobackend.web.rest
import com.tarkvaratehnika.demobackend.dto.ChallengeDto import com.tarkvaratehnika.demobackend.dto.ChallengeDto
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -33,10 +34,12 @@ import org.webeid.security.nonce.NonceGenerator
@RequestMapping("auth") @RequestMapping("auth")
class ChallengeController (val nonceGenerator: NonceGenerator) { class ChallengeController (val nonceGenerator: NonceGenerator) {
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
@GetMapping("challenge") @GetMapping("challenge")
fun challenge(): ChallengeDto { fun challenge(): ChallengeDto {
val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce()) val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce())
LOG.warn(challengeDto.nonce)
// WebEidAuthentication.addAuth(challengeDto.nonce) // For testing. // WebEidAuthentication.addAuth(challengeDto.nonce) // For testing.
return challengeDto return challengeDto
} }

View File

@@ -1,3 +1,12 @@
html {
font-size: 2vh;
}
.navbar {
padding-left: 1rem;
padding-right: 1rem;
}
.cont { .cont {
display: grid; display: grid;
width: 80%; width: 80%;

View File

@@ -11,7 +11,7 @@ function launchAuthApp(action) {
httpGetAsync(originUrl + challengeUrl, (body) => { httpGetAsync(originUrl + challengeUrl, (body) => {
let data = JSON.parse(body); let data = JSON.parse(body);
let challenge = data.nonce; let challenge = data.nonce;
let intent = createParametrizedIntentUrl(challenge, action); // TODO: Error handling. let intent = createParametrizedIntentUrl(challenge, action, originUrl); // TODO: Error handling.
console.log(intent); console.log(intent);
window.location.href = intent; window.location.href = intent;
pollForAuth(POLLING_INTERVAL, challenge); pollForAuth(POLLING_INTERVAL, challenge);
@@ -20,8 +20,8 @@ function launchAuthApp(action) {
function pollForAuth(timeout, challenge) { function pollForAuth(timeout, challenge) {
console.log("Polling for auth"); console.log("Polling for auth");
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + challenge; let encodedChallenge = encodeURIComponent(challenge);
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + encodedChallenge;
let counter = 0; let counter = 0;
let timer = setInterval(() => { let timer = setInterval(() => {
// Fetch authentication object. // Fetch authentication object.
@@ -48,7 +48,7 @@ function createParametrizedIntentUrl(challenge, action) {
else if (challenge == null) { else if (challenge == null) {
console.error("Challenge missing, can't authenticate without it.") console.error("Challenge missing, can't authenticate without it.")
} else { } else {
return intentUrl + "?" + "action=" + action + "&challenge=" + challenge + "&authUrl=" + originUrl + authenticationRequestUrl; return intentUrl + "?" + "action=" + action + "&challenge=" + encodeURIComponent(challenge) + "&authUrl=" + authenticationRequestUrl + "&originUrl=" + originUrl;
} }
} }

View File

@@ -21,7 +21,7 @@
<body> <body>
<nav class="navbar navbar-dark bg-dark"> <nav class="navbar navbar-dark bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">Auth demo web application</a> <a class="navbar-brand" href="#">Auth demo webapp</a>
</div> </div>
</nav> </nav>
<div class="cont"> <div class="cont">
@@ -29,7 +29,7 @@
website using your ID card by using the button below.</h4> website using your ID card by using the button below.</h4>
<h5>Make sure you've installed the authentication app from: <a <h5>Make sure you've installed the authentication app from: <a
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5> href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">GitHub</a></h5>
<button type="button" class="btn btn-secondary" id="loginButton" data-action="auth">Log in</button> <button type="button" class="btn btn-lg btn-secondary" id="loginButton" data-action="auth">Log in</button>
<div class="alert alert-danger d-none" role="alert" id="loginErrorAlert"> <div class="alert alert-danger d-none" role="alert" id="loginErrorAlert">
Login failed. Refresh the page to try again. Login failed. Refresh the page to try again.
</div> </div>

View File

@@ -3,38 +3,31 @@
<head> <head>
<title>Login</title> <title>Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link th:href="@{/css/main.css}" rel="stylesheet"> <link th:href="@{/css/main.css}" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
<script type="text/javascript" th:src="@{/js/signature.js}"></script> <script type="text/javascript" th:src="@{/js/signature.js}"></script>
<script type="text/javascript" th:src="@{/js/main.js}"></script> <script type="text/javascript" th:src="@{/js/main.js}"></script>
</head> </head>
<body> <body>
<nav class="navbar navbar-dark bg-dark"> <nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Auth demo web application</a> <a class="navbar-brand" href="#">Auth demo web application</a>
</div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item active"> <li class="nav-item">
<a class="nav-link" href="#">Log out<span class="sr-only">(current)</span></a> <a href="/" class="btn btn-danger">Log out</a>
</li> </li>
</ul> </ul>
</div>
</nav> </nav>
<div class="cont"> <div class="cont">
<h4>Congratulations! You have just authenticated yourself using your mobile phone and your ID-card. You can try to give a signature to a file now.</h4> <h4>Congratulations! You have just authenticated yourself using your mobile phone and your ID-card. You can try to
give a signature to a file now.</h4>
<h5>This page is still WIP, signing a document feature will be implemented later.</h5> <h5>This page is still WIP, signing a document feature will be implemented later.</h5>
<div class="input-group mb-3">
<div class="custom-file"> <div class="custom-file">
<input type="file" class="custom-file-input" id="inputGroupFile01"> <input type="file" class="custom-file-input" id="customFile">
<label class="custom-file-label" for="inputGroupFile01">Choose file</label>
</div>
</div> </div>
<button type="button" class="btn btn-secondary" id="signFile" data-action="auth">Sign</button> <button type="button" class="btn btn-secondary" id="signFile" data-action="auth">Sign</button>
</div> </div>