Pass custom object to authenticated routes - kotlin

I was wondering if there was a way to configure the Authentication plugin so that I can pass some params (e.g. a user object) to my route handlers (similar to how UserIdPrincipal is passed in).
As an example, it might look something like this
install(Authentication) {
basic("auth-basic") {
validate { credentials ->
val user = userRepo.verifyUser(credentials.name, credentials.password)
if (user != null) {
// UserIdPrincipal(credentials.name) // current impl
user // desired
} else {
log.info("Unauthorized route access with credential name: ${credentials.name}")
null
}
}
}
}
then in my routes, I could do something like
post("/foo") {
val user = call.receive<User>()
}

You can implement the Principal interface for your User class to receive it in a route. Here is an example:
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(Authentication) {
basic("auth-basic") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
userRepo.verifyUser(credentials.name, credentials.password)
} else {
null
}
}
}
}
routing {
authenticate("auth-basic") {
get("/login") {
val user = call.principal<User>()
println("Hello ${user?.name}")
}
}
}
}.start(wait = true)
}
class User(val name: String): Principal

Related

How to use firebase admin with ktor 2.0

Does anyone have an idea how to convert these codes to ktor 2.0.
https://gist.github.com/togisoft/d1113a83eeb1d6b52031f77fe780ce48
If someone needs to see a complete sample of Aleksei's answer, I created a sample repository.
I did make some slight tweaks to the other answer as the implementation error was missing the correct messaging from the original gist.
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseToken
import io.ktor.http.auth.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class FirebaseAuthProvider(config: FirebaseConfig): AuthenticationProvider(config) {
val authHeader: (ApplicationCall) -> HttpAuthHeader? = config.authHeader
private val authFunction = config.firebaseAuthenticationFunction
override suspend fun onAuthenticate(context: AuthenticationContext) {
val token = authHeader(context.call)
if (token == null) {
context.challenge(FirebaseJWTAuthKey, AuthenticationFailedCause.InvalidCredentials) { challengeFunc, call ->
challengeFunc.complete()
call.respond(UnauthorizedResponse(HttpAuthHeader.bearerAuthChallenge(realm = FIREBASE_AUTH)))
}
return
}
try {
val principal = verifyFirebaseIdToken(context.call, token, authFunction)
if (principal != null) {
context.principal(principal)
}
} catch (cause: Throwable) {
val message = cause.message ?: cause.javaClass.simpleName
context.error(FirebaseJWTAuthKey, AuthenticationFailedCause.Error(message))
}
}
}
class FirebaseConfig(name: String?) : AuthenticationProvider.Config(name) {
internal var authHeader: (ApplicationCall) -> HttpAuthHeader? =
{ call -> call.request.parseAuthorizationHeaderOrNull() }
var firebaseAuthenticationFunction: AuthenticationFunction<FirebaseToken> = {
throw NotImplementedError(FirebaseImplementationError)
}
fun validate(validate: suspend ApplicationCall.(FirebaseToken) -> User?) {
firebaseAuthenticationFunction = validate
}
}
public fun AuthenticationConfig.firebase(name: String? = FIREBASE_AUTH, configure: FirebaseConfig.() -> Unit) {
val provider = FirebaseAuthProvider(FirebaseConfig(name).apply(configure))
register(provider)
}
suspend fun verifyFirebaseIdToken(
call: ApplicationCall,
authHeader: HttpAuthHeader,
tokenData: suspend ApplicationCall.(FirebaseToken) -> Principal?
): Principal? {
val token: FirebaseToken = try {
if (authHeader.authScheme == "Bearer" && authHeader is HttpAuthHeader.Single) {
withContext(Dispatchers.IO) {
FirebaseAuth.getInstance().verifyIdToken(authHeader.blob)
}
} else {
null
}
} catch (ex: Exception) {
ex.printStackTrace()
return null
} ?: return null
return tokenData(call, token)
}
private fun HttpAuthHeader.Companion.bearerAuthChallenge(realm: String): HttpAuthHeader {
return HttpAuthHeader.Parameterized("Bearer", mapOf(HttpAuthHeader.Parameters.Realm to realm))
}
private fun ApplicationRequest.parseAuthorizationHeaderOrNull() = try {
parseAuthorizationHeader()
} catch (ex: IllegalArgumentException) {
println("failed to parse token")
null
}
const val FIREBASE_AUTH = "FIREBASE_AUTH"
private const val FirebaseJWTAuthKey: String = "FirebaseAuth"
private const val FirebaseImplementationError =
"Firebase auth validate function is not specified, use firebase { validate { ... } }to fix"
Then to actually use in your project created an extension function on Application. Be sure that the Firebase Admin SDK has been initialized with credentials before installing the Firebase authentication plugin on Ktor.
fun Application.configureFirebaseAuth() {
FirebaseAdmin.init()
install(Authentication) {
firebase {
validate {
// TODO look up user profile to fill in any additional information on top of firebase user profile
User(it.uid, it.name)
}
}
}
}
Finally wrap a route with the authentication function:
authenticate(FIREBASE_AUTH) {
get("/authenticated") {
val user: User = call.principal() ?: return#get call.respond(HttpStatusCode.Unauthorized)
call.respond("User is authenticated: $user")
}
}
The converted to Ktor 2.0.* code is the following:
import io.ktor.http.auth.*
import io.ktor.serialization.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseToken
class FirebaseAuthProvider(config: FirebaseConfig): AuthenticationProvider(config) {
val authHeader: (ApplicationCall) -> HttpAuthHeader? = config.authHeader
private val authFunction = config.firebaseAuthenticationFunction
override suspend fun onAuthenticate(context: AuthenticationContext) {
val token = authHeader(context.call)
if (token == null) {
context.challenge(FirebaseJWTAuthKey, AuthenticationFailedCause.InvalidCredentials) { challengeFunc, call ->
challengeFunc.complete()
call.respond(UnauthorizedResponse(HttpAuthHeader.bearerAuthChallenge(realm = "firebaseAuth")))
}
return
}
try {
val principal = verifyFirebaseIdToken(context.call, token, authFunction)
if (principal != null) {
context.principal(principal)
}
} catch (cause: Throwable) {
val message = cause.message ?: cause.javaClass.simpleName
context.error(FirebaseJWTAuthKey, AuthenticationFailedCause.Error(message))
}
}
}
class FirebaseConfig(name: String?) : AuthenticationProvider.Config(name) {
internal var authHeader: (ApplicationCall) -> HttpAuthHeader? =
{ call -> call.request.parseAuthorizationHeaderOrNull() }
var firebaseAuthenticationFunction: AuthenticationFunction<FirebaseToken> = {
throw NotImplementedError(FirebaseImplementationError)
}
fun validate(validate: suspend ApplicationCall.(FirebaseToken) -> User?) {
firebaseAuthenticationFunction = validate
}
}
public fun AuthenticationConfig.firebase(name: String? = "firebaseAuth", configure: FirebaseConfig.() -> Unit) {
val provider = FirebaseAuthProvider(FirebaseConfig(name).apply(configure))
register(provider)
}
suspend fun verifyFirebaseIdToken(
call: ApplicationCall,
authHeader: HttpAuthHeader,
tokenData: suspend ApplicationCall.(FirebaseToken) -> Principal?
): Principal? {
val token: FirebaseToken = try {
if (authHeader.authScheme == "Bearer" && authHeader is HttpAuthHeader.Single) {
withContext(Dispatchers.IO) {
FirebaseAuth.getInstance().verifyIdToken(authHeader.blob)
}
} else {
null
}
} catch (ex: Exception) {
ex.printStackTrace()
return null
} ?: return null
return tokenData(call, token)
}
private fun HttpAuthHeader.Companion.bearerAuthChallenge(realm: String): HttpAuthHeader {
return HttpAuthHeader.Parameterized("Bearer", mapOf(HttpAuthHeader.Parameters.Realm to realm))
}
private fun ApplicationRequest.parseAuthorizationHeaderOrNull() = try {
parseAuthorizationHeader()
} catch (ex: IllegalArgumentException) {
println("failed to parse token")
null
}
private const val FirebaseJWTAuthKey: String = "FirebaseAuth"
private const val FirebaseImplementationError =
"Firebase auth validate function is not specified, use firebase { { ... } }to fix"

