Skip to content

Commit

Permalink
Merge pull request #647 from okta/lr-fix-638-null-response-after-rate…
Browse files Browse the repository at this point in the history
…-limit

Fix "Null response after hitting rate limit" issue
  • Loading branch information
laurarodriguez-okta authored May 25, 2023
2 parents 5efd966 + 1ad5bb2 commit f031765
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 13 deletions.
2 changes: 1 addition & 1 deletion API_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Allows customers to easily access the Okta Management APIs
This C# SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:

- API version: 3.0.0
- SDK version: 6.0.10
- SDK version: 6.0.11
- Build package: org.openapitools.codegen.languages.CSharpNetCoreClientCodegen
For more information, please visit [https://developer.okta.com/](https://developer.okta.com/)

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
Running changelog of releases since `3.1.1`

## 6.0.11

- Fix "Create/Update Account returns NULL when okta tenant hits rate limits" issue (#638)

## 6.0.10

- Fix "DeleteFactorAsync not removing phone with removeEnrollmentRecovery" issue (#630)
Expand Down
2 changes: 1 addition & 1 deletion openapi3/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"packageName" : "Okta.Sdk",
"outputDir" : "../",
"inputSpec" : "./management.yaml",
"packageVersion" : "6.0.10",
"packageVersion" : "6.0.11",
"packageDescription" : "Official .NET SDK for the Okta API",
"packageTitle" : "Official .NET SDK for the Okta API",
"packageCompany" : "Okta, Inc.",
Expand Down
24 changes: 20 additions & 4 deletions openapi3/templates/ApiClient.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,27 @@ namespace {{packageName}}.Client
if (policy != null)
{
var policyResult = await policy.ExecuteAndCaptureAsync(action: (ctx) => ExecuteAsyncWithRetryHeaders(ctx, req, client), new Context()).ConfigureAwait(false);
response = (policyResult.Outcome == OutcomeType.Successful) ? client.Deserialize<T>(policyResult.Result) : new RestResponse<T>
if (policyResult.Outcome == OutcomeType.Successful)
{
response = client.Deserialize<T>(policyResult.Result);
}
else //Failure
{
Request = req,
ErrorException = policyResult.FinalException
};
if (policyResult.FinalHandledResult != null)
{
RestResponse finalHandledResult = (RestResponse)policyResult.FinalHandledResult;
response = (RestResponse<T>)finalHandledResult;
}
else
{
response = new RestResponse<T>
{
Request = req,
ErrorException = policyResult.FinalException
};
}
}
}
else
{
Expand Down
157 changes: 157 additions & 0 deletions src/Okta.Sdk.UnitTest/Api/UsersApiTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using FluentAssertions;
using Okta.Sdk.Api;
using Okta.Sdk.Client;
using Okta.Sdk.Model;
using Okta.Sdk.UnitTest.Internal;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using Xunit;

namespace Okta.Sdk.UnitTest.Api
{
public class UsersApiTests
{
private readonly WireMockServer _server;


public UsersApiTests()
{
_server = WireMockServer.Start();
}

public void Dispose()
{
_server.Stop();
}

private void CreateStubReturningRateLimitResponse()
{
var dateHeader = new DateTimeOffset(DateTime.Now);
var resetTime = dateHeader.AddSeconds(1).ToUnixTimeSeconds();

_server.Given(
Request.Create().WithPath("/api/v1/users/foo")
).AtPriority(1)
.RespondWith(
Response.Create()
.WithStatusCode(429)
.WithHeader("Content-Type", "text/json")
.WithHeader("Date", dateHeader.ToString())
.WithHeader("x-rate-limit-reset", resetTime.ToString())
.WithHeader(DefaultRetryStrategy.XOktaRequestId, "foo")
.WithBody(@"{
""errorCode"": ""E0000047"",
""errorSummary"": ""API call exceeded rate limit due to too many requests."",
""errorLink"": ""E0000047"",
""errorId"": ""sample0eww_TKcLhu8FoK7qM4"",
""errorCauses"": []
}")

);
}

private void CreateStubReturningOAuthTokenResponse()
{
var tokenResponse =
@"{""access_token"" : ""eyJhbGciOiJSUzI1NiJ9.eyJ2ZXIiOjEsImlzcyI6Imh0dHA6Ly9yYWluLm9rdGExLmNvbToxODAyIiwiaWF0IjoxNDQ5NjI0MDI2LCJleHAiOjE0NDk2Mjc2MjYsImp0aSI6IlVmU0lURzZCVVNfdHA3N21BTjJxIiwic2NvcGVzIjpbIm9wZW5pZCIsImVtYWlsIl0sImNsaWVudF9pZCI6InVBYXVub2ZXa2FESnh1a0NGZUJ4IiwidXNlcl9pZCI6IjAwdWlkNEJ4WHc2STZUVjRtMGczIn0.HaBu5oQxdVCIvea88HPgr2O5evqZlCT4UXH4UKhJnZ5px-ArNRqwhxXWhHJisslswjPpMkx1IgrudQIjzGYbtLFjrrg2ueiU5-YfmKuJuD6O2yPWGTsV7X6i7ABT6P-t8PRz_RNbk-U1GXWIEkNnEWbPqYDAm_Ofh7iW0Y8WDA5ez1jbtMvd-oXMvJLctRiACrTMLJQ2e5HkbUFxgXQ_rFPNHJbNSUBDLqdi2rg_ND64DLRlXRY7hupNsvWGo0gF4WEUk8IZeaLjKw8UoIs-ETEwJlAMcvkhoVVOsN5dPAaEKvbyvPC1hUGXb4uuThlwdD3ECJrtwgKqLqcWonNtiw"",
""token_type"" : ""Bearer"",
""expires_in"" : 3600,
""scope"" : ""openid email"",
""refresh_token"" : ""a9VpZDRCeFh3Nkk2VdY"",
""id_token"" : ""eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHVpZDRCeFh3Nkk2VFY0bTBnMyIsImVtYWlsIjoid2VibWFzdGVyQGNsb3VkaXR1ZG
UubmV0IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MSwiaXNzIjoiaHR0cDovL3JhaW4ub2t0YTEuY29tOjE4MDIiLCJsb
2dpbiI6ImFkbWluaXN0cmF0b3IxQGNsb3VkaXR1ZGUubmV0IiwiYXVkIjoidUFhdW5vZldrYURKeHVrQ0ZlQngiLCJpYXQiOjE0
NDk2MjQwMjYsImV4cCI6MTQ0OTYyNzYyNiwiYW1yIjpbInB3ZCJdLCJqdGkiOiI0ZUFXSk9DTUIzU1g4WGV3RGZWUiIsImF1dGh
fdGltZSI6MTQ0OTYyNDAyNiwiYXRfaGFzaCI6ImNwcUtmZFFBNWVIODkxRmY1b0pyX1EifQ.Btw6bUbZhRa89DsBb8KmL9rfhku
--_mbNC2pgC8yu8obJnwO12nFBepui9KzbpJhGM91PqJwi_AylE6rp-ehamfnUAO4JL14PkemF45Pn3u_6KKwxJnxcWxLvMuuis
nvIs7NScKpOAab6ayZU0VL8W6XAijQmnYTtMWQfSuaaR8rYOaWHrffh3OypvDdrQuYacbkT0csxdrayXfBG3UF5-ZAlhfch1fhF
T3yZFdWwzkSDc0BGygfiFyNhCezfyT454wbciSZgrA9ROeHkfPCaX7KCFO8GgQEkGRoQntFBNjluFhNLJIUkEFovEDlfuB4tv_M
8BM75celdy3jkpOurg""
}";
_server.Given(
Request.Create().WithPath("/oauth2/v1/token*")
).AtPriority(1)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "text/json")
.WithBody(tokenResponse.Replace("\r|\n", string.Empty))
);
}

[Fact]
public async Task PartialUpdateUserAsyncThrowOnRateLimitErrorAfterMaxWithApiToken()
{
var configuration = new Configuration
{
OktaDomain = _server.Url!,
Token = "foo",
DisableOktaDomainCheck = true,
MaxRetries = 1,

};

var userApi = new UserApi(configuration);

CreateStubReturningRateLimitResponse();

var exception = await Assert.ThrowsAsync<ApiException>(async () => await userApi.PartialUpdateUserAsync("foo",
new UpdateUserRequest
{
Profile = new UserProfile
{
Email = "[email protected]",
}
}));

exception.Should().NotBeNull();
exception.ErrorCode.Should().Be(429);
}

[Fact]
public async Task PartialUpdateUserAsyncThrowOnRateLimitErrorAfterMaxRetriesWithOAuthToken()
{
var jsonPrivateKey = @"{
""p"":""2-8pgwYv9jrkM2KsbnQmnJZnr69Rsj95M20I1zx5HhM3tgjGSa7d_dELPRkp9Usy8UGISt7eUHpYOVl529irHwbXevuId1Q804aQ_AtNJwpbRY48rw2T8LdtyVSaEyoFMCa8PJwtzZYzKJCKAe5eoXvW5zxB65RaIct0igYcoIs"",
""kty"":""RSA"",
""q"":""slkNUY_SCIn95ip7HoPs_IIiNoKOGeesIV1gacyoAycly1vBPMhtar9gwx51nN1tCMVGlSOk583eRJe2omAbqkIEYm1jSWtMdJKQSOJvx812xbF1afMgJDlJ6iRIlcnWEYhNNMCK5s_UR5zE0Mc5jktxDFeiEatriyu6o9hQix8"",
""d"":""LIpJTKCi9hPTiuUU954hayd3lXNwTVS6Fdny2iUj6iZ22eRp1V_UswECuMy5B-8lWbp1Gu_eASvhElSCB26m3UgHRVy8LP6Lmvm9VlJuZ5NtOK5D0R-gzFLINGdQH1PehzEc44jsTWyu297lgCLrVy-VScHQJodni3txTzxY4jwjILMfLB7OWdKVkvDQ4g70BYTVN5kZKjA9B0lLsofi1gUY_EVlojuvSKbm3HY0JR_JThtEd_nZw_tXTYmlP58plVYX-9JnA8NcFRB6dUNO7XqcXU1SafWqoM9yam1nGSMYRknwjSSTKRdBXHSy7PVxVHhpC72wb3aWNsOqWNo0ZQ"",
""e"":""AQAB"",
""qi"":""u1mS53N4BUSoMeOHFd0o4_n3QGH4izNUsiJVBIkv_UZUAk4LYudPEikTWRLqWsrcXmOyZYao5sSaZyt-B2XCkfdnkIhl-Q7b0_W0yt3Eh5XjAzH9oy5Dklog255lh-Y0yoWXvLjq-KEDs7Nd2uIT4gvKU4ymTqybvzazr2jY9qQ"",""dp"":""nCtPBsK1-9oFgJdoaWYApOAH8DBFipSXs3SQ-oTuW_S5coD4jAmniDuQB2p-6LblDXrDFKb8pZi6XL60UO-hUv7As4s4c8NVDb5X5SEBP9-Sv-koHgU-L4eQZY21ejY0SOS4dTFRNNKasQsxc_2XJIOTLc8T3_wPpD-cGQYN_dE"",""dq"":""ZWb4iZ0qICzFLW6N3gXIYrFi3ndQcC4m0jmTLdRs2o4RkRQ0RGj4vS7ex1G0MWI8MjZoMTe49Qs6Cunvr1bRo_YxI_1p7D6Tk9wZKTeFsqaBl1mUlo7jgXUJL5U9p9zAV-uVah7nWuBjo-vgg4wij2MZfZj9zuoWFWThk3LUKKU"",""n"":""mTjMc8AxU102LT1Jf-1qkGmaSiK4L7DDlC1SMvtyCRbDaiJDIagedfp1w8Pgud8YWOaS5FFx0S6JqGGP2U8OtpowzBcv5sYa-e5LHfnoueTJPj_jnI3fj5omZM1w-ofhFLPZoYEQ7DFYw0yLrzf8zaKB5-9BZ8yyOLhSKqxaOl2s7lw2TrwBRuQpPXmEir70oDPvazd8-An5ow6F5q7mzMtHAt61DJqrosRHiRwh4N37zIX_RNu-Tn1aMktCBl01rdoDyVq7Y4iwNH8ZAtT5thKK2eo8d-jb9TF9PH6LGffYCth157w-K4AZwXw74Ybo5NOux3XpIpKRbFTwvBLp1Q""
}";

var configuration = new Configuration();
configuration.Scopes = new HashSet<string> { "okta.user.read" };
configuration.ClientId = "foo";
configuration.PrivateKey = new JsonWebKeyConfiguration(jsonPrivateKey);
configuration.AuthorizationMode = AuthorizationMode.PrivateKey;
configuration.OktaDomain = _server.Url!;
configuration.MaxRetries = 1;
configuration.DisableOktaDomainCheck = true;

var userApi = new UserApi(configuration);

CreateStubReturningOAuthTokenResponse();
CreateStubReturningRateLimitResponse();

var exception = await Assert.ThrowsAsync<ApiException>(async () => await userApi.PartialUpdateUserAsync("foo",
new UpdateUserRequest
{
Profile = new UserProfile
{
Email = "[email protected]",
}
}));

exception.Should().NotBeNull();
exception.ErrorCode.Should().Be(429);
}

}
}
24 changes: 20 additions & 4 deletions src/Okta.Sdk/Client/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -529,11 +529,27 @@ internal RestClient GetConfiguredClient(IReadableConfiguration configuration, ID
if (policy != null)
{
var policyResult = await policy.ExecuteAndCaptureAsync(action: (ctx) => ExecuteAsyncWithRetryHeaders(ctx, req, client), new Context()).ConfigureAwait(false);
response = (policyResult.Outcome == OutcomeType.Successful) ? client.Deserialize<T>(policyResult.Result) : new RestResponse<T>

if (policyResult.Outcome == OutcomeType.Successful)
{
response = client.Deserialize<T>(policyResult.Result);
}
else //Failure
{
Request = req,
ErrorException = policyResult.FinalException
};
if (policyResult.FinalHandledResult != null)
{
RestResponse finalHandledResult = (RestResponse)policyResult.FinalHandledResult;
response = (RestResponse<T>)finalHandledResult;
}
else
{
response = new RestResponse<T>
{
Request = req,
ErrorException = policyResult.FinalException
};
}
}
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions src/Okta.Sdk/Client/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class Configuration : IReadableConfiguration
/// Version of the package.
/// </summary>
/// <value>Version of the package.</value>
public const string Version = "6.0.10";
public const string Version = "6.0.11";

/// <summary>
/// Identifier for ISO 8601 DateTime Format
Expand Down Expand Up @@ -758,7 +758,7 @@ public static string ToDebugReport()
report += " OS: " + System.Environment.OSVersion + "\n";
report += " .NET Framework Version: " + System.Environment.Version + "\n";
report += " Version of the API: 3.0.0\n";
report += " SDK Package Version: 6.0.10\n";
report += " SDK Package Version: 6.0.11\n";

return report;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Okta.Sdk/Okta.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<Description>Official .NET SDK for the Okta API</Description>
<Copyright>Okta, Inc.</Copyright>
<RootNamespace>Okta.Sdk</RootNamespace>
<Version>6.0.10</Version>
<Version>6.0.11</Version>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Okta.Sdk.xml</DocumentationFile>
<RepositoryUrl>https://github.com/okta/okta-sdk-dotnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand Down

0 comments on commit f031765

Please sign in to comment.