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