How to restrict route access in ktor framework?

How to restrict route access in ktor framework?
//only admin
post("/add") {
call.respondText { "add" }
}
post("/delete") {
call.respondText { "delete" }
}
You can write a method that creates a route that restricts access for admins only. Inside that method, the newly created route is intercepted to inject the code for validation. In the following example, if the header admin has the value 1 then a request is made from an admin otherwise for the /add and /delete routes the response with the 401 Unauthorized status will be returned.
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.util.pipeline.*
fun main() {
embeddedServer(Netty, port = 5555, host = "0.0.0.0") {
routing {
admin {
post("/add") {
call.respondText { "add" }
}
post("/delete") {
call.respondText { "delete" }
}
}
post("/show") {
call.respondText { "show" }
}
}
}.start(wait = false)
}
private val validationPhase = PipelinePhase("Validate")
fun Route.admin(build: Route.() -> Unit): Route {
val route = createChild(AdminSelector())
route.insertPhaseAfter(ApplicationCallPipeline.Features, Authentication.ChallengePhase)
route.insertPhaseAfter(Authentication.ChallengePhase, validationPhase)
route.intercept(validationPhase) {
if (!isAdmin(call.request)) {
call.respond(HttpStatusCode.Forbidden)
finish()
}
}
route.build()
return route
}
class AdminSelector: RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int) = RouteSelectorEvaluation.Transparent
}
fun isAdmin(request: ApplicationRequest): Boolean {
return request.headers["admin"] == "1"
}
For Ktor 2.x the solution proposed by Alexsei does not work anymore because ChallengePhase is now marked as internal as they completely restructured the plugin system.
This code snippet seems to be working for me.
fun Route.authorization(build: Route.() -> Unit): Route {
val route = createChild(CustomSelector())
val plugin = createRouteScopedPlugin("CustomAuthorization") {
on(AuthenticationChecked) { call ->
val principal = call.authentication.principal
// custom logic
}
}
route.install(plugin)
route.build()
return route
}
private class CustomSelector : RouteSelector() {
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int) = RouteSelectorEvaluation.Transparent
}
Of course, you can add parameters to the function specifying the restriction like required roles.
To secure a route...
fun Route.myRoute() = route("test") {
authorization {
get { ... }
}
}
For more details: https://ktor.io/docs/custom-plugins.html

Testing endpoint under auth with Ktor

I'm struggling to write tests for an endpoint that is under auth (token). In particular, when writing my test, I'm not able to chain the login request with my second request, despite providing the token received as part of the login request.
LoginEndpoint.kt
const val LOGIN_ENDPOINT = "$API_VERSION/login"
#Location(LOGIN_ENDPOINT)
class Login
fun Route.loginEndpoint(patientsRepository: Repository<Patient>, authProvider: AuthProvider) {
post<Login> {
val params = call.receive<Parameters>()
val userId = params["id"] ?: return#post call.respond(status = HttpStatusCode.BadRequest, message = "Missing user id")
val user = patientsRepository.get(userId)
if (user != null) {
val token = authProvider.generateToken(user.id!!)
call.respond(TextContent("{ \"token\": \"$token\" }", ContentType.Application.Json))
} else {
call.respond(status = HttpStatusCode.NotFound, message = "User with id $userId does not exist")
}
}
}
LoginEndpointTest.kt (passing, all good)
#Test
fun `given user exists then returns 200 and token`() {
val userId = "paco123"
val token = "magic_token_123"
withTestApplication({
givenTestModule()
}) {
every { authProvider.generateToken(userId) } answers { token }
givenPatientExists(userId)
with(handleRequest(HttpMethod.Post, "/$API_VERSION/login") {
addHeader("content-type", "application/x-www-form-urlencoded")
setBody("id=$userId")
}) {
assertEquals(HttpStatusCode.OK, response.status())
assertEquals("{ \"token\": \"magic_token_123\" }", response.content)
}
}
}
private fun Application.givenTestModule() {
module(
testing = true,
repositoryModule = TestingRepositoryModule,
authProvider = authProvider
)
}
Now, the problematic endpoint.
ProfileEndpoint.kt
const val PATIENTS_API_ENDPOINT = "$API_VERSION/profile"
#Location(PATIENTS_API_ENDPOINT)
class ProfileEndpoint
fun Route.profileEndpoint(patientsRepository: Repository<Patient>) {
authenticate("jwt") {
get<ProfileEndpoint> {
val apiUser: Patient = call.apiUser!!
val id = apiUser.id!!
val patient = patientsRepository.get(id)
when (patient != null) {
false -> call.respond(status = HttpStatusCode.NotFound, message = "Patient with id $id does not exist")
true -> call.respond(status = HttpStatusCode.OK, message = patient.map())
}
}
}
}
Finally, my failing test
ProfileEndpointTest.kt
#Test
fun `given user is logged in then returns 200 and profile`() {
val userId = "paco123"
val token = "magic_token_123"
withTestApplication({
givenTestModule()
}) {
every { authProvider.generateToken(userId) } answers { token }
givenPatientExists(userId)
handleRequest(HttpMethod.Post, "/$API_VERSION/login") {
addHeader("content-type", "application/x-www-form-urlencoded")
setBody("id=$userId")
}
handleRequest(HttpMethod.Get, "/$API_VERSION/profile") {
addHeader("Authorization", "Bearer $token")
}.apply {
assertEquals(HttpStatusCode.OK, response.status())
}
}
}
The error is:
expected:<200 OK> but was:<401 Unauthorized>
Expected :200 OK
Actual :401 Unauthorized
AuthProvider.kt
open class AuthProvider(secret: String = System.getenv(Environment.JWT_SECRET)) {
private val algorithm = Algorithm.HMAC512(secret)
fun getVerifier(): JWTVerifier = JWT
.require(algorithm)
.withIssuer(APP_NAME)
.build()
fun generateToken(userId: String): String = JWT.create()
.withSubject(SUBJECT)
.withIssuer(APP_NAME)
.withClaim(CLAIM, userId)
.withExpiresAt(expiresAt())
.sign(algorithm)
private fun expiresAt() = Date(System.currentTimeMillis() + MILLIES_PER_DAY * TOKEN_DAYS_LENGTH)
}
val ApplicationCall.apiUser get() = authentication.principal<Patient>()
I've tried using cookiesSession like in this documentation's example https://ktor.io/servers/testing.html#preserving-cookies but it didn't work. Any help would be much appreciated.

Observing Firebase Authenfication with ObservableObject

I am trying to observe firebase authentification and update my View accordingly.
I have an SessionStore object:
class SessionStore: ObservableObject {
#Published var session: Account?
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
I use it in a view like this:
struct TabBarView: View {
#EnvironmentObject var sessionStore: SessionStore
#State var selectedTab = Tab.swiping
enum Tab: Int {
case swiping, matches, profil
}
func getUser() {
sessionStore.listen()
}
var body: some View {
Group {
if (sessionStore.session != nil) {
TabView(selection: $selectedTab) {
SwipingView().tabItem {
TabBarItem(text: "Text", image: "pause.circle")
}.tag(Tab.swiping)
}
} else {
LoginView()
}
}.onAppear(perform: getUser).onDisappear(perform: sessionStore.stopListen)
}
}
And call it like this:
sessionStore = SessionStore()
TabBarView().environmentObject(sessionStore!)
But it is only showing the LoginView even when the session is not nil. I made some code changes this is actually the solution.
I think this is the way to do that
class SessionStore: ObservableObject {
#Published var session: Account?
Also you referenced self inside the state closure meaning your object will never deinit. Add unowned or weak like so:
handle = Auth.auth().addStateDidChangeListener { [unowned self] (auth, user) in
https://www.avanderlee.com/swift/weak-self/
The correct implementation is:
class SessionStore: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var session: Account? {
didSet {
objectWillChange.send()
}
}
var handle: AuthStateDidChangeListenerHandle?
deinit {
stopListen()
}
func listen() {
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
print("User logged in: \(user)")
self.session = Account.preData
} else {
self.session = nil
}
}
}
func stopListen() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
}
More info about it:
https://www.pointfree.co/blog/posts/30-swiftui-and-state-management-corrections

What's the right way to get permissions for phone call intent

How to request permissions using Kotlin.
I am trying to make a phone call function
fun buChargeEvent(view: View){
var number: Int = txtCharge.text.toString().toInt()
val intentChrage = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$number")
startActivity(intentChrage)
}
I added user permissions in manifest
but still having the same
error .
You need to add permission to your manifest first
<uses-permission android:name="android.permission.CALL_PHONE" />
After permission added in manifest following code would work fine for you "Number_to_call" will be youe number that is need to be replaced
val call = Intent(Intent.ACTION_DIAL)
call.setData(Uri.parse("tel:" +"Number_to_call"))
startActivity(call)
You need to add the run time permission. Download the source code from here
//Click function of layout:
rl_call.setOnClickListener {
if (boolean_call) {
phonecall()
}else {
fn_permission(Manifest.permission.CALL_PHONE,CALLMODE)
}
}
// Request permission response
fun fn_permission(permission:String,mode:Int){
requestPermissions(permission, object : PermissionCallBack {
override fun permissionGranted() {
super.permissionGranted()
Log.v("Call permissions", "Granted")
boolean_call=true
phonecall()
}
override fun permissionDenied() {
super.permissionDenied()
Log.v("Call permissions", "Denied")
boolean_call=false
}
})
}
// function to call intent
fun phonecall() {
val intent = Intent(Intent.ACTION_CALL);
intent.data = Uri.parse("tel:1234567890s")
startActivity(intent)
}
Thanks!
First you need to add permission to your manifest first :
<uses-permission android:name="android.permission.CALL_PHONE" />
This bit of code is used on the place of your method :
fun buChargeEvent(view: View) {
var number: Int = txtCharge.text.toString().toInt()
val callIntent = Intent(Intent.ACTION_CALL)
callIntent.data = Uri.parse("tel:$number")
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this as Activity,
Manifest.permission.CALL_PHONE)) {
} else {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CALL_PHONE),
MY_PERMISSIONS_REQUEST_CALL_PHONE)
}
}
startActivity(callIntent)
}
You need to request the runtime permission, since Android 6.0 certain permissions require you to ask at install and again at runtime.
Following the instructions here explains how to ask for permission at runtime.
This is the complete code for runtime permissions for Call Phone
Step 1:- add permission in manifest
<uses-permission android:name="android.permission.CALL_PHONE" />
Step 2:- Call this method checkAndroidVersion() in onCreate()
fun checkAndroidVersion() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkAndRequestPermissions()) {
} else {
}
} else {
// do code for pre-lollipop devices
}
}
val REQUEST_ID_MULTIPLE_PERMISSIONS = 1
fun checkAndRequestPermissions(): Boolean {
val call = ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CALL_PHONE)
val listPermissionsNeeded = ArrayList<String>()
if (call != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.CALL_PHONE)
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this#MainActivity, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS)
return false
}
return true
}
fun checkAndRequestPermissions(): Boolean {
val call = ContextCompat.checkSelfPermission(this#MainActivity, Manifest.permission.CALL_PHONE)
val listPermissionsNeeded = ArrayList<String>()
if (call != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.CALL_PHONE)
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this#MainActivity, listPermissionsNeeded.toTypedArray(), REQUEST_ID_MULTIPLE_PERMISSIONS)
return false
}
return true
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>, grantResults: IntArray) {
Log.d("in fragment on request", "Permission callback called-------")
when (requestCode) {
REQUEST_ID_MULTIPLE_PERMISSIONS -> {
val perms = HashMap<String, Int>()
// Initialize the map with both permissions
perms[Manifest.permission.CALL_PHONE] = PackageManager.PERMISSION_GRANTED
// Fill with actual results from user
if (grantResults.size > 0) {
for (i in permissions.indices)
perms[permissions[i]] = grantResults[i]
// Check for both permissions
if (perms[Manifest.permission.CALL_PHONE] == PackageManager.PERMISSION_GRANTED
) {
print("Storage permissions are required")
// process the normal flow
//else any one or both the permissions are not granted
} else {
Log.d("in fragment on request", "Some permissions are not granted ask again ")
//permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
// // shouldShowRequestPermissionRationale will return true
//show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
if (ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.READ_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this#MainActivity, Manifest.permission.ACCESS_FINE_LOCATION)) {
showDialogOK("Call permission is required for this app",
DialogInterface.OnClickListener { dialog, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> checkAndRequestPermissions()
DialogInterface.BUTTON_NEGATIVE -> {
}
}// proceed with logic by disabling the related features or quit the app.
})
} else {
Toast.makeText(this#MainActivity, "Go to settings and enable permissions", Toast.LENGTH_LONG)
.show()
// //proceed with logic by disabling the related features or quit the app.
}//permission is denied (and never ask again is checked)
//shouldShowRequestPermissionRationale will return false
}
}
}
}
}
fun showDialogOK(message: String, okListener: DialogInterface.OnClickListener) {
AlertDialog.Builder(this#MainActivity)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", okListener)
.create()
.show()
}
**Step 3**:- On button click
fun buChargeEvent(view: View){
if(checkAndRequestPermissions(){
var number: Int = txtCharge.text.toString().toInt()
val intentChrage = Intent(Intent.ACTION_CALL)
intent.data = Uri.parse("tel:$number")
startActivity(intentChrage)
}
}