diff --git a/Vonage.Test/NumberInsightsV2/FraudCheck/SerializationTest.cs b/Vonage.Test/NumberInsightsV2/FraudCheck/SerializationTest.cs index 8498400b3..bca8866fe 100644 --- a/Vonage.Test/NumberInsightsV2/FraudCheck/SerializationTest.cs +++ b/Vonage.Test/NumberInsightsV2/FraudCheck/SerializationTest.cs @@ -14,80 +14,82 @@ public class SerializationTest private readonly SerializationTestHelper helper = new SerializationTestHelper( typeof(SerializationTest).Namespace, JsonSerializerBuilder.BuildWithSnakeCase()); - + [Fact] public void ShouldDeserialize200() => this.helper.Serializer .DeserializeObject(this.helper.GetResponseJson()) .Should() .BeSuccess(GetExpectedFraudCheckResponse()); - + [Fact] public void ShouldDeserialize200WithoutFraudScore() => this.helper.Serializer .DeserializeObject(this.helper.GetResponseJson()) .Should() .BeSuccess(GetExpectedFraudCheckResponseWithoutFraudScore()); - + [Fact] public void ShouldDeserialize200WithoutSimSwap() => this.helper.Serializer .DeserializeObject(this.helper.GetResponseJson()) .Should() .BeSuccess(GetExpectedFraudCheckResponseWithoutSimSwap()); - + [Fact] public void ShouldSerializeWithFraudScore() => BuildRequestWithFraudScore() .GetStringContent() .Should() .BeSuccess(this.helper.GetRequestJson()); - + [Fact] public void ShouldSerializeWithFraudScoreAndSimSwap() => BuildRequestWithFraudScoreAndSimSwap() .GetStringContent() .Should() .BeSuccess(this.helper.GetRequestJson()); - + [Fact] public void ShouldSerializeWithSimSwap() => BuildRequestWithSimSwap() .GetStringContent() .Should() .BeSuccess(this.helper.GetRequestJson()); - + private static FraudCheckResponse GetExpectedFraudCheckResponseWithoutFraudScore() => new FraudCheckResponse( new Guid("6cb4c489-0fc8-4c40-8c3d-95e7e74f9450"), "phone", new PhoneData("16197363066", "Orange France", "MOBILE"), Maybe.None, - Maybe.Some(new SimSwap(SimSwapStatus.Completed, true, "Mobile Network Operator Not Supported"))); - + Maybe.Some(new NumberInsightV2.FraudCheck.SimSwap(SimSwapStatus.Completed, + true, "Mobile Network Operator Not Supported"))); + private static FraudCheckResponse GetExpectedFraudCheckResponseWithoutSimSwap() => new FraudCheckResponse( new Guid("6cb4c489-0fc8-4c40-8c3d-95e7e74f9450"), "phone", new PhoneData("16197363066", "Orange France", "MOBILE"), Maybe.Some(new FraudScore("54", RiskRecommendation.Block, FraudScoreLabel.Low, "completed")), - Maybe.None); - + Maybe.None); + internal static Result BuildRequestWithFraudScore() => FraudCheckRequest.Build() .WithPhone("447009000000") .WithFraudScore() .Create(); - + internal static Result BuildRequestWithFraudScoreAndSimSwap() => FraudCheckRequest.Build() .WithPhone("447009000000") .WithFraudScore() .WithSimSwap() .Create(); - + internal static Result BuildRequestWithSimSwap() => FraudCheckRequest.Build() .WithPhone("447009000000") .WithSimSwap() .Create(); - + internal static FraudCheckResponse GetExpectedFraudCheckResponse() => new FraudCheckResponse( new Guid("6cb4c489-0fc8-4c40-8c3d-95e7e74f9450"), "phone", new PhoneData("16197363066", "Orange France", "MOBILE"), Maybe.Some(new FraudScore("54", RiskRecommendation.Flag, FraudScoreLabel.Medium, "completed")), - Maybe.Some(new SimSwap(SimSwapStatus.Failed, true, "Mobile Network Operator Not Supported"))); + Maybe.Some(new NumberInsightV2.FraudCheck.SimSwap(SimSwapStatus.Failed, + true, "Mobile Network Operator Not Supported"))); } \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs new file mode 100644 index 000000000..0c9a13331 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs @@ -0,0 +1,68 @@ +using FluentAssertions; +using Vonage.Common.Failures; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Request")] +public class AuthenticateRequest +{ + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Parse_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Should() + .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); + + [Fact] + public void Parse_ShouldReturnNumberWithPlusIndicator() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789") + .Map(number => number.PhoneNumber.NumberWithInternationalIndicator) + .Should() + .BeSuccess("+123456789"); + + [Fact] + public void Parse_ShouldReturnFailure_GivenNumberContainsNonDigits() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("1234567abc123").Should() + .BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits.")); + + [Fact] + public void Parse_ShouldReturnFailure_GivenNumberLengthIsHigherThan7() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456").Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7.")); + + [Fact] + public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("1234567890123456").Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be higher than 15.")); + + [Theory] + [InlineData("1234567", "1234567")] + [InlineData("123456789012345", "123456789012345")] + public void Parse_ShouldReturnSuccess_GivenNumberIsValid(string value, string expected) => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Map(number => number.PhoneNumber.Number).Should() + .BeSuccess(expected); + + [Theory] + [InlineData("+1234567890", "1234567890")] + [InlineData("+123456789012345", "123456789012345")] + [InlineData("+++1234567890", "1234567890")] + public void Parse_ShouldReturnSuccess_GivenNumberStartWithPlus(string value, string expected) => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Map(number => number.PhoneNumber.Number).Should() + .BeSuccess(expected); + + [Fact] + public void ToString_ShouldReturnNumber() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789").Map(number => number.PhoneNumber.ToString()) + .Should() + .BeSuccess("123456789"); + + [Fact] + public void BuildAuthorizeRequest() + { + var request = Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789").GetSuccessUnsafe(); + request.BuildAuthorizeRequest().Number.Should().Be(request.PhoneNumber); + } +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs b/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs new file mode 100644 index 000000000..f8becb43f --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/AuthorizeRequestTest.cs @@ -0,0 +1,15 @@ +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Request")] +public class AuthorizeRequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789") + .Map(request => request.BuildAuthorizeRequest()) + .Map(r => r.GetEndpointPath()) + .Should().BeSuccess("oauth2/bc-authorize"); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/AuthorizeResponseTest.cs b/Vonage.Test/SimSwap/Authenticate/AuthorizeResponseTest.cs new file mode 100644 index 000000000..8dfb76070 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/AuthorizeResponseTest.cs @@ -0,0 +1,15 @@ +using FluentAssertions; +using Vonage.SimSwap.Authenticate; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Request")] +public class AuthorizeResponseTest +{ + [Fact] + public void BuildGetTokenRequest() => + new AuthorizeResponse("123456", "0", "0") + .BuildGetTokenRequest() + .Should().Be(new GetTokenRequest("123456")); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAccessToken-response.json b/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAccessToken-response.json new file mode 100644 index 000000000..73ab58daa --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAccessToken-response.json @@ -0,0 +1,5 @@ +{ + "access_token": "ABCDEFG", + "token_type": "Bearer", + "expires_in": 3600 +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAuthorize-response.json b/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAuthorize-response.json new file mode 100644 index 000000000..7b4249193 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/Data/ShouldDeserializeAuthorize-response.json @@ -0,0 +1,5 @@ +{ + "auth_req_id": "123456789", + "expires_in": "120", + "interval": "2" +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/E2ETest.cs b/Vonage.Test/SimSwap/Authenticate/E2ETest.cs new file mode 100644 index 000000000..7ab8df50c --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/E2ETest.cs @@ -0,0 +1,40 @@ +using System.Net; +using System.Threading.Tasks; +using Vonage.SimSwap.Authenticate; +using Vonage.Test.Common.Extensions; +using WireMock.ResponseBuilders; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "E2E")] +public class E2ETest : E2EBase +{ + public E2ETest() : base(typeof(E2ETest).Namespace) + { + } + + [Fact] + public async Task Authenticate() + { + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/bc-authorize") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody( + "login_hint=tel:%2B447700900000&scope=openid+dpv%3AFraudPreventionAndDetection%23check-sim-swap") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAuthorize)))); + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/token") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody("auth_req_id=123456789&grant_type=urn:openid:params:grant-type:ciba") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAccessToken)))); + await this.Helper.VonageClient.SimSwapClient + .AuthenticateAsync(Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("447700900000")) + .Should() + .BeSuccessAsync(new AuthenticateResponse("ABCDEFG")); + } +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/GetTokenRequestTest.cs b/Vonage.Test/SimSwap/Authenticate/GetTokenRequestTest.cs new file mode 100644 index 000000000..c621ea673 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/GetTokenRequestTest.cs @@ -0,0 +1,13 @@ +using FluentAssertions; +using Vonage.SimSwap.Authenticate; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Request")] +public class GetTokenRequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + new GetTokenRequest("123456").GetEndpointPath().Should().Be("oauth2/token"); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Authenticate/SerializationTest.cs b/Vonage.Test/SimSwap/Authenticate/SerializationTest.cs new file mode 100644 index 000000000..69395a3a3 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/SerializationTest.cs @@ -0,0 +1,31 @@ +using Vonage.Serialization; +using Vonage.SimSwap.Authenticate; +using Vonage.Test.Common; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Serialization")] +public class SerializationTest +{ + private readonly SerializationTestHelper helper = new SerializationTestHelper( + typeof(SerializationTest).Namespace, + JsonSerializerBuilder.BuildWithSnakeCase()); + + [Fact] + public void ShouldDeserializeAuthorize() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedAuthorizeResponse()); + + [Fact] + public void ShouldDeserializeAccessToken() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedTokenResponse()); + + internal static AuthorizeResponse GetExpectedAuthorizeResponse() => new AuthorizeResponse("123456789", "120", "2"); + + internal static GetTokenResponse GetExpectedTokenResponse() => new GetTokenResponse("ABCDEFG", "Bearer", 3600); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/E2EBase.cs b/Vonage.Test/SimSwap/E2EBase.cs new file mode 100644 index 000000000..10d16354b --- /dev/null +++ b/Vonage.Test/SimSwap/E2EBase.cs @@ -0,0 +1,16 @@ +using Vonage.Serialization; +using Vonage.Test.Common; +using Vonage.Test.TestHelpers; + +namespace Vonage.Test.SimSwap; + +public class E2EBase +{ + internal readonly TestingContext Helper; + internal readonly SerializationTestHelper Serialization; + + protected E2EBase(string serializationNamespace) : this() => this.Serialization = + new SerializationTestHelper(serializationNamespace, JsonSerializerBuilder.BuildWithSnakeCase()); + + protected E2EBase() => this.Helper = TestingContext.WithBearerCredentials("Url.Api.EMEA"); +} \ No newline at end of file diff --git a/Vonage.Test/Vonage.Test.csproj b/Vonage.Test/Vonage.Test.csproj index 472a4a1b4..d9279e5b4 100644 --- a/Vonage.Test/Vonage.Test.csproj +++ b/Vonage.Test/Vonage.Test.csproj @@ -47,6 +47,7 @@ + PreserveNewest @@ -1101,6 +1102,12 @@ PreserveNewest + + Always + + + Always + diff --git a/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs b/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs new file mode 100644 index 000000000..240a42fcd --- /dev/null +++ b/Vonage/SimSwap/Authenticate/AuthenticateRequest.cs @@ -0,0 +1,28 @@ +using Vonage.Common; +using Vonage.Common.Monads; + +namespace Vonage.SimSwap.Authenticate; + +/// +/// Represents a request to authenticate towards SimSwap API. +/// +public readonly struct AuthenticateRequest +{ + /// + /// Parses the input into an AuthenticateRequest. + /// + /// The phone number. + /// Success if the input matches all requirements. Failure otherwise. + public static Result Parse(string number) => + PhoneNumber.Parse(number).Map(phoneNumber => new AuthenticateRequest + { + PhoneNumber = phoneNumber, + }); + + /// + /// Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'. + /// + public PhoneNumber PhoneNumber { get; private init; } + + internal AuthorizeRequest BuildAuthorizeRequest() => new AuthorizeRequest(this.PhoneNumber); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs b/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs new file mode 100644 index 000000000..4e95dd510 --- /dev/null +++ b/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs @@ -0,0 +1,7 @@ +namespace Vonage.SimSwap.Authenticate; + +/// +/// Represents an authentication response. +/// +/// The access token. +public record AuthenticateResponse(string AccessToken); \ No newline at end of file diff --git a/Vonage/SimSwap/Authenticate/AuthorizeRequest.cs b/Vonage/SimSwap/Authenticate/AuthorizeRequest.cs new file mode 100644 index 000000000..2037bc124 --- /dev/null +++ b/Vonage/SimSwap/Authenticate/AuthorizeRequest.cs @@ -0,0 +1,31 @@ +using System.Net; +using System.Net.Http; +using System.Text; +using Vonage.Common; +using Vonage.Common.Client; + +namespace Vonage.SimSwap.Authenticate; + +internal record AuthorizeRequest(PhoneNumber Number) : IVonageRequest +{ + public string GetEndpointPath() => "oauth2/bc-authorize"; + + public HttpRequestMessage BuildRequestMessage() => + VonageRequestBuilder + .Initialize(HttpMethod.Post, this.GetEndpointPath()) + .WithContent(this.GetRequestContent()) + .Build(); + + private string GetUrlEncoded() + { + var builder = new StringBuilder(); + builder.Append("login_hint=tel:"); + builder.Append(WebUtility.UrlEncode(this.Number.NumberWithInternationalIndicator)); + builder.Append("&scope="); + builder.Append(WebUtility.UrlEncode("openid dpv:FraudPreventionAndDetection#check-sim-swap")); + return builder.ToString(); + } + + private StringContent GetRequestContent() => + new StringContent(this.GetUrlEncoded(), Encoding.UTF8, "application/x-www-form-urlencoded"); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Authenticate/AuthorizeResponse.cs b/Vonage/SimSwap/Authenticate/AuthorizeResponse.cs new file mode 100644 index 000000000..1149b0647 --- /dev/null +++ b/Vonage/SimSwap/Authenticate/AuthorizeResponse.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace Vonage.SimSwap.Authenticate; + +internal record AuthorizeResponse( + [property: JsonPropertyName("auth_req_id")] + string RequestId, + [property: JsonPropertyName("expires_in")] + string ExpiresIn, + [property: JsonPropertyName("interval")] + string Interval) +{ + internal GetTokenRequest BuildGetTokenRequest() => new GetTokenRequest(this.RequestId); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Authenticate/GetTokenRequest.cs b/Vonage/SimSwap/Authenticate/GetTokenRequest.cs new file mode 100644 index 000000000..3fa69a61a --- /dev/null +++ b/Vonage/SimSwap/Authenticate/GetTokenRequest.cs @@ -0,0 +1,29 @@ +using System.Net; +using System.Net.Http; +using System.Text; +using Vonage.Common.Client; + +namespace Vonage.SimSwap.Authenticate; + +internal record GetTokenRequest(string RequestId) : IVonageRequest +{ + public HttpRequestMessage BuildRequestMessage() => + VonageRequestBuilder + .Initialize(HttpMethod.Post, this.GetEndpointPath()) + .WithContent(this.GetRequestContent()) + .Build(); + + public string GetEndpointPath() => "oauth2/token"; + + private string GetUrlEncoded() + { + var builder = new StringBuilder(); + builder.Append("auth_req_id="); + builder.Append(WebUtility.UrlEncode(this.RequestId)); + builder.Append("&grant_type=urn:openid:params:grant-type:ciba"); + return builder.ToString(); + } + + private StringContent GetRequestContent() => + new StringContent(this.GetUrlEncoded(), Encoding.UTF8, "application/x-www-form-urlencoded"); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Authenticate/GetTokenResponse.cs b/Vonage/SimSwap/Authenticate/GetTokenResponse.cs new file mode 100644 index 000000000..75e045c96 --- /dev/null +++ b/Vonage/SimSwap/Authenticate/GetTokenResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace Vonage.SimSwap.Authenticate; + +internal record GetTokenResponse( + [property: JsonPropertyName("access_token")] + string AccessToken, + [property: JsonPropertyName("token_type")] + string TokenType, + [property: JsonPropertyName("expires_in")] + int ExpiresIn); \ No newline at end of file diff --git a/Vonage/SimSwap/ISimSwapClient.cs b/Vonage/SimSwap/ISimSwapClient.cs new file mode 100644 index 000000000..199374709 --- /dev/null +++ b/Vonage/SimSwap/ISimSwapClient.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Vonage.Common.Monads; +using Vonage.SimSwap.Authenticate; + +namespace Vonage.SimSwap; + +/// +/// Exposes SimSwap features. +/// +public interface ISimSwapClient +{ + /// + /// Authenticates towards SimSwap API to retrieve an authentication token. + /// + /// The request. + /// Success or Failure. + Task> AuthenticateAsync(Result request); +} \ No newline at end of file diff --git a/Vonage/SimSwap/SimSwapClient.cs b/Vonage/SimSwap/SimSwapClient.cs new file mode 100644 index 000000000..7e81a8ccf --- /dev/null +++ b/Vonage/SimSwap/SimSwapClient.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Vonage.Common.Client; +using Vonage.Common.Monads; +using Vonage.Serialization; +using Vonage.SimSwap.Authenticate; + +namespace Vonage.SimSwap; + +internal class SimSwapClient : ISimSwapClient +{ + private readonly VonageHttpClient vonageClient; + + internal SimSwapClient(VonageHttpClientConfiguration configuration) => + this.vonageClient = new VonageHttpClient(configuration, JsonSerializerBuilder.BuildWithSnakeCase()); + + /// + /// > + public Task> AuthenticateAsync(Result request) => + request.Map(BuildAuthorizeRequest) + .BindAsync(this.SendAuthorizeRequest) + .Map(BuildGetTokenRequest) + .BindAsync(this.SendGetTokenRequest) + .Map(BuildAuthenticateResponse); + + private static AuthenticateResponse BuildAuthenticateResponse(GetTokenResponse response) => + new AuthenticateResponse(response.AccessToken); + + private Task> SendGetTokenRequest(GetTokenRequest request) => + this.vonageClient.SendWithResponseAsync(request); + + private Task> SendAuthorizeRequest(AuthorizeRequest request) => + this.vonageClient.SendWithResponseAsync(request); + + private static GetTokenRequest BuildGetTokenRequest(AuthorizeResponse request) => request.BuildGetTokenRequest(); + + private static AuthorizeRequest BuildAuthorizeRequest(AuthenticateRequest request) => + request.BuildAuthorizeRequest(); +} \ No newline at end of file diff --git a/Vonage/VonageClient.cs b/Vonage/VonageClient.cs index fab0fac58..3dff90939 100644 --- a/Vonage/VonageClient.cs +++ b/Vonage/VonageClient.cs @@ -19,6 +19,7 @@ using Vonage.Redaction; using Vonage.Request; using Vonage.ShortCodes; +using Vonage.SimSwap; using Vonage.SubAccounts; using Vonage.Users; using Vonage.Verify; @@ -36,37 +37,37 @@ public class VonageClient private readonly Maybe configuration = Maybe.None; private readonly ITimeProvider timeProvider = new TimeProvider(); private Credentials credentials; - + /// /// Constructor for VonageClient. /// /// Credentials to be used for further HTTP calls. public VonageClient(Credentials credentials) => this.Credentials = credentials; - + internal VonageClient(Credentials credentials, Configuration configuration, ITimeProvider timeProvider) { this.timeProvider = timeProvider; this.configuration = configuration; this.Credentials = credentials; } - + internal VonageClient(Configuration configuration) { this.configuration = this.GetConfiguration(); this.Credentials = configuration.BuildCredentials(); } - + public IAccountClient AccountClient { get; private set; } - + public IApplicationClient ApplicationClient { get; private set; } - + /// /// Exposes Conversations features. /// public IConversationsClient ConversationsClient { get; private set; } - + public IConversionClient ConversionClient { get; private set; } - + /// /// Gets or sets credentials for this client. /// @@ -81,65 +82,67 @@ public Credentials Credentials this.PropagateCredentials(); } } - + /// /// Exposes Meetings features. /// public IMeetingsClient MeetingsClient { get; private set; } - + public IMessagesClient MessagesClient { get; private set; } - + public INumberInsightClient NumberInsightClient { get; private set; } - + /// /// Exposes Number Insight V2 features. /// public INumberInsightV2Client NumberInsightV2Client { get; private set; } - + public INumbersClient NumbersClient { get; private set; } - + public IPricingClient PricingClient { get; private set; } - + /// /// Exposes ProactiveConnect features. /// public IProactiveConnectClient ProactiveConnectClient { get; private set; } - + public IRedactClient RedactClient { get; private set; } - + public IShortCodesClient ShortCodesClient { get; private set; } - + public ISmsClient SmsClient { get; private set; } - + /// /// Exposes SubAccounts features. /// public ISubAccountsClient SubAccountsClient { get; private set; } - + /// /// Exposes User management features. /// public IUsersClient UsersClient { get; private set; } - + public IVerifyClient VerifyClient { get; private set; } - + /// /// Exposes VerifyV2 features. /// public IVerifyV2Client VerifyV2Client { get; private set; } - + /// /// Exposes Video features. /// public IVideoClient VideoClient { get; private set; } - + public IVoiceClient VoiceClient { get; private set; } - + + public ISimSwapClient SimSwapClient { get; private set; } + private VonageHttpClientConfiguration BuildConfiguration(HttpClient client) => new(client, this.Credentials.GetAuthenticationHeader(), this.Credentials.GetUserAgent()); - + private Configuration GetConfiguration() => this.configuration.IfNone(Configuration.Instance); - + private void PropagateCredentials() { var currentConfiguration = this.GetConfiguration(); @@ -167,6 +170,7 @@ private void PropagateCredentials() this.ConversationsClient = new ConversationsClient(nexmoConfiguration); this.MeetingsClient = new MeetingsClient(euConfiguration, new FileSystem()); this.ProactiveConnectClient = new ProactiveConnectClient(euConfiguration); + this.SimSwapClient = new SimSwapClient(euConfiguration); this.VideoClient = new VideoClient(videoConfiguration); } } \ No newline at end of file