Compare commits
91 Commits
iteration2
...
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 | |
TanelOrumaa | 9b0cb1a22d | |
TanelOrumaa | 1d665f02bf | |
TanelOrumaa | 82b1538867 | |
Henrik Lepson | c51f0e4277 | |
Henrik Lepson | aaa8d8f13c | |
Henrik Lepson | d60cecdd26 | |
Henrik Lepson | f0c7ab96bb | |
Henrik Lepson | 1b79eba4a4 | |
TanelOrumaa | c46c3082b7 | |
TanelOrumaa | e4a9a4da1b | |
TanelOrumaa | f9cd30922e | |
Henrik Lepson | 4252e3e637 | |
Henrik Lepson | 64357ca1d3 | |
Henrik Lepson | eca3f92468 | |
TanelOrumaa | 44469b8533 | |
Henrik Lepson | cc3a3c10d6 | |
Henrik Lepson | 08430c897c | |
Henrik Lepson | ebc541af08 | |
Henrik Lepson | a9336b790f | |
Henrik Lepson | 364fc7c45b | |
Henrik Lepson | 71db5cc9e6 | |
Lemmo Lavonen | 62888a7299 | |
Henrik Lepson | 3e5f02f842 | |
Henrik Lepson | bd686739fc | |
Henrik Lepson | 2678fd3c42 | |
Henrik Lepson | 1dd11b21fc | |
Henrik Lepson | 8637a4182a | |
Henrik Lepson | 141dfb18db | |
Henrik Lepson | 48817f9057 | |
Lemmo Lavonen | 850ab8fc66 | |
Lemmo Lavonen | 824d33d635 | |
Lemmo Lavonen | ef7015abb8 | |
Lemmo Lavonen | 29c7ecfa12 | |
Lemmo Lavonen | 9c48cc9c1a | |
Lemmo Lavonen | 25c01803cb | |
Lemmo Lavonen | 1c8a606376 | |
Lemmo Lavonen | d2ad8920a1 | |
TanelOrumaa | 96595d924b | |
TanelOrumaa | 4ae5d3c0c4 | |
TanelOrumaa | 490a04bb44 |
|
@ -59,4 +59,13 @@ dependencies {
|
|||
//For cryptography
|
||||
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.69'
|
||||
|
||||
//SecureDataStoring
|
||||
implementation("androidx.security:security-crypto:1.0.0")
|
||||
|
||||
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
|
||||
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.60',
|
||||
'io.jsonwebtoken:jjwt-gson:0.11.2'
|
||||
|
||||
implementation 'com.koushikdutta.ion:ion:3.1.0'
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
package="com.tarkvaraprojekt.mobileauthapp">
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@ -19,6 +20,13 @@
|
|||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<!-- for launching the app with deep links -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="authapp" android:host="start" android:path="/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ package com.tarkvaraprojekt.mobileauthapp
|
|||
|
||||
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
|
||||
|
@ -10,14 +12,18 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
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.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
||||
import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
import java.lang.Exception
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Fragment that asks the user to detect the ID card with mobile NFC chip.
|
||||
|
@ -28,7 +34,12 @@ class AuthFragment : Fragment() {
|
|||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentAuthBinding? = null
|
||||
private val paramsModel: ParametersViewModel by activityViewModels()
|
||||
|
||||
private var _binding: FragmentAuthBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val args: CanFragmentArgs by navArgs()
|
||||
|
||||
private lateinit var timer: CountDownTimer
|
||||
|
||||
|
@ -39,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?) {
|
||||
|
@ -49,49 +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)
|
||||
val response = comms.readPersonalData(byteArrayOf(1, 2, 6))
|
||||
viewModel.setUserFirstName(response[1])
|
||||
viewModel.setUserLastName(response[0])
|
||||
viewModel.setUserIdentificationNumber(response[2])
|
||||
requireActivity().runOnUiThread{
|
||||
binding!!.timeCounter.text = getString(R.string.data_read)
|
||||
val jws = Authenticator(comms).authenticate(
|
||||
paramsModel.challenge,
|
||||
paramsModel.origin,
|
||||
viewModel.userPin
|
||||
)
|
||||
paramsModel.setToken(jws)
|
||||
requireActivity().runOnUiThread {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
@ -99,19 +153,8 @@ class AuthFragment : Fragment() {
|
|||
}, NfcAdapter.FLAG_READER_NFC_A, null)
|
||||
}
|
||||
|
||||
private fun goToNextFragment() {
|
||||
timer.cancel()
|
||||
findNavController().navigate(R.id.action_authFragment_to_userFragment)
|
||||
}
|
||||
|
||||
private fun goToTheStart() {
|
||||
viewModel.clearUserInfo()
|
||||
timer.cancel()
|
||||
findNavController().navigate(R.id.action_authFragment_to_homeFragment)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -1,61 +1,134 @@
|
|||
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 six digit CAN
|
||||
* Fragment that deals with asking the user for a six digit CAN. If the CAN is already saved
|
||||
* then the fragment is skipped immediately and if the CAN is not saved then the user
|
||||
* is asked whether it should be saved for the future or not before continuing.
|
||||
*/
|
||||
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.
|
||||
private val args: CanFragmentArgs by navArgs()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
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)
|
||||
|
||||
binding!!.nextButton.setOnClickListener { goToNextFragment() }
|
||||
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||
checkIfSkip()
|
||||
binding.canTextField.editText?.addTextChangedListener {
|
||||
checkEnteredCan()
|
||||
}
|
||||
binding.buttonCancel.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
private fun goToNextFragment() {
|
||||
val enteredCan = binding!!.canEditText.editText?.text.toString()
|
||||
if (enteredCan.length != 6) {
|
||||
Toast.makeText(requireContext(), getString(R.string.length_can), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
} else {
|
||||
viewModel.setUserCan(
|
||||
binding!!.canEditText.editText?.text.toString()
|
||||
)
|
||||
findNavController().navigate(R.id.action_canFragment_to_authFragment)
|
||||
/**
|
||||
* Checks if the current fragment can be skipped or not.
|
||||
* If the user has CAN saved on the device there is no need to ask it again.
|
||||
*/
|
||||
private fun checkIfSkip() {
|
||||
if (viewModel.userCan.length == 6) {
|
||||
goToTheNextFragment()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes user to the next fragment, which is PinFragment.
|
||||
*/
|
||||
private fun goToTheNextFragment() {
|
||||
val action = CanFragmentDirections.actionCanFragmentToPinFragment(auth = args.auth, mobile = args.mobile)
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
viewModel.clearUserInfo()
|
||||
findNavController().navigate(R.id.action_canFragment_to_homeFragment)
|
||||
if (args.saving) {
|
||||
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,42 +1,371 @@
|
|||
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
|
||||
* is launched by another application or a website then this Fragment will be skipped.
|
||||
* This fragment uses the fields from the MainActivity by casting the activity to MainActivity.
|
||||
* This might not be the best practice, but the application uses a single activity design so it should
|
||||
* always work.
|
||||
*/
|
||||
class HomeFragment : Fragment() {
|
||||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var binding: FragmentHomeBinding? = null
|
||||
private val intentParams: ParametersViewModel by activityViewModels()
|
||||
|
||||
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)
|
||||
return binding!!.root
|
||||
_binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||
// Making settings menu active again
|
||||
(activity as MainActivity).menuAvailable = true
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding!!.beginButton.setOnClickListener { goToNextFragment() }
|
||||
initialChecks()
|
||||
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) {
|
||||
startAuthentication(mobile)
|
||||
} else {
|
||||
receiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(p0: Context?, p1: Intent?) {
|
||||
updateAction(canSaved)
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
|
||||
requireActivity().registerReceiver(receiver, filter)
|
||||
updateAction(canSaved)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToNextFragment() {
|
||||
findNavController().navigate(R.id.action_homeFragment_to_pinFragment)
|
||||
/**
|
||||
* Starts the process of interacting with the ID card by sending user to the CAN fragment.
|
||||
*/
|
||||
private fun goToTheNextFragment(mobile: Boolean = false) {
|
||||
(activity as MainActivity).menuAvailable = false
|
||||
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
|
||||
* wishes to make changes to the saved CAN or PIN 1.
|
||||
*/
|
||||
private fun displayStates() {
|
||||
canState()
|
||||
pinState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method where all the initial checks that should be completed before any user input is accepted should be conducted.
|
||||
*/
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the error message and allows the user to try again
|
||||
*/
|
||||
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 {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,24 +1,87 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.nfc.NfcAdapter
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
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
|
||||
|
||||
|
||||
/**
|
||||
* The only activity of the application (single activity design).
|
||||
*/
|
||||
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)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
||||
navigationController = navHostFragment.navController
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.menu_settings_option -> {
|
||||
if (menuAvailable) {
|
||||
navigationController.navigate(R.id.action_homeFragment_to_settingsFragment)
|
||||
menuAvailable = false
|
||||
inMenu = true
|
||||
true
|
||||
} else {
|
||||
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 { _, _ ->
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import java.security.MessageDigest;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -30,47 +31,43 @@ import javax.crypto.spec.IvParameterSpec;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class Comms {
|
||||
private static final byte[] master = { // select Main AID
|
||||
0, -92, 4, 12, 16, -96, 0, 0, 0, 119, 1, 8, 0, 7, 0, 0, -2, 0, 0, 1, 0
|
||||
};
|
||||
|
||||
private static final byte[] MSESetAT = { // manage security environment: set authentication template
|
||||
0, 34, -63, -92, 15, -128, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -125, 1, 2, 0
|
||||
};
|
||||
private static final byte[] selectMaster = Hex.decode("00a4040c10a000000077010800070000fe00000100");
|
||||
|
||||
private static final byte[] GAGetNonce = { // general authenticate: get nonce
|
||||
16, -122, 0, 0, 2, 124, 0, 0
|
||||
};
|
||||
private static final byte[] MSESetAT = Hex.decode("0022c1a40f800a04007f0007020204020483010200");
|
||||
|
||||
private static final byte[] GAMapNonceIncomplete = {
|
||||
16, -122, 0, 0, 69, 124, 67, -127, 65
|
||||
};
|
||||
private static final byte[] GAGetNonce = Hex.decode("10860000027c0000");
|
||||
|
||||
private static final byte[] GAKeyAgreementIncomplete = {
|
||||
16, -122, 0, 0, 69, 124, 67, -125, 65
|
||||
};
|
||||
private static final byte[] GAMapNonceIncomplete = Hex.decode("10860000457c438141");
|
||||
|
||||
private static final byte[] GAMutualAuthenticationIncomplete = {
|
||||
0, -122, 0, 0, 12, 124, 10, -123, 8
|
||||
};
|
||||
private static final byte[] GAKeyAgreementIncomplete = Hex.decode("10860000457c438341");
|
||||
|
||||
private static final byte[] dataForMACIncomplete = {
|
||||
127, 73, 79, 6, 10, 4, 0, 127, 0, 7, 2, 2, 4, 2, 4, -122, 65
|
||||
};
|
||||
private static final byte[] GAMutualAuthenticationIncomplete = Hex.decode("008600000c7c0a8508");
|
||||
|
||||
private static final byte[] masterSec = {
|
||||
12, -92, 4, 12, 45, -121, 33, 1
|
||||
};
|
||||
private static final byte[] dataForMACIncomplete = Hex.decode("7f494f060a04007f000702020402048641");
|
||||
|
||||
private static final byte[] personal = { // select personal data DF
|
||||
12, -92, 1, 12, 29, -121, 17, 1
|
||||
};
|
||||
private static final byte[] selectFile = Hex.decode("0ca4010c1d871101");
|
||||
|
||||
private static final byte[] read = { // read binary
|
||||
12, -80, 0, 0, 13, -105, 1, 0
|
||||
};
|
||||
private static final byte[] readFile = Hex.decode("0cb000000d970100");
|
||||
|
||||
private IsoDep idCard;
|
||||
private static final byte[] verifyPIN1 = Hex.decode("0c2000011d871101");
|
||||
|
||||
private static final byte[] verifyPIN2 = Hex.decode("0c2000851d871101");
|
||||
|
||||
private static final byte[] MSESetEnv = Hex.decode("0c2241A41d871101");
|
||||
|
||||
private static final byte[] Env = Hex.decode("8004FF200800840181");
|
||||
|
||||
private static final byte[] InternalAuthenticate = Hex.decode("0c8800001d871101");
|
||||
|
||||
private static final byte[] IASECCFID = {0x3f, 0x00};
|
||||
private static final byte[] personalDF = {0x50, 0x00};
|
||||
private static final byte[] AWP = {(byte) 0xad, (byte) 0xf1};
|
||||
private static final byte[] QSCD = {(byte) 0xad, (byte) 0xf2};
|
||||
private static final byte[] authCert = {0x34, 0x01};
|
||||
private static final byte[] signCert = {0x34, 0x1f};
|
||||
|
||||
private final IsoDep idCard;
|
||||
private final byte[] keyEnc;
|
||||
private final byte[] keyMAC;
|
||||
private byte ssc; // Send sequence counter.
|
||||
|
@ -84,21 +81,12 @@ public class Comms {
|
|||
public Comms(IsoDep idCard, String CAN) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||
|
||||
idCard.connect();
|
||||
|
||||
this.idCard = idCard;
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
byte[][] keys = PACE(CAN);
|
||||
Log.i("Pace duration", String.valueOf(System.currentTimeMillis() - start));
|
||||
|
||||
byte[][] keys = PACE(CAN.getBytes(StandardCharsets.UTF_8));
|
||||
keyEnc = keys[0];
|
||||
keyMAC = keys[1];
|
||||
}
|
||||
|
||||
public byte[] getAuthenticationCertificate() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the message authentication code
|
||||
*
|
||||
|
@ -151,40 +139,51 @@ public class Comms {
|
|||
* @param CAN the card access number provided by the user
|
||||
* @return the decrypted nonce
|
||||
*/
|
||||
private byte[] decryptNonce(byte[] encryptedNonce, String CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||
byte[] decryptionKey = createKey(CAN.getBytes(StandardCharsets.UTF_8), (byte) 3);
|
||||
private byte[] decryptNonce(byte[] encryptedNonce, byte[] CAN) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
|
||||
byte[] decryptionKey = createKey(CAN, (byte) 3);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptionKey, "AES"), new IvParameterSpec(new byte[16]));
|
||||
return cipher.doFinal(encryptedNonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Communicates with the card and logs the response
|
||||
*
|
||||
* @param APDU The command
|
||||
* @param log Information for logging
|
||||
* @return The response
|
||||
*/
|
||||
private byte[] getResponse(byte[] APDU, String log) throws IOException {
|
||||
byte[] response = idCard.transceive(APDU);
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
throw new RuntimeException(String.format("%s failed.", log));
|
||||
}
|
||||
Log.i(log, Hex.toHexString(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to use the PACE protocol to create a secure channel with an Estonian ID-card
|
||||
*
|
||||
* @param CAN the card access number
|
||||
*/
|
||||
private byte[][] PACE(String CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
|
||||
private byte[][] PACE(byte[] CAN) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
|
||||
|
||||
// select the ECC applet on the chip
|
||||
byte[] response = idCard.transceive(master);
|
||||
Log.i("Select applet", Hex.toHexString(response));
|
||||
// select the IAS-ECC application on the chip
|
||||
getResponse(selectMaster, "Select the master application");
|
||||
|
||||
// initiate PACE
|
||||
response = idCard.transceive(MSESetAT);
|
||||
Log.i("Authentication template", Hex.toHexString(response));
|
||||
getResponse(MSESetAT, "Set authentication template");
|
||||
|
||||
// get nonce
|
||||
response = idCard.transceive(GAGetNonce);
|
||||
Log.i("Get nonce", Hex.toHexString(response));
|
||||
byte[] response = getResponse(GAGetNonce, "Get nonce");
|
||||
byte[] decryptedNonce = decryptNonce(Arrays.copyOfRange(response, 4, response.length - 2), CAN);
|
||||
|
||||
// generate an EC keypair and exchange public keys with the chip
|
||||
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256r1");
|
||||
BigInteger privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE); // should be in [1, spec.getN()-1], but this is good enough for this application
|
||||
ECPoint publicKey = spec.getG().multiply(privateKey).normalize();
|
||||
byte[] APDU = createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i("Map nonce", Hex.toHexString(response));
|
||||
response = getResponse(createAPDU(GAMapNonceIncomplete, publicKey.getEncoded(false), 66), "Map nonce");
|
||||
ECPoint cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69));
|
||||
|
||||
// calculate the new base point, use it to generate a new keypair, and exchange public keys
|
||||
|
@ -192,35 +191,41 @@ public class Comms {
|
|||
ECPoint mappedECBasePoint = spec.getG().multiply(new BigInteger(1, decryptedNonce)).add(sharedSecret).normalize();
|
||||
privateKey = new BigInteger(255, new SecureRandom()).add(BigInteger.ONE);
|
||||
publicKey = mappedECBasePoint.multiply(privateKey).normalize();
|
||||
APDU = createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i("Key agreement", Hex.toHexString(response));
|
||||
response = getResponse(createAPDU(GAKeyAgreementIncomplete, publicKey.getEncoded(false), 66), "Key agreement");
|
||||
cardPublicKey = spec.getCurve().decodePoint(Arrays.copyOfRange(response, 4, 69));
|
||||
|
||||
// generate the session keys and exchange MACs to verify them
|
||||
sharedSecret = cardPublicKey.multiply(privateKey).normalize();
|
||||
byte[] encodedSecret = sharedSecret.getAffineXCoord().getEncoded();
|
||||
byte[] keyEnc = createKey(encodedSecret, (byte) 1);
|
||||
byte[] keyMAC = createKey(encodedSecret, (byte) 2);
|
||||
APDU = createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65);
|
||||
byte[] MAC = getMAC(APDU, keyMAC);
|
||||
APDU = createAPDU(GAMutualAuthenticationIncomplete, MAC, 9);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i("Mutual authentication", Hex.toHexString(response));
|
||||
byte[] secret = cardPublicKey.multiply(privateKey).normalize().getAffineXCoord().getEncoded();
|
||||
byte[] keyEnc = createKey(secret, (byte) 1);
|
||||
byte[] keyMAC = createKey(secret, (byte) 2);
|
||||
byte[] MAC = getMAC(createAPDU(dataForMACIncomplete, cardPublicKey.getEncoded(false), 65), keyMAC);
|
||||
response = getResponse(createAPDU(GAMutualAuthenticationIncomplete, MAC, 9), "Mutual authentication");
|
||||
|
||||
// if the chip-side verification fails, crash and burn
|
||||
if (response.length == 2) throw new RuntimeException("Invalid CAN.");
|
||||
|
||||
// otherwise verify chip's MAC and return session keys
|
||||
APDU = createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65);
|
||||
MAC = getMAC(APDU, keyMAC);
|
||||
// verify chip's MAC and return session keys
|
||||
MAC = getMAC(createAPDU(dataForMACIncomplete, publicKey.getEncoded(false), 65), keyMAC);
|
||||
if (!Hex.toHexString(response, 4, 8).equals(Hex.toHexString(MAC))) {
|
||||
throw new RuntimeException("Could not verify chip's MAC."); // Should never happen.
|
||||
throw new RuntimeException("Could not verify chip's MAC."); // *Should* never happen.
|
||||
}
|
||||
return new byte[][]{keyEnc, keyMAC};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a file and reads its contents
|
||||
*
|
||||
* @param FID file identifier of the required file
|
||||
* @param info string for logging
|
||||
* @return decrypted file contents
|
||||
*/
|
||||
private byte[] readFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
selectFile(FID, info);
|
||||
byte[] response = getResponse(new byte[0], readFile, "Read binary");
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
throw new RuntimeException(String.format("Could not read %s", info));
|
||||
}
|
||||
return encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts the APDU data
|
||||
*
|
||||
|
@ -255,74 +260,182 @@ public class Comms {
|
|||
byte[] macData = new byte[data.length > 0 ? 48 + length : 48];
|
||||
macData[15] = ssc; // first block contains the ssc
|
||||
System.arraycopy(incomplete, 0, macData, 16, 4); // second block has the command
|
||||
macData[20] = -128; // elements are terminated by 0x80 and zero-padded to the next block
|
||||
macData[20] = (byte) 0x80; // elements are terminated by 0x80 and zero-padded to the next block
|
||||
System.arraycopy(incomplete, 5, macData, 32, 3); // third block contains appropriately encapsulated data/Le
|
||||
if (data.length > 0) { // if the APDU has data, add padding and encrypt it
|
||||
byte[] paddedData = Arrays.copyOf(data, length);
|
||||
paddedData[data.length] = -128;
|
||||
paddedData[data.length] = (byte) 0x80;
|
||||
encryptedData = encryptDecryptData(paddedData, Cipher.ENCRYPT_MODE);
|
||||
System.arraycopy(encryptedData, 0, macData, 35, encryptedData.length);
|
||||
}
|
||||
macData[35 + encryptedData.length] = -128;
|
||||
macData[35 + encryptedData.length] = (byte) 0x80;
|
||||
byte[] MAC = getMAC(macData, keyMAC);
|
||||
|
||||
// construct the APDU using the encrypted data and the MAC
|
||||
byte[] APDU = new byte[incomplete.length + encryptedData.length + MAC.length + 3];
|
||||
System.arraycopy(incomplete, 0, APDU, 0, incomplete.length);
|
||||
byte[] APDU = Arrays.copyOf(incomplete, incomplete.length + encryptedData.length + MAC.length + 3);
|
||||
if (encryptedData.length > 0) {
|
||||
System.arraycopy(encryptedData, 0, APDU, incomplete.length, encryptedData.length);
|
||||
}
|
||||
System.arraycopy(new byte[]{-114, 8}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E
|
||||
System.arraycopy(new byte[]{(byte) 0x8E, 0x08}, 0, APDU, incomplete.length + encryptedData.length, 2); // MAC is encapsulated using the tag 0x8E
|
||||
System.arraycopy(MAC, 0, APDU, incomplete.length + encryptedData.length + 2, MAC.length);
|
||||
|
||||
ssc++;
|
||||
return APDU;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the personal data dedicated file
|
||||
*
|
||||
* @param FID the last bytes of file identifiers being requested
|
||||
* @return array containing the data strings
|
||||
* Selects a FILE by its identifier
|
||||
*
|
||||
*/
|
||||
public String[] readPersonalData(byte[] FID) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
private void selectFile(byte[] FID, String info) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
byte[] response = getResponse(FID, selectFile, String.format("Select %s", info));
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
throw new RuntimeException(String.format("Could not select %s", info));
|
||||
}
|
||||
}
|
||||
|
||||
String[] personalData = new String[FID.length];
|
||||
byte[] data;
|
||||
byte[] APDU;
|
||||
byte[] response;
|
||||
/**
|
||||
* Gets the contents of the personal data dedicated file
|
||||
*
|
||||
* @param lastBytes the last bytes of the personal data file identifiers (0 < x < 16)
|
||||
* @return array containing the corresponding data strings
|
||||
*/
|
||||
public String[] readPersonalData(byte[] lastBytes) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
|
||||
String[] personalData = new String[lastBytes.length];
|
||||
int stringIndex = 0;
|
||||
|
||||
// select the master application
|
||||
selectFile(IASECCFID, "the master application");
|
||||
|
||||
// select the personal data dedicated file
|
||||
data = new byte[]{80, 0}; // personal data DF FID
|
||||
APDU = createSecureAPDU(data, personal);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i("Select personal data DF", Hex.toHexString(response));
|
||||
selectFile(personalDF, "the personal data DF");
|
||||
|
||||
// select and read the first 8 elementary files in the DF
|
||||
for (int i = 0; i < FID.length; i++) {
|
||||
byte[] FID = Arrays.copyOf(personalDF, personalDF.length);
|
||||
// select and read the personal data elementary files
|
||||
for (byte index : lastBytes) {
|
||||
|
||||
byte index = FID[i];
|
||||
if (index > 15 || index < 1) throw new RuntimeException("Invalid personal data FID.");
|
||||
|
||||
data[1] = index;
|
||||
APDU = createSecureAPDU(data, personal);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i(String.format("Select EF 500%d", index), Hex.toHexString(response));
|
||||
|
||||
APDU = createSecureAPDU(new byte[0], read);
|
||||
response = idCard.transceive(APDU);
|
||||
Log.i(String.format("Read binary EF 500%d", index), Hex.toHexString(response));
|
||||
FID[1] = index;
|
||||
|
||||
// store the decrypted datum
|
||||
byte[] raw = encryptDecryptData(Arrays.copyOfRange(response, 3, 19), Cipher.DECRYPT_MODE);
|
||||
int indexOfTerminator = Hex.toHexString(raw).lastIndexOf("80") / 2;
|
||||
personalData[i] = new String(Arrays.copyOfRange(raw, 0, indexOfTerminator));
|
||||
byte[] response = readFile(FID, "a personal data EF");
|
||||
int indexOfTerminator = Hex.toHexString(response).lastIndexOf("80") / 2;
|
||||
personalData[stringIndex++] = new String(Arrays.copyOfRange(response, 0, indexOfTerminator));
|
||||
|
||||
}
|
||||
|
||||
return personalData;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to verify the selected PIN
|
||||
*
|
||||
* @param PIN user-provided PIN
|
||||
* @param oneOrTwo true for PIN1, false for PIN2
|
||||
*/
|
||||
private void verifyPIN(byte[] PIN, boolean oneOrTwo) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException {
|
||||
|
||||
selectFile(IASECCFID, "the master application");
|
||||
if (!oneOrTwo) {
|
||||
selectFile(QSCD, "the application");
|
||||
}
|
||||
|
||||
// pad the PIN and use the chip for verification
|
||||
byte[] paddedPIN = Hex.decode("ffffffffffffffffffffffff");
|
||||
System.arraycopy(PIN, 0, paddedPIN, 0, PIN.length);
|
||||
byte[] response = getResponse(paddedPIN, oneOrTwo ? verifyPIN1 : verifyPIN2, "PIN verification");
|
||||
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
if (response[response.length - 2] == 0x69 && response[response.length - 1] == (byte) 0x83) {
|
||||
throw new RuntimeException("Invalid PIN. Authentication method blocked.");
|
||||
} else {
|
||||
throw new RuntimeException(String.format("Invalid PIN. Attempts left: %d.", response[response.length - 1] + 64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the authentication or signature certificate from the chip
|
||||
*
|
||||
* @param authOrSign true for auth, false for sign cert
|
||||
* @return the requested certificate
|
||||
*/
|
||||
public byte[] getCertificate(boolean authOrSign) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, IOException {
|
||||
|
||||
selectFile(IASECCFID, "the master application");
|
||||
|
||||
selectFile(authOrSign ? AWP : QSCD, "the application");
|
||||
|
||||
selectFile(authOrSign ? authCert : signCert, "the certificate");
|
||||
|
||||
byte[] certificate = new byte[0];
|
||||
byte[] readCert = Arrays.copyOf(readFile, readFile.length);
|
||||
// Construct the certificate byte array n=indexOfTerminator bytes at a time
|
||||
for (int i = 0; i < 16; i++) {
|
||||
|
||||
// Set the P1/P2 values to incrementally read the certificate
|
||||
readCert[2] = (byte) (certificate.length / 256);
|
||||
readCert[3] = (byte) (certificate.length % 256);
|
||||
byte[] response = getResponse(new byte[0], readCert, "Read the certificate");
|
||||
if (response[response.length - 2] == 0x6b && response[response.length - 1] == 0x00) {
|
||||
throw new RuntimeException("Wrong read parameters.");
|
||||
}
|
||||
|
||||
// Set the range containing a portion of the certificate and decrypt it
|
||||
int start = response[2] == 1 ? 3 : 4;
|
||||
int end = start + (response[start - 2] + 256) % 256 - 1;
|
||||
byte[] decrypted = encryptDecryptData(Arrays.copyOfRange(response, start, end), Cipher.DECRYPT_MODE);
|
||||
int indexOfTerminator = Hex.toHexString(decrypted).lastIndexOf("80") / 2;
|
||||
certificate = Arrays.copyOf(certificate, certificate.length + indexOfTerminator);
|
||||
System.arraycopy(decrypted, 0, certificate, certificate.length - indexOfTerminator, indexOfTerminator);
|
||||
|
||||
if (response[response.length - 2] == (byte) 0x90 && response[response.length - 1] == 0x00) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return certificate;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the authentication token hash
|
||||
*
|
||||
* @param PIN1 PIN1
|
||||
* @param token the token hash to be signed
|
||||
* @return authentication token hash signature
|
||||
*/
|
||||
public byte[] authenticate(String PIN1, byte[] token) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
|
||||
verifyPIN(PIN1.getBytes(StandardCharsets.UTF_8), true);
|
||||
|
||||
selectFile(AWP, "the AWP application");
|
||||
|
||||
byte[] response = getResponse(Env, MSESetEnv, "Set environment");
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
throw new RuntimeException("Setting the environment failed.");
|
||||
}
|
||||
|
||||
InternalAuthenticate[4] = (byte) (0x1d + 16 * (token.length / 16));
|
||||
InternalAuthenticate[6] = (byte) (0x11 + 16 * (token.length / 16));
|
||||
response = getResponse(token, InternalAuthenticate, "Internal Authenticate");
|
||||
|
||||
if (response[response.length - 2] != (byte) 0x90 || response[response.length - 1] != 0x00) {
|
||||
throw new RuntimeException("Signing the token failed.");
|
||||
}
|
||||
|
||||
byte[] signature = encryptDecryptData(Arrays.copyOfRange(response, 3, 115), Cipher.DECRYPT_MODE);
|
||||
int indexOfTerminator = Hex.toHexString(signature).lastIndexOf("80") / 2;
|
||||
|
||||
return Arrays.copyOf(signature, indexOfTerminator);
|
||||
}
|
||||
|
||||
|
||||
private byte[] getResponse(byte[] data, byte[] command, String log) throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, IOException {
|
||||
byte[] response = idCard.transceive(createSecureAPDU(data, command));
|
||||
Log.i(log, Hex.toHexString(response));
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentPin2Binding
|
||||
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
|
||||
|
||||
/**
|
||||
* Fragment that deals with asking PIN 2 from the user. Basically the same as PIN 1 fragment.
|
||||
*/
|
||||
class Pin2Fragment : Fragment() {
|
||||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.nextButton.setOnClickListener {
|
||||
checkPin2Length()
|
||||
}
|
||||
binding.cancelButton.setOnClickListener {
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the length of the entered PIN 2 is in range 5..12 and if it is
|
||||
* then it is saved to the viewModel.
|
||||
*/
|
||||
private fun checkPin2Length() {
|
||||
val enteredPin2 = binding.pin2EditText.editText?.text.toString()
|
||||
if (enteredPin2.length in 5..12) {
|
||||
viewModel.setUserPin2(enteredPin2)
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.length_pin2), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication process is cancelled when cancel button is clicked and the application
|
||||
* will be closed.
|
||||
*/
|
||||
private fun cancel() {
|
||||
val resultIntent = Intent()
|
||||
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
}
|
|
@ -1,67 +1,158 @@
|
|||
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
|
||||
|
||||
/**
|
||||
* Fragment that deals with asking the user for PIN1
|
||||
* Fragment that deals with asking the user for PIN 1. If the user has already saved the PIN 1 then it is not asked again
|
||||
* and the fragment is skipped and if the PIN 1 is not saved then the user is asked whether it should be saved or
|
||||
* not before continuing.
|
||||
*/
|
||||
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
|
||||
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)
|
||||
|
||||
binding!!.nextButton.setOnClickListener { goToNextFragment() }
|
||||
binding!!.cancelButton.setOnClickListener { goToTheStart() }
|
||||
// Currently PIN 1 is not required and thus this step is immediately skipped.
|
||||
// In the future the UI flow will be changed in the nav_graph.
|
||||
goToNextFragment()
|
||||
checkIfSkip()
|
||||
// Switch should be not visible when user is in savings mode
|
||||
if (args.saving) {
|
||||
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() }
|
||||
}
|
||||
|
||||
private fun goToNextFragment() {
|
||||
val enteredPin1 = binding!!.pinEditText.editText?.text.toString()
|
||||
if (enteredPin1.length in 4..12) {
|
||||
viewModel.setUserPin(
|
||||
binding!!.pinEditText.editText?.text.toString()
|
||||
)
|
||||
findNavController().navigate(R.id.action_pinFragment_to_canFragment)
|
||||
/**
|
||||
* Takes user to the next fragment, which is AuthFragment.
|
||||
*/
|
||||
private fun goToTheNextFragment() {
|
||||
val action = PinFragmentDirections.actionPinFragmentToAuthFragment(auth = args.auth, mobile = args.mobile)
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_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 {
|
||||
// Currently it is not important to enter PIN1 so we will allow the user to leave this field empty
|
||||
//Toast.makeText(requireContext(), getString(R.string.length_pin), Toast.LENGTH_SHORT)
|
||||
// .show()
|
||||
viewModel.setUserPin("1234")
|
||||
findNavController().navigate(R.id.action_pinFragment_to_canFragment)
|
||||
findNavController().navigate(R.id.action_canFragment_to_homeFragment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goToTheStart() {
|
||||
viewModel.clearUserInfo()
|
||||
findNavController().navigate(R.id.action_pinFragment_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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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 org.json.JSONObject
|
||||
|
||||
/**
|
||||
* ResultFragment is used to create a JWT and to send response to the website/application
|
||||
* that launched the MobileAuthApp. If the mobile auth app was started by a website
|
||||
* the result is sent to a server with a POST request.
|
||||
*/
|
||||
class ResultFragment : Fragment() {
|
||||
|
||||
private val paramsModel: ParametersViewModel by activityViewModels()
|
||||
|
||||
private var _binding: FragmentResultBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val args: ResultFragmentArgs by navArgs()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentResultBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a POST request to the backend server with a tokenItem
|
||||
*/
|
||||
fun postToken() {
|
||||
val json = JsonObject()
|
||||
json.addProperty("auth-token", paramsModel.token)
|
||||
json.addProperty("error", 200)
|
||||
|
||||
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
|
||||
.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
}
|
|
@ -20,26 +20,39 @@ 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)
|
||||
|
||||
binding!!.userName.text =
|
||||
getString(R.string.user_name, viewModel.userFirstName, viewModel.userLastName)
|
||||
binding!!.identificationNumber.text = viewModel.userIdentificationNumber
|
||||
binding!!.clearButton.setOnClickListener { goToTheStart() }
|
||||
displayInformation()
|
||||
binding.clearButton.setOnClickListener { goToTheStart() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns text values to the fields in order to display user information.
|
||||
*/
|
||||
private fun displayInformation() {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates user back to the start and also deletes any temporary information.
|
||||
*/
|
||||
private fun goToTheStart() {
|
||||
viewModel.clearUserInfo()
|
||||
findNavController().navigate(R.id.action_userFragment_to_homeFragment)
|
||||
|
@ -47,6 +60,6 @@ class UserFragment : Fragment() {
|
|||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -1,22 +1,59 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.auth
|
||||
|
||||
import android.nfc.tech.IsoDep
|
||||
import android.util.Log
|
||||
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
|
||||
import java.math.BigInteger
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class Authenticator(val comms : Comms) {
|
||||
class Authenticator(val comms: Comms) {
|
||||
|
||||
public fun authenticate(nonce: BigInteger, challengeUrl: String, pin1: String) {
|
||||
val type = "JWT"
|
||||
val algorithm = "ES384"
|
||||
var iss = "https://self-issued.me" // Will be specified at a later date.
|
||||
val algorithmUsedForSigning = SignatureAlgorithm.ES384
|
||||
|
||||
fun authenticate(challenge: String, originUrl: String, pin1: String): String {
|
||||
|
||||
// Ask PIN 1 from the user and get the authentication certificate from the ID card.
|
||||
val authenticationCertificate : ByteArray = comms.getAuthenticationCertificate();
|
||||
val authenticationCertificate: ByteArray = comms.getCertificate(true);
|
||||
|
||||
// Create the authentication token (OpenID X509)
|
||||
// Encode the certificate in base64.
|
||||
val base64cert = java.util.Base64.getEncoder().encodeToString(authenticationCertificate)
|
||||
|
||||
// Hash the authentication token.
|
||||
// Get current epoch time.
|
||||
val epoch = LocalDateTime.now(ZoneOffset.UTC).atZone(ZoneOffset.UTC).toEpochSecond()
|
||||
|
||||
// Get expiration time.
|
||||
val exp = LocalDateTime.now(ZoneOffset.UTC).plusSeconds(5 * 60L).atZone(ZoneOffset.UTC)
|
||||
.toEpochSecond()
|
||||
|
||||
// TODO: Get subject value.
|
||||
val sub = "FAMILYNAME.NAME"
|
||||
|
||||
// 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":""}}"""
|
||||
|
||||
val jwt = base64Encode(header.toByteArray(Charsets.UTF_8)) + "." + base64Encode(
|
||||
claims.toByteArray(Charsets.UTF_8)
|
||||
)
|
||||
|
||||
// Send the authentication token hash to the ID card for signing and get signed authentication token as response.
|
||||
val encoded =
|
||||
MessageDigest.getInstance("SHA-384").digest(jwt.toByteArray(StandardCharsets.UTF_8))
|
||||
val signed = comms.authenticate(pin1, encoded)
|
||||
|
||||
// Return the signed authentication token.
|
||||
return jwt + "." + base64Encode(signed)
|
||||
}
|
||||
|
||||
fun base64Encode(bytes: ByteArray): String? {
|
||||
val encoded = java.util.Base64.getUrlEncoder().encodeToString(bytes)
|
||||
return encoded.replace("=", "")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
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
|
||||
|
||||
/**
|
||||
* This fragment allows the user to save the CAN and the PIN 1 and also to delete them if necessary.
|
||||
* Should only be accessible for the user from the HomeFragment and not during the
|
||||
* authentication process itself.
|
||||
*/
|
||||
class SettingsFragment : Fragment() {
|
||||
|
||||
private val viewModel: SmartCardViewModel by activityViewModels()
|
||||
|
||||
private var _binding: FragmentSettingsBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var showPin: Boolean = false
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
_binding = FragmentSettingsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
showCanField()
|
||||
showPinField()
|
||||
togglePinButton()
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for showing the CAN field to the user and can be used to refresh the field as well.
|
||||
*/
|
||||
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)
|
||||
} else {
|
||||
binding.canSaved.text = getString(R.string.saved_can, getString(R.string.missing))
|
||||
binding.canMenuAction.text = getString(R.string.add_can_text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that allows the user to delete saved CAN from the device and also to save new a CAN if
|
||||
* currently there is no CAN saved.
|
||||
*/
|
||||
private fun canAction() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for showing the PIN 1 field to the user and can be used to refresh the field as well.
|
||||
* The PIN 1 is hidden by default and when it is hidden it is always shown as **** despite the
|
||||
* length of the PIN 1. Can be made visible with toggle button.
|
||||
*/
|
||||
private fun showPinField() {
|
||||
if (viewModel.userPin.length in 4..12) {
|
||||
binding.pinMenuShow.visibility = Button.VISIBLE
|
||||
if (showPin)
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that allows the user to delete saved PIN 1 from the device and also to save a new PIN 1 if
|
||||
* currently there is no PIN 1 saved.
|
||||
*/
|
||||
private fun pinAction() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the PIN 1 or makes it visible.
|
||||
*/
|
||||
private fun togglePin() {
|
||||
showPin = !showPin
|
||||
togglePinButton()
|
||||
showPinField()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the text on the button that controls the visiblity of the PIN 1.
|
||||
*/
|
||||
private fun togglePinButton() {
|
||||
if (showPin) {
|
||||
binding.pinMenuShow.text = getString(R.string.hide)
|
||||
} else {
|
||||
binding.pinMenuShow.text = getString(R.string.show)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.model
|
||||
|
||||
import android.util.Log
|
||||
import android.util.Log.WARN
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class ParametersViewModel: ViewModel() {
|
||||
|
||||
private var _challenge: String = ""
|
||||
val challenge get() = _challenge
|
||||
|
||||
private var _authUrl: String = ""
|
||||
val authUrl get() = _authUrl
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fun setAuthUrl(newAuthUrl: String) {
|
||||
_authUrl = newAuthUrl
|
||||
}
|
||||
|
||||
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,12 +1,19 @@
|
|||
package com.tarkvaraprojekt.mobileauthapp.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
|
||||
class SmartCardViewModel: ViewModel() {
|
||||
|
||||
private var _userPin: String = ""
|
||||
val userPin get() = _userPin
|
||||
|
||||
private var _userPin2: String = ""
|
||||
val userPin2 get() = _userPin2
|
||||
|
||||
private var _userCan: String = ""
|
||||
val userCan get() = _userCan
|
||||
|
||||
|
@ -19,18 +26,35 @@ class SmartCardViewModel: ViewModel() {
|
|||
private var _userIdentificationNumber: String = ""
|
||||
val userIdentificationNumber get() = _userIdentificationNumber
|
||||
|
||||
private var _gender: String = ""
|
||||
val gender get() = _gender
|
||||
|
||||
private var _expiration: String = ""
|
||||
val expiration get() = _expiration
|
||||
|
||||
private var _citizenship: String = ""
|
||||
val citizenship get() = _citizenship
|
||||
|
||||
fun clearUserInfo() {
|
||||
_userPin = ""
|
||||
_userPin2 = ""
|
||||
_userCan = ""
|
||||
_userFirstName = ""
|
||||
_userLastName = ""
|
||||
_userIdentificationNumber = ""
|
||||
_expiration = ""
|
||||
_citizenship = ""
|
||||
_gender = ""
|
||||
}
|
||||
|
||||
fun setUserPin(newUserPin: String) {
|
||||
_userPin = newUserPin
|
||||
}
|
||||
|
||||
fun setUserPin2(newUserPin2: String) {
|
||||
_userPin2 = newUserPin2
|
||||
}
|
||||
|
||||
fun setUserCan(newUserCan: String) {
|
||||
_userCan = newUserCan
|
||||
}
|
||||
|
@ -47,4 +71,67 @@ class SmartCardViewModel: ViewModel() {
|
|||
_userIdentificationNumber = newUserIdentificationNumber
|
||||
}
|
||||
|
||||
fun setExpiration(newExpiration: String) {
|
||||
_expiration = newExpiration
|
||||
}
|
||||
|
||||
fun setCitizenship(newCitizenship: String) {
|
||||
_citizenship = newCitizenship
|
||||
}
|
||||
|
||||
fun setGender(newGender: String) {
|
||||
_gender = newGender
|
||||
}
|
||||
|
||||
|
||||
private fun getSharedPreferences(context: Context): SharedPreferences {
|
||||
val masterKeyAlias: String = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
return EncryptedSharedPreferences.create(
|
||||
"user_creds",
|
||||
masterKeyAlias,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
||||
|
||||
fun storeCan(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().putString("CAN", userCan).apply()
|
||||
}
|
||||
|
||||
fun checkCan(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
val foundCan = sharedPreferences.getString("CAN", null)
|
||||
foundCan?.let {
|
||||
_userCan = it
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called from AuthFragment as well, when CAN is wrong.
|
||||
fun deleteCan(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().remove("CAN").apply()
|
||||
_userCan = ""
|
||||
}
|
||||
|
||||
fun storePin(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().putString("PIN1", userPin).apply()
|
||||
}
|
||||
|
||||
fun checkPin(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
val foundPin = sharedPreferences.getString("PIN1", null)
|
||||
foundPin?.let {
|
||||
_userPin = it
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePin(context: Context) {
|
||||
val sharedPreferences: SharedPreferences = getSharedPreferences(context)
|
||||
sharedPreferences.edit().remove("PIN1").apply()
|
||||
_userPin = ""
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- For material card views as recommended in the material.io website -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
|
||||
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_checked="false"/>
|
||||
</selector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
|
||||
</vector>
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||
</vector>
|
|
@ -6,12 +6,28 @@
|
|||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
app:navGraph="@navigation/nav_graph"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -4,47 +4,51 @@
|
|||
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">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="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" />
|
||||
|
||||
|
@ -57,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="18sp"
|
||||
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" />
|
||||
|
@ -68,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="18sp"
|
||||
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,79 +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: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="18sp"
|
||||
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="18dp"
|
||||
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,27 +4,125 @@
|
|||
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">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_fragment_text"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:id="@+id/saved_states"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/home_fragment"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/can_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/can_status_logo"
|
||||
android:layout_marginStart="@dimen/margin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/can_status_text"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/pin_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_big"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pin_status_logo"
|
||||
android:layout_marginStart="@dimen/margin"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pin_status_text"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:padding="@dimen/margin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</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>
|
||||
|
||||
<!-- Temporary button for testing purposes -->
|
||||
<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"
|
||||
app:layout_constraintTop_toBottomOf="@id/home_fragment_text"
|
||||
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,76 +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: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"
|
||||
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"
|
||||
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>
|
|
@ -0,0 +1,84 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:context=".Pin2Fragment">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pin2_fragment_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:text="@string/pin2_fragment" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/pin2_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_pin2"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="12"
|
||||
app:endIconMode="password_toggle"
|
||||
app:errorEnabled="true"
|
||||
app:helperText="@string/example_pin2"
|
||||
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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/continue_button"
|
||||
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"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/card_view" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,49 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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="@dimen/margin"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_text"
|
||||
android:text="@string/result_text"
|
||||
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="@dimen/padding_small"
|
||||
android:textSize="@dimen/regular_text"
|
||||
android:layout_marginVertical="@dimen/margin_small"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,80 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/padding"
|
||||
tools:context=".menu.SettingsFragment">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/settings_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:strokeWidth="1dp"
|
||||
app:strokeColor="@color/stroke_color"
|
||||
app:cardElevation="0dp">
|
||||
<LinearLayout
|
||||
android:id="@+id/can_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_small">
|
||||
<TextView
|
||||
android:id="@+id/can_saved"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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="@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="@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="vertical">
|
||||
<Button
|
||||
android:id="@+id/pin_menu_show"
|
||||
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>
|
||||
|
||||
<Button
|
||||
android:id="@+id/return_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/return_text"
|
||||
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" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,72 +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">
|
||||
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" />
|
||||
|
||||
</LinearLayout>
|
||||
<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" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<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/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_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" />
|
||||
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<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>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_settings_option"
|
||||
android:title="@string/menu_settings_title"
|
||||
android:icon="@drawable/ic_settings"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
|
@ -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>
|
|
@ -9,66 +9,150 @@
|
|||
android:id="@+id/homeFragment"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.HomeFragment"
|
||||
android:label="fragment_home"
|
||||
tools:layout="@layout/fragment_home" >
|
||||
tools:layout="@layout/fragment_home">
|
||||
<action
|
||||
android:id="@+id/action_homeFragment_to_pinFragment"
|
||||
app:destination="@id/pinFragment"
|
||||
/>
|
||||
android:id="@+id/action_homeFragment_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<action
|
||||
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"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.PinFragment"
|
||||
android:label="fragment_pin"
|
||||
tools:layout="@layout/fragment_pin" >
|
||||
<action
|
||||
android:id="@+id/action_pinFragment_to_canFragment"
|
||||
app:destination="@id/canFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
tools:layout="@layout/fragment_pin">
|
||||
<action
|
||||
android:id="@+id/action_pinFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_pinFragment_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment"
|
||||
app:popUpTo="@id/settingsFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<argument
|
||||
android:name="saving"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
<action
|
||||
android:id="@+id/action_pinFragment_to_authFragment"
|
||||
app:destination="@id/authFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument
|
||||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="mobile"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/canFragment"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.CanFragment"
|
||||
android:label="fragment_can"
|
||||
tools:layout="@layout/fragment_can" >
|
||||
<action
|
||||
android:id="@+id/action_canFragment_to_authFragment"
|
||||
app:destination="@id/authFragment"
|
||||
app:popUpTo="@id/homeFragment"/>
|
||||
tools:layout="@layout/fragment_can">
|
||||
<action
|
||||
android:id="@+id/action_canFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_canFragment_to_settingsFragment"
|
||||
app:destination="@id/settingsFragment"
|
||||
app:popUpTo="@id/settingsFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<argument
|
||||
android:name="saving"
|
||||
android:defaultValue="false"
|
||||
app:argType="boolean" />
|
||||
<action
|
||||
android:id="@+id/action_canFragment_to_pinFragment"
|
||||
app:destination="@id/pinFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument
|
||||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
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"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.AuthFragment"
|
||||
android:label="fragment_auth"
|
||||
tools:layout="@layout/fragment_auth" >
|
||||
tools:layout="@layout/fragment_auth">
|
||||
<action
|
||||
android:id="@+id/action_authFragment_to_userFragment"
|
||||
app:destination="@id/userFragment"
|
||||
app:popUpTo="@id/homeFragment"/>
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<action
|
||||
android:id="@+id/action_authFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
<action
|
||||
android:id="@+id/action_authFragment_to_resultFragment"
|
||||
app:destination="@id/resultFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument
|
||||
android:name="auth"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
<argument
|
||||
android:name="mobile"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/userFragment"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.UserFragment"
|
||||
android:label="fragment_user"
|
||||
tools:layout="@layout/fragment_user" >
|
||||
tools:layout="@layout/fragment_user">
|
||||
<action
|
||||
android:id="@+id/action_userFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true"/>
|
||||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/settingsFragment"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.menu.SettingsFragment"
|
||||
android:label="fragment_settings"
|
||||
tools:layout="@layout/fragment_settings">
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_canFragment"
|
||||
app:destination="@id/canFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_pinFragment"
|
||||
app:destination="@id/pinFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_homeFragment"
|
||||
app:destination="@id/homeFragment"
|
||||
app:popUpTo="@id/homeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/resultFragment"
|
||||
android:name="com.tarkvaraprojekt.mobileauthapp.ResultFragment"
|
||||
android:label="fragment_result"
|
||||
tools:layout="@layout/fragment_result" >
|
||||
<argument
|
||||
android:name="mobile"
|
||||
app:argType="boolean"
|
||||
android:defaultValue="false" />
|
||||
</fragment>
|
||||
</navigation>
|
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Must translate to English, but should work now -->
|
||||
<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="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_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>
|
||||
<string name="enter_pin2">PIN 2</string>
|
||||
<string name="example_pin2">Example. 123456</string>
|
||||
<string name="length_pin2">Allowed length for PIN 2 is 5..12</string>
|
||||
|
||||
<!-- string resources for CanFragment -->
|
||||
<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</string>
|
||||
<string name="time_left">Time left %d sek</string>
|
||||
<string name="no_time">No time left</string>
|
||||
|
||||
<!-- string resources for UserFragment layout -->
|
||||
<string name="user_name_label">NAME</string>
|
||||
<string name="user_name">%1$s %2$s</string>
|
||||
<string name="identification_number_label">IDENTIFICATION NUMBER</string>
|
||||
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||
<string name="citizenship_label">CITIZENSHIP</string>
|
||||
<string name="gender_label">SEX</string>
|
||||
|
||||
<!-- string resources for ResultFragment layout-->
|
||||
<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="saved_can">CAN: %s</string>
|
||||
<string name="can_delete">Delete CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Add PIN1</string>
|
||||
<string name="pin1_delete">Delete PIN1</string>
|
||||
<string name="missing">not saved</string>
|
||||
<string name="show">SHOW</string>
|
||||
<string name="hide">HIDE</string>
|
||||
<string name="hidden_pin">****</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,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NFC autentija</string>
|
||||
|
||||
<!-- Buttons -->
|
||||
<string name="cancel_text">KATKESTA</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_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>
|
||||
<string name="enter_pin2">PIN 2</string>
|
||||
<string name="example_pin2">Näide. 123456</string>
|
||||
<string name="length_pin2">PIN 2 lubatud pikkus on 5..12</string>
|
||||
|
||||
<!-- string resources for CanFragment -->
|
||||
<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">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 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="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">Tulemust kontrollitakse</string>
|
||||
<string name="result_info">Rakendus sulgeb ennast ise</string>
|
||||
|
||||
<!-- menu -->
|
||||
<string name="menu_settings_title">Seaded</string>
|
||||
<string name="saved_can">CAN: %s</string>
|
||||
<string name="can_delete">Kustuta CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Lisa PIN1</string>
|
||||
<string name="pin1_delete">Kustuta PIN1</string>
|
||||
<string name="missing">puudub</string>
|
||||
<string name="show">NÄITA</string>
|
||||
<string name="hide">PEIDA</string>
|
||||
<string name="hidden_pin">****</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>
|
|
@ -1,6 +1,6 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.MobileAuthApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<style name="Theme.MobileAuthApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/blue_200</item>
|
||||
<item name="colorPrimaryVariant">@color/blue_700</item>
|
||||
|
|
|
@ -8,10 +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,34 +1,85 @@
|
|||
<resources>
|
||||
<string name="app_name">Mobile Authenticator</string>
|
||||
<string name="home_fragment">Work in progress</string>
|
||||
<string name="app_name">NFC authenticator</string>
|
||||
|
||||
<string name="begin_text">ALUSTA</string>
|
||||
<string name="next_text">EDASI</string>
|
||||
<string name="cancel_text">KATKESTA</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="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">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_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>
|
||||
<string name="enter_pin2">PIN 2</string>
|
||||
<string name="example_pin2">Example. 123456</string>
|
||||
<string name="length_pin2">Allowed length for PIN 2 is 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_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="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 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 resources for UserFragment layout -->
|
||||
<string name="user_name_label">NIMI</string>
|
||||
<string name="user_name_label">NAME</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="identification_number_label">IDENTIFICATION NUMBER</string>
|
||||
<string name="expiration_label">DATE OF EXPIRY</string>
|
||||
<string name="citizenship_label">CITIZENSHIP</string>
|
||||
<string name="gender_label">SEX</string>
|
||||
|
||||
<!-- string resources for ResultFragment layout-->
|
||||
<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="saved_can">CAN: %s</string>
|
||||
<string name="can_delete">Delete CAN</string>
|
||||
<string name="saved_pin">PIN1: %s</string>
|
||||
<string name="pin1_add">Add PIN 1</string>
|
||||
<string name="pin1_delete">Delete PIN 1</string>
|
||||
<string name="missing">not saved</string>
|
||||
<string name="show">SHOW</string>
|
||||
<string name="hide">HIDE</string>
|
||||
<string name="hidden_pin">****</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>
|
|
@ -1,6 +1,6 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.MobileAuthApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<style name="Theme.MobileAuthApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/blue_500</item>
|
||||
<item name="colorPrimaryVariant">@color/blue_700</item>
|
||||
|
|
|
@ -9,7 +9,7 @@ buildscript {
|
|||
kotlin_version = "1.4.30"
|
||||
}
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:7.0.2"
|
||||
classpath 'com.android.tools.build:gradle:7.0.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
|
22
README.md
|
@ -2,16 +2,24 @@
|
|||
|
||||
This is a proof-of-concept project for creating an Android app for authenticating yourself using an NFC-enabled Estonian ID card. This project will be created for the University of Tartu course "Software project".
|
||||
|
||||
[Project Vision](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-Vision)
|
||||
### Requirements to use the application
|
||||
* The smartphone's operating system must be Android 8.0 or newer
|
||||
* 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)
|
||||
|
||||
[Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes)
|
||||
### 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.
|
||||
|
||||
[Project tasks](https://tvp-mobile-authentication.atlassian.net/jira/software/projects/MOB/boards/1/backlog) (Ask Tanel for JIRA permissions if needed).
|
||||
More information about using real devices with Android studio: https://developer.android.com/studio/run/device
|
||||
|
||||
[Project plan](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-plan)
|
||||
The second and more reliable option is to get the .apk that is generated under the Artifacts of GitHub Actions when the project is built. Download the .apk file and move it to the smartphone and install it (phone permissions might have to be changed because it is not installed through Google Play). After the application has been installed it should open as any other application.
|
||||
|
||||
[Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases) *updated 08.10*
|
||||
More info about installing third party applications on the Android phones: https://www.androidauthority.com/how-to-install-apks-31494/
|
||||
|
||||
[User stories](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/User-stories)
|
||||
**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.**
|
||||
|
||||
[Use Case Tests](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Case-Tests)
|
||||
### 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,16 @@
|
|||
*.iml
|
||||
.gradle
|
||||
.idea
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,47 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 31
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.example.testmobileapp"
|
||||
minSdk 26
|
||||
targetSdk 31
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.koushikdutta.ion:ion:3.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.6.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
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"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package com.example.testmobileapp
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.example.testmobileapp", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?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"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.TestMobileApp">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,94 @@
|
|||
package com.example.testmobileapp
|
||||
|
||||
import android.app.Activity
|
||||
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 org.json.JSONObject
|
||||
|
||||
/**
|
||||
* 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)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
authLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { response ->
|
||||
if (response.resultCode == Activity.RESULT_OK) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
showLogin()
|
||||
|
||||
binding.loginOptionNfcButton.setOnClickListener {
|
||||
launchAuth()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that creates an intent to launch the MobileAuthApp
|
||||
*/
|
||||
private fun launchAuth() {
|
||||
val launchIntent = Intent()
|
||||
launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity")
|
||||
launchIntent.putExtra("action", "auth")
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -0,0 +1,76 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="24dp"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_text_view"
|
||||
android:text="@string/login_text"
|
||||
android:textSize="20sp"
|
||||
android:padding="12dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_options"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/login_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/choose_method_text_view"
|
||||
android:text="@string/choose_login_method"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_option_nfc_button"
|
||||
android:text="@string/method_nfc"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginVertical="6dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</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>
|
|
@ -0,0 +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" />
|
||||
</adaptive-icon>
|
|
@ -0,0 +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" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 7.6 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">TestMobileApp</string>
|
||||
<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">Logged in</string>
|
||||
<string name="auth_failure">Response failed</string>
|
||||
<string name="forget_button">Forget</string>
|
||||
<string name="hello">Hello, %s!</string>
|
||||
</resources>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">TestMobileApp</string>
|
||||
<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">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>
|
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.TestMobileApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/red_200</item>
|
||||
<item name="colorPrimaryVariant">@color/red_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/amber_200</item>
|
||||
<item name="colorSecondaryVariant">@color/amber_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<!-- New colors -->
|
||||
<color name="red_200">#ef9a9a</color>
|
||||
<color name="red_500">#f44336</color>
|
||||
<color name="red_700">#d32f2f</color>
|
||||
<color name="amber_200">#ffe082</color>
|
||||
<color name="amber_700">#ffa000</color>
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<resources>
|
||||
<string name="app_name">TestMobileApp</string>
|
||||
<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">Logged in</string>
|
||||
<string name="auth_failure">Response failed</string>
|
||||
<string name="forget_button">Forget</string>
|
||||
<string name="hello">Hello, %s!</string>
|
||||
</resources>
|
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.TestMobileApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/red_500</item>
|
||||
<item name="colorPrimaryVariant">@color/red_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/amber_200</item>
|
||||
<item name="colorSecondaryVariant">@color/amber_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,17 @@
|
|||
package com.example.testmobileapp
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// 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()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
|
@ -0,0 +1,6 @@
|
|||
#Tue Oct 26 10:33:23 EEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,89 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,10 @@
|
|||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
jcenter() // Warning: this repository is going to shut down soon
|
||||
}
|
||||
}
|
||||
rootProject.name = "TestMobileApp"
|
||||
include ':app'
|
|
@ -0,0 +1,36 @@
|
|||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### web-eid.js ###
|
||||
src/demo-website/src/web-eid.js
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if (mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if (mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if (!outputFile.getParentFile().exists()) {
|
||||
if (!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
|
@ -0,0 +1,39 @@
|
|||
# Demo backend + website for mobile authentication project.
|
||||
|
||||
## How to run.
|
||||
|
||||
|
||||
### 1. Clone GIT repository
|
||||
### 2. Setup HTTPS
|
||||
Since Web eID only works over HTTPS connection, you'll need to serve the backend and website with an HTTPS certificate. A suitable tool for that is ngrok (https://ngrok.com/).
|
||||
To use ngrok, download it and then run the command (may need administrator rights)
|
||||
```ngrok http 8080```
|
||||
and you should see something like this:
|
||||
```
|
||||
ngrok by @inconshreveable (Ctrl+C to quit)
|
||||
|
||||
Session Status online
|
||||
Account TanelOrumaa (Plan: Free)
|
||||
Version 2.3.40
|
||||
Region United States (us)
|
||||
Web Interface http://127.0.0.1:4040
|
||||
Forwarding http://somethinghere.ngrok.io -> http://localhost:8080
|
||||
Forwarding https://somethinghere.ngrok.io -> http://localhost:8080
|
||||
|
||||
Connections ttl opn rt1 rt5 p50 p90
|
||||
1508 0 0.00 0.00 2.31 75.59
|
||||
|
||||
HTTP Requests
|
||||
-------------
|
||||
```
|
||||
|
||||
Copy the second forwarding link (the one with https) and put it in ```com.tarkvaratehnika.demobackend.config.ApplicationConfiguration.kt``` as ```val WEBSITE_ORIGIN_URL = "https://yourlinkhere.com"```
|
||||
|
||||
### 3. Run the project
|
||||
Use your favourite IDE or just run it via commandline with ```./mvnw spring-boot:run```
|
||||
|
||||
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...
|
||||
...go out to creators of https://github.com/web-eid/web-eid-spring-boot-example. That example project was used in some parts as an example (files where inspiration was taken are correctly annotated with the appropriate license text).
|
|
@ -0,0 +1,310 @@
|
|||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
else
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
fi
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if $cygwin; then
|
||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
else
|
||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
||||
else
|
||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
||||
fi
|
||||
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaClass=`cygpath --path --windows "$javaClass"`
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
|
@ -0,0 +1,182 @@
|
|||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM https://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
|
||||
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
|
@ -0,0 +1,210 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.6</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.tarkvaratehnika</groupId>
|
||||
<artifactId>demoBackend</artifactId>
|
||||
<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>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-kotlin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<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>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
<version>${javaxcache.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>${caffeine.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>jcache</artifactId>
|
||||
<version>${caffeine.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-config</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>gitlab</id>
|
||||
<url>https://gitlab.com/api/v4/projects/19948337/packages/maven</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<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>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<args>
|
||||
<arg>-Xjsr305=strict</arg>
|
||||
</args>
|
||||
<compilerPlugins>
|
||||
<plugin>spring</plugin>
|
||||
</compilerPlugins>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-allopen</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
</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>
|
||||
|
||||
</project>
|
|
@ -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?
|
|
@ -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}
|
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>
|