Skip to content

Commit b79c2af

Browse files
committed
Save API key manually - add unit tests
1 parent ae7db42 commit b79c2af

File tree

6 files changed

+63
-12
lines changed

6 files changed

+63
-12
lines changed

src/Umbraco.AuthorizedServices/AuthorizedServicesComposer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ private static void RegisterServices(IUmbracoBuilder builder)
5757

5858
builder.Services.AddUnique<ITokenFactory, TokenFactory>();
5959
builder.Services.AddUnique<ITokenStorage, DatabaseTokenStorage>();
60+
builder.Services.AddUnique<IKeyStorage, DatabaseKeyStorage>();
6061

6162
builder.Services.AddSingleton<JsonSerializerFactory>();
6263
}

src/Umbraco.AuthorizedServices/Services/Implement/AuthorizedServiceCaller.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,12 @@ public async Task<string> SendRequestRawAsync<TRequest>(string serviceAlias, str
8181
HttpRequestMessage requestMessage;
8282
if (serviceDetail.AuthenticationMethod == AuthenticationMethod.ApiKey)
8383
{
84+
string? key = KeyStorage.GetKey(serviceAlias);
8485
requestMessage = _authorizedRequestBuilder.CreateRequestMessageWithApiKey(
8586
serviceDetail,
8687
path,
8788
httpMethod,
88-
string.IsNullOrEmpty(serviceDetail.ApiKey)
89-
? KeyStorage.GetKey(serviceAlias) ?? string.Empty
90-
: serviceDetail.ApiKey,
89+
key is not null ? key : (!string.IsNullOrEmpty(serviceDetail.ApiKey) ? serviceDetail.ApiKey : string.Empty),
9190
requestContent);
9291
}
9392
else
@@ -119,7 +118,10 @@ public async Task<string> SendRequestRawAsync<TRequest>(string serviceAlias, str
119118
public string? GetApiKey(string serviceAlias)
120119
{
121120
ServiceDetail serviceDetail = GetServiceDetail(serviceAlias);
122-
return serviceDetail?.ApiKey;
121+
string? key = KeyStorage.GetKey(serviceAlias);
122+
return key is not null
123+
? key
124+
: serviceDetail?.ApiKey;
123125
}
124126

125127
public string? GetToken(string serviceAlias)

src/Umbraco.AuthorizedServices/appsettings-schema.Umbraco.AuthorizedServices.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@
7272
},
7373
"CanManuallyProvideToken": {
7474
"type": "boolean",
75-
"description": "Get or sets a value indicating whether editor can manually add token."
75+
"description": "Get or sets a value indicating whether an administrator can manually add token."
76+
},
77+
"CanManuallyProvideApiKey": {
78+
"type": "boolean",
79+
"description": "Get or sets a value indicating whether an administrator can manually add API key."
7680
},
7781
"AuthorizationUrlRequiresRedirectUrl": {
7882
"type": "boolean",

tests/Umbraco.AuthorizedServices.Tests/Services/AuthorizedServiceAuthorizerTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal class AuthorizedServiceAuthorizerTests : AuthorizedServiceTestsBase
1515
public void SetUp()
1616
{
1717
TokenStorageMock = new Mock<ITokenStorage>();
18+
KeyStorageMock = new Mock<IKeyStorage>();
1819
}
1920

2021
[Test]

tests/Umbraco.AuthorizedServices.Tests/Services/AuthorizedServiceCallerTests.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ internal class AuthorizedServiceCallerTests : AuthorizedServiceTestsBase
1818
public void SetUp()
1919
{
2020
TokenStorageMock = new Mock<ITokenStorage>();
21+
KeyStorageMock = new Mock<IKeyStorage>();
2122
}
2223

2324
[Test]
@@ -40,6 +41,23 @@ public async Task SendRequestAsync_WithoutData_WithValidAccessToken_WithSuccessR
4041
.Verify(x => x.SaveToken(It.IsAny<string>(), It.IsAny<Token>()), Times.Never);
4142
}
4243

44+
[Test]
45+
public async Task SendRequestAsync_WithoutData_WithApiKeyFromStorage_WithSuccessReponse_ReturnsExpectedResponse()
46+
{
47+
// Arrange
48+
StoreApiKey();
49+
50+
var path = "/api/test/";
51+
AuthorizedServiceCaller sut = CreateService(HttpStatusCode.OK, authenticationMethod: AuthenticationMethod.ApiKey);
52+
53+
// Act
54+
TestResponseData? result = await sut.SendRequestAsync<TestResponseData>(ServiceAlias, path, HttpMethod.Get);
55+
56+
// Assert
57+
KeyStorageMock
58+
.Verify(x => x.SaveKey(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
59+
}
60+
4361
[Test]
4462
public async Task SendRequestAsync_WithoutData_WithValidAccessToken_WithFaileReponse_ThrowsExpectedException()
4563
{
@@ -151,7 +169,7 @@ public async Task SendRequestAsync_WithExpiredAccessToken_WithRefreshTokenSucces
151169
public void GetApiKey_WithExistingApiKey_ReturnsApiKey()
152170
{
153171
// Arrange
154-
AuthorizedServiceCaller sut = CreateService(includeApiKey: true);
172+
AuthorizedServiceCaller sut = CreateService(authenticationMethod: AuthenticationMethod.ApiKey);
155173

156174
// Act
157175
var result = sut.GetApiKey(ServiceAlias);
@@ -161,6 +179,21 @@ public void GetApiKey_WithExistingApiKey_ReturnsApiKey()
161179
result!.Should().Be("test-api-key");
162180
}
163181

182+
[Test]
183+
public void GetApiKey_WithStoredApiKey_ReturnsStoredApiKey()
184+
{
185+
// Arrange
186+
StoreApiKey();
187+
AuthorizedServiceCaller sut = CreateService(authenticationMethod: AuthenticationMethod.ApiKey);
188+
189+
// Act
190+
var result = sut.GetApiKey(ServiceAlias);
191+
192+
// Assert
193+
result.Should().NotBeNull();
194+
result!.Should().Be("stored-test-api-key");
195+
}
196+
164197
[Test]
165198
public void GetApiKey_WithoutExistingApiKey_ReturnsEmptyString()
166199
{
@@ -207,11 +240,17 @@ private void StoreToken(int daysUntilExpiry = 7) =>
207240
.Setup(x => x.GetToken(It.Is<string>(y => y == ServiceAlias)))
208241
.Returns(new Token("abc", "def", DateTime.Now.AddDays(daysUntilExpiry)));
209242

243+
private void StoreApiKey() =>
244+
KeyStorageMock
245+
.Setup(x => x.GetKey(It.Is<string>(y => y == ServiceAlias)))
246+
.Returns("stored-test-api-key");
247+
248+
210249
private AuthorizedServiceCaller CreateService(
211250
HttpStatusCode statusCode = HttpStatusCode.OK,
212251
string? responseContent = null,
213252
HttpStatusCode refreshTokenStatusCode = HttpStatusCode.OK,
214-
bool includeApiKey = false)
253+
AuthenticationMethod authenticationMethod = AuthenticationMethod.OAuth2AuthorizationCode)
215254
{
216255
var authorizationRequestSenderMock = new Mock<IAuthorizationRequestSender>();
217256

@@ -225,7 +264,7 @@ private AuthorizedServiceCaller CreateService(
225264
.Setup(x => x.SendRequest(It.Is<ServiceDetail>(y => y.Alias == ServiceAlias), It.Is<Dictionary<string, string>>(y => y["grant_type"] == "refresh_token")))
226265
.ReturnsAsync(httpResponseMessage);
227266

228-
Mock<IOptionsMonitor<ServiceDetail>> optionsMonitorServiceDetailMock = CreateOptionsMonitorServiceDetail(includeApiKey);
267+
Mock<IOptionsMonitor<ServiceDetail>> optionsMonitorServiceDetailMock = CreateOptionsMonitorServiceDetail(authenticationMethod);
229268
var factory = new JsonSerializerFactory(optionsMonitorServiceDetailMock.Object, new JsonNetSerializer());
230269

231270
return new AuthorizedServiceCaller(

tests/Umbraco.AuthorizedServices.Tests/Services/AuthorizedServiceTestsBase.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ internal abstract class AuthorizedServiceTestsBase
88
{
99
protected const string ServiceAlias = "testService";
1010

11-
protected Mock<ITokenStorage> TokenStorageMock { get; set; } = null!;
11+
protected static Mock<ITokenStorage> TokenStorageMock { get; set; } = null!;
1212

13-
protected Mock<IKeyStorage> KeyStorageMock { get; set; } = null!;
13+
protected static Mock<IKeyStorage> KeyStorageMock { get; set; } = null!;
1414

15-
protected static Mock<IOptionsMonitor<ServiceDetail>> CreateOptionsMonitorServiceDetail(bool includeApiKey = false)
15+
protected static Mock<IOptionsMonitor<ServiceDetail>> CreateOptionsMonitorServiceDetail(
16+
AuthenticationMethod authenticationMethod = AuthenticationMethod.OAuth2AuthorizationCode)
1617
{
1718
var optionsMonitorServiceDetailMock = new Mock<IOptionsMonitor<ServiceDetail>>();
1819
optionsMonitorServiceDetailMock.Setup(o => o.Get(ServiceAlias)).Returns(new ServiceDetail()
1920
{
2021
Alias = ServiceAlias,
2122
ApiHost = "https://service.url",
23+
AuthenticationMethod = authenticationMethod,
2224
JsonSerializer = JsonSerializerOption.JsonNet,
23-
ApiKey = includeApiKey ? "test-api-key" : string.Empty
25+
ApiKey = authenticationMethod == AuthenticationMethod.ApiKey ? "test-api-key" : string.Empty,
26+
ApiKeyProvision = authenticationMethod == AuthenticationMethod.ApiKey
27+
? new ApiKeyProvision { Method = ApiKeyProvisionMethod.QueryString, Key = "key"} : null
2428
});
2529

2630
return optionsMonitorServiceDetailMock;

0 commit comments

Comments
 (0)