2005-01-11 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / corlib / System.Security / PermissionSet.cs
1 //
2 // System.Security.PermissionSet.cs
3 //
4 // Authors:
5 //      Nick Drochak(ndrochak@gol.com)
6 //      Sebastien Pouliot  <sebastien@ximian.com>
7 //
8 // (C) Nick Drochak
9 // Portions (C) 2003, 2004 Motus Technologies Inc. (http://www.motus.com)
10 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Collections;
33 using System.Diagnostics;
34 using System.IO;
35 using System.Reflection;
36 using System.Runtime.InteropServices;
37 using System.Runtime.Serialization;
38 using System.Runtime.Serialization.Formatters.Binary;
39 using System.Security.Permissions;
40 using System.Security.Policy;
41 using System.Text;
42 using System.Threading;
43
44 namespace System.Security {
45
46         [Serializable]
47         public class PermissionSet: ISecurityEncodable, ICollection, IEnumerable, IStackWalk, IDeserializationCallback {
48
49                 private static string tagName = "PermissionSet";
50                 private const int version = 1;
51                 private static object[] psNone = new object [1] { PermissionState.None };
52
53                 private PermissionState state;
54                 private ArrayList list;
55                 private int _hashcode;
56                 private PolicyLevel _policyLevel;
57                 private bool _declsec;
58
59                 // constructors
60
61                 // for PolicyLevel (to avoid validation duplication)
62                 internal PermissionSet () 
63                 {
64                         list = new ArrayList ();
65                 }
66
67                 public PermissionSet (PermissionState state) : this ()
68                 {
69                         if (!Enum.IsDefined (typeof (PermissionState), state))
70                                 throw new System.ArgumentException ("state");
71                         this.state = state;
72                 }
73
74                 public PermissionSet (PermissionSet permSet) : this ()
75                 {
76                         // LAMESPEC: This would be handled by the compiler.  No way permSet is not a PermissionSet.
77                         //if (!(permSet is PermissionSet))
78                         //      throw new System.ArgumentException(); // permSet is not an instance of System.Security.PermissionSet.
79                         if (permSet == null)
80                                 state = PermissionState.Unrestricted;
81                         else {
82                                 state = permSet.state;
83                                 foreach (IPermission p in permSet.list)
84                                         list.Add (p);
85                         }
86                 }
87
88                 internal PermissionSet (string xml)
89                         : this ()
90                 {
91                         state = PermissionState.None;
92                         if (xml != null) {
93                                 SecurityElement se = SecurityElement.FromString (xml);
94                                 FromXml (se);
95                         }
96                 }
97
98                 // Light version for creating a (non unrestricted) PermissionSet with
99                 // a single permission. This allows to relax most validations.
100                 internal PermissionSet (IPermission perm)
101                         : this ()
102                 {
103                         if (perm != null) {
104                                 // note: we do not copy IPermission like AddPermission
105                                 list.Add (perm);
106                         }
107                 }
108
109                 // methods
110
111                 public virtual IPermission AddPermission (IPermission perm)
112                 {
113                         if (perm == null)
114                                 return null;
115
116                         // we don't add to an unrestricted permission set unless...
117                         if (state == PermissionState.Unrestricted) {
118                                 // we're adding identity permission as they don't support unrestricted
119                                 if (perm is IUnrestrictedPermission) {
120                                         // we return the union of the permission with unrestricted
121                                         // which results in a permission of the same type initialized 
122                                         // with PermissionState.Unrestricted
123                                         object[] args = new object [1] { PermissionState.Unrestricted };
124                                         return (IPermission) Activator.CreateInstance (perm.GetType (), args);
125                                 }
126                         }
127
128                         // we can't add two permissions of the same type in a set
129                         // so we remove an existing one, union with it and add it back
130                         IPermission existing = RemovePermission (perm.GetType ());
131                         if (existing != null) {
132                                 perm = perm.Union (existing);
133                         }
134
135                         // note: Add doesn't copy
136                         list.Add (perm);
137                         return perm;
138                 }
139
140                 [MonoTODO ("Imperative mode isn't supported")]
141                 public virtual void Assert ()
142                 {
143                         new SecurityPermission (SecurityPermissionFlag.Assertion).Demand ();
144
145                         int count = this.Count;
146
147                         // we (current frame) must have the permission to assert it to others
148                         // otherwise we don't assert (but we don't throw an exception)
149                         foreach (IPermission p in list) {
150                                 // note: we ignore non-CAS permissions
151                                 if (p is IStackWalk) {
152                                         if (!SecurityManager.IsGranted (p)) {
153                                                 return;
154                                         }
155                                 } else
156                                         count--;
157                         }
158
159                         // note: we must ignore the stack modifiers for the non-CAS permissions
160                         if (count > 0)
161                                 throw new NotSupportedException ("Currently only declarative Assert are supported.");
162                 }
163
164                 internal void Clear () 
165                 {
166                         list.Clear ();
167                 }
168
169                 public virtual PermissionSet Copy ()
170                 {
171                         return new PermissionSet (this);
172                 }
173
174                 public virtual void CopyTo (Array array, int index)
175                 {
176                         if (null == array)
177                                 throw new ArgumentNullException ("array");
178
179                         if (list.Count > 0) {
180                                 if (array.Rank > 1) {
181                                         throw new ArgumentException (Locale.GetText (
182                                                 "Array has more than one dimension"));
183                                 }
184                                 if (index < 0 || index >= array.Length) {
185                                         throw new IndexOutOfRangeException ("index");
186                                 }
187
188                                 list.CopyTo (array, index);
189                         }
190                 }
191
192                 [MonoTODO ("Imperative Assert, Deny and PermitOnly aren't yet supported")]
193                 public virtual void Demand ()
194                 {
195                         // Note: SecurityEnabled only applies to CAS permissions
196                         // so we're not checking for it (yet)
197                         if (IsEmpty ())
198                                 return;
199
200                         PermissionSet cas = this;
201                         // avoid copy (if possible)
202                         if (ContainsNonCodeAccessPermissions ()) {
203                                 // non CAS permissions (e.g. PrincipalPermission) do not requires a stack walk
204                                 cas = this.Copy ();
205                                 foreach (IPermission p in list) {
206                                         Type t = p.GetType ();
207                                         if (!t.IsSubclassOf (typeof (CodeAccessPermission))) {
208                                                 p.Demand ();
209                                                 // we wont have to process this one in the stack walk
210                                                 cas.RemovePermission (t);
211                                         }
212                                 }
213                         }
214
215                         // don't start the stack walk if
216                         // - the permission set only contains non CAS permissions; or
217                         // - security isn't enabled (applis only to CAS!)
218                         if ((cas.Count > 0) && SecurityManager.SecurityEnabled)
219                                 CasOnlyDemand (_declsec ? 3 : 2);
220                 }
221
222                 // The number of frames to skip depends on who's calling
223                 // - CodeAccessPermission.Demand (imperative)
224                 // - PermissionSet.Demand (imperative)
225                 // - SecurityManager.InternalDemand (declarative)
226                 internal void CasOnlyDemand (int skip)
227                 {
228                         Assembly current = null;
229
230                         // skip ourself, Demand and other security runtime methods
231                         foreach (SecurityFrame sf in SecurityFrame.GetStack (skip)) {
232                                 if (ProcessFrame (sf, ref current))
233                                         return; // reached Assert
234                         }
235
236                         // Is there a CompressedStack to handle ?
237                         CompressedStack stack = Thread.CurrentThread.GetCompressedStack ();
238                         if ((stack != null) && !stack.IsEmpty ()) {
239                                 foreach (SecurityFrame frame in stack.List) {
240                                         if (ProcessFrame (frame, ref current))
241                                                 return; // reached Assert
242                                 }
243                         }
244                 }
245
246                 [MonoTODO ("Imperative mode isn't supported")]
247                 public virtual void Deny ()
248                 {
249                         foreach (IPermission p in list) {
250                                 // note: we ignore non-CAS permissions
251                                 if (p is IStackWalk) {
252                                         throw new NotSupportedException ("Currently only declarative Deny are supported.");
253                                 }
254                         }
255                 }
256
257                 [MonoTODO ("adjust class version with current runtime - unification")]
258                 public virtual void FromXml (SecurityElement et)
259                 {
260                         if (et == null)
261                                 throw new ArgumentNullException ("et");
262                         if (et.Tag != tagName) {
263                                 string msg = String.Format ("Invalid tag {0} expected {1}", et.Tag, tagName);
264                                 throw new ArgumentException (msg, "et");
265                         }
266
267                         if (CodeAccessPermission.IsUnrestricted (et))
268                                 state = PermissionState.Unrestricted;
269                         else
270                                 state = PermissionState.None;
271
272                         list.Clear ();
273                         if (et.Children != null) {
274                                 foreach (SecurityElement se in et.Children) {
275                                         string className = se.Attribute ("class");
276                                         if (className == null) {
277                                                 throw new ArgumentException (Locale.GetText (
278                                                         "No permission class is specified."));
279                                         }
280                                         if (Resolver != null) {
281                                                 // policy class names do not have to be fully qualified
282                                                 className = Resolver.ResolveClassName (className);
283                                         }
284                                         // TODO: adjust class version with current runtime (unification)
285                                         // http://blogs.msdn.com/shawnfa/archive/2004/08/05/209320.aspx
286                                         Type classType = Type.GetType (className);
287                                         if (classType != null) {
288                                                 IPermission p = (IPermission) Activator.CreateInstance (classType, psNone);
289                                                 p.FromXml (se);
290                                                 list.Add (p);
291                                         }
292 #if !NET_2_0
293                                         else {
294                                                 string msg = Locale.GetText ("Can't create an instance of permission class {0}.");
295                                                 throw new ArgumentException (String.Format (msg, se.Attribute ("class")));
296                                         }
297 #endif
298                                 }
299                         }
300                 }
301
302                 public virtual IEnumerator GetEnumerator ()
303                 {
304                         return list.GetEnumerator ();
305                 }
306
307                 public virtual bool IsSubsetOf (PermissionSet target)
308                 {
309                         // if target is empty we must be empty too
310                         if ((target == null) || (target.IsEmpty ()))
311                                 return this.IsEmpty ();
312
313                         // TODO - non CAS permissions must be evaluated for unrestricted
314
315                         // if target is unrestricted then we are a subset
316                         if (!this.IsUnrestricted () && target.IsUnrestricted ())
317                                 return true;
318                         // else target isn't unrestricted.
319                         // so if we are unrestricted, the we can't be a subset
320                         if (this.IsUnrestricted () && !target.IsUnrestricted ())
321                                 return false;
322
323                         // if each of our permission is (a) present and (b) a subset of target
324                         foreach (IPermission p in list) {
325                                 // for every type in both list
326                                 IPermission i = target.GetPermission (p.GetType ());
327                                 if (i == null)
328                                         return false; // not present (condition a)
329                                 if (!p.IsSubsetOf (i))
330                                         return false; // not a subset (condition b)
331                         }
332                         return true;
333                 }
334
335                 [MonoTODO ("Imperative mode isn't supported")]
336                 public virtual void PermitOnly ()
337                 {
338                         foreach (IPermission p in list) {
339                                 // note: we ignore non-CAS permissions
340                                 if (p is IStackWalk) {
341                                         throw new NotSupportedException ("Currently only declarative Deny are supported.");
342                                 }
343                         }
344                 }
345
346                 public bool ContainsNonCodeAccessPermissions () 
347                 {
348                         foreach (IPermission p in list) {
349                                 if (! p.GetType ().IsSubclassOf (typeof (CodeAccessPermission)))
350                                         return true;
351                         }
352                         return false;
353                 }
354
355                 [MonoTODO ("little documentation in Fx 2.0 beta 1")]
356                 public static byte[] ConvertPermissionSet (string inFormat, byte[] inData, string outFormat) 
357                 {
358                         if (inFormat == null)
359                                 throw new ArgumentNullException ("inFormat");
360                         if (outFormat == null)
361                                 throw new ArgumentNullException ("outFormat");
362                         if (inData == null)
363                                 return null;
364
365                         if (inFormat == outFormat)
366                                 return inData;
367
368                         PermissionSet ps = null;
369
370                         if (inFormat == "BINARY") {
371                                 if (outFormat.StartsWith ("XML")) {
372                                         using (MemoryStream ms = new MemoryStream (inData)) {
373                                                 BinaryFormatter formatter = new BinaryFormatter ();
374                                                 ps = (PermissionSet) formatter.Deserialize (ms);
375                                                 ms.Close ();
376                                         }
377                                         string xml = ps.ToString ();
378                                         switch (outFormat) {
379                                                 case "XML":
380                                                 case "XMLASCII":
381                                                         return Encoding.ASCII.GetBytes (xml);
382                                                 case "XMLUNICODE":
383                                                         return Encoding.Unicode.GetBytes (xml);
384                                         }
385                                 }
386                         }
387                         else if (inFormat.StartsWith ("XML")) {
388                                 if (outFormat == "BINARY") {
389                                         string xml = null;
390                                         switch (inFormat) {
391                                                 case "XML":
392                                                 case "XMLASCII":
393                                                         xml = Encoding.ASCII.GetString (inData);
394                                                         break;
395                                                 case "XMLUNICODE":
396                                                         xml = Encoding.Unicode.GetString (inData);
397                                                         break;
398                                         }
399                                         if (xml != null) {
400                                                 ps = new PermissionSet (PermissionState.None);
401                                                 ps.FromXml (SecurityElement.FromString (xml));
402
403                                                 MemoryStream ms = new MemoryStream ();
404                                                 BinaryFormatter formatter = new BinaryFormatter ();
405                                                 formatter.Serialize (ms, ps);
406                                                 ms.Close ();
407                                                 return ms.ToArray ();
408                                         }
409                                 }
410                                 else if (outFormat.StartsWith ("XML")) {
411                                         string msg = String.Format (Locale.GetText ("Can't convert from {0} to {1}"), inFormat, outFormat);
412 #if NET_2_0
413                                         throw new XmlSyntaxException (msg);
414 #else
415                                         throw new ArgumentException (msg);
416 #endif
417                                 }
418                         }
419                         else {
420                                 // unknown inFormat, returns null
421                                 return null;
422                         }
423                         // unknown outFormat, throw
424                         throw new SerializationException (String.Format (Locale.GetText ("Unknown output format {0}."), outFormat));
425                 }
426
427                 public virtual IPermission GetPermission (Type permClass) 
428                 {
429                         foreach (object o in list) {
430                                 if (o.GetType ().Equals (permClass))
431                                         return (IPermission) o;
432                         }
433                         // it's normal to return null for unrestricted sets
434                         return null;
435                 }
436
437                 public virtual PermissionSet Intersect (PermissionSet other) 
438                 {
439                         // no intersection possible
440                         if ((other == null) || (other.IsEmpty ()) || (this.IsEmpty ()))
441                                 return null;
442
443                         PermissionState state = PermissionState.None;
444                         if (this.IsUnrestricted () && other.IsUnrestricted ())
445                                 state = PermissionState.Unrestricted;
446
447                         PermissionSet interSet = new PermissionSet (state);
448                         if (state == PermissionState.Unrestricted) {
449                                 InternalIntersect (interSet, this, other, true);
450                                 InternalIntersect (interSet, other, this, true);
451                         }
452                         else if (this.IsUnrestricted ()) {
453                                 InternalIntersect (interSet, this, other, true);
454                         }
455                         else if (other.IsUnrestricted ()) {
456                                 InternalIntersect (interSet, other, this, true);
457                         }
458                         else {
459                                 InternalIntersect (interSet, this, other, false);
460                         }
461                         return interSet;
462                 }
463
464                 internal void InternalIntersect (PermissionSet intersect, PermissionSet a, PermissionSet b, bool unrestricted)
465                 {
466                         foreach (IPermission p in b.list) {
467                                 // for every type in both list
468                                 IPermission i = a.GetPermission (p.GetType ());
469                                 if (i != null) {
470                                         // add intersection for this type
471                                         intersect.AddPermission (p.Intersect (i));
472                                 }
473                                 else if (unrestricted && (p is IUnrestrictedPermission)) {
474                                         intersect.AddPermission (p);
475                                 }
476                                 // or reject!
477                         }
478                 }
479
480                 public virtual bool IsEmpty () 
481                 {
482                         // note: Unrestricted isn't empty
483                         if (state == PermissionState.Unrestricted)
484                                 return false;
485                         if ((list == null) || (list.Count == 0))
486                                 return true;
487                         // the set may include some empty permissions
488                         foreach (IPermission p in list) {
489                                 // empty == fully restricted == IsSubsetOg(null) == true
490                                 if (!p.IsSubsetOf (null))
491                                         return false;
492                         }
493                         return true;
494                 }
495
496                 public virtual bool IsUnrestricted () 
497                 {
498                         return (state == PermissionState.Unrestricted);
499                 }
500
501                 public virtual IPermission RemovePermission (Type permClass) 
502                 {
503                         if (permClass == null)
504                                 return null;
505
506                         foreach (object o in list) {
507                                 if (o.GetType ().Equals (permClass)) {
508                                         list.Remove (o);
509                                         return (IPermission) o;
510                                 }
511                         }
512                         return null;
513                 }
514
515                 public virtual IPermission SetPermission (IPermission perm) 
516                 {
517                         if (perm == null)
518                                 return null;
519                         if (perm is IUnrestrictedPermission)
520                                 state = PermissionState.None;
521                         RemovePermission (perm.GetType ());
522                         list.Add (perm);
523                         return perm;
524                 }
525
526                 public override string ToString ()
527                 {
528                         return ToXml ().ToString ();
529                 }
530
531                 public virtual SecurityElement ToXml ()
532                 {
533                         SecurityElement se = new SecurityElement (tagName);
534                         se.AddAttribute ("class", GetType ().FullName);
535                         se.AddAttribute ("version", version.ToString ());
536                         if (state == PermissionState.Unrestricted)
537                                 se.AddAttribute ("Unrestricted", "true");
538
539                         // required for permissions that do not implement IUnrestrictedPermission
540                         foreach (IPermission p in list) {
541                                 se.AddChild (p.ToXml ());
542                         }
543                         return se;
544                 }
545
546                 public virtual PermissionSet Union (PermissionSet other)
547                 {
548                         if (other == null)
549                                 return this.Copy ();
550
551                         PermissionSet copy = this.Copy ();
552                         if (this.IsUnrestricted () || other.IsUnrestricted ()) {
553                                 // so we keep the "right" type
554                                 copy.Clear ();
555                                 copy.state = PermissionState.Unrestricted;
556                                 // copy all permissions that do not implement IUnrestrictedPermission
557                                 foreach (IPermission p in this.list) {
558                                         if (!(p is IUnrestrictedPermission))
559                                                 copy.AddPermission (p);
560                                 }
561                                 foreach (IPermission p in other.list) {
562                                         if (!(p is IUnrestrictedPermission))
563                                                 copy.AddPermission (p);
564                                 }
565                         }
566                         else {
567                                 // PermissionState.None -> copy all permissions
568                                 foreach (IPermission p in other.list) {
569                                         copy.AddPermission (p);
570                                 }
571                         }
572                         return copy;
573                 }
574
575                 public virtual int Count {
576                         get { return list.Count; }
577                 }
578
579                 public virtual bool IsSynchronized {
580                         get { return list.IsSynchronized; }
581                 }
582
583                 public virtual bool IsReadOnly {
584                         get { return false; } // always false
585                 }
586
587                 public virtual object SyncRoot {
588                         get { return this; }
589                 }
590
591                 internal bool DeclarativeSecurity {
592                         get { return _declsec; }
593                         set { _declsec = value; }
594                 }
595
596                 [MonoTODO()]
597                 void IDeserializationCallback.OnDeserialization (object sender) 
598                 {
599                 }
600
601 #if NET_2_0
602                 [ComVisible (false)]
603                 public override bool Equals (object obj)
604                 {
605                         if (obj == null)
606                                 return false;
607                         PermissionSet ps = (obj as PermissionSet);
608                         if (ps == null)
609                                 return false;
610                         if (list.Count != ps.Count)
611                                 return false;
612
613                         for (int i=0; i < list.Count; i++) {
614                                 bool found = false;
615                                 for (int j=0; i < ps.list.Count; j++) {
616                                         if (list [i].Equals (ps.list [j])) {
617                                                 found = true;
618                                                 break;
619                                         }
620                                 }
621                                 if (!found)
622                                         return false;
623                         }
624                         return true;
625                 }
626
627                 [ComVisible (false)]
628                 public override int GetHashCode ()
629                 {
630                         return (list.Count == 0) ? (int) state : base.GetHashCode ();
631                 }
632
633                 [MonoTODO ("what's it doing here?")]
634                 static public void RevertAssert ()
635                 {
636                         // FIXME: There's probably a reason this was added here ?
637                         CodeAccessPermission.RevertAssert ();
638                 }
639 #endif
640
641                 // internal
642
643                 internal PolicyLevel Resolver {
644                         get { return _policyLevel; }
645                         set { _policyLevel = value; }
646                 }
647
648
649                 internal void ImmediateCallerDemand ()
650                 {
651                         if (IsEmpty ())
652                                 return;
653
654                         // skip ourself
655                         SecurityFrame sf = new SecurityFrame (1);       // FIXME skip
656                         foreach (IPermission p in list) {
657                                 // note: this may contains non CAS permissions
658                                 if (p is CodeAccessPermission) {
659                                         if (SecurityManager.SecurityEnabled)
660                                                 SecurityManager.IsGranted (sf.Assembly, p);
661                                 } else {
662                                         p.Demand ();
663                                 }
664                         }
665                 }
666
667                 // Note: Non-CAS demands aren't affected by SecurityManager.SecurityEnabled
668                 internal void ImmediateCallerNonCasDemand ()
669                 {
670                         if (IsEmpty ())
671                                 return;
672
673                         // non CAS permissions (e.g. PrincipalPermission) requires direct call to Demand
674                         foreach (IPermission p in list) {
675                                 p.Demand ();
676                         }
677                 }
678
679                 internal bool ProcessFrame (SecurityFrame frame, ref Assembly current)
680                 {
681                         foreach (CodeAccessPermission cap in list) {
682                                 if (cap.ProcessFrame (frame, ref current))
683                                         return true; // Assert reached - abort stack walk!
684                         }
685                         return false;
686                 }
687         }
688 }