diff --git a/MobileAuthApp/app/build.gradle b/MobileAuthApp/app/build.gradle index 0d59bc9..8cff52f 100644 --- a/MobileAuthApp/app/build.gradle +++ b/MobileAuthApp/app/build.gradle @@ -67,4 +67,7 @@ dependencies { 'org.bouncycastle:bcprov-jdk15on:1.60', 'io.jsonwebtoken:jjwt-gson:0.11.2' + // Retrofit + Moshi Converter + implementation 'com.squareup.retrofit2:converter-moshi:2.9.0' + implementation 'com.squareup.moshi:moshi-kotlin:1.9.3' } \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/AndroidManifest.xml b/MobileAuthApp/app/src/main/AndroidManifest.xml index f592e7e..d13134c 100644 --- a/MobileAuthApp/app/src/main/AndroidManifest.xml +++ b/MobileAuthApp/app/src/main/AndroidManifest.xml @@ -19,6 +19,13 @@ + + + + + + + diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt index 6cf616e..91f0811 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/AuthFragment.kt @@ -1,7 +1,6 @@ package com.tarkvaraprojekt.mobileauthapp -import android.app.Activity -import android.content.Context +import android.content.Intent import android.nfc.NfcAdapter import android.nfc.tech.IsoDep import android.os.Bundle @@ -10,15 +9,16 @@ 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.SmartCardViewModel import java.lang.Exception -import kotlin.concurrent.thread /** * Fragment that asks the user to detect the ID card with mobile NFC chip. @@ -31,6 +31,8 @@ class AuthFragment : Fragment() { private var binding: FragmentAuthBinding? = null + private val args: CanFragmentArgs by navArgs() + private lateinit var timer: CountDownTimer private var timeRemaining: Int = 90 @@ -69,51 +71,66 @@ class AuthFragment : Fragment() { } private fun getInfoFromIdCard(adapter: NfcAdapter) { - adapter.enableReaderMode(activity, { tag -> - timer.cancel() - requireActivity().runOnUiThread { - 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, 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{ - binding!!.timeCounter.text = getString(R.string.data_read) - } - } catch (e: Exception) { - requireActivity().runOnUiThread { - binding!!.timeCounter.text = getString(R.string.no_success) - } - // If the CAN is wrong we will also delete the saved CAN so that the user won't use it again. - viewModel.deleteCan(requireContext()) - // Gives user some time to read the error message - Thread.sleep(1000) - goToTheStart() - } finally { - adapter.disableReaderMode(activity) + if (args.reading) { + adapter.enableReaderMode(activity, { tag -> + timer.cancel() + requireActivity().runOnUiThread { + binding!!.timeCounter.text = getString(R.string.card_detected) } - } - }, NfcAdapter.FLAG_READER_NFC_A, null) + 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 { + binding!!.timeCounter.text = getString(R.string.data_read) + } + } catch (e: Exception) { + requireActivity().runOnUiThread { + binding!!.timeCounter.text = getString(R.string.no_success) + } + // If the CAN is wrong we will also delete the saved CAN so that the user won't use it again. + viewModel.deleteCan(requireContext()) + // Gives user some time to read the error message + Thread.sleep(1000) + goToTheStart() + } finally { + adapter.disableReaderMode(activity) + } + } + }, NfcAdapter.FLAG_READER_NFC_A, null) + } else { //We want to create a JWT instead of reading the info from the card. + goToNextFragment() + } } private fun goToNextFragment() { timer.cancel() - findNavController().navigate(R.id.action_authFragment_to_userFragment) + if (args.auth) { + val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile) + findNavController().navigate(action) + } else { + findNavController().navigate(R.id.action_authFragment_to_userFragment) + } } private fun goToTheStart() { viewModel.clearUserInfo() timer.cancel() - findNavController().navigate(R.id.action_authFragment_to_homeFragment) + if (args.reading) { + findNavController().navigate(R.id.action_authFragment_to_homeFragment) + } else { + val resultIntent = Intent() + requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) + requireActivity().finish() + } } override fun onDestroy() { diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt index cdf9e4e..7ad1207 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/CanFragment.kt @@ -1,11 +1,13 @@ package com.tarkvaraprojekt.mobileauthapp import android.app.AlertDialog +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 androidx.navigation.fragment.findNavController @@ -65,7 +67,7 @@ class CanFragment : Fragment() { * Takes user to the next fragment, which is PinFragment. */ private fun goToTheNextFragment() { - val action = CanFragmentDirections.actionCanFragmentToPinFragment(reading = args.reading) + val action = CanFragmentDirections.actionCanFragmentToPinFragment(reading = args.reading, auth = args.auth, mobile = args.mobile) findNavController().navigate(action) } @@ -122,8 +124,13 @@ class CanFragment : Fragment() { * not the HomeFragment. */ private fun goToTheStart() { + // TODO: Needs special handling when the app is launched with intent. Temporary solution at the moment. if (args.saving) { findNavController().navigate(R.id.action_canFragment_to_settingsFragment) + } else if (args.auth) { + val resultIntent = Intent() + requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) + requireActivity().finish() } else { findNavController().navigate(R.id.action_canFragment_to_homeFragment) } diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt index 77a2552..e53cd54 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/HomeFragment.kt @@ -1,6 +1,8 @@ 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 @@ -37,6 +39,14 @@ class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initialChecks() + var mobile = false + if (requireActivity().intent.data?.getQueryParameter("arg1") != null) { + mobile = true + } + val auth = requireActivity().intent.getBooleanExtra("auth", false) + if (auth || mobile){ + goToTheNextFragment(true, mobile) + } binding!!.beginButton.setOnClickListener { goToTheNextFragment() } } @@ -52,13 +62,19 @@ class HomeFragment : Fragment() { /** * Starts the process of interacting with the ID card by sending user to the CAN fragment. */ - private fun goToTheNextFragment() { + private fun goToTheNextFragment(auth: Boolean = false, mobile: Boolean = false) { // Making settings menu inactive (activity as MainActivity).menuAvailable = false // Currently saving is true because the application is not yet integrated with // other applications or websites. - val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = true) - findNavController().navigate(action) + // TODO: Check the navigation action default values. Not everything has to be declared explicitly. + if (auth) { + val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = false, auth = true, mobile = mobile) + findNavController().navigate(action) + } else { + val action = HomeFragmentDirections.actionHomeFragmentToCanFragment(reading = true, auth = false, mobile = mobile) + findNavController().navigate(action) + } } /** diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/Pin2Fragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/Pin2Fragment.kt new file mode 100644 index 0000000..189c440 --- /dev/null +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/Pin2Fragment.kt @@ -0,0 +1,72 @@ +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 + + 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 + } + +} \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt index 68b6355..3248ef0 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/PinFragment.kt @@ -1,11 +1,13 @@ package com.tarkvaraprojekt.mobileauthapp import android.app.AlertDialog +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 androidx.navigation.fragment.findNavController @@ -68,7 +70,8 @@ class PinFragment : Fragment() { * Takes user to the next fragment, which is AuthFragment. */ private fun goToTheNextFragment() { - findNavController().navigate(R.id.action_pinFragment_to_authFragment) + val action = PinFragmentDirections.actionPinFragmentToAuthFragment(reading = args.reading, auth = args.auth, mobile = args.mobile) + findNavController().navigate(action) } /** @@ -124,10 +127,13 @@ class PinFragment : Fragment() { */ private fun goToTheStart() { if (args.saving) { - findNavController().navigate(R.id.action_pinFragment_to_settingsFragment) + findNavController().navigate(R.id.action_canFragment_to_settingsFragment) + } else if (args.auth) { + val resultIntent = Intent() + requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent) + requireActivity().finish() } else { - viewModel.clearUserInfo() - findNavController().navigate(R.id.action_pinFragment_to_homeFragment) + findNavController().navigate(R.id.action_canFragment_to_homeFragment) } } diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/ResultFragment.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/ResultFragment.kt new file mode 100644 index 0000000..9d01c8f --- /dev/null +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/ResultFragment.kt @@ -0,0 +1,58 @@ +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.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding +import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel + +/** + * 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 viewModel: SmartCardViewModel by activityViewModels() + + private var binding: FragmentResultBinding? = null + + private val args: CanFragmentArgs 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) + binding!!.resultBackButton.setOnClickListener { + if (!args.mobile) { + createResponse() + } + } + } + + private fun createResponse() { + val resultIntent = Intent() + requireActivity().setResult(AppCompatActivity.RESULT_OK, resultIntent) + requireActivity().finish() + } + + override fun onDestroy() { + super.onDestroy() + binding = null + } + +} \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/model/SmartCardViewModel.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/model/SmartCardViewModel.kt index 1b66115..8780736 100644 --- a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/model/SmartCardViewModel.kt +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/model/SmartCardViewModel.kt @@ -11,6 +11,9 @@ 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 @@ -34,6 +37,7 @@ class SmartCardViewModel: ViewModel() { fun clearUserInfo() { _userPin = "" + _userPin2 = "" _userCan = "" _userFirstName = "" _userLastName = "" @@ -47,6 +51,10 @@ class SmartCardViewModel: ViewModel() { _userPin = newUserPin } + fun setUserPin2(newUserPin2: String) { + _userPin2 = newUserPin2 + } + fun setUserCan(newUserCan: String) { _userCan = newUserCan } diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/Response.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/Response.kt new file mode 100644 index 0000000..199b913 --- /dev/null +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/Response.kt @@ -0,0 +1,9 @@ +package com.tarkvaraprojekt.mobileauthapp.network + +/** + * Placeholder ResponseItem. + */ +data class ResponseItem ( + val data1: Int, + val data2: Int, +) \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/TokenApiService.kt b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/TokenApiService.kt new file mode 100644 index 0000000..aa08490 --- /dev/null +++ b/MobileAuthApp/app/src/main/java/com/tarkvaraprojekt/mobileauthapp/network/TokenApiService.kt @@ -0,0 +1,37 @@ +package com.tarkvaraprojekt.mobileauthapp.network + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.POST + +/** + * Class for making HTTP requests + * Based on https://developer.android.com/courses/pathways/android-basics-kotlin-unit-4-pathway-2 + */ +private const val BASE_URL = + "add-endpoint-url-here" + +private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() +private val retrofit = Retrofit.Builder().addConverterFactory(MoshiConverterFactory.create(moshi)) + .baseUrl(BASE_URL).build() + +interface TokenApiService { + @GET("something") + suspend fun getData(): ResponseItem + + @Headers("Content-Type: application/json") + @POST("posts") + suspend fun addData(@Body data: ResponseItem): Response +} + +object TokenApi { + val retrofitService : TokenApiService by lazy { + retrofit.create(TokenApiService::class.java) + } +} \ No newline at end of file diff --git a/MobileAuthApp/app/src/main/res/layout/fragment_pin2.xml b/MobileAuthApp/app/src/main/res/layout/fragment_pin2.xml new file mode 100644 index 0000000..8929989 --- /dev/null +++ b/MobileAuthApp/app/src/main/res/layout/fragment_pin2.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + +