1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ using System . Text ;
5
+ using System . Text . Json ;
6
+ using System . Text . Json . Serialization ;
7
+ using System . Text . RegularExpressions ;
8
+ using System . Web ;
9
+ using Microsoft . DevProxy . Abstractions ;
10
+
11
+ namespace Microsoft . DevProxy . Plugins . MockResponses ;
12
+
13
+ class IdToken {
14
+ [ JsonPropertyName ( "aud" ) ]
15
+ public string ? Aud { get ; set ; }
16
+ [ JsonPropertyName ( "iss" ) ]
17
+ public string ? Iss { get ; set ; }
18
+ [ JsonPropertyName ( "iat" ) ]
19
+ public int ? Iat { get ; set ; }
20
+ [ JsonPropertyName ( "nbf" ) ]
21
+ public int ? Nbf { get ; set ; }
22
+ [ JsonPropertyName ( "exp" ) ]
23
+ public int ? Exp { get ; set ; }
24
+ [ JsonPropertyName ( "name" ) ]
25
+ public string ? Name { get ; set ; }
26
+ [ JsonPropertyName ( "nonce" ) ]
27
+ public string ? Nonce { get ; set ; }
28
+ [ JsonPropertyName ( "oid" ) ]
29
+ public string ? Oid { get ; set ; }
30
+ [ JsonPropertyName ( "preferred_username" ) ]
31
+ public string ? PreferredUsername { get ; set ; }
32
+ [ JsonPropertyName ( "rh" ) ]
33
+ public string ? Rh { get ; set ; }
34
+ [ JsonPropertyName ( "sub" ) ]
35
+ public string ? Sub { get ; set ; }
36
+ [ JsonPropertyName ( "tid" ) ]
37
+ public string ? Tid { get ; set ; }
38
+ [ JsonPropertyName ( "uti" ) ]
39
+ public string ? Uti { get ; set ; }
40
+ [ JsonPropertyName ( "ver" ) ]
41
+ public string ? Ver { get ; set ; }
42
+ }
43
+
44
+ public class M365MockResponsePlugin : GraphMockResponsePlugin
45
+ {
46
+ private string ? lastNonce ;
47
+ public override string Name => nameof ( M365MockResponsePlugin ) ;
48
+
49
+ protected override void ProcessMockResponse ( ref byte [ ] body , IList < MockResponseHeader > headers , ProxyRequestArgs e , MockResponse ? matchingResponse )
50
+ {
51
+ base . ProcessMockResponse ( ref body , headers , e , matchingResponse ) ;
52
+
53
+ var bodyString = Encoding . UTF8 . GetString ( body ) ;
54
+ var changed = false ;
55
+
56
+ StoreLastNonce ( e ) ;
57
+ UpdateMsalState ( ref bodyString , e , ref changed ) ;
58
+ UpdateIdToken ( ref bodyString , e , ref changed ) ;
59
+
60
+ if ( changed )
61
+ {
62
+ body = Encoding . UTF8 . GetBytes ( bodyString ) ;
63
+ }
64
+ }
65
+
66
+ private void StoreLastNonce ( ProxyRequestArgs e )
67
+ {
68
+ if ( e . Session . HttpClient . Request . RequestUri . Query . Contains ( "nonce=" ) )
69
+ {
70
+ var queryString = HttpUtility . ParseQueryString ( e . Session . HttpClient . Request . RequestUri . Query ) ;
71
+ lastNonce = queryString [ "nonce" ] ;
72
+ }
73
+ }
74
+
75
+ private void UpdateIdToken ( ref string body , ProxyRequestArgs e , ref bool changed )
76
+ {
77
+ if ( ! body . Contains ( "id_token\" :\" @dynamic" ) ||
78
+ string . IsNullOrEmpty ( lastNonce ) )
79
+ {
80
+ return ;
81
+ }
82
+
83
+ var idTokenRegex = new Regex ( "id_token\" :\" ([^\" ]+)\" " ) ;
84
+
85
+ var idToken = idTokenRegex . Match ( body ) . Groups [ 1 ] . Value ;
86
+ idToken = idToken . Replace ( "@dynamic." , "" ) ;
87
+ var tokenChunks = idToken . Split ( '.' ) ;
88
+ // base64 decode the second chunk from the array
89
+ // before decoding, we need to pad the base64 to a multiple of 4
90
+ // or Convert.FromBase64String will throw an exception
91
+ var decodedToken = Encoding . UTF8 . GetString ( Convert . FromBase64String ( PadBase64 ( tokenChunks [ 1 ] ) ) ) ;
92
+ var token = JsonSerializer . Deserialize < IdToken > ( decodedToken ) ;
93
+ if ( token is null )
94
+ {
95
+ return ;
96
+ }
97
+
98
+ token . Nonce = lastNonce ;
99
+
100
+ tokenChunks [ 1 ] = Convert . ToBase64String ( Encoding . UTF8 . GetBytes ( JsonSerializer . Serialize ( token ) ) ) ;
101
+ body = idTokenRegex . Replace ( body , $ "id_token\" :\" { string . Join ( '.' , tokenChunks ) } \" ") ;
102
+ changed = true ;
103
+ }
104
+
105
+ private string PadBase64 ( string base64 )
106
+ {
107
+ var padding = new string ( '=' , ( 4 - base64 . Length % 4 ) % 4 ) ;
108
+ return base64 + padding ;
109
+ }
110
+
111
+ private void UpdateMsalState ( ref string body , ProxyRequestArgs e , ref bool changed )
112
+ {
113
+ if ( ! body . Contains ( "state=@dynamic" ) ||
114
+ ! e . Session . HttpClient . Request . RequestUri . Query . Contains ( "state=" ) )
115
+ {
116
+ return ;
117
+ }
118
+
119
+ var queryString = HttpUtility . ParseQueryString ( e . Session . HttpClient . Request . RequestUri . Query ) ;
120
+ var msalState = queryString [ "state" ] ;
121
+ body = body . Replace ( "state=@dynamic" , $ "state={ msalState } ") ;
122
+ changed = true ;
123
+ }
124
+ }
0 commit comments