2010-04-28 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Security / RolePrincipal.cs
1 //
2 // System.Web.Security.RolePrincipal
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Sebastien Pouliot  <sebastien@ximian.com>
7 //
8 // (C) 2003 Ben Maurer
9 // Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Collections.Specialized;
32 using System.Security.Cryptography;
33 using System.Security.Permissions;
34 using System.Security.Principal;
35 using System.Web.Configuration;
36 using System.IO;
37 using System.Text;
38
39 namespace System.Web.Security {
40
41         [Serializable]
42         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
43 #if NET_4_0
44         public
45 #else
46         public sealed
47 #endif
48         class RolePrincipal : IPrincipal {
49
50                 IIdentity _identity;
51                 bool _listChanged;
52                 string[] _cachedArray;
53                 HybridDictionary _cachedRoles;
54                 readonly string _providerName;
55
56                 int _version = 1;
57                 string _cookiePath;
58                 DateTime _issueDate;
59                 DateTime _expireDate;
60
61
62                 public RolePrincipal (IIdentity identity)
63                 {
64                         if (identity == null)
65                                 throw new ArgumentNullException ("identity");
66                         
67                         this._identity = identity;
68                         this._cookiePath = RoleManagerConfig.CookiePath;
69                         this._issueDate = DateTime.Now;
70                         this._expireDate = _issueDate.Add (RoleManagerConfig.CookieTimeout);
71                 }
72
73                 public RolePrincipal (IIdentity identity, string encryptedTicket)
74                         : this (identity)
75                 {
76                         DecryptTicket (encryptedTicket);
77                 }
78
79                 public RolePrincipal (string providerName, IIdentity identity)
80                         : this (identity)
81                 {
82                         if (providerName == null)
83                                 throw new ArgumentNullException ("providerName");
84
85                         this._providerName = providerName;
86                 }
87
88                 public RolePrincipal (string providerName, IIdentity identity, string encryptedTicket)
89                         : this (providerName, identity)
90                 {
91                         DecryptTicket (encryptedTicket);
92                 }
93
94                 public string [] GetRoles ()
95                 {
96                         if (!_identity.IsAuthenticated)
97                                 return new string[0];
98
99                         if (!IsRoleListCached || Expired) {
100                                 _cachedArray = Provider.GetRolesForUser (_identity.Name);
101                                 _cachedRoles = new HybridDictionary (true);
102
103                                 foreach (string r in _cachedArray)
104                                         _cachedRoles.Add(r, r);
105
106                                 _listChanged = true;
107                         }
108
109                         return _cachedArray;
110                 }
111                 
112                 public bool IsInRole (string role)
113                 {
114                         if (!_identity.IsAuthenticated)
115                                 return false;
116
117                         GetRoles ();
118
119                         return _cachedRoles [role] != null;
120                 }
121                 
122                 public string ToEncryptedTicket ()
123                 {
124                         string roles = string.Join (",", GetRoles ());
125                         string cookiePath = RoleManagerConfig.CookiePath;
126                         int approxTicketLen = roles.Length + cookiePath.Length + 64;
127
128                         if (_cachedArray.Length > Roles.MaxCachedResults)
129                                return null;
130
131                         MemoryStream ticket = new MemoryStream (approxTicketLen);
132                         BinaryWriter writer = new BinaryWriter (ticket);
133
134                         // version
135                         writer.Write (Version);
136                 
137                         // issue datetime
138                         DateTime issueDate = DateTime.Now;
139                         writer.Write (issueDate.Ticks);
140
141                         // expiration datetime
142                         writer.Write (_expireDate.Ticks);
143
144                         writer.Write (cookiePath);
145                         writer.Write (roles);
146
147                         CookieProtection cookieProtection = RoleManagerConfig.CookieProtection;
148
149                         if (cookieProtection == CookieProtection.None)
150                                 return GetBase64FromBytes (ticket.GetBuffer (), 0, (int) ticket.Position);
151                         
152                         if (cookieProtection == CookieProtection.All || cookieProtection == CookieProtection.Validation) {
153
154                                 byte [] hashBytes = null;
155                                 byte [] validationBytes = MachineKeySectionUtils.ValidationKeyBytes (MachineConfig);
156                                 writer.Write (validationBytes);
157
158                                 switch (MachineConfig.Validation) {
159                                         case MachineKeyValidation.MD5:
160                                                 hashBytes = MD5.Create ().ComputeHash (ticket.GetBuffer (), 0, (int) ticket.Position);
161                                                 break;
162
163                                         case MachineKeyValidation.TripleDES:
164                                         case MachineKeyValidation.SHA1:
165                                                 hashBytes = SHA1.Create ().ComputeHash (ticket.GetBuffer (), 0, (int) ticket.Position);
166                                                 break;
167                                 }
168
169                                 writer.Seek (-validationBytes.Length, SeekOrigin.Current);
170                                 writer.Write (hashBytes);
171                         }
172
173                         byte [] ticketBytes = null;
174                         if (cookieProtection == CookieProtection.All || cookieProtection == CookieProtection.Encryption) {
175                                 ICryptoTransform enc;
176                                 enc = TripleDES.Create ().CreateEncryptor (MachineKeySectionUtils.DecryptionKey192Bits (MachineConfig),
177                                                                            InitVector);
178                                 ticketBytes = enc.TransformFinalBlock (ticket.GetBuffer (), 0, (int) ticket.Position);
179                         }
180
181                         if (ticketBytes == null)
182                                 return GetBase64FromBytes (ticket.GetBuffer (), 0, (int) ticket.Position);
183                         else
184                                 return GetBase64FromBytes (ticketBytes, 0, ticketBytes.Length);
185                 }
186
187                 void DecryptTicket (string encryptedTicket)
188                 {
189                         if (encryptedTicket == null || encryptedTicket == String.Empty)
190                                 throw new ArgumentException ("Invalid encrypted ticket", "encryptedTicket");
191
192                         byte [] ticketBytes = GetBytesFromBase64 (encryptedTicket);
193                         byte [] decryptedTicketBytes = null;
194
195                         CookieProtection cookieProtection = RoleManagerConfig.CookieProtection;
196                         if (cookieProtection == CookieProtection.All || cookieProtection == CookieProtection.Encryption) {
197                                 ICryptoTransform decryptor;
198                                 decryptor = TripleDES.Create ().CreateDecryptor (
199                                         MachineKeySectionUtils.DecryptionKey192Bits (MachineConfig),
200                                         InitVector);
201                                 decryptedTicketBytes = decryptor.TransformFinalBlock (ticketBytes, 0, ticketBytes.Length);
202                         }
203                         else
204                                 decryptedTicketBytes = ticketBytes;
205
206                         if (cookieProtection == CookieProtection.All || cookieProtection == CookieProtection.Validation) {
207                                 byte [] validationBytes = MachineKeySectionUtils.ValidationKeyBytes (MachineConfig);
208                                 byte [] rolesWithValidationBytes = null;
209                                 byte [] tmpValidation = null;
210
211                                 int hashSize = (MachineConfig.Validation == MachineKeyValidation.MD5) ? 16 : 20; //md5 is 16 bytes, sha1 is 20 bytes
212
213                                 rolesWithValidationBytes = new byte [decryptedTicketBytes.Length - hashSize + validationBytes.Length];
214
215                                 Buffer.BlockCopy (decryptedTicketBytes, 0, rolesWithValidationBytes, 0, decryptedTicketBytes.Length - hashSize);
216                                 Buffer.BlockCopy (validationBytes, 0, rolesWithValidationBytes, decryptedTicketBytes.Length - hashSize, validationBytes.Length);
217
218                                 switch (MachineConfig.Validation) {
219                                         case MachineKeyValidation.MD5:
220                                                 tmpValidation = MD5.Create ().ComputeHash (rolesWithValidationBytes);
221                                                 break;
222
223                                         case MachineKeyValidation.TripleDES:
224                                         case MachineKeyValidation.SHA1:
225                                                 tmpValidation = SHA1.Create ().ComputeHash (rolesWithValidationBytes);
226                                                 break;
227                                 }
228                                 for (int i = 0; i < tmpValidation.Length; i++) {
229                                         if (i >= decryptedTicketBytes.Length ||
230                                                 tmpValidation [i] != decryptedTicketBytes [i + decryptedTicketBytes.Length - hashSize])
231                                                 throw new HttpException ("ticket validation failed");
232                                 }
233                         }
234
235                         MemoryStream ticket = new MemoryStream (decryptedTicketBytes);
236                         BinaryReader reader = new BinaryReader (ticket);
237
238                         // version
239                         _version = reader.ReadInt32 ();
240
241                         // issued date
242                         _issueDate = new DateTime (reader.ReadInt64 ());
243
244                         // expire date
245                         _expireDate = new DateTime (reader.ReadInt64 ());
246
247                         // cookie path
248                         _cookiePath = reader.ReadString ();
249                         
250                         // roles
251                         string roles = reader.ReadString ();
252
253                         if (!Expired) {
254                                 InitializeRoles (roles);
255                                 //update ticket if less than half of CookieTimeout remaining.
256                                 if (Roles.CookieSlidingExpiration){
257                                         if (_expireDate-DateTime.Now < TimeSpan.FromTicks (RoleManagerConfig.CookieTimeout.Ticks/2))    {
258                                                 _issueDate = DateTime.Now;
259                                                 _expireDate = DateTime.Now.Add (RoleManagerConfig.CookieTimeout);
260                                                 SetDirty ();
261                                         }
262                                 }
263                         } else {
264                                 // issue a new ticket
265                                 _issueDate = DateTime.Now;
266                                 _expireDate = _issueDate.Add (RoleManagerConfig.CookieTimeout);
267                         }
268                 }
269
270                 void InitializeRoles (string decryptedRoles)
271                 {
272                         _cachedArray = decryptedRoles.Split (',');
273                         _cachedRoles = new HybridDictionary (true);
274
275                         foreach (string r in _cachedArray)
276                                 _cachedRoles.Add (r, r);
277                 }
278
279                 byte [] InitVector
280                 {
281                         get { return new byte [] { 1, 2, 3, 4, 5, 6, 7, 8 }; }
282                 }
283                 
284                 public bool CachedListChanged {
285                         get { return _listChanged; }
286                 }
287                 
288                 public string CookiePath {
289                         get { return _cookiePath; }
290                 }
291                 
292                 public bool Expired {
293                         get { return ExpireDate < DateTime.Now; }
294                 }
295                 
296                 public DateTime ExpireDate {
297                         get { return _expireDate; }
298                 }
299                 
300                 public IIdentity Identity {
301                         get { return _identity; }
302                 }
303                 
304                 public bool IsRoleListCached {
305                         get { return (_cachedRoles != null) && RoleManagerConfig.CacheRolesInCookie; }
306                 }
307                 
308                 public DateTime IssueDate {
309                         get { return _issueDate; }
310                 }
311                 
312                 public string ProviderName {
313                         get { return String.IsNullOrEmpty(_providerName) ? Provider.Name : _providerName; }
314                 }
315                 
316                 public int Version {
317                         get { return _version; }
318                 }
319
320                 RoleProvider Provider {
321                         get {
322                                 if (String.IsNullOrEmpty (_providerName))
323                                         return Roles.Provider;
324
325                                 return Roles.Providers [_providerName];
326                         }
327                 }
328
329                 public void SetDirty ()
330                 {
331                         _listChanged = true;
332                         _cachedRoles = null;
333                         _cachedArray = null;
334                 }
335
336                 static string GetBase64FromBytes (byte [] bytes, int offset, int len)
337                 {
338                         return Convert.ToBase64String (bytes, offset, len);
339                 }
340
341                 static byte [] GetBytesFromBase64 (string base64String)
342                 {
343                         return Convert.FromBase64String (base64String);
344                 }
345
346                 RoleManagerSection RoleManagerConfig
347                 {
348                         get { return (RoleManagerSection) WebConfigurationManager.GetSection ("system.web/roleManager"); }
349                 }
350
351                 MachineKeySection MachineConfig
352                 {
353                         get { return (MachineKeySection) WebConfigurationManager.GetSection ("system.web/machineKey"); }
354                 }
355         }
356 }
357
358