1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
+ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
+
4
+ using System ;
5
+ using System . Collections . Generic ;
6
+ using System . IO ;
7
+ using System . Net . Http ;
8
+ using System . Security . Cryptography ;
9
+ using System . Text . RegularExpressions ;
10
+ using System . Threading . Tasks ;
11
+ using Xunit ;
12
+ using Xunit . Abstractions ;
13
+
14
+ namespace Microsoft . AspNetCore . Identity . Test
15
+ {
16
+ public class CdnScriptTagTests
17
+ {
18
+ private readonly ITestOutputHelper _output ;
19
+
20
+ public CdnScriptTagTests ( ITestOutputHelper output )
21
+ {
22
+ _output = output ;
23
+ }
24
+
25
+ [ Fact ]
26
+ public async Task IdentityUI_ScriptTags_SubresourceIntegrityCheck ( )
27
+ {
28
+ var slnDir = GetSolutionDir ( ) ;
29
+ var sourceDir = Path . Combine ( slnDir , "src" , "UI" ) ;
30
+ var cshtmlFiles = Directory . GetFiles ( sourceDir , "*.cshtml" , SearchOption . AllDirectories ) ;
31
+
32
+ var scriptTags = new List < ScriptTag > ( ) ;
33
+ foreach ( var cshtmlFile in cshtmlFiles )
34
+ {
35
+ scriptTags . AddRange ( GetScriptTags ( cshtmlFile ) ) ;
36
+ }
37
+
38
+ Assert . NotEmpty ( scriptTags ) ;
39
+
40
+ var shasum = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
41
+ using ( var client = new HttpClient ( ) )
42
+ {
43
+ foreach ( var script in scriptTags )
44
+ {
45
+ if ( shasum . ContainsKey ( script . Src ) )
46
+ {
47
+ continue ;
48
+ }
49
+
50
+ using ( var resp = await client . GetStreamAsync ( script . Src ) )
51
+ using ( var alg = SHA384 . Create ( ) )
52
+ {
53
+ var hash = alg . ComputeHash ( resp ) ;
54
+ shasum . Add ( script . Src , "sha384-" + Convert . ToBase64String ( hash ) ) ;
55
+ }
56
+ }
57
+ }
58
+
59
+ Assert . All ( scriptTags , t =>
60
+ {
61
+ Assert . True ( shasum [ t . Src ] == t . Integrity , userMessage : $ "Expected integrity on script tag to be { shasum [ t . Src ] } but it was { t . Integrity } . { t . FileName } ") ;
62
+ } ) ;
63
+ }
64
+
65
+ private struct ScriptTag
66
+ {
67
+ public string Src ;
68
+ public string Integrity ;
69
+ public string FileName ;
70
+ }
71
+
72
+ private static readonly Regex _scriptRegex = new Regex ( @"<script[^>]*src=""(?'src'http[^""]+)""[^>]*integrity=""(?'integrity'[^""]+)""([^>]*)>" , RegexOptions . Multiline ) ;
73
+
74
+ private IEnumerable < ScriptTag > GetScriptTags ( string cshtmlFile )
75
+ {
76
+ string contents ;
77
+ using ( var reader = new StreamReader ( File . OpenRead ( cshtmlFile ) ) )
78
+ {
79
+ contents = reader . ReadToEnd ( ) ;
80
+ }
81
+
82
+ var match = _scriptRegex . Match ( contents ) ;
83
+ while ( match != null && match != Match . Empty )
84
+ {
85
+ var tag = new ScriptTag
86
+ {
87
+ Src = match . Groups [ "src" ] . Value ,
88
+ Integrity = match . Groups [ "integrity" ] . Value ,
89
+ FileName = Path . GetFileName ( cshtmlFile )
90
+ } ;
91
+ yield return tag ;
92
+ _output . WriteLine ( $ "Found script tag in '{ tag . FileName } ', src='{ tag . Src } ' integrity='{ tag . Integrity } '") ;
93
+ match = match . NextMatch ( ) ;
94
+ }
95
+ }
96
+
97
+ private static string GetSolutionDir ( )
98
+ {
99
+ var dir = new DirectoryInfo ( AppContext . BaseDirectory ) ;
100
+ while ( dir != null )
101
+ {
102
+ if ( File . Exists ( Path . Combine ( dir . FullName , "Identity.sln" ) ) )
103
+ {
104
+ break ;
105
+ }
106
+ dir = dir . Parent ;
107
+ }
108
+ return dir . FullName ;
109
+ }
110
+ }
111
+ }
0 commit comments