Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow alphanumeric SMS sender ids #8

Merged
merged 1 commit into from
Dec 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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