Skip to content

Commit 58e302f

Browse files
Merge pull request #5687 from dfe-analytical-services/EES-5913-hotfix-fix-preview-tokens
EES-5913 - fix issues with using Preview Tokens on draft DataSets
2 parents d61046c + 62661cb commit 58e302f

File tree

18 files changed

+1144
-326
lines changed

18 files changed

+1144
-326
lines changed

src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerTests.cs

+710-250
Large diffs are not rendered by default.

src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/PublicationsControllerTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ await TestApp.AddTestData<PublicDataDbContext>(context =>
520520
Assert.Equal(dataSet.Summary, result.Summary);
521521
Assert.Equal(dataSet.Status, result.Status);
522522
Assert.Equal(dataSet.SupersedingDataSetId, result.SupersedingDataSetId);
523+
Assert.NotNull(result.LatestVersion);
523524
Assert.Equal(dataSetVersion.PublicVersion, result.LatestVersion.Version);
524525
Assert.Equal(
525526
dataSetVersion.Published.TruncateNanoseconds(),

src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Security/AuthorizationHandlers/QueryDataSetVersionAuthorizationHandlerTests.cs

+12-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class QueryDataSetVersionAuthorizationHandlerTests
2222
[Theory]
2323
[MemberData(nameof(DataSetVersionStatusQueryTheoryData.AvailableStatuses),
2424
MemberType = typeof(DataSetVersionStatusQueryTheoryData))]
25-
public void Success(DataSetVersionStatus status)
25+
public async Task Success(DataSetVersionStatus status)
2626
{
2727
DataSetVersion dataSetVersion = _dataFixture
2828
.DefaultDataSetVersion()
@@ -31,15 +31,15 @@ public void Success(DataSetVersionStatus status)
3131
var handler = BuildHandler();
3232
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion);
3333

34-
handler.HandleAsync(context);
34+
await handler.HandleAsync(context);
3535

3636
Assert.True(context.HasSucceeded);
3737
}
3838

3939
[Theory]
4040
[MemberData(nameof(DataSetVersionStatusQueryTheoryData.UnavailableStatuses),
4141
MemberType = typeof(DataSetVersionStatusQueryTheoryData))]
42-
public void Failure(DataSetVersionStatus status)
42+
public async Task Failure(DataSetVersionStatus status)
4343
{
4444
DataSetVersion dataSetVersion = _dataFixture
4545
.DefaultDataSetVersion()
@@ -48,15 +48,15 @@ public void Failure(DataSetVersionStatus status)
4848
var handler = BuildHandler();
4949
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion);
5050

51-
handler.HandleAsync(context);
51+
await handler.HandleAsync(context);
5252

5353
Assert.False(context.HasSucceeded);
5454
}
5555

5656
[Theory]
5757
[MemberData(nameof(DataSetVersionStatusQueryTheoryData.AvailableStatusesIncludingDraft),
5858
MemberType = typeof(DataSetVersionStatusQueryTheoryData))]
59-
public void Success_PreviewTokenIsActive(DataSetVersionStatus status)
59+
public async Task Success_PreviewTokenIsActive(DataSetVersionStatus status)
6060
{
6161
DataSetVersion dataSetVersion = _dataFixture
6262
.DefaultDataSetVersion()
@@ -79,13 +79,13 @@ public void Success_PreviewTokenIsActive(DataSetVersionStatus status)
7979

8080
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion);
8181

82-
handler.HandleAsync(context);
82+
await handler.HandleAsync(context);
8383

8484
Assert.True(context.HasSucceeded);
8585
}
8686

8787
[Fact]
88-
public void Failure_PreviewTokenIsExpired()
88+
public async Task Failure_PreviewTokenIsExpired()
8989
{
9090
DataSetVersion dataSetVersion = _dataFixture
9191
.DefaultDataSetVersion()
@@ -108,13 +108,13 @@ public void Failure_PreviewTokenIsExpired()
108108

109109
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion);
110110

111-
handler.HandleAsync(context);
111+
await handler.HandleAsync(context);
112112

113113
Assert.False(context.HasSucceeded);
114114
}
115115

116116
[Fact]
117-
public void Failure_PreviewTokenIsForWrongDataSetVersion()
117+
public async Task Failure_PreviewTokenIsForWrongDataSetVersion()
118118
{
119119
var (dataSetVersion1, dataSetVersion2) = _dataFixture
120120
.DefaultDataSetVersion()
@@ -139,15 +139,15 @@ public void Failure_PreviewTokenIsForWrongDataSetVersion()
139139

140140
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion1);
141141

142-
handler.HandleAsync(context);
142+
await handler.HandleAsync(context);
143143

144144
Assert.False(context.HasSucceeded);
145145
}
146146

147147
[Theory]
148148
[MemberData(nameof(DataSetVersionStatusQueryTheoryData.UnavailableStatusesExceptDraft),
149149
MemberType = typeof(DataSetVersionStatusQueryTheoryData))]
150-
public void Failure_PreviewTokenIsForUnavailableDataSetVersion(DataSetVersionStatus status)
150+
public async Task Failure_PreviewTokenIsForUnavailableDataSetVersion(DataSetVersionStatus status)
151151
{
152152
DataSetVersion dataSetVersion = _dataFixture
153153
.DefaultDataSetVersion()
@@ -170,7 +170,7 @@ public void Failure_PreviewTokenIsForUnavailableDataSetVersion(DataSetVersionSta
170170

171171
var context = CreateAnonymousAuthContext<QueryDataSetVersionRequirement, DataSetVersion>(dataSetVersion);
172172

173-
handler.HandleAsync(context);
173+
await handler.HandleAsync(context);
174174

175175
Assert.False(context.HasSucceeded);
176176
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
using GovUk.Education.ExploreEducationStatistics.Common.Extensions;
12
using GovUk.Education.ExploreEducationStatistics.Common.Tests.Fixtures;
3+
using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Constants;
24
using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Security.AuthorizationHandlers;
5+
using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Services;
6+
using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Services.Security;
37
using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.TheoryData;
48
using GovUk.Education.ExploreEducationStatistics.Public.Data.Model;
9+
using GovUk.Education.ExploreEducationStatistics.Public.Data.Model.Database;
510
using GovUk.Education.ExploreEducationStatistics.Public.Data.Model.Tests.Fixtures;
11+
using Microsoft.AspNetCore.Hosting;
12+
using Microsoft.AspNetCore.Http;
13+
using Microsoft.Extensions.Primitives;
14+
using Moq;
15+
using Moq.EntityFrameworkCore;
616
using static GovUk.Education.ExploreEducationStatistics.Common.Security.AuthorizationHandlerContextFactory;
717

818
namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Security.AuthorizationHandlers;
@@ -14,7 +24,7 @@ public class ViewDataSetAuthorizationHandlerTests
1424
[Theory]
1525
[MemberData(nameof(DataSetStatusTheoryData.AvailableStatuses),
1626
MemberType = typeof(DataSetStatusTheoryData))]
17-
public void Success(DataSetStatus status)
27+
public async Task DataSetHasAvailableStatus_Success(DataSetStatus status)
1828
{
1929
DataSet dataSet = _dataFixture
2030
.DefaultDataSet()
@@ -23,15 +33,15 @@ public void Success(DataSetStatus status)
2333
var handler = BuildHandler();
2434
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
2535

26-
handler.HandleAsync(context);
36+
await handler.HandleAsync(context);
2737

2838
Assert.True(context.HasSucceeded);
2939
}
3040

3141
[Theory]
3242
[MemberData(nameof(DataSetStatusTheoryData.UnavailableStatuses),
3343
MemberType = typeof(DataSetStatusTheoryData))]
34-
public void Failure(DataSetStatus status)
44+
public async Task DataSetHasUnavailableStatus_Failure(DataSetStatus status)
3545
{
3646
DataSet dataSet = _dataFixture
3747
.DefaultDataSet()
@@ -40,13 +50,219 @@ public void Failure(DataSetStatus status)
4050
var handler = BuildHandler();
4151
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
4252

43-
handler.HandleAsync(context);
53+
await handler.HandleAsync(context);
4454

4555
Assert.False(context.HasSucceeded);
4656
}
57+
58+
[Theory]
59+
[MemberData(nameof(DataSetStatusTheoryData.AllStatuses),
60+
MemberType = typeof(DataSetStatusTheoryData))]
61+
public async Task PreviewTokenForDraftDataSetVersionActive_Success(DataSetStatus status)
62+
{
63+
DataSet dataSet = _dataFixture
64+
.DefaultDataSet()
65+
.WithStatus(status);
66+
67+
DataSetVersion dataSetVersion = _dataFixture
68+
.DefaultDataSetVersion()
69+
.WithStatus(DataSetVersionStatus.Draft)
70+
.WithPreviewTokens(() => [_dataFixture.DefaultPreviewToken()])
71+
.FinishWith(dsv => dataSet.LatestDraftVersionId = dsv.Id);
72+
73+
var publicDataDbContext = new Mock<PublicDataDbContext>();
74+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSets).ReturnsDbSet([dataSet]);
75+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSetVersions).ReturnsDbSet([dataSetVersion]);
76+
publicDataDbContext.SetupGet(dbContext => dbContext.PreviewTokens).ReturnsDbSet(dataSetVersion.PreviewTokens);
77+
78+
var handler = BuildHandler(
79+
publicDataDbContext: publicDataDbContext.Object,
80+
requestHeaders:
81+
[
82+
PreviewTokenRequestHeader(dataSetVersion.PreviewTokens[0])
83+
]);
84+
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
85+
86+
await handler.HandleAsync(context);
87+
88+
Assert.True(context.HasSucceeded);
89+
}
90+
91+
/// <summary>
92+
/// Despite the Preview Token being used is expired, the DataSet's status itself is
93+
/// available to the public, and so the auth succeeds.
94+
/// </summary>
95+
[Theory]
96+
[MemberData(nameof(DataSetStatusTheoryData.AvailableStatuses),
97+
MemberType = typeof(DataSetStatusTheoryData))]
98+
public async Task PreviewTokenForDraftDataSetVersionExpired_DataSetStatusAvailable_Success(DataSetStatus status)
99+
{
100+
DataSet dataSet = _dataFixture
101+
.DefaultDataSet()
102+
.WithStatus(status);
103+
104+
DataSetVersion dataSetVersion = _dataFixture
105+
.DefaultDataSetVersion()
106+
.WithStatus(DataSetVersionStatus.Draft)
107+
.WithPreviewTokens(() => [_dataFixture.DefaultPreviewToken(expired: true)])
108+
.FinishWith(dsv => dataSet.LatestDraftVersionId = dsv.Id);
109+
110+
var publicDataDbContext = new Mock<PublicDataDbContext>();
111+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSets).ReturnsDbSet([dataSet]);
112+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSetVersions).ReturnsDbSet([dataSetVersion]);
113+
publicDataDbContext.SetupGet(dbContext => dbContext.PreviewTokens).ReturnsDbSet(dataSetVersion.PreviewTokens);
114+
115+
var handler = BuildHandler(
116+
publicDataDbContext: publicDataDbContext.Object,
117+
requestHeaders:
118+
[
119+
PreviewTokenRequestHeader(dataSetVersion.PreviewTokens[0])
120+
]);
121+
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
122+
123+
await handler.HandleAsync(context);
124+
125+
Assert.True(context.HasSucceeded);
126+
}
127+
128+
/// <summary>
129+
/// The Preview Token being used is expired and the DataSet's status is
130+
/// unavailable to the public, and so the auth fails.
131+
/// </summary>
132+
[Theory]
133+
[MemberData(nameof(DataSetStatusTheoryData.UnavailableStatuses),
134+
MemberType = typeof(DataSetStatusTheoryData))]
135+
public async Task PreviewTokenForDraftDataSetVersionExpired_DataSetStatusUnavailable_Failure(DataSetStatus status)
136+
{
137+
DataSet dataSet = _dataFixture
138+
.DefaultDataSet()
139+
.WithStatus(status);
140+
141+
DataSetVersion dataSetVersion = _dataFixture
142+
.DefaultDataSetVersion()
143+
.WithStatus(DataSetVersionStatus.Draft)
144+
.WithPreviewTokens(() => [_dataFixture.DefaultPreviewToken(expired: true)])
145+
.FinishWith(dsv => dataSet.LatestDraftVersionId = dsv.Id);
146+
147+
var publicDataDbContext = new Mock<PublicDataDbContext>();
148+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSets).ReturnsDbSet([dataSet]);
149+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSetVersions).ReturnsDbSet([dataSetVersion]);
150+
publicDataDbContext.SetupGet(dbContext => dbContext.PreviewTokens).ReturnsDbSet(dataSetVersion.PreviewTokens);
151+
152+
var handler = BuildHandler(
153+
publicDataDbContext: publicDataDbContext.Object,
154+
requestHeaders:
155+
[
156+
PreviewTokenRequestHeader(dataSetVersion.PreviewTokens[0])
157+
]);
158+
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
159+
160+
await handler.HandleAsync(context);
161+
162+
Assert.False(context.HasSucceeded);
163+
}
164+
165+
/// <summary>
166+
/// Despite the Preview Token being used is for a non-draft DataSetVersion, the DataSet's
167+
/// status itself is available to the public, and so the auth succeeds.
168+
/// </summary>
169+
[Theory]
170+
[MemberData(nameof(DataSetStatusTheoryData.AvailableStatuses),
171+
MemberType = typeof(DataSetStatusTheoryData))]
172+
public async Task PreviewTokenActiveButForLiveDataSetVersion_DataSetStatusAvailable_Success(DataSetStatus status)
173+
{
174+
DataSet dataSet = _dataFixture
175+
.DefaultDataSet()
176+
.WithStatus(status);
177+
178+
DataSetVersion dataSetVersion = _dataFixture
179+
.DefaultDataSetVersion()
180+
.WithStatus(DataSetVersionStatus.Published)
181+
.WithPreviewTokens(() => [_dataFixture.DefaultPreviewToken()])
182+
.FinishWith(dsv => dataSet.LatestLiveVersionId = dsv.Id);
183+
184+
var publicDataDbContext = new Mock<PublicDataDbContext>();
185+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSets).ReturnsDbSet([dataSet]);
186+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSetVersions).ReturnsDbSet([dataSetVersion]);
187+
publicDataDbContext.SetupGet(dbContext => dbContext.PreviewTokens).ReturnsDbSet(dataSetVersion.PreviewTokens);
188+
189+
var handler = BuildHandler(
190+
publicDataDbContext: publicDataDbContext.Object,
191+
requestHeaders:
192+
[
193+
PreviewTokenRequestHeader(dataSetVersion.PreviewTokens[0])
194+
]);
195+
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
196+
197+
await handler.HandleAsync(context);
198+
199+
Assert.True(context.HasSucceeded);
200+
}
201+
202+
/// <summary>
203+
/// The Preview Token being used is for a non-draft DataSetVersion and the DataSet's
204+
/// status itself is unavailable to the public, and so the auth false.
205+
/// </summary>
206+
[Theory]
207+
[MemberData(nameof(DataSetStatusTheoryData.UnavailableStatuses),
208+
MemberType = typeof(DataSetStatusTheoryData))]
209+
public async Task PreviewTokenActiveButForLiveDataSetVersion_DataSetStatusUnavailable_Failure(DataSetStatus status)
210+
{
211+
DataSet dataSet = _dataFixture
212+
.DefaultDataSet()
213+
.WithStatus(status);
214+
215+
DataSetVersion dataSetVersion = _dataFixture
216+
.DefaultDataSetVersion()
217+
.WithStatus(DataSetVersionStatus.Published)
218+
.WithPreviewTokens(() => [_dataFixture.DefaultPreviewToken()])
219+
.FinishWith(dsv => dataSet.LatestLiveVersionId = dsv.Id);
220+
221+
var publicDataDbContext = new Mock<PublicDataDbContext>();
222+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSets).ReturnsDbSet([dataSet]);
223+
publicDataDbContext.SetupGet(dbContext => dbContext.DataSetVersions).ReturnsDbSet([dataSetVersion]);
224+
publicDataDbContext.SetupGet(dbContext => dbContext.PreviewTokens).ReturnsDbSet(dataSetVersion.PreviewTokens);
225+
226+
var handler = BuildHandler(
227+
publicDataDbContext: publicDataDbContext.Object,
228+
requestHeaders:
229+
[
230+
PreviewTokenRequestHeader(dataSetVersion.PreviewTokens[0])
231+
]);
232+
var context = CreateAnonymousAuthContext<ViewDataSetRequirement, DataSet>(dataSet);
233+
234+
await handler.HandleAsync(context);
235+
236+
Assert.False(context.HasSucceeded);
237+
}
238+
239+
private static ViewDataSetAuthorizationHandler BuildHandler(
240+
PublicDataDbContext? publicDataDbContext = null,
241+
IList<KeyValuePair<string, StringValues>>? requestHeaders = null)
242+
{
243+
var dbContext = publicDataDbContext ?? Mock.Of<PublicDataDbContext>();
244+
245+
var httpContextAccessor = new HttpContextAccessor
246+
{
247+
HttpContext = new DefaultHttpContext()
248+
};
249+
250+
var headers = httpContextAccessor.HttpContext.Request.Headers;
251+
requestHeaders?.ForEach(header =>
252+
headers.Append(header.Key, header.Value));
253+
254+
var previewTokenService = new PreviewTokenService(dbContext);
255+
256+
var authorizationHandlerService = new AuthorizationHandlerService(
257+
httpContextAccessor: httpContextAccessor,
258+
environment: Mock.Of<IWebHostEnvironment>(),
259+
previewTokenService);
260+
261+
return new ViewDataSetAuthorizationHandler(authorizationHandlerService);
262+
}
47263

48-
private static ViewDataSetAuthorizationHandler BuildHandler()
264+
private static KeyValuePair<string, StringValues> PreviewTokenRequestHeader(PreviewToken previewToken)
49265
{
50-
return new ViewDataSetAuthorizationHandler();
266+
return new KeyValuePair<string, StringValues>(RequestHeaderNames.PreviewToken, previewToken.Id.ToString());
51267
}
52268
}

0 commit comments

Comments
 (0)