Skip to content

Commit

Permalink
allow alphanumeric SMS sender ids (#8)
Browse files Browse the repository at this point in the history
also fixes bug, that AndroidSmsProvider does not send answer
  • Loading branch information
benkuly authored Dec 28, 2020
1 parent 3b791f8 commit 397d40b
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 179 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ repositories {
}

group = "net.folivo"
version = "0.5.5"
version = "0.5.6"
java.sourceCompatibility = JavaVersion.VERSION_11

tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar>() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import net.folivo.matrix.bot.event.MessageContext
import net.folivo.matrix.bot.membership.MatrixMembershipService
import net.folivo.matrix.bot.user.MatrixUserService
import net.folivo.matrix.bridge.sms.SmsBridgeProperties
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.core.model.MatrixId.RoomId
import net.folivo.matrix.core.model.MatrixId.UserId
import org.apache.tools.ant.types.Commandline
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.folivo.matrix.bridge.sms.provider
package net.folivo.matrix.bridge.sms.handler

import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.NumberParseException.ErrorType.NOT_A_NUMBER
Expand All @@ -11,6 +11,8 @@ class PhoneNumberService(private val smsBridgeProperties: SmsBridgeProperties) {

private val phoneNumberUtil: PhoneNumberUtil = PhoneNumberUtil.getInstance()

private val alphanumericRegex = "^(?=.*[a-zA-Z])(?=.*[a-zA-Z0-9])([a-zA-Z0-9 ]{1,11})\$".toRegex()

fun parseToInternationalNumber(raw: String): String {
return phoneNumberUtil.parse(raw, smsBridgeProperties.defaultRegion)
.let {
Expand All @@ -21,4 +23,8 @@ class PhoneNumberService(private val smsBridgeProperties: SmsBridgeProperties) {
"+${it.countryCode}${it.nationalNumber}"
}
}

fun isAlphanumeric(raw: String): Boolean {
return alphanumericRegex.matches(raw)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ReceiveSmsService(
private val messageService: MatrixMessageService,
private val membershipService: MatrixMembershipService,
private val roomService: MatrixRoomService,
private val phoneNumberService: PhoneNumberService,
private val matrixBotProperties: MatrixBotProperties,
private val smsBridgeProperties: SmsBridgeProperties
) {
Expand All @@ -31,68 +32,93 @@ class ReceiveSmsService(
private val LOG = LoggerFactory.getLogger(this::class.java)
}

suspend fun receiveSms(body: String, sender: String): String? {
val userIdLocalpart = "sms_${sender.substringAfter('+')}"
val userId =
if (sender.matches(Regex("\\+[0-9]{6,15}"))) {
UserId(userIdLocalpart, matrixBotProperties.serverName)
} else {
throw IllegalArgumentException("The sender did not match our regex for international telephone numbers.")
suspend fun receiveSms(body: String, providerSender: String): String? {
if (phoneNumberService.isAlphanumeric(providerSender)) {
sendMessageFromInvalidNumberToDefaultRoom(body, providerSender)
return null
} else {
val sender: String
try {
sender = phoneNumberService.parseToInternationalNumber(providerSender)
} catch (error: Throwable) {
LOG.debug("could not parse to international number", error)
sendMessageFromInvalidNumberToDefaultRoom(body, providerSender)
return null
}

val mappingTokenMatch = Regex("#[0-9]{1,9}").find(body)
val mappingToken = mappingTokenMatch?.value?.substringAfter('#')?.toInt()
val userIdLocalpart = "sms_${sender.removePrefix("+")}"
val userId = UserId(userIdLocalpart, matrixBotProperties.serverName)

val cleanedBody = mappingTokenMatch?.let { body.removeRange(it.range) }?.trim() ?: body
val mappingTokenMatch = Regex("#[0-9]{1,9}").find(body)
val mappingToken = mappingTokenMatch?.value?.substringAfter('#')?.toInt()

val roomIdFromMappingToken = mappingService.getRoomId(
userId = userId,
mappingToken = mappingToken
)
if (roomIdFromMappingToken != null) {
LOG.debug("receive SMS from $sender to $roomIdFromMappingToken")
matrixClient.roomsApi.sendRoomEvent(
roomIdFromMappingToken,
TextMessageEventContent(cleanedBody),
asUserId = userId
)
return null
} else if (smsBridgeProperties.singleModeEnabled) {
LOG.debug("receive SMS without or wrong mappingToken from $sender to single room")
val roomAliasId = RoomAliasId(userIdLocalpart, matrixBotProperties.serverName)
val roomIdFromAlias = roomService.getRoomAlias(roomAliasId)?.roomId
?: matrixClient.roomsApi.getRoomAlias(roomAliasId).roomId
val cleanedBody = mappingTokenMatch?.let { body.removeRange(it.range) }?.trim() ?: body

messageService.sendRoomMessage(
MatrixMessage(roomIdFromAlias, cleanedBody, isNotice = false, asUserId = userId)
val roomIdFromMappingToken = mappingService.getRoomId(
userId = userId,
mappingToken = mappingToken
)
if (roomIdFromMappingToken != null) {
LOG.debug("receive SMS from $sender to $roomIdFromMappingToken")
matrixClient.roomsApi.sendRoomEvent(
roomIdFromMappingToken,
TextMessageEventContent(cleanedBody),
asUserId = userId
)
return null
} else if (smsBridgeProperties.singleModeEnabled) {
LOG.debug("receive SMS without or wrong mappingToken from $sender to single room")
val roomAliasId = RoomAliasId(userIdLocalpart, matrixBotProperties.serverName)
val roomIdFromAlias = roomService.getRoomAlias(roomAliasId)?.roomId
?: matrixClient.roomsApi.getRoomAlias(roomAliasId).roomId

messageService.sendRoomMessage(
MatrixMessage(roomIdFromAlias, cleanedBody, isNotice = false, asUserId = userId)
)

if (membershipService.hasRoomOnlyManagedUsersLeft(roomIdFromAlias)) {
if (smsBridgeProperties.defaultRoomId != null) {
val message = templates.defaultRoomIncomingMessageWithSingleMode
.replace("{sender}", sender)
.replace("{roomAlias}", roomAliasId.toString())
matrixClient.roomsApi.sendRoomEvent(
smsBridgeProperties.defaultRoomId,
TextMessageEventContent(message)
)
} else return templates.answerInvalidTokenWithoutDefaultRoom.takeIf { !it.isNullOrEmpty() }
}
return null
} else {
LOG.debug("receive SMS without or wrong mappingToken from $sender to default room ${smsBridgeProperties.defaultRoomId}")

if (membershipService.hasRoomOnlyManagedUsersLeft(roomIdFromAlias)) {
if (smsBridgeProperties.defaultRoomId != null) {
val message = templates.defaultRoomIncomingMessageWithSingleMode
return if (smsBridgeProperties.defaultRoomId != null) {
val message = templates.defaultRoomIncomingMessage
.replace("{sender}", sender)
.replace("{roomAlias}", roomAliasId.toString())
.replace("{body}", cleanedBody)

matrixClient.roomsApi.sendRoomEvent(
smsBridgeProperties.defaultRoomId,
TextMessageEventContent(message)
)
} else return templates.answerInvalidTokenWithoutDefaultRoom.takeIf { !it.isNullOrEmpty() }
templates.answerInvalidTokenWithDefaultRoom.takeIf { !it.isNullOrEmpty() }
} else templates.answerInvalidTokenWithoutDefaultRoom.takeIf { !it.isNullOrEmpty() }
}
return null
} else {
LOG.debug("receive SMS without or wrong mappingToken from $sender to default room ${smsBridgeProperties.defaultRoomId}")
}
}

return if (smsBridgeProperties.defaultRoomId != null) {
val message = templates.defaultRoomIncomingMessage
.replace("{sender}", sender)
.replace("{body}", cleanedBody)
private suspend fun sendMessageFromInvalidNumberToDefaultRoom(body: String, providerSender: String) {
if (smsBridgeProperties.defaultRoomId != null) {
LOG.debug("receive SMS with invalid or alphanumeric number from sender $providerSender to default room ${smsBridgeProperties.defaultRoomId}")
val message = templates.defaultRoomIncomingMessage
.replace("{sender}", providerSender)
.replace("{body}", body)

matrixClient.roomsApi.sendRoomEvent(
smsBridgeProperties.defaultRoomId,
TextMessageEventContent(message)
)
templates.answerInvalidTokenWithDefaultRoom.takeIf { !it.isNullOrEmpty() }
} else templates.answerInvalidTokenWithoutDefaultRoom.takeIf { !it.isNullOrEmpty() }
matrixClient.roomsApi.sendRoomEvent(
smsBridgeProperties.defaultRoomId,
TextMessageEventContent(message)
)
} else {
LOG.warn("you got a message from an alphanumeric or invalid sender. You should enable default room to receive this message regular: $providerSender sent: $body")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.google.i18n.phonenumbers.NumberParseException
import kotlinx.coroutines.runBlocking
import net.folivo.matrix.bridge.sms.SmsBridgeProperties
import net.folivo.matrix.bridge.sms.handler.SmsSendCommand.RoomCreationMode.AUTO
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.core.model.MatrixId.UserId
import org.slf4j.LoggerFactory
import java.time.LocalDateTime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package net.folivo.matrix.bridge.sms.provider.android

import com.google.i18n.phonenumbers.NumberParseException
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.reactive.awaitFirstOrNull
import net.folivo.matrix.bridge.sms.SmsBridgeProperties
import net.folivo.matrix.bridge.sms.handler.ReceiveSmsService
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.bridge.sms.provider.SmsProvider
import net.folivo.matrix.core.model.events.m.room.message.NoticeMessageEventContent
import net.folivo.matrix.restclient.MatrixClient
Expand All @@ -16,7 +14,6 @@ import org.springframework.web.reactive.function.client.awaitBody

class AndroidSmsProvider(
private val receiveSmsService: ReceiveSmsService,
private val phoneNumberService: PhoneNumberService,
private val processedRepository: AndroidSmsProcessedRepository,
private val outSmsMessageRepository: AndroidOutSmsMessageRepository,
private val webClient: WebClient,
Expand Down Expand Up @@ -85,21 +82,15 @@ class AndroidSmsProvider(
response.messages
.sortedBy { it.id }
.fold(lastProcessed, { process, message ->
val answer = receiveSmsService.receiveSms(
message.body,
message.sender
)
try {
receiveSmsService.receiveSms(
message.body,
phoneNumberService.parseToInternationalNumber(message.sender)
)
} catch (error: NumberParseException) {
if (smsBridgeProperties.defaultRoomId != null)
matrixClient.roomsApi.sendRoomEvent(
smsBridgeProperties.defaultRoomId,
NoticeMessageEventContent(
smsBridgeProperties.templates.defaultRoomIncomingMessage
.replace("{sender}", message.sender)
.replace("{body}", message.body)
)
)
if (answer != null) sendSms(message.sender, answer)
} catch (error: Throwable) {
LOG.error("could not answer ${message.sender} with message $answer. Reason: ${error.message}")
LOG.debug("details:", error)
}
processedRepository.save(
process?.copy(lastProcessedId = message.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package net.folivo.matrix.bridge.sms.provider.android
import io.netty.handler.ssl.SslContextBuilder
import net.folivo.matrix.bridge.sms.SmsBridgeProperties
import net.folivo.matrix.bridge.sms.handler.ReceiveSmsService
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.restclient.MatrixClient
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
Expand Down Expand Up @@ -77,7 +76,6 @@ class AndroidSmsProviderConfiguration(private val properties: AndroidSmsProvider
@Bean
fun androidSmsProvider(
receiveSmsService: ReceiveSmsService,
phoneNumberService: PhoneNumberService,
processedRepository: AndroidSmsProcessedRepository,
outSmsMessageRepository: AndroidOutSmsMessageRepository,
@Qualifier("androidSmsProviderWebClient")
Expand All @@ -87,7 +85,6 @@ class AndroidSmsProviderConfiguration(private val properties: AndroidSmsProvider
): AndroidSmsProvider {
return AndroidSmsProvider(
receiveSmsService,
phoneNumberService,
processedRepository,
outSmsMessageRepository,
webClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package net.folivo.matrix.bridge.sms.provider.gammu

import kotlinx.coroutines.reactor.mono
import net.folivo.matrix.bridge.sms.handler.ReceiveSmsService
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.bridge.sms.provider.SmsProvider
import net.folivo.matrix.core.api.ErrorResponse
import net.folivo.matrix.core.api.MatrixServerException
Expand Down Expand Up @@ -32,8 +31,7 @@ import kotlin.text.Charsets.UTF_8
@EnableConfigurationProperties(GammuSmsProviderProperties::class)
class GammuSmsProvider(
private val properties: GammuSmsProviderProperties,
private val receiveSmsService: ReceiveSmsService,
private val phoneNumberService: PhoneNumberService
private val receiveSmsService: ReceiveSmsService
) : SmsProvider {

companion object {
Expand Down Expand Up @@ -127,9 +125,8 @@ class GammuSmsProvider(
body: String
): Mono<Void> {
return mono {
val phoneNumber = phoneNumberService.parseToInternationalNumber(sender)
receiveSmsService.receiveSms(body = body, sender = phoneNumber)
?.also { sendSms(receiver = phoneNumber, body = it) }
receiveSmsService.receiveSms(body = body, providerSender = sender)
?.also { sendSms(receiver = sender, body = it) }
}.then()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import net.folivo.matrix.bot.membership.MatrixMembershipService
import net.folivo.matrix.bot.user.MatrixUser
import net.folivo.matrix.bot.user.MatrixUserService
import net.folivo.matrix.bridge.sms.SmsBridgeProperties
import net.folivo.matrix.bridge.sms.provider.PhoneNumberService
import net.folivo.matrix.core.model.MatrixId.*

class MessageToBotHandlerTest : DescribeSpec(testBody())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.folivo.matrix.bridge.sms.provider
package net.folivo.matrix.bridge.sms.handler

import com.google.i18n.phonenumbers.NumberParseException
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.mockk.every
import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
Expand Down Expand Up @@ -36,4 +38,13 @@ class PhoneNumberServiceTest {

}
}

@Test
fun `should detect alphanumeric numbers`() {
cut.isAlphanumeric("DINODINO").shouldBeTrue()
cut.isAlphanumeric("DINODINODINO").shouldBeFalse()
cut.isAlphanumeric("123-DINO").shouldBeFalse()
cut.isAlphanumeric("+49123456789").shouldBeFalse()
cut.isAlphanumeric("0123456789").shouldBeFalse()
}
}
Loading

0 comments on commit 397d40b

Please sign in to comment.