Skip to content

Commit

Permalink
Classify Infobip's SIGNALS_BLOCKED response as "rejected as fraud"
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-signal committed Apr 26, 2024
1 parent 41cdfbf commit 3e5832c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.infobip.ApiException;
import com.infobip.model.MessageStatus;
import io.micronaut.http.HttpStatus;
import org.signal.registration.sender.SenderFraudBlockException;
import org.signal.registration.sender.SenderRejectedRequestException;
import org.signal.registration.util.CompletionExceptions;
import javax.annotation.Nullable;
Expand All @@ -21,6 +22,7 @@ public class InfobipExceptions {
HttpStatus.UNAUTHORIZED
);

// See https://www.infobip.com/docs/essentials/response-status-and-error-codes
private static final Set<Integer> REJECTED_GROUP_IDS = Set.of(
2, // Message not delivered
4, // Expired
Expand Down Expand Up @@ -67,6 +69,14 @@ public static Exception toSenderException(final ApiException exception) {
return null;
}

public static void maybeThrowSenderFraudBlockException(final MessageStatus status) throws SenderFraudBlockException {
// Group 4 is "EXPIRED" and ID 87 is "SIGNALS_BLOCKED", which is defined as "Message has been rejected due to an
// anti-fraud mechanism"
if (status.getGroupId() == 4 && status.getId() == 87) {
throw new SenderFraudBlockException("Message has been rejected due to an anti-fraud mechanism");
}
}

public static void maybeThrowInfobipRejectedRequestException(final MessageStatus status) throws InfobipRejectedRequestException {
if (REJECTED_GROUP_IDS.contains(status.getGroupId())) {
throw new InfobipRejectedRequestException(status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,9 @@ private static String checkSenderRejectedAndExtractMessageId(@Nullable final Lis
.findFirst()
.orElseThrow(() -> new SenderRejectedRequestException("No SMS response with a message ID"));

// Infobip enabled our account to return a 200 response with groupId 5 ("rejected") in the response body
// for a create SMS request, so we check for that and convert it into an exception.
// Infobip enabled our account to return a 200 response with groupId 4 ("expired") or 5 ("rejected") in the response
// body for a create SMS request, so we check for that and convert it into an exception.
InfobipExceptions.maybeThrowSenderFraudBlockException(finalResponseDetail.getStatus());
InfobipExceptions.maybeThrowInfobipRejectedRequestException(finalResponseDetail.getStatus());
return finalResponseDetail.getMessageId();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.signal.registration.sender.infobip.classic;

import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
Expand All @@ -17,11 +18,13 @@
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletionException;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.signal.registration.sender.ApiClientInstrumenter;
import org.signal.registration.sender.ClientType;
import org.signal.registration.sender.MessageTransport;
import org.signal.registration.sender.SenderFraudBlockException;
import org.signal.registration.sender.VerificationCodeGenerator;
import org.signal.registration.sender.VerificationSmsBodyProvider;
import org.signal.registration.sender.infobip.InfobipSenderConfiguration;
Expand Down Expand Up @@ -81,4 +84,29 @@ void sendAndVerify() throws ApiException {

assertTrue(sender.checkVerificationCode("123456", senderData).join());
}

@Test
void sendRejectedSignalsBlocked() throws ApiException {
final SmsApi.SendSmsMessageRequest messageRequest = mock(SmsApi.SendSmsMessageRequest.class);
final SmsResponse response = mock(SmsResponse.class);
final SmsResponseDetails details = mock(SmsResponseDetails.class);
final MessageStatus status = mock(MessageStatus.class);

when(codeGenerator.generateVerificationCode()).thenReturn("123456");
when(client.sendSmsMessage(any())).thenReturn(messageRequest);
when(messageRequest.execute()).thenReturn(response);
when(response.getMessages()).thenReturn(List.of(details));
when(details.getStatus()).thenReturn(status);
when(details.getMessageId()).thenReturn(RandomStringUtils.randomNumeric(22));
when(status.getGroupId()).thenReturn(4);
when(status.getId()).thenReturn(87);

final CompletionException completionException = assertThrows(CompletionException.class,
() -> sender.sendVerificationCode(MessageTransport.SMS,
PhoneNumberUtil.getInstance().getExampleNumber("US"),
Locale.LanguageRange.parse("en"), ClientType.UNKNOWN)
.join());

assertInstanceOf(SenderFraudBlockException.class, completionException.getCause());
}
}

0 comments on commit 3e5832c

Please sign in to comment.