MOB-42 Added JWT emitting

This commit is contained in:
TanelOrumaa 2021-11-08 23:17:36 +02:00
commit 1d665f02bf
11 changed files with 175 additions and 46 deletions

View File

@ -3,6 +3,7 @@
package="com.tarkvaraprojekt.mobileauthapp"> package="com.tarkvaraprojekt.mobileauthapp">
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"

View File

@ -2,6 +2,7 @@ package com.tarkvaraprojekt.mobileauthapp
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter import android.nfc.NfcAdapter
import android.nfc.tech.IsoDep import android.nfc.tech.IsoDep
import android.os.Bundle import android.os.Bundle
@ -10,15 +11,18 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.tarkvaraprojekt.mobileauthapp.NFC.Comms import com.tarkvaraprojekt.mobileauthapp.NFC.Comms
import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator import com.tarkvaraprojekt.mobileauthapp.auth.Authenticator
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentAuthBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import java.lang.Exception 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. * Fragment that asks the user to detect the ID card with mobile NFC chip.
@ -29,8 +33,12 @@ class AuthFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private val intentParameters: ParametersViewModel by activityViewModels()
private var binding: FragmentAuthBinding? = null private var binding: FragmentAuthBinding? = null
private val args: CanFragmentArgs by navArgs()
private lateinit var timer: CountDownTimer private lateinit var timer: CountDownTimer
private var timeRemaining: Int = 90 private var timeRemaining: Int = 90
@ -50,9 +58,9 @@ class AuthFragment : Fragment() {
override fun onTick(p0: Long) { override fun onTick(p0: Long) {
timeRemaining-- timeRemaining--
if (timeRemaining == 0) { if (timeRemaining == 0) {
binding!!.timeCounter.text = getString(R.string.no_time) binding?.timeCounter?.text = getString(R.string.no_time)
} else { } else {
binding!!.timeCounter.text = getString(R.string.time_left, timeRemaining) binding?.timeCounter?.text = getString(R.string.time_left, timeRemaining)
} }
} }
@ -79,6 +87,14 @@ class AuthFragment : Fragment() {
card.use { card.use {
try { try {
val comms = Comms(it, viewModel.userCan) val comms = Comms(it, viewModel.userCan)
if (args.auth) {
val jws = Authenticator(comms).authenticate(
intentParameters.challenge,
intentParameters.authUrl,
viewModel.userPin
)
intentParameters.setToken(jws)
} else {
val response = comms.readPersonalData(byteArrayOf(1, 2, 6, 3, 4, 8)) val response = comms.readPersonalData(byteArrayOf(1, 2, 6, 3, 4, 8))
viewModel.setUserFirstName(response[1]) viewModel.setUserFirstName(response[1])
viewModel.setUserLastName(response[0]) viewModel.setUserLastName(response[0])
@ -86,6 +102,7 @@ class AuthFragment : Fragment() {
viewModel.setGender(response[3]) viewModel.setGender(response[3])
viewModel.setCitizenship(response[4]) viewModel.setCitizenship(response[4])
viewModel.setExpiration(response[5]) viewModel.setExpiration(response[5])
}
requireActivity().runOnUiThread { requireActivity().runOnUiThread {
binding!!.timeCounter.text = getString(R.string.data_read) binding!!.timeCounter.text = getString(R.string.data_read)
} }
@ -107,13 +124,30 @@ class AuthFragment : Fragment() {
private fun goToNextFragment() { private fun goToNextFragment() {
timer.cancel() timer.cancel()
if (args.auth) {
val action = AuthFragmentDirections.actionAuthFragmentToResultFragment(mobile = args.mobile)
findNavController().navigate(action)
} else {
findNavController().navigate(R.id.action_authFragment_to_userFragment) findNavController().navigate(R.id.action_authFragment_to_userFragment)
} }
}
private fun goToTheStart() { private fun goToTheStart() {
viewModel.clearUserInfo() viewModel.clearUserInfo()
timer.cancel() timer.cancel()
if (args.reading) {
findNavController().navigate(R.id.action_authFragment_to_homeFragment) findNavController().navigate(R.id.action_authFragment_to_homeFragment)
} else {
if (!args.mobile) {
//Currently for some reason the activity is not killed entirely. Must be looked into further.
requireActivity().finish()
exitProcess(0)
} else {
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
}
}
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -6,11 +6,14 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentHomeBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import java.lang.Exception
/** /**
* HomeFragment is only shown to the user when then the user launches the application. When the application * HomeFragment is only shown to the user when then the user launches the application. When the application
@ -23,6 +26,8 @@ class HomeFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val viewModel: SmartCardViewModel by activityViewModels()
private val intentParams: ParametersViewModel by activityViewModels()
private var binding: FragmentHomeBinding? = null private var binding: FragmentHomeBinding? = null
override fun onCreateView( override fun onCreateView(
@ -39,12 +44,30 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initialChecks() initialChecks()
var mobile = false var auth = false
if (requireActivity().intent.data?.getQueryParameter("arg1") != null) { if (requireActivity().intent.data?.getQueryParameter("action") != null) {
mobile = true // Currently we only support authentication not signing.
auth = true
} }
val auth = requireActivity().intent.getBooleanExtra("auth", false) val mobile = requireActivity().intent.getBooleanExtra("mobile", false)
if (auth || mobile){ if (auth || mobile){
try {
if (mobile) {
// We use !! because we want an exception when something is not right.
intentParams.setChallenge(requireActivity().intent.getStringExtra("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.getStringExtra("authUrl")!!)
} else { //Website
// Currently the test website won't send the authUrl parameter
//Log.i("intentDebugging", requireActivity().intent.data.toString())
intentParams.setChallenge(requireActivity().intent.data!!.getQueryParameter("challenge")!!)
intentParams.setAuthUrl(requireActivity().intent.data!!.getQueryParameter("authUrl")!!)
}
} catch (e: Exception) {
// There was a problem with parameters, which means that authentication is not possible.
val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_CANCELED, resultIntent)
requireActivity().finish()
}
goToTheNextFragment(true, mobile) goToTheNextFragment(true, mobile)
} }
binding!!.beginButton.setOnClickListener { goToTheNextFragment() } binding!!.beginButton.setOnClickListener { goToTheNextFragment() }

View File

@ -30,7 +30,7 @@ class PinFragment : Fragment() {
// saving = true means that the user must be returned to the settings menu // saving = true means that the user must be returned to the settings menu
// reading = true means that we are reading information from the ID card that does // reading = true means that we are reading information from the ID card that does
// not require PIN 1 so it is not necessary to ask it. // not require PIN 1 so it is not necessary to ask it.
private val args: CanFragmentArgs by navArgs() private val args: PinFragmentArgs by navArgs()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View File

@ -11,7 +11,16 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding import com.tarkvaraprojekt.mobileauthapp.databinding.FragmentResultBinding
import com.tarkvaraprojekt.mobileauthapp.model.ParametersViewModel
import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
import com.tarkvaraprojekt.mobileauthapp.network.BASE_URL
import com.tarkvaraprojekt.mobileauthapp.network.TokenApi
import com.tarkvaraprojekt.mobileauthapp.network.TokenApiService
import com.tarkvaraprojekt.mobileauthapp.network.TokenItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.system.exitProcess
/** /**
* ResultFragment is used to create a JWT and to send response to the website/application * ResultFragment is used to create a JWT and to send response to the website/application
@ -20,11 +29,11 @@ import com.tarkvaraprojekt.mobileauthapp.model.SmartCardViewModel
*/ */
class ResultFragment : Fragment() { class ResultFragment : Fragment() {
private val viewModel: SmartCardViewModel by activityViewModels() private val paramsModel: ParametersViewModel by activityViewModels()
private var binding: FragmentResultBinding? = null private var binding: FragmentResultBinding? = null
private val args: CanFragmentArgs by navArgs() private val args: ResultFragmentArgs by navArgs()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -38,15 +47,47 @@ class ResultFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding!!.resultBackButton.setOnClickListener { binding!!.resultBackButton.setOnClickListener {
if (!args.mobile) { // if (args.mobile) {
createResponse() // createResponse()
// }
postToken()
}
}
/**
* Makes a POST request to the backend server with a tokenItem
*/
fun postToken() {
val tokenData = TokenItem(
paramsModel.token,
paramsModel.challenge
)
CoroutineScope(Dispatchers.Default).launch {
val response = TokenApi.retrofitService.postToken(tokenData)
Log.v("Response", response.message())
if (response.isSuccessful) {
Log.v("GREAAAT", "SUCCESSSS")
//Success scenario here
} else {
//Failure scenario here
if (args.mobile) {
createResponse(false)
} else {
//Currently for some reason the activity is not killed entirely. Must be looked into further.
requireActivity().finish()
exitProcess(0)
}
} }
} }
} }
private fun createResponse() { /**
* Only used when the MobileAuthApp was launched by an app. Not for website use.
*/
private fun createResponse(success: Boolean = true) {
val responseCode = if (success) AppCompatActivity.RESULT_OK else AppCompatActivity.RESULT_CANCELED
val resultIntent = Intent() val resultIntent = Intent()
requireActivity().setResult(AppCompatActivity.RESULT_OK, resultIntent) requireActivity().setResult(responseCode, resultIntent)
requireActivity().finish() requireActivity().finish()
} }

View File

@ -0,0 +1,27 @@
package com.tarkvaraprojekt.mobileauthapp.model
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
fun setChallenge(newChallenge: String) {
_challenge = newChallenge
}
fun setAuthUrl(newAuthUrl: String) {
_authUrl = newAuthUrl
}
fun setToken(newToken: String) {
_token = newToken
}
}

View File

@ -1,9 +0,0 @@
package com.tarkvaraprojekt.mobileauthapp.network
/**
* Placeholder ResponseItem.
*/
data class ResponseItem (
val data1: Int,
val data2: Int,
)

View File

@ -14,20 +14,17 @@ import retrofit2.http.POST
* Class for making HTTP requests * Class for making HTTP requests
* Based on https://developer.android.com/courses/pathways/android-basics-kotlin-unit-4-pathway-2 * Based on https://developer.android.com/courses/pathways/android-basics-kotlin-unit-4-pathway-2
*/ */
private const val BASE_URL = const val BASE_URL =
"add-endpoint-url-here" "https://6bb0-85-253-195-252.ngrok.io"
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val retrofit = Retrofit.Builder().addConverterFactory(MoshiConverterFactory.create(moshi)) private val retrofit = Retrofit.Builder().addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL).build() .baseUrl(BASE_URL).build()
interface TokenApiService { interface TokenApiService {
@GET("something")
suspend fun getData(): ResponseItem
@Headers("Content-Type: application/json") @Headers("Content-Type: application/json")
@POST("posts") @POST("auth/authentication")
suspend fun addData(@Body data: ResponseItem): Response<ResponseItem> suspend fun postToken(@Body data: TokenItem): Response<TokenItem>
} }
object TokenApi { object TokenApi {

View File

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

View File

@ -22,7 +22,7 @@ More info about installing third party applications on the Android phones: https
### Wiki pages relevant for the "Software project" subject ### Wiki pages relevant for the "Software project" subject
* [Project Vision](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-Vision) *last updated on 10.10* * [Project Vision](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-Vision) *last updated on 10.10*
* [Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes) *last updated for iteration 2 on 10.10* * [Release Notes](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Release-notes) *last updated for iteration3 on 08.11*
* [Project tasks](https://tvp-mobile-authentication.atlassian.net/jira/software/projects/MOB/boards/1/backlog) (Ask Tanel for JIRA permissions if needed). * [Project tasks](https://tvp-mobile-authentication.atlassian.net/jira/software/projects/MOB/boards/1/backlog) (Ask Tanel for JIRA permissions if needed).
* [Project plan](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-plan) *last updated on 10.10* * [Project plan](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Project-plan) *last updated on 10.10*
* [Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases) *last updated on 10.10* * [Use Cases](https://github.com/TanelOrumaa/Estonian-ID-card-mobile-authenticator-POC/wiki/Use-Cases) *last updated on 10.10*

View File

@ -35,17 +35,20 @@ class MainActivity : AppCompatActivity() {
} }
binding.loginOptionNfcButton.setOnClickListener { launchAuth() } binding.loginOptionNfcButton.setOnClickListener { launchAuth() }
//binding.loginOptionNfcButton.setOnClickListener { getData() }
} }
/** /**
* Method that creates an intent to launch the MobileAuthApp * Method that creates an intent to launch the MobileAuthApp
*/ */
private fun launchAuth(arg: String = "nothing") { private fun launchAuth(challenge: String = "challenge", authUrl: String = "authUrl") {
val launchIntent = Intent() val launchIntent = Intent()
launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity") launchIntent.setClassName("com.tarkvaraprojekt.mobileauthapp", "com.tarkvaraprojekt.mobileauthapp.MainActivity")
launchIntent.putExtra("auth", true) launchIntent.putExtra("action", "auth")
launchIntent.putExtra("nonce", arg) // Currently nothing launchIntent.putExtra("challenge", challenge)
launchIntent.putExtra("authUrl", authUrl)
launchIntent.putExtra("mobile", true)
authLauncher.launch(launchIntent) authLauncher.launch(launchIntent)
} }
@ -54,14 +57,17 @@ class MainActivity : AppCompatActivity() {
* Ion library is used as it is very convenient for making simple GET requests. * Ion library is used as it is very convenient for making simple GET requests.
*/ */
private fun getData() { private fun getData() {
val url = "real-address-here" // Enter the server endpoint address to here
val baseUrl = "enter-base-url-here"
val url = "$baseUrl/auth/challenge"
Ion.with(applicationContext) Ion.with(applicationContext)
.load(url) .load(url)
.asJsonObject() .asJsonObject()
.setCallback { _, result -> .setCallback { _, result ->
try { try {
// Get data from the result and call launchAuth method // Get data from the result and call launchAuth method
launchAuth() val challenge = result.asJsonObject["nonce"].toString()
launchAuth(challenge, baseUrl)
} catch (e: Exception) { } catch (e: Exception) {
Log.i("GETrequest", "was unsuccessful") Log.i("GETrequest", "was unsuccessful")
} }