Compare commits
51 Commits
iteration-
...
main
Author | SHA1 | Date |
---|---|---|
TanelOrumaa | 869f11f8a8 | |
TanelOrumaa | bfa5a91ef3 | |
TanelOrumaa | d67c815aad | |
TanelOrumaa | c28fc2be48 | |
TanelOrumaa | c232a1f734 | |
Henrik Lepson | b95115af4f | |
Henrik Lepson | 866c3c24a7 | |
Henrik Lepson | 32336ffb2b | |
Kevin | 7edd8189a4 | |
Henrik Lepson | b889b9cda7 | |
TanelOrumaa | e5931692b6 | |
TanelOrumaa | b66c2386f0 | |
TanelOrumaa | 04933f2705 | |
TanelOrumaa | 8b78ddf51a | |
TanelOrumaa | 13a0a9430f | |
TanelOrumaa | b565f6846d | |
TanelOrumaa | d92656d982 | |
TanelOrumaa | 0da3e17b28 | |
TanelOrumaa | 2b660eeda0 | |
TanelOrumaa | 5719712bef | |
Henrik Lepson | 1e26f83db2 | |
TanelOrumaa | 44430bfab2 | |
TanelOrumaa | 7482c88a4e | |
Henrik Lepson | 74d97827f8 | |
TanelOrumaa | 4096201bef | |
TanelOrumaa | da2dbeb0fc | |
Henrik Lepson | 60207319b7 | |
TanelOrumaa | 7daea4b6c2 | |
Henrik Lepson | e5300dfa5e | |
Henrik Lepson | d4c2a11521 | |
Henrik Lepson | 09c4fa6be3 | |
Henrik Lepson | 63bc89b0e4 | |
Henrik Lepson | 152fd16162 | |
Henrik Lepson | 716b983389 | |
Henrik Lepson | 94fad95364 | |
Henrik Lepson | c33fba1a14 | |
Henrik Lepson | 825335ea5f | |
Henrik Lepson | 0f6f31c995 | |
Henrik Lepson | 762a8c8cc2 | |
Henrik Lepson | 1138abcb11 | |
Henrik Lepson | f085076631 | |
Henrik Lepson | edc444c027 | |
Henrik Lepson | df5febabb7 | |
Henrik Lepson | 1b9a59d4eb | |
TanelOrumaa | bbd5039a0b | |
Henrik Lepson | 2c5430977d | |
Henrik Lepson | 68a7db2e77 | |
Henrik Lepson | a4caf24a35 | |
TanelOrumaa | 5b70a8f997 | |
Henrik Lepson | 168c9be010 | |
TanelOrumaa | 636beeb7f3 |
|
@ -68,8 +68,4 @@ dependencies {
|
|||
'io.jsonwebtoken:jjwt-gson:0.11.2'
|
||||
|
||||
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'
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.TagLostException
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
import android.os.CountDownTimer
|
||||
|
@ -33,9 +34,10 @@ class AuthFragment : Fragment() {
|
|||
|
||||
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()
|
||||
|
||||
|
@ -48,8 +50,8 @@ class AuthFragment : Fragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentAuthBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentAuthBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -58,63 +60,92 @@ class AuthFragment : Fragment() {
|
|||
override fun onTick(p0: Long) {
|
||||
timeRemaining--
|
||||
if (timeRemaining == 0) {
|
||||
binding?.timeCounter?.text = getString(R.string.no_time)
|
||||
binding.timeCounter.text = getString(R.string.no_time)
|
||||
} else {
|
||||
binding?.timeCounter?.text = getString(R.string.time_left, timeRemaining)
|
||||
binding.timeCounter.text = getString(R.string.time_left, timeRemaining)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
Thread.sleep(750)
|
||||
goToTheStart()
|
||||
cancelAuth(408)
|
||||
}
|
||||
}.start()
|
||||
binding!!.nextButton.setOnClickListener { goToNextFragment() }
|
||||
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||
// The button exists in code for testing reasons, but not visible to the user anymore unless visibility is changed in the code.
|
||||
binding.nextButton.visibility = View.GONE
|
||||
binding.nextButton.setOnClickListener { goToNextFragment() }
|
||||
binding.cancelButton.setOnClickListener { cancelAuth(444) }
|
||||
val adapter = NfcAdapter.getDefaultAdapter(activity)
|
||||
if (adapter != null)
|
||||
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(447) // 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(code: Int) {
|
||||
viewModel.clearUserInfo()
|
||||
timer.cancel()
|
||||
if (args.mobile) {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
(activity as MainActivity).returnError(code)
|
||||
requireActivity().finishAndRemoveTask()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInfoFromIdCard(adapter: NfcAdapter) {
|
||||
adapter.enableReaderMode(activity, { tag ->
|
||||
timer.cancel()
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.card_detected)
|
||||
binding.timeCounter.text = getString(R.string.card_detected)
|
||||
}
|
||||
val card = IsoDep.get(tag)
|
||||
card.timeout = 32768
|
||||
card.use {
|
||||
try {
|
||||
val comms = Comms(it, viewModel.userCan)
|
||||
if (args.auth) {
|
||||
val jws = Authenticator(comms).authenticate(
|
||||
intentParameters.challenge,
|
||||
intentParameters.authUrl,
|
||||
viewModel.userPin
|
||||
)
|
||||
intentParameters.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])
|
||||
}
|
||||
val jws = Authenticator(comms).authenticate(
|
||||
paramsModel.challenge,
|
||||
paramsModel.origin,
|
||||
viewModel.userPin
|
||||
)
|
||||
paramsModel.setToken(jws)
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.data_read)
|
||||
goToNextFragment()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.no_success)
|
||||
when(e) {
|
||||
is TagLostException -> requireActivity().runOnUiThread {
|
||||
binding!!.timeCounter.text = getString(R.string.id_card_removed_early)
|
||||
cancelAuth(444)
|
||||
}
|
||||
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())
|
||||
cancelAuth(449)
|
||||
}
|
||||
else -> requireActivity().runOnUiThread {
|
||||
binding.timeCounter.text = getString(R.string.wrong_can_text)
|
||||
viewModel.deleteCan(requireContext())
|
||||
cancelAuth(449)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the CAN is wrong we will also delete the saved CAN so that the user won't use it again.
|
||||
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)
|
||||
} finally {
|
||||
adapter.disableReaderMode(activity)
|
||||
}
|
||||
|
@ -122,36 +153,8 @@ class AuthFragment : Fragment() {
|
|||
}, 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() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -3,17 +3,22 @@ package com.tarkvaraprojekt.mobileauthapp
|
|||
import android.app.AlertDialog
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentCanBinding
|
||||
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
|
||||
|
@ -24,12 +29,11 @@ class CanFragment : Fragment() {
|
|||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentCanBinding? = null
|
||||
private var _binding: FragmentCanBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
// Navigation arguments:
|
||||
// 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()
|
||||
|
||||
override fun onCreateView(
|
||||
|
@ -37,20 +41,17 @@ class CanFragment : Fragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentCanBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentCanBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
checkIfSkip()
|
||||
// If the user arrives from the settings menu then the button should say
|
||||
// save instead of continue.
|
||||
if (args.saving) {
|
||||
binding!!.nextButton.text = getString(R.string.save_text)
|
||||
binding.canTextField.editText?.addTextChangedListener {
|
||||
checkEnteredCan()
|
||||
}
|
||||
binding!!.nextButton.setOnClickListener { checkEnteredCan() }
|
||||
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||
binding.buttonCancel.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,77 +68,67 @@ class CanFragment : Fragment() {
|
|||
* Takes user to the next fragment, which is PinFragment.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* If the user arrived from the settings menu then the start is the settings menu
|
||||
* not the HomeFragment.
|
||||
*/
|
||||
private fun goToTheStart() {
|
||||
// TODO: Needs special handling when the app is launched with intent. Temporary solution at the moment.
|
||||
if (args.saving) {
|
||||
findNavController().navigate(R.id.action_canFragment_to_settingsFragment)
|
||||
} else if (args.auth) {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
if (args.fromhome) {
|
||||
findNavController().navigate(R.id.action_canFragment_to_homeFragment)
|
||||
} else {
|
||||
findNavController().navigate(R.id.action_canFragment_to_settingsFragment)
|
||||
}
|
||||
} else if (args.auth || args.mobile) {
|
||||
if (args.mobile) {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
(activity as MainActivity).returnError(444)
|
||||
requireActivity().finishAndRemoveTask()
|
||||
}
|
||||
} else {
|
||||
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() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -1,19 +1,32 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.TagLostException
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.koushikdutta.ion.Ion
|
||||
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
import org.json.JSONObject
|
||||
import java.lang.Exception
|
||||
import java.lang.RuntimeException
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* HomeFragment is only shown to the user when then the user launches the application. When the application
|
||||
|
@ -28,78 +41,201 @@ class HomeFragment : Fragment() {
|
|||
|
||||
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(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
// Making settings menu active again
|
||||
(activity as MainActivity).menuAvailable = true
|
||||
return binding!!.root
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initialChecks()
|
||||
var auth = false
|
||||
if (requireActivity().intent.data?.getQueryParameter("action") != null) {
|
||||
// Currently we only support authentication not signing.
|
||||
auth = true
|
||||
}
|
||||
val mobile = requireActivity().intent.getBooleanExtra("mobile", false)
|
||||
if (auth || mobile){
|
||||
try {
|
||||
if (mobile) {
|
||||
// We use !! because we want an exception when something is not right.
|
||||
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!)
|
||||
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!)
|
||||
} 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")!!)
|
||||
if (auth || mobile) {
|
||||
startAuthentication(mobile)
|
||||
} else {
|
||||
receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||
updateAction(canSaved)
|
||||
}
|
||||
} 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.
|
||||
*/
|
||||
private fun goToTheNextFragment(auth: Boolean = false, mobile: Boolean = false) {
|
||||
// Making settings menu inactive
|
||||
private fun goToTheNextFragment(mobile: Boolean = false) {
|
||||
(activity as MainActivity).menuAvailable = false
|
||||
// Currently saving is true because the application is not yet integrated with
|
||||
// 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)
|
||||
} else {
|
||||
val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = true, auth = false, mobile = mobile)
|
||||
findNavController().navigate(action)
|
||||
val action =
|
||||
HomeFragmentDirections.actionHomeFragmentToCanFragment(auth = true, mobile = mobile)
|
||||
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")!!)
|
||||
val challengeUrl = requireActivity().intent.getStringExtra("challenge")!!
|
||||
val headers = requireActivity().intent.getStringExtra("headers")!!
|
||||
val map: HashMap<String, String> = HashMap()
|
||||
map.put("sessionId", headers)
|
||||
intentParams.setHeaders(map)
|
||||
Ion.getDefault(activity).conscryptMiddleware.enable(false)
|
||||
Ion.with(activity)
|
||||
.load(challengeUrl)
|
||||
.setHeader("sessionId", headers)
|
||||
.asJsonObject()
|
||||
.setCallback { _, result ->
|
||||
try {
|
||||
val challenge = result.asJsonObject["nonce"].toString().replace("\"", "")
|
||||
intentParams.setChallenge(challenge)
|
||||
goToTheNextFragment(mobile)
|
||||
} catch (e: Exception) {
|
||||
Log.i("GETrequest", e.toString())
|
||||
}
|
||||
}
|
||||
} 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")!!)
|
||||
*/
|
||||
var getAuthChallengeUrl =
|
||||
requireActivity().intent.data!!.getQueryParameter("getAuthChallengeUrl")!!
|
||||
getAuthChallengeUrl =
|
||||
getAuthChallengeUrl.substring(1, getAuthChallengeUrl.length - 1)
|
||||
var postAuthTokenUrl =
|
||||
requireActivity().intent.data!!.getQueryParameter("postAuthTokenUrl")!!
|
||||
postAuthTokenUrl = postAuthTokenUrl.substring(1, postAuthTokenUrl.length - 1)
|
||||
val headers =
|
||||
getHeaders(requireActivity().intent.data!!.getQueryParameter("headers")!!)
|
||||
intentParams.setAuthUrl(postAuthTokenUrl)
|
||||
val address = "https://" + URL(getAuthChallengeUrl).host
|
||||
intentParams.setOrigin(address)
|
||||
intentParams.setHeaders(headers)
|
||||
Ion.getDefault(activity).conscryptMiddleware.enable(false)
|
||||
val ion = Ion.with(activity)
|
||||
.load(getAuthChallengeUrl)
|
||||
|
||||
// Set headers.
|
||||
for ((header, value) in intentParams.headers) {
|
||||
ion.setHeader(header, value)
|
||||
}
|
||||
|
||||
ion
|
||||
.asJsonObject()
|
||||
.setCallback { _, result ->
|
||||
try {
|
||||
// Get data from the result and call launchAuth method
|
||||
val challenge =
|
||||
result.asJsonObject["nonce"].toString().replace("\"", "")
|
||||
intentParams.setChallenge(challenge)
|
||||
goToTheNextFragment(mobile)
|
||||
} catch (e: Exception) {
|
||||
Log.i("GETrequest", "was unsuccessful" + e.message)
|
||||
throw RuntimeException()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} 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 {
|
||||
message.setMessage(getString(R.string.problem_other))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHeaders(headersString: String): Map<String, String> {
|
||||
val headers = HashMap<String, String>()
|
||||
val headersStringFormatted = headersString.substring(1, headersString.length - 1)
|
||||
val headersJsonObject = JSONObject(headersStringFormatted)
|
||||
|
||||
for (name in headersJsonObject.keys()) {
|
||||
headers[name] = headersJsonObject[name].toString()
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays texts that inform the user whether the CAN and PIN 1 are saved on the device or not.
|
||||
* This might help the user to save some time as checking menu is not necessary unless the user
|
||||
|
@ -111,33 +247,125 @@ 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() {
|
||||
if (viewModel.userCan.length == 6) {
|
||||
binding!!.canStatusText.text = getString(R.string.can_status_saved)
|
||||
binding!!.canStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
||||
private fun initialChecks() {
|
||||
viewModel.checkCan(requireContext())
|
||||
viewModel.checkPin(requireContext())
|
||||
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 {
|
||||
binding!!.canStatusText.text = getString(R.string.can_status_negative)
|
||||
binding!!.canStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
||||
binding.detectionActionText.text = getString(R.string.action_detect_unavailable)
|
||||
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() {
|
||||
if (viewModel.userPin.length in 4..12) {
|
||||
binding!!.pinStatusText.text = getString(R.string.pin_status_saved)
|
||||
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_check_logo)
|
||||
private fun reset() {
|
||||
binding.homeActionButton.text = getString(R.string.try_again_text)
|
||||
binding.homeActionButton.setOnClickListener {
|
||||
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 {
|
||||
binding!!.pinStatusText.text = getString(R.string.pin_status_negative)
|
||||
binding!!.pinStatusLogo.setImageResource(R.drawable.ic_info_logo)
|
||||
adapter.enableReaderMode(activity, { tag ->
|
||||
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() {
|
||||
super.onDestroyView()
|
||||
binding = null
|
||||
if (receiver != null) {
|
||||
requireActivity().unregisterReceiver(receiver)
|
||||
}
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -5,9 +5,17 @@ import android.os.Bundle
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.gson.JsonObject
|
||||
import com.koushikdutta.ion.Ion
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.ActivityMainBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
|
||||
|
||||
|
||||
/**
|
||||
|
@ -16,10 +24,14 @@ import com.tarkvaraprojekt.mobileauthapp.databinding.ActivityMainBinding
|
|||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var navigationController: NavController
|
||||
private val paramsModel: ParametersViewModel by viewModels()
|
||||
|
||||
|
||||
// If true the settings menu can be accessed from the toolbar in the upper part of the screen.
|
||||
var menuAvailable: Boolean = true
|
||||
|
||||
var inMenu: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
@ -40,12 +52,36 @@ class MainActivity : AppCompatActivity() {
|
|||
R.id.menu_settings_option -> {
|
||||
if (menuAvailable) {
|
||||
navigationController.navigate(R.id.action_homeFragment_to_settingsFragment)
|
||||
menuAvailable = false
|
||||
inMenu = true
|
||||
true
|
||||
} 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
|
||||
}
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun returnError(errorCode: Int) {
|
||||
val json = JsonObject()
|
||||
json.addProperty("auth-token", "")
|
||||
json.addProperty("error", errorCode)
|
||||
|
||||
Ion.getDefault(this).conscryptMiddleware.enable(false)
|
||||
val ion = Ion.with(this)
|
||||
.load(paramsModel.authUrl)
|
||||
for ((header, value) in paramsModel.headers) {
|
||||
ion.setHeader(header, value)
|
||||
}
|
||||
|
||||
ion
|
||||
.setJsonObjectBody(json)
|
||||
.asJsonObject()
|
||||
.setCallback { _, _ ->
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,23 +19,24 @@ class Pin2Fragment : Fragment() {
|
|||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentPin2Binding? = null
|
||||
private var _binding: FragmentPin2Binding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentPin2Binding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentPin2Binding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding!!.nextButton.setOnClickListener {
|
||||
binding.nextButton.setOnClickListener {
|
||||
checkPin2Length()
|
||||
}
|
||||
binding!!.cancelButton.setOnClickListener {
|
||||
binding.cancelButton.setOnClickListener {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +46,7 @@ class Pin2Fragment : Fragment() {
|
|||
* then it is saved to the viewModel.
|
||||
*/
|
||||
private fun checkPin2Length() {
|
||||
val enteredPin2 = binding!!.pin2EditText.editText?.text.toString()
|
||||
val enteredPin2 = binding.pin2EditText.editText?.text.toString()
|
||||
if (enteredPin2.length in 5..12) {
|
||||
viewModel.setUserPin2(enteredPin2)
|
||||
} else {
|
||||
|
@ -66,7 +67,7 @@ class Pin2Fragment : Fragment() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +1,22 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentPinBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
|
||||
|
@ -24,121 +29,130 @@ class PinFragment : Fragment() {
|
|||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentPinBinding? = null
|
||||
private var _binding: FragmentPinBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
// Navigation arguments:
|
||||
// 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 var saveToggle = true
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentPinBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentPinBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
checkIfSkip()
|
||||
// If the user arrives from the settings menu then the button says
|
||||
// save instead of continue.
|
||||
// Switch should be not visible when user is in savings mode
|
||||
if (args.saving) {
|
||||
binding!!.nextButton.text = getString(R.string.save_text)
|
||||
}
|
||||
binding!!.nextButton.setOnClickListener { checkEnteredPin() }
|
||||
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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.saveSwitch.setOnCheckedChangeListener { _, isChecked ->
|
||||
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
|
||||
}
|
||||
}
|
||||
binding.buttonContinue.setOnClickListener { checkEnteredPin() }
|
||||
binding.buttonCancel.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes user to the next fragment, which is AuthFragment.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* settings menu not the HomeFragment.
|
||||
*/
|
||||
private fun goToTheStart() {
|
||||
if (args.saving) {
|
||||
findNavController().navigate(R.id.action_canFragment_to_settingsFragment)
|
||||
} else if (args.auth) {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
findNavController().navigate(R.id.action_pinFragment_to_settingsFragment)
|
||||
} else if (args.auth || args.mobile) {
|
||||
if (args.mobile) {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
} else {
|
||||
(activity as MainActivity).returnError(444)
|
||||
requireActivity().finishAndRemoveTask()
|
||||
}
|
||||
} else {
|
||||
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() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -11,18 +11,11 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.koushikdutta.ion.Ion
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding
|
||||
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
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* ResultFragment is used to create a JWT and to send response to the website/application
|
||||
|
@ -33,7 +26,8 @@ class ResultFragment : Fragment() {
|
|||
|
||||
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()
|
||||
|
||||
|
@ -42,18 +36,33 @@ class ResultFragment : Fragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentResultBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentResultBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
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.
|
||||
* Not really the safest way of doing things, but sufficient for POC purposes.
|
||||
*/
|
||||
private fun createResponse(
|
||||
success: Boolean = true,
|
||||
idCode: String = "noCode",
|
||||
name: String = "noName",
|
||||
authority: String = "noAuthority"
|
||||
) {
|
||||
val responseCode =
|
||||
if (success) AppCompatActivity.RESULT_OK else AppCompatActivity.RESULT_CANCELED
|
||||
val resultIntent = Intent()
|
||||
resultIntent.putExtra("idCode", idCode)
|
||||
resultIntent.putExtra("name", name)
|
||||
resultIntent.putExtra("authority", authority)
|
||||
requireActivity().setResult(responseCode, resultIntent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,50 +70,44 @@ class ResultFragment : Fragment() {
|
|||
*/
|
||||
fun postToken() {
|
||||
val json = JsonObject()
|
||||
json.addProperty("token", paramsModel.token)
|
||||
json.addProperty("challenge", paramsModel.challenge)
|
||||
json.addProperty("auth-token", paramsModel.token)
|
||||
json.addProperty("error", 200)
|
||||
|
||||
Ion.getDefault(activity).getConscryptMiddleware().enable(false)
|
||||
Ion.getDefault(activity).conscryptMiddleware.enable(false)
|
||||
val ion = Ion.with(activity)
|
||||
.load(paramsModel.authUrl)
|
||||
for ((header, value) in paramsModel.headers) {
|
||||
ion.setHeader(header, value)
|
||||
}
|
||||
|
||||
Ion.with(activity)
|
||||
.load("https://6bb0-85-253-195-252.ngrok.io/auth/authentication")
|
||||
.setJsonObjectBody(json)
|
||||
.asJsonObject()
|
||||
.setCallback { e, result ->
|
||||
// do stuff with the result or error
|
||||
Log.i("Log thingy", result.toString())
|
||||
ion
|
||||
.setJsonObjectBody(json)
|
||||
.asJsonObject()
|
||||
.setCallback { e, result ->
|
||||
Log.i("resultTag", result.toString())
|
||||
if (result == null) {
|
||||
if (args.mobile) {
|
||||
createResponse(false)
|
||||
} else {
|
||||
requireActivity().finishAndRemoveTask()
|
||||
}
|
||||
} else {
|
||||
if (args.mobile) {
|
||||
val userData = result.asJsonObject["userData"]
|
||||
val idCode = userData.asJsonObject["idCode"].asString
|
||||
val name = userData.asJsonObject["name"].asString
|
||||
val authority = result.asJsonObject["roles"].asJsonArray[0].asJsonObject["authority"].asString
|
||||
createResponse(true, idCode, name, authority)
|
||||
} 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() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
|
||||
}
|
|
@ -20,33 +20,34 @@ class UserFragment : Fragment() {
|
|||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentUserBinding? = null
|
||||
private var _binding: FragmentUserBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentUserBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentUserBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
displayInformation()
|
||||
binding!!.clearButton.setOnClickListener { goToTheStart() }
|
||||
binding.clearButton.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns text values to the fields in order to display user information.
|
||||
*/
|
||||
private fun displayInformation() {
|
||||
binding!!.userName.text =
|
||||
binding.userName.text =
|
||||
getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName)
|
||||
binding!!.identificationNumber.text = viewModel.userIdentificationNumber
|
||||
binding!!.gender.text = viewModel.gender
|
||||
binding!!.expiration.text = viewModel.expiration.replace(" ", "/")
|
||||
binding!!.citizenship.text = viewModel.citizenship
|
||||
binding.identificationNumber.text = viewModel.userIdentificationNumber
|
||||
binding.gender.text = viewModel.gender
|
||||
binding.expiration.text = viewModel.expiration.replace(" ", "/")
|
||||
binding.citizenship.text = viewModel.citizenship
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,6 +60,6 @@ class UserFragment : Fragment() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import java.security.MessageDigest
|
|||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class Authenticator(val comms : Comms) {
|
||||
class Authenticator(val comms: Comms) {
|
||||
|
||||
val type = "JWT"
|
||||
val algorithm = "ES384"
|
||||
|
@ -36,7 +36,7 @@ class Authenticator(val comms : Comms) {
|
|||
// Get header and claims.
|
||||
val header = """{"typ":"$type","alg":"$algorithm","x5c":["$base64cert"]}"""
|
||||
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(
|
||||
claims.toByteArray(Charsets.UTF_8)
|
||||
|
@ -51,7 +51,7 @@ class Authenticator(val comms : Comms) {
|
|||
return jwt + "." + base64Encode(signed)
|
||||
}
|
||||
|
||||
fun base64Encode(bytes: ByteArray) : String? {
|
||||
fun base64Encode(bytes: ByteArray): String? {
|
||||
val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes)
|
||||
return encoded.replace("=", "")
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.menu
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
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.databinding.FragmentSettingsBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
|
@ -21,7 +25,8 @@ class SettingsFragment : Fragment() {
|
|||
|
||||
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
|
||||
|
||||
|
@ -30,8 +35,8 @@ class SettingsFragment : Fragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -39,10 +44,20 @@ class SettingsFragment : Fragment() {
|
|||
showCanField()
|
||||
showPinField()
|
||||
togglePinButton()
|
||||
binding!!.canMenuAction.setOnClickListener { canAction() }
|
||||
binding!!.pinMenuAction.setOnClickListener { pinAction() }
|
||||
binding!!.pinMenuShow.setOnClickListener { togglePin() }
|
||||
binding!!.returnButton.setOnClickListener { backToHome() }
|
||||
binding.canMenuAction.setOnClickListener { canAction() }
|
||||
binding.pinMenuAction.setOnClickListener { pinAction() }
|
||||
binding.pinMenuShow.setOnClickListener { togglePin() }
|
||||
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() {
|
||||
if (viewModel.userCan.length == 6) {
|
||||
binding!!.canSaved.text = getString(R.string.saved_can, viewModel.userCan)
|
||||
binding!!.canMenuAction.text = getString(R.string.can_delete)
|
||||
binding.canSaved.text = getString(R.string.saved_can, viewModel.userCan)
|
||||
binding.canMenuAction.text = getString(R.string.can_delete)
|
||||
} else {
|
||||
binding!!.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
|
||||
binding!!.canMenuAction.text = getString(R.string.can_add)
|
||||
binding.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
|
||||
binding.canMenuAction.text = getString(R.string.add_can_text)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +81,9 @@ class SettingsFragment : Fragment() {
|
|||
if (viewModel.userCan.length == 6) {
|
||||
viewModel.deleteCan(requireContext())
|
||||
showCanField()
|
||||
showSnackbar(getString(R.string.can_deleted))
|
||||
} else {
|
||||
(activity as MainActivity).inMenu = false
|
||||
val action = SettingsFragmentDirections.actionSettingsFragmentToCanFragment(saving = true)
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
@ -79,16 +96,16 @@ class SettingsFragment : Fragment() {
|
|||
*/
|
||||
private fun showPinField() {
|
||||
if (viewModel.userPin.length in 4..12) {
|
||||
binding!!.pinMenuShow.visibility = Button.VISIBLE
|
||||
binding.pinMenuShow.visibility = Button.VISIBLE
|
||||
if (showPin)
|
||||
binding!!.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin)
|
||||
binding.pinSaved.text = getString(R.string.saved_pin, viewModel.userPin)
|
||||
else
|
||||
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin))
|
||||
binding!!.pinMenuAction.text = getString(R.string.pin1_delete)
|
||||
binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.hidden_pin))
|
||||
binding.pinMenuAction.text = getString(R.string.pin1_delete)
|
||||
} else {
|
||||
binding!!.pinMenuShow.visibility = Button.GONE
|
||||
binding!!.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing))
|
||||
binding!!.pinMenuAction.text = getString(R.string.pin1_add)
|
||||
binding.pinMenuShow.visibility = Button.GONE
|
||||
binding.pinSaved.text = getString(R.string.saved_pin, getString(R.string.missing))
|
||||
binding.pinMenuAction.text = getString(R.string.pin1_add)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,7 +117,9 @@ class SettingsFragment : Fragment() {
|
|||
if (viewModel.userPin.length in 4..12) {
|
||||
viewModel.deletePin(requireContext())
|
||||
showPinField()
|
||||
showSnackbar(getString(R.string.pin_deleted))
|
||||
} else {
|
||||
(activity as MainActivity).inMenu = false
|
||||
val action = SettingsFragmentDirections.actionSettingsFragmentToPinFragment(saving = true)
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
@ -120,9 +139,9 @@ class SettingsFragment : Fragment() {
|
|||
*/
|
||||
private fun togglePinButton() {
|
||||
if (showPin) {
|
||||
binding!!.pinMenuShow.text = getString(R.string.hide)
|
||||
binding.pinMenuShow.text = getString(R.string.hide)
|
||||
} 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.
|
||||
*/
|
||||
private fun backToHome() {
|
||||
(activity as MainActivity).inMenu = false
|
||||
findNavController().navigate(R.id.action_settingsFragment_to_homeFragment)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.model
|
||||
|
||||
import android.util.Log
|
||||
import android.util.Log.WARN
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class ParametersViewModel: ViewModel() {
|
||||
|
@ -13,6 +15,12 @@ class ParametersViewModel: ViewModel() {
|
|||
private var _token: String = ""
|
||||
val token get() = _token
|
||||
|
||||
private var _origin: String = ""
|
||||
val origin get() = _origin
|
||||
|
||||
private var _headers: Map<String, String> = HashMap<String, String>()
|
||||
val headers get() =_headers
|
||||
|
||||
fun setChallenge(newChallenge: String) {
|
||||
_challenge = newChallenge
|
||||
}
|
||||
|
@ -24,4 +32,13 @@ class ParametersViewModel: ViewModel() {
|
|||
fun setToken(newToken: String) {
|
||||
_token = newToken
|
||||
}
|
||||
|
||||
fun setOrigin(newOrigin: String) {
|
||||
_origin = newOrigin
|
||||
}
|
||||
|
||||
fun setHeaders(newHeaders: Map<String, String>) {
|
||||
Log.i("HEADERS", newHeaders.toList().toString())
|
||||
_headers = newHeaders
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.network
|
||||
|
||||
/**
|
||||
* TokenItem for making POST request.
|
||||
*/
|
||||
data class TokenItem (
|
||||
val token: String,
|
||||
val challenge: String,
|
||||
)
|
|
@ -5,7 +5,7 @@
|
|||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:fillColor="#001970"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".AuthFragment">
|
||||
|
||||
<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"
|
||||
android:layout_margin="@dimen/margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -23,31 +23,32 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20sp">
|
||||
android:padding="@dimen/padding_small">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/auth_fragment_instruction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:gravity="center"
|
||||
android:layout_margin="@dimen/margin"
|
||||
android:gravity="left"
|
||||
android:text="@string/auth_instruction_text"
|
||||
android:textSize="20sp" />
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nfc_logo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="@dimen/logo_big"
|
||||
android:layout_height="@dimen/logo_big"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="6dp"
|
||||
android:layout_margin="@dimen/margin"
|
||||
android:padding="@dimen/margin_huge"
|
||||
android:src="@drawable/nfc_logo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time_counter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:textSize="14sp"
|
||||
android:layout_margin="@dimen/margin"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/auth_fragment_instruction"
|
||||
tools:text="@string/time_left" />
|
||||
|
||||
|
@ -60,9 +61,9 @@
|
|||
android:id="@+id/next_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/next_text"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/continue_button"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/cancel_button"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_view" />
|
||||
|
@ -71,10 +72,10 @@
|
|||
android:id="@+id/cancel_button"
|
||||
android:layout_width="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:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/next_button"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_view" />
|
||||
|
||||
|
|
|
@ -4,82 +4,57 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:context=".CanFragment">
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
android:text="@string/can_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:textSize="@dimen/headline_text"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
android:fontFamily="sans-serif"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/canTextField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
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:counterMaxLength="6"
|
||||
app:counterTextAppearance="@style/helper"
|
||||
app:counterOverflowTextAppearance="@style/helper"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20sp">
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:fontFamily="sans-serif"
|
||||
android:inputType="number"
|
||||
android:singleLine="true"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/enter_can"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:text="@string/enter_can"
|
||||
android:textSize="20sp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/can_edit_text"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:hint="@string/text_can"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="6"
|
||||
app:endIconMode="password_toggle"
|
||||
app:errorEnabled="true"
|
||||
app:helperText="@string/example_can"
|
||||
app:helperTextEnabled="true"
|
||||
app:startIconDrawable="@drawable/can_logo">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/next_button"
|
||||
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:id="@+id/button_cancel"
|
||||
android:text="@string/cancel_text"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/next_button"
|
||||
android:textSize="@dimen/regular_text"
|
||||
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_constraintTop_toBottomOf="@id/card_view" />
|
||||
app:layout_constraintTop_toBottomOf="@id/canTextField" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".HomeFragment">
|
||||
|
||||
<LinearLayout
|
||||
|
@ -20,7 +20,7 @@
|
|||
android:id="@+id/can_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
@ -32,14 +32,14 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/can_status_logo"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginStart="@dimen/margin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/can_status_text"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
|||
android:id="@+id/pin_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
@ -63,14 +63,14 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/pin_status_logo"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginStart="@dimen/margin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pin_status_text"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
|
@ -80,15 +80,49 @@
|
|||
|
||||
</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
|
||||
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_height="wrap_content"
|
||||
android:text="@string/begin_text"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/saved_states"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:layout_marginStart="@dimen/margin_huge"
|
||||
android:visibility="gone"
|
||||
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>
|
|
@ -4,81 +4,111 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:context=".PinFragment">
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/pin_view"
|
||||
android:textSize="@dimen/headline_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/pinTextField"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
android:hint="@string/hint_pin"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="12"
|
||||
app:counterOverflowTextAppearance="@style/helper"
|
||||
app:counterTextAppearance="@style/helper"
|
||||
app:endIconMode="password_toggle"
|
||||
app:helperText="@string/pin_helper_text"
|
||||
app:helperTextEnabled="true"
|
||||
app:helperTextTextAppearance="@style/helper"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_text">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
android:fontFamily="sans-serif"
|
||||
android:inputType="numberPassword"
|
||||
android:singleLine="true"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pin_fragment_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:text="@string/pin_fragment" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/pin_edit_text"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:hint="@string/enter_pin"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="12"
|
||||
app:endIconMode="password_toggle"
|
||||
app:errorEnabled="true"
|
||||
app:helperText="@string/example_pin"
|
||||
app:helperTextEnabled="true"
|
||||
app:startIconDrawable="@drawable/can_logo">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="numberPassword"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/next_button"
|
||||
<TextView
|
||||
android:id="@+id/save_pin_question"
|
||||
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:textSize="15sp"
|
||||
app:layout_constraintEnd_toStartOf="@id/next_button"
|
||||
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/card_view" />
|
||||
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>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_continue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/continue_button"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
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>
|
|
@ -64,7 +64,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/next_text"
|
||||
android:text="@string/continue_button"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/cancel_button"
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".ResultFragment">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/can_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_margin="@dimen/margin"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -27,33 +27,23 @@
|
|||
<TextView
|
||||
android:id="@+id/result_text"
|
||||
android:text="@string/result_text"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/padding_small"
|
||||
android:layout_marginVertical="@dimen/margin_small"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_info_text"
|
||||
android:text="@string/result_info"
|
||||
android:padding="12dp"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:padding="@dimen/padding_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:layout_marginVertical="@dimen/margin_small"
|
||||
android:layout_width="match_parent"
|
||||
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>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_margin="@dimen/padding"
|
||||
tools:context=".menu.SettingsFragment">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
|
@ -22,44 +22,46 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
android:padding="@dimen/padding_small">
|
||||
<TextView
|
||||
android:id="@+id/can_saved"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:text="@string/saved_can" />
|
||||
<Button
|
||||
android:id="@+id/can_menu_action"
|
||||
android:layout_margin="12dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:id="@+id/pin_saved"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:text="@string/saved_pin"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<Button
|
||||
android:id="@+id/pin_menu_action"
|
||||
android:layout_margin="12dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:orientation="vertical">
|
||||
<Button
|
||||
android:id="@+id/pin_menu_show"
|
||||
android:layout_margin="12dp"
|
||||
android:textSize="15sp"
|
||||
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"
|
||||
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>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
@ -69,8 +71,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/return_text"
|
||||
android:layout_margin="24dp"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginVertical="@dimen/margin"
|
||||
android:layout_marginStart="@dimen/padding"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/settings_card"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
|
|
@ -1,123 +1,127 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:context=".UserFragment">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
android:layout_height="wrap_content">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
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">
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/padding"
|
||||
tools:context=".UserFragment">
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20sp">
|
||||
android:layout_margin="@dimen/margin"
|
||||
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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_name_label"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/user_name_label"
|
||||
android:textSize="14sp" />
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_tiny">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/user_name"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/user_name_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/user_name_label"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/identification_number_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/identification_number_label"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:text="@string/user_name"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/identification_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/identification_number_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/identification_number_label"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gender_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/gender_label"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/identification_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/gender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/gender_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/gender_label"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/expiration_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/expiration_label"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/gender"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/expiration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/expiration_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/expiration_label"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/citizenship_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/citizenship_label"
|
||||
android:textSize="14sp" />
|
||||
<TextView
|
||||
android:id="@+id/expiration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/citizenship"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/citizenship_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/citizenship_label"
|
||||
android:textSize="@dimen/regular_text" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/citizenship"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/clear_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/clear_button"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_view" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<Button
|
||||
android:id="@+id/clear_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_big"
|
||||
android:text="@string/return_text"
|
||||
android:textSize="@dimen/regular_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_view" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@drawable/ic_check_logo" />
|
||||
</adaptive-icon>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@drawable/ic_check_logo" />
|
||||
</adaptive-icon>
|
|
@ -18,6 +18,9 @@
|
|||
android:id="@+id/action_homeFragment_to_canFragment"
|
||||
app:destination="@id/canFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_userFragment"
|
||||
app:destination="@id/userFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/pinFragment"
|
||||
|
@ -42,10 +45,6 @@
|
|||
android:id="@+id/action_pinFragment_to_authFragment"
|
||||
app:destination="@id/authFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument
|
||||
android:name="reading"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
|
@ -78,10 +77,6 @@
|
|||
android:id="@+id/action_canFragment_to_pinFragment"
|
||||
app:destination="@id/pinFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument
|
||||
android:name="reading"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
|
@ -90,6 +85,10 @@
|
|||
android:name="mobile"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="fromhome"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/authFragment"
|
||||
|
@ -113,10 +112,6 @@
|
|||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="reading"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="mobile"
|
||||
app:argType="boolean"
|
||||
|
|
|
@ -1,29 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Must translate to English, but should work now -->
|
||||
<string name="app_name">NFC authentication</string>
|
||||
<string name="home_fragment">Work in progress</string>
|
||||
<string name="app_name">NFC authenticator</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 name="pin_status_saved">PIN 1 saved</string>
|
||||
<string name="pin_status_negative">PIN 1 not saved</string>
|
||||
<string name="can_status_saved">CAN saved</string>
|
||||
<string name="can_status_negative">CAN not saved</string>
|
||||
|
||||
<string name="begin_text">READ ID CARD</string>
|
||||
<string name="next_text">NEXT</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="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 name="pin_fragment">Please enter PIN 1</string>
|
||||
<string name="enter_pin">PIN 1</string>
|
||||
<string name="example_pin">Example. 1234</string>
|
||||
<string name="length_pin">Allowed length for PIN 1 is 4..12</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="save_pin_title">Save PIN 1</string>
|
||||
<string name="pin_view">Please enter PIN 1</string>
|
||||
<string name="hint_pin">PIN 1</string>
|
||||
<string name="pin_helper_text">PIN 1 must be 4–12 digits long</string>
|
||||
<string name="save_pin">Save PIN 1</string>
|
||||
<string name="pin_save_on">On</string>
|
||||
<string name="pin_save_off">Off</string>
|
||||
|
||||
<!-- string resources for Pin2Fragment -->
|
||||
<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 resources for CanFragment -->
|
||||
<string name="example_can">Example. 123456</string>
|
||||
<string name="text_can">CAN</string>
|
||||
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</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 name="can_view">Please enter CAN</string>
|
||||
<string name="can_text">CAN</string>
|
||||
<string name="can_helper_text">CAN must be 6 digits long</string>
|
||||
|
||||
<!-- 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="no_time">No time left</string>
|
||||
<string name="no_success">Wrong CAN</string>
|
||||
|
||||
<!-- string resources for UserFragment layout -->
|
||||
<string name="user_name_label">NAME</string>
|
||||
|
@ -53,18 +65,14 @@
|
|||
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||
<string name="citizenship_label">CITIZENSHIP</string>
|
||||
<string name="gender_label">SEX</string>
|
||||
<string name="clear_button">FORGET</string>
|
||||
|
||||
<!-- string resources for ResultFragment layout-->
|
||||
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string>
|
||||
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string>
|
||||
<string name="result_text">Checking the created token</string>
|
||||
<string name="result_info">The app will close automatically</string>
|
||||
|
||||
<!-- menu -->
|
||||
<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="can_add">Add CAN</string>
|
||||
<string name="can_delete">Delete CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Add PIN1</string>
|
||||
|
@ -73,6 +81,7 @@
|
|||
<string name="show">SHOW</string>
|
||||
<string name="hide">HIDE</string>
|
||||
<string name="hidden_pin">****</string>
|
||||
<string name="unavailable">Settings currently unavailabe</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="menu_unavailable_message">Settings are currently unavailable</string>
|
||||
<string name="can_deleted">CAN deleted</string>
|
||||
<string name="pin_deleted">PIN 1 deleted</string>
|
||||
</resources>
|
|
@ -1,28 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NFC authentication</string>
|
||||
<string name="home_fragment">Work in progress</string>
|
||||
<string name="app_name">NFC autentija</string>
|
||||
|
||||
<string name="begin_text">LOE ID KAARTI</string>
|
||||
<string name="next_text">EDASI</string>
|
||||
<!-- Buttons -->
|
||||
<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="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 name="pin_status_saved">PIN 1 on 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_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 name="pin_fragment">Palun sisesta PIN 1</string>
|
||||
<string name="enter_pin">PIN 1</string>
|
||||
<string name="example_pin">Näide. 1234</string>
|
||||
<string name="length_pin">PIN 1 lubatud pikkus on 4..12</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="save_pin_title">Salvesta PIN 1</string>
|
||||
<string name="pin_view">Palun sisesta PIN 1</string>
|
||||
<string name="hint_pin">PIN 1</string>
|
||||
<string name="pin_helper_text">PIN 1 lubatud pikkus on 4..12</string>
|
||||
<string name="save_pin">Save PIN 1</string>
|
||||
<string name="pin_save_on">On</string>
|
||||
<string name="pin_save_off">Off</string>
|
||||
|
||||
<!-- string resources for Pin2Fragment -->
|
||||
<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 resources for CanFragment -->
|
||||
<string name="example_can">Näide. 123456</string>
|
||||
<string name="text_can">CAN</string>
|
||||
<string name="enter_can">Sisesta ID kaardi CAN (Card Access Number)</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 name="can_view">Please enter CAN</string>
|
||||
<string name="can_text">CAN</string>
|
||||
<string name="can_helper_text">CAN must be 6 digits long</string>
|
||||
|
||||
<!-- 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="no_time">Aeg on otsas</string>
|
||||
<string name="no_success">Vale CAN</string>
|
||||
|
||||
<!-- string resources for UserFragment layout -->
|
||||
<string name="user_name_label">NIMI</string>
|
||||
<string name="user_name">%1$s %2$s</string>
|
||||
<string name="identification_number_label">ISIKUKOOD</string>
|
||||
<string name="clear_button">UNUSTA</string>
|
||||
<string name="expiration_label">KEHTIV KUNI</string>
|
||||
<string name="citizenship_label">KODAKONDSUS</string>
|
||||
<string name="gender_label">SUGU</string>
|
||||
|
||||
<!-- string resources for ResultFragment layout-->
|
||||
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string>
|
||||
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string>
|
||||
<string name="result_text">Tulemust kontrollitakse</string>
|
||||
<string name="result_info">Rakendus sulgeb ennast ise</string>
|
||||
|
||||
<!-- menu -->
|
||||
<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="can_add">Lisa CAN</string>
|
||||
<string name="can_delete">Kustuta CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Lisa PIN1</string>
|
||||
|
@ -72,5 +80,7 @@
|
|||
<string name="show">NÄITA</string>
|
||||
<string name="hide">PEIDA</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>
|
|
@ -8,9 +8,9 @@
|
|||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="blue_200">#90caf9</color>
|
||||
<color name="blue_500">#2196f3</color>
|
||||
<color name="blue_700">#1976d2</color>
|
||||
<color name="orange_200">#ffcc80</color>
|
||||
<color name="blue_200">#d1d9ff</color>
|
||||
<color name="blue_500">#002984</color>
|
||||
<color name="blue_700">#001970</color>
|
||||
<color name="orange_200">#ffab91</color>
|
||||
<color name="orange_700">#f57c00</color>
|
||||
</resources>
|
|
@ -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>
|
|
@ -1,27 +1,44 @@
|
|||
<resources>
|
||||
<string name="app_name">NFC authentication</string>
|
||||
<string name="home_fragment">Work in progress</string>
|
||||
<string name="app_name">NFC authenticator</string>
|
||||
|
||||
<string name="begin_text">READ ID CARD</string>
|
||||
<string name="next_text">NEXT</string>
|
||||
<!-- BUTTONS -->
|
||||
<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="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 name="pin_status_saved">PIN 1 saved</string>
|
||||
<string name="pin_status_negative">PIN 1 not saved</string>
|
||||
<string name="can_status_saved">CAN 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 name="pin_fragment">Please enter PIN 1</string>
|
||||
<string name="enter_pin">PIN 1</string>
|
||||
<string name="example_pin">Example. 1234</string>
|
||||
<string name="length_pin">Allowed length for PIN 1 is 4..12</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="save_pin_title">Save PIN 1</string>
|
||||
<string name="pin_view">Please enter PIN 1</string>
|
||||
<string name="hint_pin">PIN 1</string>
|
||||
<string name="pin_helper_text">PIN 1 must be 4–12 digits long</string>
|
||||
<string name="save_pin">Save PIN 1</string>
|
||||
<string name="pin_save_on">On</string>
|
||||
<string name="pin_save_off">Off</string>
|
||||
|
||||
<!-- string resources for Pin2Fragment -->
|
||||
<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 resources for CanFragment -->
|
||||
<string name="example_can">Example. 123456</string>
|
||||
<string name="text_can">CAN</string>
|
||||
<string name="enter_can">Enter ID card\'s CAN (Card Access Number)</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 name="can_view">Please enter CAN</string>
|
||||
<string name="can_text">CAN</string>
|
||||
<string name="can_helper_text">CAN must be 6 digits long</string>
|
||||
|
||||
<!-- 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="no_time">No time left</string>
|
||||
<string name="no_success">Wrong CAN</string>
|
||||
|
||||
<!-- string resources for UserFragment layout -->
|
||||
<string name="user_name_label">NAME</string>
|
||||
|
@ -52,18 +63,14 @@
|
|||
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||
<string name="citizenship_label">CITIZENSHIP</string>
|
||||
<string name="gender_label">SEX</string>
|
||||
<string name="clear_button">FORGET</string>
|
||||
|
||||
<!-- string resources for ResultFragment layout-->
|
||||
<string name="result_text">See Fragment vastutab vastuse tagastamise eest.</string>
|
||||
<string name="result_info">Hiljem sulgeb rakendus automaatselt.</string>
|
||||
<string name="result_text">Controlling the created token</string>
|
||||
<string name="result_info">The app will close automatically</string>
|
||||
|
||||
<!-- menu -->
|
||||
<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="can_add">Add CAN</string>
|
||||
<string name="can_delete">Delete CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Add PIN 1</string>
|
||||
|
@ -72,5 +79,7 @@
|
|||
<string name="show">SHOW</string>
|
||||
<string name="hide">HIDE</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>
|
|
@ -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>
|
15
README.md
15
README.md
|
@ -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 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
|
||||
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,8 @@ 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.**
|
||||
|
||||
### 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*
|
||||
* [Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes) *last updated for iteration3 on 08.11*
|
||||
* [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*
|
||||
* [Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases) *last updated on 10.10*
|
||||
* [User stories](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/User-stories) *last updated on 10.10*
|
||||
* [Use Case Tests](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Case-Tests) *WIP*
|
||||
### 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.
|
||||
|
||||
### See the [Wiki](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki) for pages relevant for the "Software project" subject
|
||||
|
|
|
@ -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.
|
|
@ -43,4 +43,5 @@ dependencies {
|
|||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.testmobileapp">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
|
|
@ -5,72 +5,90 @@ import android.content.Intent
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.example.testmobileapp.databinding.ActivityMainBinding
|
||||
import com.koushikdutta.ion.Ion
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
* Test mobile app to demonstrate how other applications can use MobileAuthApp.
|
||||
* Base url where the requests should be made. Add yours here. It must use https.
|
||||
*/
|
||||
private const val BASE_URL = "https://a0fe-2001-7d0-88ab-b880-7571-cba0-5db2-11b7.ngrok.io"
|
||||
private const val AUTH_URL = "$BASE_URL/auth/login"
|
||||
private const val CHALLENGE_URL = "$BASE_URL/auth/challenge"
|
||||
|
||||
/**
|
||||
* Test mobile app to demonstrate how other applications could potentially use MobileAuthApp.
|
||||
* Single purpose app that launches the MobileAuthApp and gets the response back (JWT).
|
||||
* Only for demo purposes.
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var authLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response ->
|
||||
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)
|
||||
// Logs are used to show what information can be retrieved from the mobileauthapp.
|
||||
Log.i("getResult", response.data?.getStringExtra("idCode").toString())
|
||||
Log.i("getResult", response.data?.getStringExtra("name").toString())
|
||||
Log.i("getResult", response.data?.getStringExtra("authority").toString())
|
||||
var user = ""
|
||||
try {
|
||||
user = response.data?.getStringExtra("name").toString()
|
||||
} catch (e: Exception) {
|
||||
Log.i("getResult", "unable to retrieve name")
|
||||
}
|
||||
showResult(user)
|
||||
}
|
||||
if (response.resultCode == Activity.RESULT_CANCELED) {
|
||||
binding.loginTextView.text = getString(R.string.auth_failure)
|
||||
}
|
||||
}
|
||||
|
||||
binding.loginOptionNfcButton.setOnClickListener { launchAuth() }
|
||||
//binding.loginOptionNfcButton.setOnClickListener { getData() }
|
||||
showLogin()
|
||||
|
||||
binding.loginOptionNfcButton.setOnClickListener {
|
||||
launchAuth()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that creates an intent to launch the MobileAuthApp
|
||||
*/
|
||||
private fun launchAuth(challenge: String = "challenge", authUrl: String = "authUrl") {
|
||||
private fun launchAuth() {
|
||||
val launchIntent = Intent()
|
||||
launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity")
|
||||
launchIntent.putExtra("action", "auth")
|
||||
launchIntent.putExtra("challenge", challenge)
|
||||
launchIntent.putExtra("authUrl", authUrl)
|
||||
launchIntent.putExtra("challenge", CHALLENGE_URL)
|
||||
launchIntent.putExtra("originUrl", BASE_URL)
|
||||
launchIntent.putExtra("authUrl", AUTH_URL)
|
||||
launchIntent.putExtra("headers","${(0..100000).random()}")
|
||||
launchIntent.putExtra("mobile", true)
|
||||
authLauncher.launch(launchIntent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for retrieving data from an endpoint.
|
||||
* Ion library is used as it is very convenient for making simple GET requests.
|
||||
*/
|
||||
private fun getData() {
|
||||
// Enter the server endpoint address to here
|
||||
val baseUrl = "enter-base-url-here"
|
||||
val url = "$baseUrl/auth/challenge"
|
||||
Ion.with(applicationContext)
|
||||
.load(url)
|
||||
.asJsonObject()
|
||||
.setCallback { _, result ->
|
||||
try {
|
||||
// Get data from the result and call launchAuth method
|
||||
val challenge = result.asJsonObject["nonce"].toString()
|
||||
launchAuth(challenge, baseUrl)
|
||||
} catch (e: Exception) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,8 @@
|
|||
android:layout_margin="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/login_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_method_text_view"
|
||||
|
@ -46,4 +47,30 @@
|
|||
|
||||
</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>
|
|
@ -4,6 +4,8 @@
|
|||
<string name="login_text">Login</string>
|
||||
<string name="choose_login_method">Choose login method</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="forget_button">Forget</string>
|
||||
<string name="hello">Hello, %s!</string>
|
||||
</resources>
|
|
@ -4,6 +4,8 @@
|
|||
<string name="login_text">Logi sisse</string>
|
||||
<string name="choose_login_method">Vali sobiv meetod</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="forget_button">Unusta</string>
|
||||
<string name="hello">Tere, %s!</string>
|
||||
</resources>
|
|
@ -3,6 +3,8 @@
|
|||
<string name="login_text">Login</string>
|
||||
<string name="choose_login_method">Choose login method</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="forget_button">Forget</string>
|
||||
<string name="hello">Hello, %s!</string>
|
||||
</resources>
|
|
@ -1,5 +1,6 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -7,7 +8,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:7.0.2"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
|
||||
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
|
|
|
@ -31,3 +31,6 @@ build/
|
|||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### web-eid.js ###
|
||||
src/demo-website/src/web-eid.js
|
|
@ -32,7 +32,7 @@ Copy the second forwarding link (the one with https) and put it in ```com.tarkva
|
|||
### 3. Run the project
|
||||
Use your favourite IDE or just run it via commandline with ```./mvnw spring-boot:run```
|
||||
|
||||
On your Android device browser navigate to the url you copied earlier and you should see the website landing page. If you have the mobile authentication app installed, you should be able to log into the website with your Estonian ID-card.
|
||||
On your browser (Android to test out from Android device or desktop to try out ID-card reader or QR-code capability) navigate to the url you copied earlier and you should see the website landing page. If you have the mobile authentication app installed, you should be able to log into the website with your Estonian ID-card.
|
||||
|
||||
|
||||
## Credits...
|
||||
|
|
|
@ -13,11 +13,14 @@
|
|||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>demoBackend</name>
|
||||
<description>demoBackend</description>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<kotlin.version>1.5.31</kotlin.version>
|
||||
<caffeine.version>2.8.5</caffeine.version>
|
||||
<javaxcache.version>1.1.1</javaxcache.version>
|
||||
<node.version>v16.13.0</node.version>
|
||||
<npm.version>8.1.4</npm.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -42,6 +45,11 @@
|
|||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.webeid.security</groupId>
|
||||
<artifactId>authtoken-validation</artifactId>
|
||||
|
@ -86,6 +94,7 @@
|
|||
<build>
|
||||
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||
<finalName>demo</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -110,6 +119,91 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<!-- Plugin to install node and npm and then build the vue project -->
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.12.0</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>Install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<nodeVersion>${node.version}</nodeVersion>
|
||||
<npmVersion>${npm.version}</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>npm install</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<phase>generate-resources</phase>
|
||||
<configuration>
|
||||
<arguments>install</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
<execution>
|
||||
<id>npm build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<phase>process-resources</phase>
|
||||
<configuration>
|
||||
<arguments>run build</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<nodeVersion>${node.version}</nodeVersion>
|
||||
<workingDirectory>src/demo-website</workingDirectory>
|
||||
<!-- <installDirectory>src/demo-website/dist</installDirectory>-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Plugin to copy built vue project from src/frontend/dist to target/classes/static -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>Copy web-eid.js file to Vue root folder.</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>src/demo-website/src</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/demo-website/node_modules/@web-eid/web-eid-library/dist/es</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>Copy Vue frontend into Spring Boot target static folder</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/classes/static</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/demo-website/dist</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
Binary file not shown.
|
@ -0,0 +1,24 @@
|
|||
# demo-website
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -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 |
|
@ -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
Binary file not shown.
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
(set -o igncr) 2>/dev/null && set -o igncr; # cygwin encoding fix
|
||||
|
||||
basedir=`dirname "$0"`
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
NODE_EXE="$basedir/node.exe"
|
||||
if ! [ -x "$NODE_EXE" ]; then
|
||||
NODE_EXE="$basedir/node"
|
||||
fi
|
||||
if ! [ -x "$NODE_EXE" ]; then
|
||||
NODE_EXE=node
|
||||
fi
|
||||
|
||||
# this path is passed to node.exe, so it needs to match whatever
|
||||
# kind of paths Node.js thinks it's using, typically win32 paths.
|
||||
CLI_BASEDIR="$("$NODE_EXE" -p 'require("path").dirname(process.execPath)')"
|
||||
NPM_CLI_JS="$CLI_BASEDIR/node_modules/npm/bin/npm-cli.js"
|
||||
|
||||
NPM_PREFIX=`"$NODE_EXE" "$NPM_CLI_JS" prefix -g`
|
||||
if [ $? -ne 0 ]; then
|
||||
# if this didn't work, then everything else below will fail
|
||||
echo "Could not determine Node.js install directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
NPM_PREFIX_NPM_CLI_JS="$NPM_PREFIX/node_modules/npm/bin/npm-cli.js"
|
||||
|
||||
# a path that will fail -f test on any posix bash
|
||||
NPM_WSL_PATH="/.."
|
||||
|
||||
# WSL can run Windows binaries, so we have to give it the win32 path
|
||||
# however, WSL bash tests against posix paths, so we need to construct that
|
||||
# to know if npm is installed globally.
|
||||
if [ `uname` = 'Linux' ] && type wslpath &>/dev/null ; then
|
||||
NPM_WSL_PATH=`wslpath "$NPM_PREFIX_NPM_CLI_JS"`
|
||||
fi
|
||||
if [ -f "$NPM_PREFIX_NPM_CLI_JS" ] || [ -f "$NPM_WSL_PATH" ]; then
|
||||
NPM_CLI_JS="$NPM_PREFIX_NPM_CLI_JS"
|
||||
fi
|
||||
|
||||
"$NODE_EXE" "$NPM_CLI_JS" "$@"
|
|
@ -0,0 +1,19 @@
|
|||
:: Created by npm, please don't edit manually.
|
||||
@ECHO OFF
|
||||
|
||||
SETLOCAL
|
||||
|
||||
SET "NODE_EXE=%~dp0\node.exe"
|
||||
IF NOT EXIST "%NODE_EXE%" (
|
||||
SET "NODE_EXE=node"
|
||||
)
|
||||
|
||||
SET "NPM_CLI_JS=%~dp0\node_modules\npm\bin\npm-cli.js"
|
||||
FOR /F "delims=" %%F IN ('CALL "%NODE_EXE%" "%NPM_CLI_JS%" prefix -g') DO (
|
||||
SET "NPM_PREFIX_NPM_CLI_JS=%%F\node_modules\npm\bin\npm-cli.js"
|
||||
)
|
||||
IF EXIST "%NPM_PREFIX_NPM_CLI_JS%" (
|
||||
SET "NPM_CLI_JS=%NPM_PREFIX_NPM_CLI_JS%"
|
||||
)
|
||||
|
||||
"%NODE_EXE%" "%NPM_CLI_JS%" %*
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "demo-website",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
"@web-eid/web-eid-library": "../../../../web-eid.js/",
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0",
|
||||
"vue-cookie-next": "^1.3.0",
|
||||
"vue-router": "^4.0.0-0",
|
||||
"vue3-cookies": "^1.0.6",
|
||||
"vuex": "^4.0.2",
|
||||
"vuex-persistedstate": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-vue-3": "^0.0.5",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^7.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,17 @@
|
|||
<!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.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<router-view/>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#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: bold;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
#nav a.router-link-exact-active {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div class="container container-md d-flex flex-column">
|
||||
<div>
|
||||
<h3 class="text-center">Welcome to Estonian ID card mobile authentication demo website.</h3>
|
||||
<p>This website to demonstrates the viability of using your NFC-enabled ID-card and your smartphone to authenticate yourself.
|
||||
This is a proof of concept solution, so currently only authentication is supported. This solution was created for <a href="https://courses.cs.ut.ee/2021/tvp/">Software Project (Tarkvaraprojekt)</a> course in the University of Tartu
|
||||
in cooperation with <a href="https://github.com/martinpaljak/">Martin Paljak</a>.</p>
|
||||
<p>This solution is meant to be web-eid.js compatible, so this example website uses a <a href="https://github.com/TanelOrumaa/web-eid.js">fork of web-eid.js</a> which supports the Android authentication app.</p>
|
||||
<h2>Usage</h2>
|
||||
<p>To get started, download and install the authentication Android app from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/releases">GitHub</a> (Android 8.0+ required).
|
||||
You can then click "Login" to authenticate yourself on this demo website with the app or if you are using a non-Android device, you can use both the app or the default web-eid.js option to login using the smartcard reader.
|
||||
</p>
|
||||
|
||||
<p class="text-center">Read more from <a href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
|
||||
</div>
|
||||
<div class="justify-content-center d-flex">
|
||||
<div id="canvas"></div>
|
||||
</div>
|
||||
<div class="justify-content-center d-flex">
|
||||
<button type="button" class="btn loginButton btn-dark" v-on:click="authenticate">
|
||||
<div v-if="loading" class="d-flex justify-content-center">
|
||||
<div class="spinner-border text-light spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>Authenticate</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="btn-group-sm d-flex justify-content-center" v-if="!isAndroidDevice" role="group" aria-label="Basic radio toggle button group">
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnCardReader" autocomplete="off" v-on:click="useCardReader">
|
||||
<label class="btn btn-outline-secondary" for="btnCardReader">using ID-card reader</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnApp" autocomplete="off" checked v-on:click="useApp">
|
||||
<label class="btn btn-outline-secondary" for="btnApp">using Android App</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import * as webeid from '../web-eid.js';
|
||||
import router from "@/router";
|
||||
|
||||
export default {
|
||||
name: 'LoginComponent',
|
||||
data() {
|
||||
return {
|
||||
useAndroidApp: true,
|
||||
loading: false,
|
||||
challenge: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
useApp: function() {
|
||||
this.useAndroidApp = true;
|
||||
},
|
||||
|
||||
useCardReader: function() {
|
||||
this.useAndroidApp = false;
|
||||
},
|
||||
|
||||
|
||||
|
||||
authenticate: async function () {
|
||||
this.loading = true;
|
||||
|
||||
const options = {
|
||||
getAuthChallengeUrl: window.location.origin + "/auth/challenge",
|
||||
postAuthTokenUrl: window.location.origin + "/auth/login",
|
||||
getAuthSuccessUrl: window.location.origin + "/auth/login",
|
||||
useAuthApp: this.useAndroidApp,
|
||||
headers: {
|
||||
"sessionId": this.$store.getters.getSessionId
|
||||
},
|
||||
};
|
||||
|
||||
console.log(options);
|
||||
|
||||
try {
|
||||
const response = await webeid.authenticate(options);
|
||||
console.log("Authentication successful! Response:", response);
|
||||
this.loading = false;
|
||||
this.$store.dispatch("setLoggedIn", true);
|
||||
await router.push("welcome");
|
||||
|
||||
} catch (error) {
|
||||
console.log("Authentication failed! Error:", error);
|
||||
alert(error.message);
|
||||
this.loading = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return this.$store.getAuthenticated;
|
||||
},
|
||||
loading() {
|
||||
return this.loading;
|
||||
},
|
||||
isAndroidDevice() {
|
||||
return this.$store.getters.getIsAndroid
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const isAndroid = webeid.isAndroidDevice();
|
||||
this.$store.dispatch("setIsAndroid", isAndroid);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container > div {
|
||||
margin-top: 2vh;
|
||||
}
|
||||
.loginButton {
|
||||
height: 4vh;
|
||||
width: 20vh;
|
||||
line-height: 3vh;
|
||||
}
|
||||
|
||||
.loginButton > p {
|
||||
font-size: 3vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
height: 30vh;
|
||||
width: 30vh;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<!-- As a heading -->
|
||||
<nav class="navbar navbar-dark bg-dark container-fluid flex-row">
|
||||
<div class="">
|
||||
<span class="navbar-brand mb-0 h1">Mobile authentication demo</span>
|
||||
</div>
|
||||
<div v-if="isLoggedIn" class="nav-item">
|
||||
<button type="button" class="btn btn-light" v-on:click="logOut">Log out</button>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@/router";
|
||||
|
||||
export default {
|
||||
name: "Navbar",
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return this.$store.getters.getAuthenticated;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
logOut: function () {
|
||||
|
||||
const requestOptions = {
|
||||
method: "POST",
|
||||
headers: {"sessionId": this.$store.getters.getSessionId}
|
||||
};
|
||||
fetch("/auth/logout", requestOptions)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
this.$store.dispatch("setLoggedIn", false);
|
||||
router.push("/");
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const sessionId = this.$cookie.getCookie("JSESSIONID");
|
||||
this.$store.dispatch("fetchSessionId", sessionId);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
nav {
|
||||
height: 7vh;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="container container-md d-flex flex-column">
|
||||
<div>
|
||||
<h3 class="text-center">Welcome {{ userName }}!</h3>
|
||||
<h4 class="text-center">{{ userIdCode }}</h4>
|
||||
<p class="text-center">You've successfully logged into this site using your ID card.</p>
|
||||
<p class="text-center">Read more from <a
|
||||
href="https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC">here.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WelcomeComponent',
|
||||
props: {},
|
||||
methods: {
|
||||
getUserData: async function () {
|
||||
const requestOptions = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"sessionid": this.$store.getters.getSessionId
|
||||
}
|
||||
};
|
||||
fetch("/auth/userData", requestOptions)
|
||||
.then((response) => {
|
||||
let data = response.body;
|
||||
data.getReader().read().then((body) => {
|
||||
let authObject = JSON.parse(new TextDecoder().decode(body.value));
|
||||
this.$store.dispatch("setUserName", authObject.userData.name);
|
||||
let idCode = authObject.userData.idCode.substring(6)
|
||||
console.log(idCode)
|
||||
this.$store.dispatch("setUserIdCode", idCode);
|
||||
});
|
||||
console.log(data);
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return this.$store.getters.getAuthenticated;
|
||||
},
|
||||
userName() {
|
||||
return this.$store.getters.getUserName;
|
||||
},
|
||||
userIdCode() {
|
||||
return this.$store.getters.getUserIdCode;
|
||||
}
|
||||
}
|
||||
,
|
||||
mounted() {
|
||||
// Get user data.
|
||||
this.getUserData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
margin-top: 2vh;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
import {createApp} from 'vue';
|
||||
import App from './App.vue';
|
||||
import {createStore} from 'vuex';
|
||||
import BootstrapVue3 from 'bootstrap-vue-3';
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import { VueCookieNext } from 'vue-cookie-next'
|
||||
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
import 'bootstrap-vue-3/dist/bootstrap-vue-3.css'
|
||||
import router from "./router/index";
|
||||
|
||||
// Create a new store instance.
|
||||
const store = createStore({
|
||||
state() {
|
||||
return {
|
||||
authenticated: false,
|
||||
jSessionId: null,
|
||||
isAndroid: false,
|
||||
userName: null,
|
||||
userIdCode: null,
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setLoggedIn(state, isLoggedIn) {
|
||||
state.authenticated = isLoggedIn;
|
||||
},
|
||||
setSessionId(state, sessionId) {
|
||||
state.jSessionId = sessionId;
|
||||
},
|
||||
setIsAndroid(state, isAndroid) {
|
||||
state.isAndroid = isAndroid;
|
||||
},
|
||||
setUserName(state, userName) {
|
||||
state.userName = userName;
|
||||
},
|
||||
setIdCode(state, idCode) {
|
||||
state.userIdCode = idCode;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchSessionId(context, sessionId) {
|
||||
context.commit("setSessionId", sessionId);
|
||||
},
|
||||
setLoggedIn(context, isLoggedIn) {
|
||||
context.commit("setLoggedIn", isLoggedIn);
|
||||
},
|
||||
setIsAndroid(context, isAndroid) {
|
||||
context.commit("setIsAndroid", isAndroid);
|
||||
},
|
||||
setUserName(context, userName) {
|
||||
context.commit("setUserName", userName);
|
||||
},
|
||||
setUserIdCode(context, userIdCode) {
|
||||
context.commit("setIdCode", userIdCode);
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getAuthenticated: state => {
|
||||
return state.authenticated;
|
||||
},
|
||||
getSessionId: state => {
|
||||
return state.jSessionId;
|
||||
},
|
||||
getIsAndroid: state => {
|
||||
return state.isAndroid;
|
||||
},
|
||||
getUserName: state => {
|
||||
return state.userName;
|
||||
},
|
||||
getUserIdCode: state => {
|
||||
return state.userIdCode;
|
||||
},
|
||||
},
|
||||
plugins: [createPersistedState()],
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
// this route requires auth, check if logged in
|
||||
// if not, redirect to login page.
|
||||
if (!store.state.authenticated) {
|
||||
next({name: 'Login'})
|
||||
} else {
|
||||
next() // go to wherever I'm going
|
||||
}
|
||||
} else {
|
||||
next() // does not require auth, make sure to always call next()!
|
||||
}
|
||||
})
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(BootstrapVue3)
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(VueCookieNext);
|
||||
app.mount('#app')
|
||||
|
||||
VueCookieNext.config({ expire: '7d' })
|
|
@ -0,0 +1,31 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Login from '@/views/Login.vue'
|
||||
import Welcome from "@/views/Welcome";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Login',
|
||||
component: Login,
|
||||
meta: {
|
||||
requiresAuth: false
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/welcome',
|
||||
name: 'Welcome',
|
||||
component: Welcome,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes
|
||||
})
|
||||
|
||||
|
||||
|
||||
export default router
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<Navbar/>
|
||||
<LoginComponent/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoginComponent from '@/components/Login'
|
||||
import Navbar from "@/components/Navbar";
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: {
|
||||
LoginComponent,
|
||||
Navbar
|
||||
},
|
||||
methods: {
|
||||
csrf_token: function () {
|
||||
return "csrf-token";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<Navbar/>
|
||||
<WelcomeComponent/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WelcomeComponent from '@/components/Welcome'
|
||||
import Navbar from "@/components/Navbar";
|
||||
|
||||
export default {
|
||||
name: 'Welcome',
|
||||
components: {
|
||||
WelcomeComponent,
|
||||
Navbar
|
||||
},
|
||||
methods: {
|
||||
csrf_token: function () {
|
||||
return "csrf-token";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,20 @@
|
|||
// vue.config.js
|
||||
module.exports = {
|
||||
chainWebpack: config => {
|
||||
config.module.rule('vue').uses.delete('cache-loader');
|
||||
config.module.rule('js').uses.delete('cache-loader');
|
||||
config.module.rule('ts').uses.delete('cache-loader');
|
||||
config.module.rule('tsx').uses.delete('cache-loader');
|
||||
},
|
||||
// https.//cli.vuejs.org/config/#devserver-proxy
|
||||
devServer: {
|
||||
port: 3000,
|
||||
proxy: {
|
||||
"/auth": {
|
||||
target: "http://localhost:8080",
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -3,14 +3,13 @@ package com.tarkvaratehnika.demobackend.config
|
|||
class ApplicationConfiguration {
|
||||
|
||||
companion object {
|
||||
// URL for intent, do not edit.
|
||||
val AUTH_APP_LAUNCH_INTENT = "authapp://start/"
|
||||
// Endpoint for challenge.
|
||||
val CHALLENGE_ENDPOINT_URL = "/auth/challenge"
|
||||
// Endpoint for 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.
|
||||
val WEBSITE_ORIGIN_URL = "https://6bb0-85-253-195-252.ngrok.io"
|
||||
val WEBSITE_ORIGIN_URL = "https://6fa5-145-14-34-146.ngrok.io"
|
||||
|
||||
// Authentication request timeout in seconds.
|
||||
val AUTH_REQUEST_TIMEOUT_MS = 120000
|
||||
|
||||
val USER_ROLE = "USER"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package com.tarkvaratehnika.demobackend.config
|
||||
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
|
||||
@EnableWebSecurity
|
||||
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
|
||||
|
||||
override fun configure(auth: AuthenticationManagerBuilder?) {
|
||||
auth?.inMemoryAuthentication()?.withUser("justSomeUser")?.password("someBackdoorPasswordThisDoesntMatterItsADemo")
|
||||
?.roles("USER")
|
||||
}
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http.authorizeRequests()
|
||||
?.antMatchers("/welcome")?.hasRole("USER")
|
||||
?.and()
|
||||
?.sessionManagement()?.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
|
||||
?.and()?.csrf()?.disable()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package com.tarkvaratehnika.demobackend.config
|
||||
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
|
||||
@Configuration
|
||||
class SessionManager {
|
||||
|
||||
companion object {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(SessionManager::class.java)
|
||||
|
||||
private val sessionRegistry = HashMap<String, AuthDto>()
|
||||
|
||||
fun registerSession(sessionId: String) {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
LOG.debug("Session already exists.")
|
||||
} else {
|
||||
sessionRegistry[sessionId] = AuthDto(arrayListOf(), hashMapOf(), 200)
|
||||
}
|
||||
}
|
||||
|
||||
fun addRoleToSession(sessionId: String, role: GrantedAuthority): AuthDto {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
val session = sessionRegistry[sessionId]
|
||||
session!!.roles.add(role)
|
||||
return session
|
||||
} else {
|
||||
throw Exception("Session with sessionId: $sessionId does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
fun addErrorToSession(sessionId: String?, authDto: AuthDto) {
|
||||
// Errors are only sent by authentication app, so we can ignore sessionId being null.
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
sessionRegistry[sessionId]!!.errorCode = authDto.errorCode
|
||||
}
|
||||
}
|
||||
|
||||
fun getError(sessionId: String) : Int {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
if (sessionRegistry[sessionId]!!.errorCode != 200) {
|
||||
return sessionRegistry[sessionId]!!.errorCode
|
||||
}
|
||||
}
|
||||
return 200
|
||||
}
|
||||
|
||||
/**
|
||||
* Function adds role and userdata specified in authDto to the current session.
|
||||
*/
|
||||
fun addRoleToCurrentSession(authDto: AuthDto) {
|
||||
val securityContext = SecurityContextHolder.getContext()
|
||||
var sessionId = getSessionId()
|
||||
if (sessionId == null) {
|
||||
// No sessionId attached to the session, get one from credentials.
|
||||
sessionId = securityContext.authentication.credentials.toString()
|
||||
}
|
||||
val authentication = UsernamePasswordAuthenticationToken(authDto.userData, sessionId, authDto.roles)
|
||||
securityContext.authentication = authentication
|
||||
}
|
||||
|
||||
fun removeRoleFromCurrentSession(headers: Map<String, String>) {
|
||||
val securityContext = SecurityContextHolder.getContext()
|
||||
var sessionId = securityContext.authentication.credentials
|
||||
if (sessionId == null || sessionId == "") {
|
||||
// Fallback to when for some reason session object doesn't have sessionId attached.
|
||||
sessionId = getSessionId(headers)
|
||||
}
|
||||
sessionRegistry[sessionId]?.roles = arrayListOf()
|
||||
val authentication = UsernamePasswordAuthenticationToken(null, sessionId, listOf())
|
||||
securityContext.authentication = authentication
|
||||
}
|
||||
|
||||
fun addUserDataToSession(sessionId: String, name: String, idCode: String): AuthDto {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
val session = sessionRegistry[sessionId]
|
||||
session!!.userData["name"] = name
|
||||
session.userData["idCode"] = idCode
|
||||
return session
|
||||
} else {
|
||||
throw Exception("Session with sessionId: $sessionId does not exist.")
|
||||
}
|
||||
}
|
||||
|
||||
fun getSessionHasRole(sessionId: String, role: String): Boolean {
|
||||
if (sessionRegistry.containsKey(sessionId)) {
|
||||
if (sessionRegistry[sessionId]!!.roles.contains(SimpleGrantedAuthority(role))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getSessionAuth(sessionId: String?): AuthDto? {
|
||||
if (sessionId == null) {
|
||||
return null
|
||||
}
|
||||
return sessionRegistry[sessionId]
|
||||
}
|
||||
|
||||
fun getSessionId(headers: Map<String, String>): String? {
|
||||
return headers["sessionid"]
|
||||
}
|
||||
|
||||
fun getSessionId(): String? {
|
||||
val context = SecurityContextHolder.getContext()
|
||||
if (context.authentication != null && context.authentication.details != null) {
|
||||
return (context.authentication.details as WebAuthenticationDetails).sessionId
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
package com.tarkvaratehnika.demobackend.config
|
||||
|
||||
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||
import org.springframework.security.core.session.SessionRegistry
|
||||
import org.springframework.security.core.session.SessionRegistryImpl
|
||||
import org.webeid.security.exceptions.JceException
|
||||
import org.webeid.security.nonce.NonceGenerator
|
||||
import org.webeid.security.nonce.NonceGeneratorBuilder
|
||||
|
@ -23,33 +28,47 @@ import javax.cache.Cache
|
|||
import javax.cache.CacheManager
|
||||
import javax.cache.Caching
|
||||
import javax.cache.configuration.CompleteConfiguration
|
||||
import javax.cache.configuration.FactoryBuilder
|
||||
import javax.cache.configuration.FactoryBuilder.factoryOf
|
||||
import javax.cache.configuration.MutableConfiguration
|
||||
import javax.cache.expiry.CreatedExpiryPolicy
|
||||
import javax.cache.expiry.Duration
|
||||
|
||||
|
||||
@Configuration
|
||||
class ValidationConfiguration {
|
||||
|
||||
private val LOG: Logger = LoggerFactory.getLogger(ValidationConfiguration::class.java)
|
||||
|
||||
private val NONCE_TTL_MINUTES: Long = 5
|
||||
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 TRUSTSTORE_PASSWORD = "changeit"
|
||||
|
||||
|
||||
|
||||
init {
|
||||
LOG.warn("Creating new ValidationConfiguration.")
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun cacheManager(): CacheManager {
|
||||
return Caching.getCachingProvider(CaffeineCachingProvider::class.java.name).cacheManager
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
fun nonceCache(): Cache<String, ZonedDateTime>? {
|
||||
val cacheManager: CacheManager = cacheManager()
|
||||
var cache =
|
||||
cacheManager.getCache<String?, ZonedDateTime?>(CACHE_NAME)
|
||||
|
||||
if (cache == null) {
|
||||
LOG.warn("Creating new cache.")
|
||||
cache = createNonceCache(cacheManager)
|
||||
}
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
|
@ -65,7 +84,7 @@ class ValidationConfiguration {
|
|||
val cacheConfig: CompleteConfiguration<String, ZonedDateTime> = MutableConfiguration<String, ZonedDateTime>()
|
||||
.setTypes(String::class.java, ZonedDateTime::class.java)
|
||||
.setExpiryPolicyFactory(
|
||||
FactoryBuilder.factoryOf(
|
||||
factoryOf(
|
||||
CreatedExpiryPolicy(
|
||||
Duration(
|
||||
TimeUnit.MINUTES,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package com.tarkvaratehnika.demobackend.dto
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
data class AuthDto(var roles: ArrayList<GrantedAuthority>, var userData: HashMap<String, String>, var errorCode: Int)
|
|
@ -0,0 +1,5 @@
|
|||
package com.tarkvaratehnika.demobackend.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
class AuthTokenDTO (@JsonProperty("auth-token") val token : String, val error : Int?)
|
|
@ -1,6 +0,0 @@
|
|||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
class AuthTokenDTO (val token : String, val challenge : String) {
|
||||
}
|
|
@ -23,11 +23,12 @@
|
|||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.ValidationConfiguration
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthTokenDTO
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.authentication.AuthenticationServiceException
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.stereotype.Component
|
||||
import org.webeid.security.exceptions.TokenValidationException
|
||||
|
@ -37,28 +38,19 @@ import java.security.cert.X509Certificate
|
|||
|
||||
|
||||
@Component
|
||||
class AuthTokenDTOAuthenticationProvider {
|
||||
|
||||
companion object {
|
||||
const val ROLE_USER : String = "ROLE_USER"
|
||||
}
|
||||
private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
|
||||
object AuthTokenDTOAuthenticationProvider {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(AuthTokenDTOAuthenticationProvider::class.java)
|
||||
|
||||
val tokenValidator: AuthTokenValidator = ValidationConfiguration().validator()
|
||||
|
||||
@Throws(AuthenticationException::class)
|
||||
fun authenticate(auth : Authentication) : Authentication {
|
||||
fun authenticate(auth : Authentication, sessionId: String?) : AuthDto {
|
||||
val authentication = auth as PreAuthenticatedAuthenticationToken
|
||||
val token = (authentication.credentials as AuthTokenDTO).token
|
||||
val challenge = (authentication.credentials as AuthTokenDTO).challenge
|
||||
|
||||
val authorities = arrayListOf<GrantedAuthority>()
|
||||
authorities.add(USER_ROLE)
|
||||
|
||||
try {
|
||||
val userCertificate: X509Certificate = tokenValidator.validate(token)
|
||||
return WebEidAuthentication.fromCertificate(userCertificate, authorities, challenge)
|
||||
return WebEidAuthentication.fromCertificate(userCertificate, sessionId)
|
||||
} catch (e : TokenValidationException) {
|
||||
// Validation failed.
|
||||
throw AuthenticationServiceException("Token validation failed. " + e.message)
|
||||
|
|
|
@ -22,17 +22,29 @@
|
|||
|
||||
package com.tarkvaratehnika.demobackend.security
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration
|
||||
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration.Companion.USER_ROLE
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.webeid.security.certificate.CertificateData
|
||||
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import java.io.Serializable
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.*
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.log
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
class WebEidAuthentication(
|
||||
private val principalName: String,
|
||||
|
@ -40,55 +52,74 @@ class WebEidAuthentication(
|
|||
private val authorities: ArrayList<GrantedAuthority>
|
||||
) : PreAuthenticatedAuthenticationToken(principalName, idCode, authorities), Authentication {
|
||||
|
||||
|
||||
// Companion object is for static functions.
|
||||
companion object {
|
||||
|
||||
private val loggedInUsers = HashMap<String, Authentication>()
|
||||
private val LOG = LoggerFactory.getLogger(WebEidAuthentication::class.java)
|
||||
|
||||
fun fromCertificate(
|
||||
userCertificate: X509Certificate,
|
||||
authorities: ArrayList<GrantedAuthority>,
|
||||
challenge: String
|
||||
): Authentication {
|
||||
val principalName = getPrincipalNameFromCertificate(userCertificate)
|
||||
sessionId: String?,
|
||||
): AuthDto {
|
||||
// Get user data.
|
||||
val name = getPrincipalNameFromCertificate(userCertificate)
|
||||
val idCode = Objects.requireNonNull(CertificateData.getSubjectIdCode(userCertificate))
|
||||
val authentication = WebEidAuthentication(principalName, idCode, authorities)
|
||||
loggedInUsers[challenge] = authentication
|
||||
return authentication
|
||||
|
||||
// Fetch valid sessionId.
|
||||
var methodIndependentSessionId = sessionId
|
||||
if (methodIndependentSessionId == null) {
|
||||
methodIndependentSessionId = SessionManager.getSessionId()
|
||||
if (methodIndependentSessionId == null) {
|
||||
throw Exception("No session")
|
||||
}
|
||||
}
|
||||
|
||||
// Add role and user data to the AuthDto and return it.
|
||||
SessionManager.addRoleToSession(methodIndependentSessionId, SimpleGrantedAuthority(USER_ROLE))
|
||||
return SessionManager.addUserDataToSession(methodIndependentSessionId, name, idCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function for getting a Spring authentication object by supplying a challenge.
|
||||
* TODO: Figure out a more secure solution in the future.
|
||||
* Function for getting a Spring authentication object for this session.
|
||||
*/
|
||||
fun fromChallenge(challenge: String): Authentication? {
|
||||
// if (ThreadLocalRandom.current().nextFloat() < 0.5f) { // TODO: For testing.
|
||||
// return null
|
||||
// }
|
||||
val auth = loggedInUsers[challenge]
|
||||
if (auth != null) {
|
||||
// If challenge is valid, delete the authentication object from the map (so this can only be fetched once).
|
||||
loggedInUsers.remove(challenge)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
return auth
|
||||
}
|
||||
fun fromSession(headers: HashMap<String, String>): ResponseEntity<String> {
|
||||
val mapper = jacksonObjectMapper()
|
||||
|
||||
// // TODO: DELETE
|
||||
//
|
||||
// const val ROLE_USER: String = "ROLE_USER"
|
||||
// private val USER_ROLE: GrantedAuthority = SimpleGrantedAuthority(ROLE_USER)
|
||||
//
|
||||
// fun addAuth(challenge: String) {
|
||||
// val authorities = arrayListOf<GrantedAuthority>()
|
||||
// authorities.add(USER_ROLE)
|
||||
// val auth = WebEidAuthentication("Somename", "11111111111", authorities)
|
||||
// loggedInUsers[challenge] = auth
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // TODO: DELETE UNTIL
|
||||
val currentTime = Date()
|
||||
|
||||
// Get sessionId for current session.
|
||||
var sessionId = SessionManager.getSessionId()
|
||||
|
||||
if (sessionId == null) {
|
||||
sessionId = SessionManager.getSessionId(headers)
|
||||
if (sessionId == null) {
|
||||
return ResponseEntity.status(400).body(mapper.writeValueAsString(400))
|
||||
}
|
||||
}
|
||||
|
||||
while (currentTime.time + ApplicationConfiguration.AUTH_REQUEST_TIMEOUT_MS > Date().time) {
|
||||
Thread.sleep(1000)
|
||||
|
||||
// Check if an error has been submitted for this session.
|
||||
val error = SessionManager.getError(sessionId)
|
||||
if (error != 200) {
|
||||
return ResponseEntity.status(error).body(mapper.writeValueAsString(error))
|
||||
}
|
||||
|
||||
// Check if this session has received a role.
|
||||
if (SessionManager.getSessionHasRole(sessionId, USER_ROLE)) {
|
||||
// Get AuthDto
|
||||
val auth = SessionManager.getSessionAuth(sessionId)
|
||||
|
||||
// Set role and user data to current session.
|
||||
SessionManager.addRoleToCurrentSession(auth!!)
|
||||
return ResponseEntity.status(200).body(mapper.writeValueAsString(auth))
|
||||
}
|
||||
}
|
||||
|
||||
// In case of timeout return 408.
|
||||
return ResponseEntity.status(408).body(mapper.writeValueAsString(408))
|
||||
}
|
||||
|
||||
private fun getPrincipalNameFromCertificate(userCertificate: X509Certificate): String {
|
||||
return Objects.requireNonNull(CertificateData.getSubjectGivenName(userCertificate)) + " " +
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package com.tarkvaratehnika.demobackend.web
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.ApplicationConfiguration
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
@Controller
|
||||
class LoginController {
|
||||
|
||||
@GetMapping
|
||||
fun login(model : Model) : String {
|
||||
model.addAttribute("intentUrl", ApplicationConfiguration.AUTH_APP_LAUNCH_INTENT)
|
||||
model.addAttribute("challengeUrl", ApplicationConfiguration.CHALLENGE_ENDPOINT_URL)
|
||||
model.addAttribute("originUrl", ApplicationConfiguration.WEBSITE_ORIGIN_URL)
|
||||
model.addAttribute("loggedInUrl", "/signature")
|
||||
model.addAttribute("authenticationRequestUrl", ApplicationConfiguration.AUTHENTICATION_ENDPOINT_URL)
|
||||
return "index"
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package com.tarkvaratehnika.demobackend.web
|
||||
|
||||
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider.Companion.ROLE_USER
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
|
||||
@Controller
|
||||
class SignatureController {
|
||||
|
||||
|
||||
@PreAuthorize("hasAuthority('$ROLE_USER')")
|
||||
@GetMapping("signature")
|
||||
fun signature(model : Model) : String {
|
||||
// model.addAttribute("intentUrl", ApplicationConfiguration.AUTH_APP_LAUNCH_INTENT)
|
||||
// model.addAttribute("challengeUrl", ApplicationConfiguration.CHALLENGE_ENDPOINT_URL)
|
||||
return "signature"
|
||||
}
|
||||
}
|
|
@ -1,15 +1,17 @@
|
|||
package com.tarkvaratehnika.demobackend.web.rest
|
||||
|
||||
import com.tarkvaratehnika.demobackend.security.AuthTokenDTO
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthDto
|
||||
import com.tarkvaratehnika.demobackend.dto.AuthTokenDTO
|
||||
import com.tarkvaratehnika.demobackend.security.AuthTokenDTOAuthenticationProvider
|
||||
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
@RestController
|
||||
@RequestMapping("auth")
|
||||
|
@ -18,23 +20,39 @@ class AuthenticationController {
|
|||
private val LOG = LoggerFactory.getLogger(AuthenticationController::class.java)
|
||||
|
||||
|
||||
@PostMapping("authentication", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun authenticate(@RequestBody body : String): Authentication {
|
||||
val parts = body.split("\"")
|
||||
val authToken = AuthTokenDTO(parts[3], parts[7])
|
||||
@PostMapping("login", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun authenticate(@RequestHeader headers: Map<String, String>, @RequestBody authTokenDTO: AuthTokenDTO): AuthDto {
|
||||
val sessionId = SessionManager.getSessionId(headers)
|
||||
|
||||
// Check if an error occurred in the auth app.
|
||||
if (authTokenDTO.error != null && authTokenDTO.error != 200) {
|
||||
val auth = AuthDto(arrayListOf(), hashMapOf(), authTokenDTO.error)
|
||||
SessionManager.addErrorToSession(sessionId, auth)
|
||||
return auth
|
||||
}
|
||||
|
||||
// Create Spring Security Authentication object with supplied token as credentials.
|
||||
val auth = PreAuthenticatedAuthenticationToken(null, authToken)
|
||||
val auth = PreAuthenticatedAuthenticationToken(null, authTokenDTO)
|
||||
|
||||
// Return authentication object if success.
|
||||
return AuthTokenDTOAuthenticationProvider().authenticate(auth)
|
||||
return AuthTokenDTOAuthenticationProvider.authenticate(auth, sessionId)
|
||||
}
|
||||
|
||||
@GetMapping("authentication", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getAuthenticated(@RequestParam challenge: String) : Authentication? {
|
||||
val auth = WebEidAuthentication.fromChallenge(challenge)
|
||||
if (auth == null) {
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "Not allowed.")
|
||||
}
|
||||
return auth
|
||||
|
||||
@GetMapping("login", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getAuthenticated(@RequestHeader headers: HashMap<String, String>) : ResponseEntity<String> {
|
||||
return WebEidAuthentication.fromSession(headers)
|
||||
}
|
||||
|
||||
@GetMapping("userData", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
fun getUserData(@RequestHeader headers: Map<String, String>) : AuthDto? {
|
||||
return SessionManager.getSessionAuth(SessionManager.getSessionId(headers))
|
||||
}
|
||||
|
||||
@PostMapping("logout")
|
||||
fun logOut(@RequestHeader headers: Map<String, String>) : HttpStatus? {
|
||||
SessionManager.removeRoleFromCurrentSession(headers)
|
||||
return HttpStatus.ACCEPTED
|
||||
|
||||
}
|
||||
}
|
|
@ -22,22 +22,55 @@
|
|||
|
||||
package com.tarkvaratehnika.demobackend.web.rest
|
||||
|
||||
import com.tarkvaratehnika.demobackend.config.SessionManager
|
||||
import com.tarkvaratehnika.demobackend.dto.ChallengeDto
|
||||
import com.tarkvaratehnika.demobackend.security.WebEidAuthentication
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestHeader
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.client.HttpClientErrorException
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import org.webeid.security.nonce.NonceGenerator
|
||||
|
||||
|
||||
@RestController
|
||||
@RequestMapping("auth")
|
||||
class ChallengeController (val nonceGenerator: NonceGenerator) {
|
||||
|
||||
private val LOG = LoggerFactory.getLogger(ChallengeController::class.java)
|
||||
|
||||
@GetMapping("challenge")
|
||||
fun challenge(): ChallengeDto {
|
||||
fun challenge(@RequestHeader headers: Map<String, String>): ChallengeDto {
|
||||
|
||||
val sessionId = SessionManager.getSessionId(headers)
|
||||
|
||||
if (sessionId == null) {
|
||||
LOG.warn("SESSION ID MISSING FOR CHALLENGE")
|
||||
throw ResponseStatusException(HttpStatus.FORBIDDEN, "SessionId missing.")
|
||||
}
|
||||
|
||||
SessionManager.registerSession(sessionId)
|
||||
|
||||
// val context = SecurityContextHolder.getContext()
|
||||
// val authorities = arrayListOf<GrantedAuthority>()
|
||||
// authorities.add(SimpleGrantedAuthority("USER"))
|
||||
// authorities.add(SimpleGrantedAuthority("ROLE_USER"))
|
||||
// val auth = context.authentication
|
||||
//
|
||||
// val newAuth: Authentication =
|
||||
// UsernamePasswordAuthenticationToken(auth.principal, auth.credentials, authorities)
|
||||
// SecurityContextHolder.getContext().authentication = newAuth;
|
||||
|
||||
// SessionManager.createSession(SessionManager.getSessionId(headers))
|
||||
val challengeDto = ChallengeDto(nonceGenerator.generateAndStoreNonce())
|
||||
// WebEidAuthentication.addAuth(challengeDto.nonce) // For testing.
|
||||
LOG.warn(challengeDto.nonce)
|
||||
return challengeDto
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
server.servlet.session.cookie.http-only=false
|
||||
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
.cont {
|
||||
display: grid;
|
||||
width: 80%;
|
||||
padding-top: 10%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 10%;
|
||||
}
|
||||
|
||||
#loginButton {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.cont > * {
|
||||
margin: 1rem;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
window.onload = () => {
|
||||
// Add event listener for login button.
|
||||
let loginButton = document.getElementById("loginButton");
|
||||
|
||||
if (loginButton != null) {
|
||||
loginButton.addEventListener("click", () => {
|
||||
let action = loginButton.getAttribute("data-action");
|
||||
loginButton.setAttribute("disabled", "true");
|
||||
loginButton.textContent = "Logging in";
|
||||
launchAuthApp(action);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
const POLLING_INTERVAL = 1000;
|
||||
const POLLING_RETRIES = 120;
|
||||
|
||||
function launchAuthApp(action) {
|
||||
if (!isAndroid()) {
|
||||
alert("Functionality only available for Android devices.")
|
||||
return null
|
||||
}
|
||||
|
||||
// Fetch challenge.
|
||||
httpGetAsync(originUrl + challengeUrl, (body) => {
|
||||
let data = JSON.parse(body);
|
||||
let challenge = data.nonce;
|
||||
let intent = createParametrizedIntentUrl(challenge, action); // TODO: Error handling.
|
||||
console.log(intent);
|
||||
window.location.href = intent;
|
||||
pollForAuth(POLLING_INTERVAL, challenge);
|
||||
})
|
||||
}
|
||||
|
||||
function pollForAuth(timeout, challenge) {
|
||||
console.log("Polling for auth");
|
||||
let requestUrl = originUrl + authenticationRequestUrl + "?challenge=" + challenge;
|
||||
|
||||
let counter = 0;
|
||||
let timer = setInterval(() => {
|
||||
// Fetch authentication object.
|
||||
httpGetAsync(requestUrl, (body) => {
|
||||
console.log(body);
|
||||
// If this is a successful request, stop the polling.
|
||||
clearInterval(timer);
|
||||
window.location.href = originUrl + loggedInUrl;
|
||||
});
|
||||
counter++;
|
||||
if (counter > POLLING_RETRIES) {
|
||||
clearInterval(timer); // Stop polling after some time.
|
||||
let loginErrorAlert = document.getElementById("loginErrorAlert");
|
||||
loginErrorAlert.classList.remove("d-none")
|
||||
}
|
||||
}, timeout)
|
||||
|
||||
}
|
||||
|
||||
function createParametrizedIntentUrl(challenge, action) {
|
||||
if (action == null) {
|
||||
console.error("There has to be an action for intent.")
|
||||
}
|
||||
else if (challenge == null) {
|
||||
console.error("Challenge missing, can't authenticate without it.")
|
||||
} else {
|
||||
return intentUrl + "?" + "action=" + action + "&challenge=" + challenge + "&authUrl=" + originUrl + authenticationRequestUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function isAndroid() {
|
||||
// Check if using Android device.
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
return ua.indexOf("android") > -1;
|
||||
}
|
||||
|
||||
function httpGetAsync(theUrl, callback) {
|
||||
console.log("Sending a request.")
|
||||
const xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
|
||||
callback(xmlHttp.responseText);
|
||||
}
|
||||
}
|
||||
xmlHttp.open("GET", theUrl, true); // true for asynchronous
|
||||
xmlHttp.send(null);
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<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 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 type="text/javascript" th:src="@{/js/index.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/main.js}"></script>
|
||||
<script th:inline="javascript">const originUrl = [[${originUrl}]];
|
||||
const intentUrl = [[${intentUrl}]];
|
||||
const challengeUrl = [[${challengeUrl}]];
|
||||
const loggedInUrl = [[${loggedInUrl}]];
|
||||
const authenticationRequestUrl = [[${authenticationRequestUrl}]]</script> <!-- Pass some values to JS -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Auth demo web application</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="cont">
|
||||
<h4>Welcome to Estonian ID card mobile authentication demo website. When using a mobile phone, you can log in to the
|
||||
website using your ID card by using the button below.</h4>
|
||||
<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>
|
||||
<button type="button" class="btn btn-secondary" id="loginButton" data-action="auth">Log in</button>
|
||||
<div class="alert alert-danger d-none" role="alert" id="loginErrorAlert">
|
||||
Login failed. Refresh the page to try again.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,42 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Login</title>
|
||||
<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 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 type="text/javascript" th:src="@{/js/signature.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/main.js}"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<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">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#">Log out<span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<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>
|
||||
<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">
|
||||
<input type="file" class="custom-file-input" id="inputGroupFile01">
|
||||
<label class="custom-file-label" for="inputGroupFile01">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" id="signFile" data-action="auth">Sign</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue