1+ // Licensed to the .NET Foundation under one or more agreements.
2+ // The .NET Foundation licenses this file to you under the MIT license.
3+ // See the LICENSE file in the project root for more information.
4+
5+ using System . Text . Json ;
6+ using System . Text . Json . Serialization ;
7+ using System . Xml ;
8+
9+ namespace DevProxy . Plugins . MinimalPermissions ;
10+
11+ enum ActionType
12+ {
13+ ObjectPath ,
14+ Query ,
15+ SetProperty
16+ }
17+
18+ enum AccessType
19+ {
20+ Delegated ,
21+ Application
22+ }
23+
24+ static class CsomParser
25+ {
26+ private static readonly JsonSerializerOptions options = new ( )
27+ {
28+ PropertyNameCaseInsensitive = true ,
29+ Converters =
30+ {
31+ new JsonStringEnumConverter ( JsonNamingPolicy . CamelCase )
32+ }
33+ } ;
34+
35+ private static string ? GetTypeName ( string objectPathId , XmlDocument doc , XmlNamespaceManager nsManager , CsomTypesDefinition typesDefinition )
36+ {
37+ var objectPath = doc . SelectSingleNode ( $ "//ns:ObjectPaths/*[@Id='{ objectPathId } ']", nsManager ) ;
38+ if ( objectPath == null )
39+ {
40+ return null ;
41+ }
42+
43+ if ( objectPath . Name == "Constructor" ||
44+ objectPath . Name == "StaticProperty" )
45+ {
46+ var typeIdAttr = objectPath . Attributes ? [ "TypeId" ] ;
47+ if ( typeIdAttr != null )
48+ {
49+ var typeId = typeIdAttr . Value . Trim ( '{' , '}' ) ;
50+ if ( typesDefinition ? . Types ? . TryGetValue ( typeId , out string ? typeName ) == true )
51+ {
52+ if ( objectPath . Name == "StaticProperty" )
53+ {
54+ var nameAttr = objectPath . Attributes ? [ "Name" ] ;
55+ if ( nameAttr != null )
56+ {
57+ return $ "{ typeName } .{ nameAttr . Value } ";
58+ }
59+ }
60+ return typeName ;
61+ }
62+ else
63+ {
64+ return null ;
65+ }
66+ }
67+ return null ;
68+ }
69+
70+ var parentIdAttr = objectPath . Attributes ? [ "ParentId" ] ;
71+ if ( parentIdAttr == null )
72+ {
73+ return null ;
74+ }
75+ var parentId = parentIdAttr . Value ;
76+
77+ return GetTypeName ( parentId , doc , nsManager , typesDefinition ) ;
78+ }
79+
80+ private static string ? GetObjectPathName ( string objectPathId , ActionType actionType , XmlDocument doc , XmlNamespaceManager nsManager , CsomTypesDefinition typesDefinition )
81+ {
82+ var objectPath = doc . SelectSingleNode ( $ "//ns:ObjectPaths/*[@Id='{ objectPathId } ']", nsManager ) ;
83+ if ( objectPath == null )
84+ {
85+ return null ;
86+ }
87+
88+ var typeName = GetTypeName ( objectPathId , doc , nsManager , typesDefinition ) ;
89+ if ( typeName == null )
90+ {
91+ return null ;
92+ }
93+
94+ if ( objectPath . Name == "Constructor" )
95+ {
96+ var suffix = actionType == ActionType . Query ? "query" : "ctor" ;
97+ return $ "{ typeName } .{ suffix } ";
98+ }
99+
100+ if ( objectPath . Name == "Method" )
101+ {
102+ var nameAttr = objectPath . Attributes ? [ "Name" ] ;
103+ if ( nameAttr == null )
104+ {
105+ return null ;
106+ }
107+ var methodName = actionType == ActionType . Query ? "query" : nameAttr . Value ;
108+ return $ "{ typeName } .{ methodName } ";
109+ }
110+
111+ if ( objectPath . Name == "Property" )
112+ {
113+ var nameAttr = objectPath . Attributes ? [ "Name" ] ;
114+ if ( nameAttr == null )
115+ {
116+ return null ;
117+ }
118+ if ( typesDefinition ? . ReturnTypes ? . TryGetValue ( $ "{ typeName } .{ nameAttr . Value } ", out string ? returnType ) == true )
119+ {
120+ var methodName = actionType == ActionType . SetProperty ? "setProperty" : nameAttr . Value ;
121+ return $ "{ returnType } .{ methodName } ";
122+ }
123+ else
124+ {
125+ return $ "{ typeName } .{ nameAttr . Value } ";
126+ }
127+ }
128+
129+ return null ;
130+ }
131+
132+ private static ActionType GetActionType ( string actionName )
133+ {
134+ return actionName switch
135+ {
136+ "ObjectPath" => ActionType . ObjectPath ,
137+ "Query" => ActionType . Query ,
138+ "SetProperty" => ActionType . SetProperty ,
139+ _ => throw new ArgumentOutOfRangeException ( nameof ( actionName ) , $ "Unknown action type: { actionName } ")
140+ } ;
141+ }
142+
143+ public static ( IEnumerable < string > Actions , IEnumerable < string > Errors ) GetActions ( string xml , CsomTypesDefinition typesDefinition )
144+ {
145+ if ( typesDefinition ? . Types == null || string . IsNullOrEmpty ( xml ) )
146+ {
147+ return ( [ ] , [ ] ) ;
148+ }
149+
150+ var actions = new List < string > ( ) ;
151+ var errors = new List < string > ( ) ;
152+
153+ try
154+ {
155+ // Load the XML
156+ var doc = new XmlDocument ( ) ;
157+ doc . LoadXml ( xml ) ;
158+
159+ var nsManager = new XmlNamespaceManager ( doc . NameTable ) ;
160+ var defaultNamespace = doc . DocumentElement ? . NamespaceURI ?? string . Empty ;
161+ if ( ! string . IsNullOrEmpty ( defaultNamespace ) )
162+ {
163+ nsManager . AddNamespace ( "ns" , defaultNamespace ) ;
164+ }
165+
166+ // Get the Actions element
167+ var actionsNode = doc . SelectSingleNode ( "//ns:Actions" , nsManager ) ;
168+ if ( actionsNode == null )
169+ {
170+ errors . Add ( "Actions node not found in XML." ) ;
171+ // If Actions node is not found, return empty list
172+ return ( actions , errors ) ;
173+ }
174+
175+ // Process all child Action elements
176+ foreach ( XmlNode actionNode in actionsNode . ChildNodes )
177+ {
178+ var actionType = GetActionType ( actionNode . Name ) ;
179+
180+ // Extract ObjectPathId attribute
181+ var objectPathIdAttr = actionNode . Attributes ? [ "ObjectPathId" ] ;
182+ if ( objectPathIdAttr == null )
183+ {
184+ errors . Add ( $ "ObjectPathId attribute not found for action: { actionNode . OuterXml } ") ;
185+ continue ;
186+ }
187+
188+ var objectPathId = objectPathIdAttr . Value ;
189+
190+ var type = GetObjectPathName ( objectPathId , actionType , doc , nsManager , typesDefinition ) ;
191+
192+ if ( type != null )
193+ {
194+ actions . Add ( type ) ;
195+ }
196+ }
197+ }
198+ catch ( Exception ex )
199+ {
200+ Console . WriteLine ( $ "Error parsing XML: { ex . Message } ") ;
201+ }
202+
203+ return ( actions , errors ) ;
204+ }
205+
206+ public static ( string [ ] MinimalScopes , string [ ] UnmatchedOperations ) GetMinimalScopes ( IEnumerable < string > actions , AccessType accessType , CsomTypesDefinition typesDefinition )
207+ {
208+ var operationsAndScopes = typesDefinition ? . Actions
209+ ? . Where ( o => o . Value . Delegated != null || o . Value . Application != null )
210+ . ToDictionary (
211+ o => o . Key ,
212+ o => accessType == AccessType . Delegated ? o . Value . Delegated : o . Value . Application
213+ ) ;
214+ return MinimalPermissionsUtils . GetMinimalScopes ( [ .. actions ] , operationsAndScopes ! ) ;
215+ }
216+ }
0 commit comments