fixed tests
[mono.git] / mcs / class / Mainsoft.Web / Mainsoft.Web.Security / DerbyMembershipProvider.cs
1 //\r
2 // Mainsoft.Web.Security.DerbyMembershipProvider\r
3 //\r
4 // Authors:\r
5 //      Ben Maurer (bmaurer@users.sourceforge.net)\r
6 //      Chris Toshok (toshok@ximian.com)\r
7 //      Vladimir Krasnov (vladimirk@mainsoft.com)\r
8 //\r
9 //\r
10 // Permission is hereby granted, free of charge, to any person obtaining\r
11 // a copy of this software and associated documentation files (the\r
12 // "Software"), to deal in the Software without restriction, including\r
13 // without limitation the rights to use, copy, modify, merge, publish,\r
14 // distribute, sublicense, and/or sell copies of the Software, and to\r
15 // permit persons to whom the Software is furnished to do so, subject to\r
16 // the following conditions:\r
17 // \r
18 // The above copyright notice and this permission notice shall be\r
19 // included in all copies or substantial portions of the Software.\r
20 // \r
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
28 //\r
29 \r
30 #if NET_2_0
31
32 using System;
33 using System.Collections;
34 using System.Collections.Specialized;
35 using System.Configuration;
36 using System.Configuration.Provider;
37 using System.Data;\r
38 using System.Data.OleDb;
39 using System.Data.Common;
40 using System.Text;
41 using System.Web.Configuration;\r
42 using System.Security;
43 using System.Security.Cryptography;\r
44 using System.Web;\r
45 using System.Web.Security;\r
46 \r
47 namespace Mainsoft.Web.Security {
48         public class DerbyMembershipProvider : MembershipProvider
49         {
50                 const int SALT_BYTES = 16;
51
52                 bool enablePasswordReset;
53                 bool enablePasswordRetrieval;
54                 int maxInvalidPasswordAttempts;
55                 MembershipPasswordFormat passwordFormat;
56                 bool requiresQuestionAndAnswer;
57                 bool requiresUniqueEmail;
58                 int minRequiredNonAlphanumericCharacters;
59                 int minRequiredPasswordLength;
60                 int passwordAttemptWindow;
61                 string passwordStrengthRegularExpression;
62                 TimeSpan userIsOnlineTimeWindow;
63                 ConnectionStringSettings connectionString;
64
65                 string applicationName;
66
67                 DbConnection CreateConnection ()
68                 {\r
69                         DerbyDBSchema.CheckSchema (connectionString.ConnectionString);\r
70 \r
71                         OleDbConnection connection = new OleDbConnection (connectionString.ConnectionString);
72                         connection.Open ();
73                         return connection;
74                 }
75
76                 void CheckParam (string pName, string p, int length)
77                 {
78                         if (p == null)
79                                 throw new ArgumentNullException (pName);
80                         if (p.Length == 0 || p.Length > length || p.IndexOf (",") != -1)
81                                 throw new ArgumentException (String.Format ("invalid format for {0}", pName));
82                 }
83
84                 public override bool ChangePassword (string username, string oldPwd, string newPwd)\r
85                 {\r
86                         if (username != null) username = username.Trim ();\r
87                         if (oldPwd != null) oldPwd = oldPwd.Trim ();\r
88                         if (newPwd != null) newPwd = newPwd.Trim ();\r
89 \r
90                         CheckParam ("username", username, 256);\r
91                         CheckParam ("oldPwd", oldPwd, 128);\r
92                         CheckParam ("newPwd", newPwd, 128);\r
93 \r
94                         using (DbConnection connection = CreateConnection ()) {\r
95                                 PasswordInfo pi = ValidateUsingPassword (username, oldPwd);\r
96 \r
97                                 if (pi != null) {\r
98                                         EmitValidatingPassword (username, newPwd, false);\r
99                                         string db_password = EncodePassword (newPwd, pi.PasswordFormat, pi.PasswordSalt);\r
100 \r
101                                         int st = DerbyMembershipHelper.Membership_SetPassword (connection, ApplicationName, username, db_password, (int) pi.PasswordFormat, pi.PasswordSalt, DateTime.UtcNow);\r
102 \r
103                                         if (st == 0)\r
104                                                 return true;\r
105                                 }\r
106                                 return false;\r
107                         }\r
108                 }
109
110                 public override bool ChangePasswordQuestionAndAnswer (string username, string password, string newPwdQuestion, string newPwdAnswer)
111                 {
112                         if (username != null) username = username.Trim ();
113                         if (newPwdQuestion != null) newPwdQuestion = newPwdQuestion.Trim ();
114                         if (newPwdAnswer != null) newPwdAnswer = newPwdAnswer.Trim ();
115
116                         CheckParam ("username", username, 256);
117                         if (RequiresQuestionAndAnswer)
118                                 CheckParam ("newPwdQuestion", newPwdQuestion, 128);
119                         if (RequiresQuestionAndAnswer)
120                                 CheckParam ("newPwdAnswer", newPwdAnswer, 128);
121
122                         using (DbConnection connection = CreateConnection ()) {
123                                 PasswordInfo pi = ValidateUsingPassword (username, password);
124
125                                 if (pi != null) {
126                                         string db_passwordAnswer = EncodePassword (newPwdAnswer, pi.PasswordFormat, pi.PasswordSalt);\r
127 \r
128                                         int st = DerbyMembershipHelper.Membership_ChangePasswordQuestionAndAnswer (connection, ApplicationName, username, newPwdQuestion, db_passwordAnswer);
129
130                                         if (st == 0)
131                                                 return true;
132                                 }
133                                 return false;
134                         }
135                 }
136
137                 public override MembershipUser CreateUser (string username,
138                                                            string password,
139                                                            string email,
140                                                            string pwdQuestion,
141                                                            string pwdAnswer,
142                                                            bool isApproved,
143                                                            object providerUserKey,
144                                                            out MembershipCreateStatus status)
145                 {
146                         if (username != null) username = username.Trim ();
147                         if (password != null) password = password.Trim ();
148                         if (email != null) email = email.Trim ();
149                         if (pwdQuestion != null) pwdQuestion = pwdQuestion.Trim ();
150                         if (pwdAnswer != null) pwdAnswer = pwdAnswer.Trim ();
151
152                         /* some initial validation */
153                         if (username == null || username.Length == 0 || username.Length > 256 || username.IndexOf (",") != -1) {
154                                 status = MembershipCreateStatus.InvalidUserName;
155                                 return null;
156                         }
157                         if (password == null || password.Length == 0 || password.Length > 128) {
158                                 status = MembershipCreateStatus.InvalidPassword;
159                                 return null;
160                         }
161
162                         if (!CheckPassword (password)) {
163                                 status = MembershipCreateStatus.InvalidPassword;
164                                 return null;
165                         }
166                         EmitValidatingPassword (username, password, true);
167
168                         if (RequiresUniqueEmail && (email == null || email.Length == 0)) {
169                                 status = MembershipCreateStatus.InvalidEmail;
170                                 return null;
171                         }
172                         if (RequiresQuestionAndAnswer &&
173                                 (pwdQuestion == null ||
174                                  pwdQuestion.Length == 0 || pwdQuestion.Length > 256)) {
175                                 status = MembershipCreateStatus.InvalidQuestion;
176                                 return null;
177                         }
178                         if (RequiresQuestionAndAnswer &&
179                                 (pwdAnswer == null ||
180                                  pwdAnswer.Length == 0 || pwdAnswer.Length > 128)) {
181                                 status = MembershipCreateStatus.InvalidAnswer;
182                                 return null;
183                         }
184                         if (providerUserKey != null && !(providerUserKey is Guid)) {
185                                 status = MembershipCreateStatus.InvalidProviderUserKey;
186                                 return null;
187                         }\r
188 \r
189                         /* encode our password/answer using the
190                          * "passwordFormat" configuration option */
191                         string passwordSalt = "";
192
193                         RandomNumberGenerator rng = RandomNumberGenerator.Create ();
194                         byte [] salt = new byte [SALT_BYTES];
195                         rng.GetBytes (salt);
196                         passwordSalt = Convert.ToBase64String (salt);
197
198                         password = EncodePassword (password, PasswordFormat, passwordSalt);
199                         if (RequiresQuestionAndAnswer)
200                                 pwdAnswer = EncodePassword (pwdAnswer, PasswordFormat, passwordSalt);
201
202                         /* make sure the hashed/encrypted password and
203                          * answer are still under 128 characters. */
204                         if (password.Length > 128) {
205                                 status = MembershipCreateStatus.InvalidPassword;
206                                 return null;
207                         }
208
209                         if (RequiresQuestionAndAnswer) {
210                                 if (pwdAnswer.Length > 128) {
211                                         status = MembershipCreateStatus.InvalidAnswer;
212                                         return null;
213                                 }
214                         }
215                         status = MembershipCreateStatus.Success;
216
217                         using (DbConnection connection = CreateConnection ()) {
218                                 try {\r
219 \r
220                                         object helperUserKey = providerUserKey != null ? providerUserKey.ToString () : null;\r
221                                         DateTime Now = DateTime.UtcNow;\r
222                                         int st = DerbyMembershipHelper.Membership_CreateUser (connection, ApplicationName, username, password, passwordSalt, email,\r
223                                                 pwdQuestion, pwdAnswer, isApproved, Now, Now, RequiresUniqueEmail, (int) PasswordFormat, ref helperUserKey);\r
224 \r
225                                         providerUserKey = new Guid ((string) helperUserKey);\r
226                                         if (st == 0)\r
227                                                 return GetUser (providerUserKey, false);
228                                         else if (st == 2)
229                                             status = MembershipCreateStatus.DuplicateUserName;
230                                         else if (st == 3)
231                                             status = MembershipCreateStatus.DuplicateEmail;
232                                         else if (st == 9)\r
233                                                 status = MembershipCreateStatus.InvalidProviderUserKey;
234                                         else if (st == 10)
235                                             status = MembershipCreateStatus.DuplicateProviderUserKey;
236                                         else
237                                             status = MembershipCreateStatus.ProviderError;\r
238 \r
239                                         return null;
240                                 }
241                                 catch (Exception) {\r
242                                         status = MembershipCreateStatus.ProviderError;
243                                         return null;
244                                 }
245                         }
246                 }
247
248                 private bool CheckPassword (string password)
249                 {
250                         if (password.Length < MinRequiredPasswordLength)
251                                 return false;
252
253                         if (MinRequiredNonAlphanumericCharacters > 0) {
254                                 int nonAlphanumeric = 0;
255                                 for (int i = 0; i < password.Length; i++) {
256                                         if (!Char.IsLetterOrDigit (password [i]))
257                                                 nonAlphanumeric++;
258                                 }
259                                 return nonAlphanumeric >= MinRequiredNonAlphanumericCharacters;
260                         }
261                         return true;
262                 }
263
264                 public override bool DeleteUser (string username, bool deleteAllRelatedData)
265                 {
266                         CheckParam ("username", username, 256);
267
268                         DeleteUserTableMask deleteBitmask = DeleteUserTableMask.MembershipUsers;
269
270                         if (deleteAllRelatedData)
271                                 deleteBitmask |=
272                                         DeleteUserTableMask.Profiles |
273                                         DeleteUserTableMask.UsersInRoles |
274                                         DeleteUserTableMask.WebPartStateUser;\r
275                         
276                         int num = 0;
277                         using (DbConnection connection = CreateConnection ()) {\r
278                                 int st = DerbyMembershipHelper.Users_DeleteUser (connection, ApplicationName, username, (int) deleteBitmask, ref num);\r
279 \r
280                                 if (num == 0)
281                                         return false;
282
283                                 if (st == 0)
284                                         return true;
285
286                                 return false;
287                         }
288                 }
289
290                 public virtual string GeneratePassword ()
291                 {
292                         return Membership.GeneratePassword (MinRequiredPasswordLength, MinRequiredNonAlphanumericCharacters);
293                 }
294
295                 public override MembershipUserCollection FindUsersByEmail (string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
296                 {
297                         CheckParam ("emailToMatch", emailToMatch, 256);
298
299                         if (pageIndex < 0)
300                                 throw new ArgumentException ("pageIndex must be >= 0");
301                         if (pageSize < 0)
302                                 throw new ArgumentException ("pageSize must be >= 0");
303                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
304                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
305 \r
306                         totalRecords = 0;
307                         using (DbConnection connection = CreateConnection ()) {\r
308                                 DbDataReader reader = null;\r
309 \r
310                                 DerbyMembershipHelper.Membership_FindUsersByEmail (connection, ApplicationName, emailToMatch, pageSize, pageIndex, out reader);\r
311                                 if (reader == null)\r
312                                         return null;\r
313
314                                 using (reader) {\r
315                                         return BuildMembershipUserCollection (reader, pageIndex, pageSize, out totalRecords);
316                                 }\r
317                         }
318                 }
319
320                 public override MembershipUserCollection FindUsersByName (string nameToMatch, int pageIndex, int pageSize, out int totalRecords)
321                 {
322                         CheckParam ("nameToMatch", nameToMatch, 256);
323
324                         if (pageIndex < 0)
325                                 throw new ArgumentException ("pageIndex must be >= 0");
326                         if (pageSize < 0)
327                                 throw new ArgumentException ("pageSize must be >= 0");
328                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
329                                 throw new ArgumentException ("pageIndex and pageSize are too large");\r
330 \r
331                         totalRecords = 0;
332                         using (DbConnection connection = CreateConnection ()) {\r
333                                 DbDataReader reader = null;\r
334 \r
335                                 DerbyMembershipHelper.Membership_FindUsersByName (connection, ApplicationName, nameToMatch, pageSize, pageIndex, out reader);\r
336                                 if (reader == null)\r
337                                         return null;\r
338 \r
339                                 using (reader) {\r
340                                         return BuildMembershipUserCollection (reader, pageIndex, pageSize, out totalRecords);\r
341                                 }\r
342                         }
343                 }
344
345                 public override MembershipUserCollection GetAllUsers (int pageIndex, int pageSize, out int totalRecords)
346                 {
347                         if (pageIndex < 0)
348                                 throw new ArgumentException ("pageIndex must be >= 0");
349                         if (pageSize < 0)
350                                 throw new ArgumentException ("pageSize must be >= 0");
351                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
352                                 throw new ArgumentException ("pageIndex and pageSize are too large");
353
354                         using (DbConnection connection = CreateConnection ()) {\r
355                                 DbDataReader reader = null;\r
356                                 totalRecords = DerbyMembershipHelper.Membership_GetAllUsers (connection, ApplicationName, pageIndex, pageSize, out reader);\r
357                                 return BuildMembershipUserCollection (reader, pageIndex, pageSize, out totalRecords);\r
358                         }
359                 }\r
360 \r
361                 MembershipUserCollection BuildMembershipUserCollection (DbDataReader reader, int pageIndex, int pageSize, out int totalRecords)
362                 {\r
363                         int num_read = 0;\r
364                         int num_added = 0;\r
365                         int num_to_skip = pageIndex * pageSize;\r
366                         MembershipUserCollection users = new MembershipUserCollection ();\r
367                         try {\r
368                                 while (reader.Read ()) {\r
369                                         if (num_read >= num_to_skip) {\r
370                                                 if (num_added < pageSize) {\r
371                                                         users.Add (GetUserFromReader (reader));\r
372                                                         num_added++;\r
373                                                 }\r
374                                         }\r
375                                         num_read++;\r
376                                 }\r
377                                 totalRecords = num_read;\r
378                                 return users;\r
379                         }\r
380                         catch (Exception) {\r
381                                 totalRecords = 0;\r
382                                 return null; /* should we let the exception through? */\r
383                         }
384                         finally {
385                                 if (reader != null)
386                                         reader.Close ();
387                         }
388                 }
389
390                 public override int GetNumberOfUsersOnline ()
391                 {
392                         using (DbConnection connection = CreateConnection ()) {
393                                 return DerbyMembershipHelper.Membership_GetNumberOfUsersOnline (connection, ApplicationName, userIsOnlineTimeWindow.Minutes, DateTime.UtcNow);\r
394                         }
395                 }
396
397                 public override string GetPassword (string username, string answer)
398                 {
399                         if (!EnablePasswordRetrieval)
400                                 throw new NotSupportedException ("this provider has not been configured to allow the retrieval of passwords");
401
402                         CheckParam ("username", username, 256);
403                         if (RequiresQuestionAndAnswer)
404                                 CheckParam ("answer", answer, 128);
405
406                         PasswordInfo pi = GetPasswordInfo (username);
407                         if (pi == null)
408                                 throw new ProviderException ("An error occurred while retrieving the password from the database");
409
410                         string user_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);
411                         string password = null;
412
413                         using (DbConnection connection = CreateConnection ()) {\r
414                                 int st = DerbyMembershipHelper.Membership_GetPassword (connection, ApplicationName, username, user_answer, MaxInvalidPasswordAttempts, PasswordAttemptWindow, DateTime.UtcNow, out password);\r
415 \r
416                                 if (st == 1)\r
417                                         throw new ProviderException ("User specified by username is not found in the membership database");\r
418 \r
419                                 if (st == 2)\r
420                                         throw new MembershipPasswordException ("The membership user identified by username is locked out");\r
421                                 
422                                 if (st == 3)\r
423                                         throw new MembershipPasswordException ("Password Answer is invalid");
424
425                                 return DecodePassword (password, pi.PasswordFormat);
426                         }
427                 }\r
428 \r
429                 MembershipUser GetUserFromReader (DbDataReader reader)\r
430                 {\r
431                         return new MembershipUser (\r
432                                 this.Name,                                          /* XXX is this right?  */\r
433                                 reader.GetString (0),                               /* name */\r
434                                 new Guid (reader.GetString (1)),                    /* providerUserKey */\r
435                                 reader.IsDBNull (2) ? null : reader.GetString (2),  /* email */\r
436                                 reader.IsDBNull (3) ? null : reader.GetString (3),  /* passwordQuestion */\r
437                                 reader.IsDBNull (4) ? null : reader.GetString (4),  /* comment */\r
438                                 reader.GetInt32 (5) > 0,                            /* isApproved */\r
439                                 reader.GetInt32 (6) > 0,                            /* isLockedOut */\r
440                                 reader.GetDateTime (7).ToLocalTime (),              /* creationDate */\r
441                                 reader.GetDateTime (8).ToLocalTime (),              /* lastLoginDate */\r
442                                 reader.GetDateTime (9).ToLocalTime (),              /* lastActivityDate */\r
443                                 reader.GetDateTime (10).ToLocalTime (),             /* lastPasswordChangedDate */\r
444                                 reader.GetDateTime (11).ToLocalTime ()              /* lastLockoutDate */);\r
445                 }
446
447                 public override MembershipUser GetUser (string username, bool userIsOnline)
448                 {
449                         if (username.Length == 0)
450                                 return null;
451
452                         CheckParam ("username", username, 256);\r
453 \r
454                         using (DbConnection connection = CreateConnection ()) {\r
455                                 DbDataReader reader = null;\r
456                                 int st = DerbyMembershipHelper.Membership_GetUserByName (connection, ApplicationName, username, userIsOnline, DateTime.UtcNow, out reader);\r
457                                 using (reader) {\r
458                                         if (st == 0 && reader != null) {\r
459                                                 MembershipUser u = GetUserFromReader (reader);\r
460                                                 return u;\r
461                                         }\r
462                                 }\r
463                         }\r
464                         return null;\r
465                 }
466
467                 public override MembershipUser GetUser (object providerUserKey, bool userIsOnline)
468                 {\r
469                         if (providerUserKey == null)\r
470                                 throw new ArgumentNullException ("providerUserKey");\r
471 \r
472                         if (!(providerUserKey is Guid))\r
473                                 throw new ArgumentException ("providerUserKey is not of type Guid", "providerUserKey");\r
474 \r
475                         using (DbConnection connection = CreateConnection ()) {\r
476                                 DbDataReader reader = null;\r
477                                 int st = DerbyMembershipHelper.Membership_GetUserByUserId (connection, providerUserKey.ToString (), userIsOnline, DateTime.UtcNow, out reader);\r
478                                 using (reader) {\r
479                                         if (st == 0 && reader != null) {\r
480                                                 MembershipUser u = GetUserFromReader (reader);\r
481                                                 return u;\r
482                                         }\r
483                                 }\r
484                         }\r
485                         return null;
486                 }
487
488                 public override string GetUserNameByEmail (string email)
489                 {
490                         CheckParam ("email", email, 256);\r
491 \r
492                         string username = null;
493
494                         using (DbConnection connection = CreateConnection ()) {\r
495                                 int st = DerbyMembershipHelper.Membership_GetUserByEmail (connection, ApplicationName, email, out username);\r
496 \r
497                                 if (st == 1)\r
498                                         return null;\r
499                                 \r
500                                 if (st == 2 && RequiresUniqueEmail)\r
501                                         throw new ProviderException ("More than one user with the same e-mail address exists in the database and RequiresUniqueEmail is true");\r
502                         }\r
503                         return username;
504                 }
505
506                 bool GetBoolConfigValue (NameValueCollection config, string name, bool def)
507                 {
508                         bool rv = def;
509                         string val = config [name];
510                         if (val != null) {
511                                 try { rv = Boolean.Parse (val); }
512                                 catch (Exception e) {
513                                         throw new ProviderException (String.Format ("{0} must be true or false", name), e);
514                                 }
515                         }
516                         return rv;
517                 }
518
519                 int GetIntConfigValue (NameValueCollection config, string name, int def)
520                 {
521                         int rv = def;
522                         string val = config [name];
523                         if (val != null) {
524                                 try { rv = Int32.Parse (val); }
525                                 catch (Exception e) {
526                                         throw new ProviderException (String.Format ("{0} must be an integer", name), e);
527                                 }
528                         }
529                         return rv;
530                 }
531
532                 int GetEnumConfigValue (NameValueCollection config, string name, Type enumType, int def)
533                 {
534                         int rv = def;
535                         string val = config [name];
536                         if (val != null) {
537                                 try { rv = (int) Enum.Parse (enumType, val); }
538                                 catch (Exception e) {
539                                         throw new ProviderException (String.Format ("{0} must be one of the following values: {1}", name, String.Join (",", Enum.GetNames (enumType))), e);
540                                 }
541                         }
542                         return rv;
543                 }
544
545                 string GetStringConfigValue (NameValueCollection config, string name, string def)
546                 {
547                         string rv = def;
548                         string val = config [name];
549                         if (val != null)
550                                 rv = val;
551                         return rv;
552                 }
553
554                 void EmitValidatingPassword (string username, string password, bool isNewUser)
555                 {
556                         ValidatePasswordEventArgs args = new ValidatePasswordEventArgs (username, password, isNewUser);
557                         OnValidatingPassword (args);
558
559                         /* if we're canceled.. */
560                         if (args.Cancel) {
561                                 if (args.FailureInformation == null)
562                                         throw new ProviderException ("Password validation canceled");
563                                 else
564                                         throw args.FailureInformation;
565                         }
566                 }
567
568                 public override void Initialize (string name, NameValueCollection config)
569                 {
570                         if (config == null)
571                                 throw new ArgumentNullException ("config");
572
573                         base.Initialize (name, config);
574
575                         applicationName = GetStringConfigValue (config, "applicationName", "/");
576                         enablePasswordReset = GetBoolConfigValue (config, "enablePasswordReset", true);
577                         enablePasswordRetrieval = GetBoolConfigValue (config, "enablePasswordRetrieval", false);
578                         requiresQuestionAndAnswer = GetBoolConfigValue (config, "requiresQuestionAndAnswer", true);
579                         requiresUniqueEmail = GetBoolConfigValue (config, "requiresUniqueEmail", false);
580                         passwordFormat = (MembershipPasswordFormat) GetEnumConfigValue (config, "passwordFormat", typeof (MembershipPasswordFormat),
581                                                                                            (int) MembershipPasswordFormat.Hashed);
582                         maxInvalidPasswordAttempts = GetIntConfigValue (config, "maxInvalidPasswordAttempts", 5);
583                         minRequiredPasswordLength = GetIntConfigValue (config, "minRequiredPasswordLength", 7);
584                         minRequiredNonAlphanumericCharacters = GetIntConfigValue (config, "minRequiredNonAlphanumericCharacters", 1);
585                         passwordAttemptWindow = GetIntConfigValue (config, "passwordAttemptWindow", 10);
586                         passwordStrengthRegularExpression = GetStringConfigValue (config, "passwordStrengthRegularExpression", "");
587
588                         MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");
589
590                         userIsOnlineTimeWindow = section.UserIsOnlineTimeWindow;
591
592                         /* we can't support password retrieval with hashed passwords */
593                         if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval)
594                                 throw new ProviderException ("password retrieval cannot be used with hashed passwords");
595
596                         string connectionStringName = config ["connectionStringName"];
597
598                         if (applicationName.Length > 256)
599                                 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
600                         if (connectionStringName == null || connectionStringName.Length == 0)
601                                 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
602
603                         connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];\r
604                         if (connectionString == null)\r
605                                 throw new ProviderException (String.Format ("The connection name '{0}' was not found in the applications configuration or the connection string is empty.", connectionStringName));\r
606 \r
607                         DerbyDBSchema.RegisterUnloadHandler (connectionString.ConnectionString);\r
608                 }
609
610                 public override string ResetPassword (string username, string answer)
611                 {
612                         if (!EnablePasswordReset)
613                                 throw new NotSupportedException ("this provider has not been configured to allow the resetting of passwords");
614
615                         CheckParam ("username", username, 256);
616
617                         if (RequiresQuestionAndAnswer)
618                                 CheckParam ("answer", answer, 128);
619
620                         using (DbConnection connection = CreateConnection ()) {
621                                 PasswordInfo pi = GetPasswordInfo (username);
622                                 if (pi == null)
623                                         throw new ProviderException (username + "is not found in the membership database");
624
625                                 string newPassword = GeneratePassword ();
626                                 EmitValidatingPassword (username, newPassword, false);
627
628                                 string db_password = EncodePassword (newPassword, pi.PasswordFormat, pi.PasswordSalt);
629                                 string db_answer = EncodePassword (answer, pi.PasswordFormat, pi.PasswordSalt);\r
630 \r
631                                 int st = DerbyMembershipHelper.Membership_ResetPassword (connection, ApplicationName, username, db_password, db_answer, (int) pi.PasswordFormat, pi.PasswordSalt, MaxInvalidPasswordAttempts, PasswordAttemptWindow, DateTime.UtcNow);
632
633                                 if (st == 0)
634                                         return newPassword;
635                                 else if (st == 1)\r
636                                         throw new ProviderException (username + " is not found in the membership database");\r
637                                 else if (st == 2)\r
638                                         throw new MembershipPasswordException ("The user account is currently locked out");\r
639                                 else if (st == 3)
640                                         throw new MembershipPasswordException ("Password Answer is invalid");
641                                 else
642                                         throw new ProviderException ("Failed to reset password");
643                         }
644                 }
645
646                 public override void UpdateUser (MembershipUser user)
647                 {
648                         if (user == null)
649                                 throw new ArgumentNullException ("user");
650
651                         if (user.UserName == null)
652                                 throw new ArgumentNullException ("user.UserName");
653
654                         if (RequiresUniqueEmail && user.Email == null)
655                                 throw new ArgumentNullException ("user.Email");
656
657                         CheckParam ("user.UserName", user.UserName, 256);
658
659                         if (user.Email.Length > 256 || (RequiresUniqueEmail && user.Email.Length == 0))
660                                 throw new ArgumentException ("invalid format for user.Email");
661
662                         using (DbConnection connection = CreateConnection ()) {\r
663                                 int st = DerbyMembershipHelper.Membership_UpdateUser (connection, ApplicationName, user.UserName, user.Email, user.Comment, user.IsApproved, RequiresUniqueEmail, user.LastLoginDate, DateTime.UtcNow, DateTime.UtcNow);
664
665                                 if (st == 1)
666                                         throw new ProviderException ("The UserName property of user was not found in the database.");
667                                 if (st == 2)
668                                         throw new ProviderException ("The Email property of user was equal to an existing e-mail address in the database and RequiresUniqueEmail is set to true.");
669                                 if (st != 0)
670                                         throw new ProviderException ("Failed to update user");
671                         }
672                 }
673
674                 public override bool ValidateUser (string username, string password)
675                 {
676                         if (username.Length == 0)
677                                 return false;
678
679                         CheckParam ("username", username, 256);
680                         EmitValidatingPassword (username, password, false);
681
682                         PasswordInfo pi = ValidateUsingPassword (username, password);
683                         if (pi != null) {
684                                 pi.LastLoginDate = DateTime.UtcNow;
685                                 UpdateUserInfo (username, pi, true, true);
686                                 return true;
687                         }
688                         return false;
689                 }
690
691                 public override bool UnlockUser (string username)
692                 {
693                         CheckParam ("username", username, 256);
694
695                         using (DbConnection connection = CreateConnection ()) {
696                                 try {\r
697                                         int st = DerbyMembershipHelper.Membership_UnlockUser (connection, ApplicationName, username);
698
699                                         if (st == 0)
700                                                 return true;
701                                 }
702                                 catch (Exception e) {
703                                         throw new ProviderException ("Failed to unlock user", e);
704                                 }
705                         }
706                         return false;
707                 }
708
709                 void UpdateUserInfo (string username, PasswordInfo pi, bool isPasswordCorrect, bool updateLoginActivity)
710                 {
711                         CheckParam ("username", username, 256);
712
713                         using (DbConnection connection = CreateConnection ()) {
714                                 try {\r
715                                         int st = DerbyMembershipHelper.Membership_UpdateUserInfo (connection, ApplicationName, username, isPasswordCorrect, updateLoginActivity,\r
716                                                 MaxInvalidPasswordAttempts, PasswordAttemptWindow, DateTime.UtcNow, pi.LastLoginDate, pi.LastActivityDate);
717
718                                         if (st == 0)
719                                                 return;
720                                 }
721                                 catch (Exception e) {
722                                         throw new ProviderException ("Failed to update Membership table", e);
723                                 }
724
725                         }
726                 }
727
728                 PasswordInfo ValidateUsingPassword (string username, string password)
729                 {
730                         MembershipUser user = GetUser (username, true);
731                         if (user == null)
732                                 return null;
733
734                         if (!user.IsApproved || user.IsLockedOut)
735                                 return null;
736
737                         PasswordInfo pi = GetPasswordInfo (username);
738
739                         if (pi == null)
740                                 return null;
741
742                         /* do the actual validation */
743                         string user_password = EncodePassword (password, pi.PasswordFormat, pi.PasswordSalt);
744
745                         if (user_password != pi.Password) {
746                                 UpdateUserInfo (username, pi, false, false);
747                                 return null;
748                         }
749
750                         return pi;
751                 }\r
752 \r
753                 private PasswordInfo GetPasswordInfo (string username)\r
754                 {\r
755                         using (DbConnection connection = CreateConnection ()) {\r
756                                 DbDataReader reader = null;\r
757                                 DerbyMembershipHelper.Membership_GetPasswordWithFormat (connection, ApplicationName, username, false, DateTime.UtcNow, out reader);\r
758 \r
759                                 PasswordInfo pi = null;\r
760                                 if (reader == null)\r
761                                         return null;\r
762 \r
763                                 using (reader) {\r
764                                         if (reader.Read ()) {\r
765                                                 int isLockedOut = reader.GetInt32 (1);\r
766                                                 if (isLockedOut > 0)\r
767                                                         return null;\r
768 \r
769                                                 pi = new PasswordInfo (\r
770                                                         reader.GetString (3),\r
771                                                         (MembershipPasswordFormat) reader.GetInt32 (4),\r
772                                                         reader.GetString (5),\r
773                                                         reader.GetInt32 (6),\r
774                                                         reader.GetInt32 (7),\r
775                                                         reader.GetInt32 (2) > 0,\r
776                                                         reader.GetDateTime (8),\r
777                                                         reader.GetDateTime (9));\r
778                                         }\r
779                                 }\r
780                                 return pi;\r
781                         }\r
782                 }\r
783 \r
784                 private string EncodePassword (string password, MembershipPasswordFormat passwordFormat, string salt)\r
785                 {\r
786                         byte [] password_bytes;\r
787                         byte [] salt_bytes;\r
788 \r
789                         switch (passwordFormat) {\r
790                                 case MembershipPasswordFormat.Clear:\r
791                                         return password;\r
792                                 case MembershipPasswordFormat.Hashed:\r
793                                         password_bytes = Encoding.Unicode.GetBytes (password);\r
794                                         salt_bytes = Convert.FromBase64String (salt);\r
795 \r
796                                         byte [] hashBytes = new byte [salt_bytes.Length + password_bytes.Length];\r
797 \r
798                                         Buffer.BlockCopy (salt_bytes, 0, hashBytes, 0, salt_bytes.Length);\r
799                                         Buffer.BlockCopy (password_bytes, 0, hashBytes, salt_bytes.Length, password_bytes.Length);\r
800 \r
801                                         MembershipSection section = (MembershipSection) WebConfigurationManager.GetSection ("system.web/membership");\r
802                                         string alg_type = section.HashAlgorithmType;\r
803                                         if (alg_type == "") {\r
804                                                 MachineKeySection keysection = (MachineKeySection) WebConfigurationManager.GetSection ("system.web/machineKey");\r
805                                                 alg_type = keysection.Validation.ToString ();\r
806                                         }\r
807                                         using (HashAlgorithm hash = HashAlgorithm.Create (alg_type)) {\r
808                                                 hash.TransformFinalBlock (hashBytes, 0, hashBytes.Length);\r
809                                                 return Convert.ToBase64String (hash.Hash);\r
810                                         }\r
811                                 case MembershipPasswordFormat.Encrypted:\r
812                                         password_bytes = Encoding.Unicode.GetBytes (password);\r
813                                         salt_bytes = Convert.FromBase64String (salt);\r
814 \r
815                                         byte [] buf = new byte [password_bytes.Length + salt_bytes.Length];\r
816 \r
817                                         Array.Copy (salt_bytes, 0, buf, 0, salt_bytes.Length);\r
818                                         Array.Copy (password_bytes, 0, buf, salt_bytes.Length, password_bytes.Length);\r
819 \r
820                                         return Convert.ToBase64String (EncryptPassword (buf));\r
821                                 default:\r
822                                         /* not reached.. */\r
823                                         return null;\r
824                         }\r
825                 }\r
826 \r
827                 private string DecodePassword (string password, MembershipPasswordFormat passwordFormat)\r
828                 {\r
829                         switch (passwordFormat) {\r
830                                 case MembershipPasswordFormat.Clear:\r
831                                         return password;\r
832                                 case MembershipPasswordFormat.Hashed:\r
833                                         throw new ProviderException ("Hashed passwords cannot be decoded.");\r
834                                 case MembershipPasswordFormat.Encrypted:\r
835                                         return Encoding.Unicode.GetString (DecryptPassword (Convert.FromBase64String (password)));\r
836                                 default:\r
837                                         /* not reached.. */\r
838                                         return null;\r
839                         }\r
840                 }
841
842                 public override string ApplicationName
843                 {
844                         get { return applicationName; }
845                         set { applicationName = value; }
846                 }
847
848                 public override bool EnablePasswordReset
849                 {
850                         get { return enablePasswordReset; }
851                 }
852
853                 public override bool EnablePasswordRetrieval
854                 {
855                         get { return enablePasswordRetrieval; }
856                 }
857
858                 public override MembershipPasswordFormat PasswordFormat
859                 {
860                         get { return passwordFormat; }
861                 }
862
863                 public override bool RequiresQuestionAndAnswer
864                 {
865                         get { return requiresQuestionAndAnswer; }
866                 }
867
868                 public override bool RequiresUniqueEmail
869                 {
870                         get { return requiresUniqueEmail; }
871                 }
872
873                 public override int MaxInvalidPasswordAttempts
874                 {
875                         get { return maxInvalidPasswordAttempts; }
876                 }
877
878                 public override int MinRequiredNonAlphanumericCharacters
879                 {
880                         get { return minRequiredNonAlphanumericCharacters; }
881                 }
882
883                 public override int MinRequiredPasswordLength
884                 {
885                         get { return minRequiredPasswordLength; }
886                 }
887
888                 public override int PasswordAttemptWindow
889                 {
890                         get { return passwordAttemptWindow; }
891                 }
892
893                 public override string PasswordStrengthRegularExpression
894                 {
895                         get { return passwordStrengthRegularExpression; }
896                 }
897
898                 [Flags]
899                 private enum DeleteUserTableMask
900                 {
901                         MembershipUsers = 1,
902                         UsersInRoles = 2,
903                         Profiles = 4,
904                         WebPartStateUser = 8
905                 }
906
907                 private sealed class PasswordInfo
908                 {
909                         private string _password;
910                         private MembershipPasswordFormat _passwordFormat;
911                         private string _passwordSalt;
912                         private int _failedPasswordAttemptCount;
913                         private int _failedPasswordAnswerAttemptCount;
914                         private bool _isApproved;
915                         private DateTime _lastLoginDate;
916                         private DateTime _lastActivityDate;
917
918                         internal PasswordInfo (
919                                 string password,
920                                 MembershipPasswordFormat passwordFormat,
921                                 string passwordSalt,
922                                 int failedPasswordAttemptCount,
923                                 int failedPasswordAnswerAttemptCount,
924                                 bool isApproved,
925                                 DateTime lastLoginDate,
926                                 DateTime lastActivityDate)
927                         {
928                                 _password = password;
929                                 _passwordFormat = passwordFormat;
930                                 _passwordSalt = passwordSalt;
931                                 _failedPasswordAttemptCount = failedPasswordAttemptCount;
932                                 _failedPasswordAnswerAttemptCount = failedPasswordAnswerAttemptCount;
933                                 _isApproved = isApproved;
934                                 _lastLoginDate = lastLoginDate;
935                                 _lastActivityDate = lastActivityDate;
936                         }
937
938                         public string Password
939                         {
940                                 get { return _password; }
941                                 set { _password = value; }
942                         }
943                         public MembershipPasswordFormat PasswordFormat
944                         {
945                                 get { return _passwordFormat; }
946                                 set { _passwordFormat = value; }
947                         }
948                         public string PasswordSalt
949                         {
950                                 get { return _passwordSalt; }
951                                 set { _passwordSalt = value; }
952                         }
953                         public int FailedPasswordAttemptCount
954                         {
955                                 get { return _failedPasswordAttemptCount; }
956                                 set { _failedPasswordAttemptCount = value; }
957                         }
958                         public int FailedPasswordAnswerAttemptCount
959                         {
960                                 get { return _failedPasswordAnswerAttemptCount; }
961                                 set { _failedPasswordAnswerAttemptCount = value; }
962                         }
963                         public bool IsApproved
964                         {
965                                 get { return _isApproved; }
966                                 set { _isApproved = value; }
967                         }
968                         public DateTime LastLoginDate
969                         {
970                                 get { return _lastLoginDate; }
971                                 set { _lastLoginDate = value; }
972                         }
973                         public DateTime LastActivityDate
974                         {
975                                 get { return _lastActivityDate; }
976                                 set { _lastActivityDate = value; }
977                         }
978                 }
979         }
980 }\r
981 #endif
982