Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Services.Authentication Prototype/Service/Provider/HeuristicLabMembershipProvider.cs @ 4014

Last change on this file since 4014 was 4014, checked in by jwolfing, 14 years ago

added new comments to methods, createUser, changePasswordQuestionAndAnswer (#1079)

File size: 22.5 KB
Line 
1using System;
2using System.Collections.Specialized;
3using System.Configuration;
4using System.Configuration.Provider;
5using System.IO;
6using System.Linq;
7using System.Security.Cryptography;
8using System.Text;
9using System.Web.Configuration;
10using System.Web.Security;
11using Persistence;
12
13
14namespace Service.Provider {
15  class HeuristicLabMembershipProvider : MembershipProvider {
16
17    #region variables
18
19    private string pApplicationName;
20    private bool pEnablePasswordReset;
21    private bool pEnablePasswordRetrieval;
22    private bool pRequiresQuestionAndAnswer;
23    private bool pRequiresUniqueEmail;
24    private int pMaxInvalidPasswordAttempts = 3;
25    private int pPasswordAttemptWindow;
26    private int pMinRequiredPasswordLength = 5;
27    private MembershipPasswordFormat pPasswordFormat = MembershipPasswordFormat.Clear;
28
29    #endregion
30
31    #region properties
32
33    public override string ApplicationName {
34      get { return pApplicationName; }
35      set { pApplicationName = value; }
36    }
37
38    public override bool EnablePasswordReset {
39      get { return pEnablePasswordReset; }
40    }
41
42    public override bool EnablePasswordRetrieval {
43      get { return pEnablePasswordRetrieval; }
44    }
45
46    public override int MaxInvalidPasswordAttempts {
47      get { return pMaxInvalidPasswordAttempts; }
48    }
49
50    public override int MinRequiredNonAlphanumericCharacters {
51      get { return 0; }
52    }
53
54    public override int MinRequiredPasswordLength {
55      get { return pMinRequiredPasswordLength; }
56    }
57
58    public override int PasswordAttemptWindow {
59      get { return pPasswordAttemptWindow; }
60    }
61
62    public override MembershipPasswordFormat PasswordFormat {
63      get { return pPasswordFormat; }
64    }
65
66    public override string PasswordStrengthRegularExpression {
67      get { return string.Empty; }
68    }
69
70    public override bool RequiresQuestionAndAnswer {
71      get { return false; }
72    }
73
74    public override bool RequiresUniqueEmail {
75      get { return pRequiresUniqueEmail; }
76    }
77
78    #endregion
79
80    /// <summary>
81    /// this is the methode to initialize all important and needed
82    /// variables and settings
83    /// The most settings will be read from the web.config
84    /// </summary>
85    /// <param name="name"></param>
86    /// <param name="config"></param>
87    /// <returns></returns>
88
89    public override void Initialize(string name, NameValueCollection config) {
90      //
91      // Initialize values from web.config.
92      //
93
94      if (config == null)
95        throw new ArgumentNullException("config");
96
97      if (string.IsNullOrEmpty(name) || name.Length == 0)
98        name = "HeuristicLabMembershipProvider";
99
100      if (String.IsNullOrEmpty(config["description"])) {
101        config.Remove("description");
102        config.Add("description", "Heuristic Lab Membership provider");
103      }
104
105      // Initialize the abstract base class.
106      base.Initialize(name, config);
107
108      pApplicationName = GetConfigValue(config["applicationName"], System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
109      pMaxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
110      pPasswordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
111      pMinRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "7"));
112      pEnablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
113      pEnablePasswordRetrieval = Convert.ToBoolean(GetConfigValue(config["enablePasswordRetrieval"], "true"));
114      pRequiresQuestionAndAnswer = Convert.ToBoolean(GetConfigValue(config["requiresQuestionAndAnswer"], "false"));
115      pRequiresUniqueEmail = Convert.ToBoolean(GetConfigValue(config["requiresUniqueEmail"], "true"));
116
117      string tempFormat = config["passwordFormat"];
118      if (tempFormat == null) {
119        tempFormat = "Clear";
120      }
121
122      switch (tempFormat) {
123        case "Hashed":
124          pPasswordFormat = MembershipPasswordFormat.Hashed;
125          break;
126        case "Encrypted":
127          pPasswordFormat = MembershipPasswordFormat.Encrypted;
128          break;
129        case "Clear":
130          pPasswordFormat = MembershipPasswordFormat.Clear;
131          break;
132        default:
133          throw new ProviderException("Password format not supported.");
134      }
135    }
136
137
138    /// <summary>
139    /// this mehtode change the password of an existent user
140    /// the methode look for db connection and connect to
141    /// such db if it possible
142    /// Also neccessary requirements of password complexity is used
143    /// by this methode
144    /// </summary>
145    /// <param name="username"></param>
146    /// <param name="oldPassword"></param>
147    /// <param name="newPassword"></param>
148    /// <returns>
149    /// return true if password is changed, or false if it is not able
150    /// to set the password
151    /// </returns>
152    public override bool ChangePassword(string username, string oldPassword, string newPassword) {
153      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
154        // check database connection
155        if (db == null) {
156          return false;
157        }
158
159        if (newPassword.Length < MinRequiredPasswordLength) {
160          return false;
161        }
162
163        try {
164          // try to get user
165          HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username);
166
167          if (u.Password == EncodePassword(oldPassword) && newPassword.Length > 0) {
168            u.Password = EncodePassword(newPassword);
169            db.SubmitChanges();
170            return true;
171          }
172          return false;
173
174
175        }
176        catch (Exception) {
177          return false;
178        }
179      }
180    }
181
182    /// <summary>
183    /// change the password questionandanswer for specified user
184    ///
185    /// </summary>
186    /// <param name="username"></param>
187    /// <param name="password"></param>
188    /// <param name="newPasswordQuestion"></param>
189    /// <param name="newPasswordAnswer"></param>
190    /// <returns>
191    /// return true if changes are submitted
192    /// false if are no user found
193    /// </returns>
194    public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) {
195      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
196
197        if (db.HeuristicLabUsers.Count(x => x.UserName == username) > 0 && newPasswordQuestion.Length > 0 && newPasswordAnswer.Length > 0) {
198          HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username);
199          u.PasswordAnswer = newPasswordAnswer;
200          u.PasswordQuestion = newPasswordQuestion;
201         
202          db.SubmitChanges();
203         
204          return true;
205        }
206        return false;
207      }
208    }
209
210    /// <summary>
211    /// create a new user with username, password
212    /// email, passwordquestion, passwordAnswer,
213    /// Approvedstate, whether is providerUserKey and certain creation MembershipCreateStatus status
214    /// </summary>
215    /// <param name="username"></param>
216    /// <param name="password"></param>
217    /// <param name="email"></param>
218    /// <param name="passwordQuestion"></param>
219    /// <param name="passwordAnswer"></param>
220    /// <param name="providerUserKey"></param>
221    /// <param name="isApproved"></param>
222    /// <param name="status"></param>
223    /// <returns>
224    /// return a created user if there is no error occoured
225    /// return null if there is an error occoured
226    /// </returns>
227    public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) {
228      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
229        // check database connection
230        if (db == null) {
231          status = MembershipCreateStatus.ProviderError;
232          return null;
233        }
234        try {
235          // check for duplicate entries
236          if (db.HeuristicLabUsers.Count(x => x.UserName == username) > 0) {
237            status = MembershipCreateStatus.DuplicateUserName;
238            return null;
239          }
240          if (db.HeuristicLabUsers.Count(x => x.Email == email) > 0) {
241            status = MembershipCreateStatus.DuplicateEmail;
242            return null;
243          }
244          if (password.Length < MinRequiredPasswordLength) {
245            status = MembershipCreateStatus.InvalidPassword;
246            return null;
247          }
248
249          // create new user
250          HeuristicLabUser u = new HeuristicLabUser(username, email, passwordQuestion, "");
251          password = EncodePassword(password);
252          u.Password = password;
253          u.PasswordAnswer = passwordAnswer;
254          u.PasswordQuestion = passwordQuestion;
255          // save user into database
256          db.HeuristicLabUsers.InsertOnSubmit(u);
257          db.SubmitChanges();
258
259          // success
260          status = MembershipCreateStatus.Success;
261          return u.getMembershipUser(this.Name);
262        }
263        catch (Exception) {
264          // error
265          status = MembershipCreateStatus.ProviderError;
266          return null;
267        }
268      }
269    }
270
271    public override bool DeleteUser(string username, bool deleteAllRelatedData) {
272      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
273        // check database connection
274        if (db == null) {
275          return false;
276        }
277        try {
278          // try to get user
279          HeuristicLabUser u =
280            db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.UserName == username);
281
282          // optionally delete related data
283          if (deleteAllRelatedData) {
284            db.HeuristicLabUserRole.DeleteAllOnSubmit<HeuristicLabUserRole>(u.HeuristicLabUserRoles);
285          }
286
287          // delete user
288          db.HeuristicLabUsers.DeleteOnSubmit(u);
289          db.SubmitChanges();
290          return true;
291        }
292        catch (Exception) {
293          return false;
294        }
295      }
296    }
297
298
299    /// <summary>
300    /// Returns a paged collection of users that match a given e-mail address
301    /// TODO: Return a sorted result set
302    /// </summary>
303    /// <param name="emailToMatch"></param>
304    /// <param name="pageIndex"></param>
305    /// <param name="pageSize"></param>
306    /// <param name="totalRecords"></param>
307    /// <returns></returns>
308    public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) {
309      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
310        IQueryable<HeuristicLabUser> users = (from u in db.HeuristicLabUsers where u.Email == emailToMatch select u);
311        return PagedCollection(users, pageIndex, pageSize, out totalRecords);
312      }
313    }
314
315    /// <summary>
316    /// Returns a paged collection of users that match a given username
317    /// TODO: Return a sorted result set
318    /// </summary>
319    /// <param name="usernameToMatch"></param>
320    /// <param name="pageIndex"></param>
321    /// <param name="pageSize"></param>
322    /// <param name="totalRecords"></param>
323    /// <returns></returns>
324    public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) {
325      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
326        IQueryable<HeuristicLabUser> users = (from u in db.HeuristicLabUsers where u.UserName == usernameToMatch select u);
327        return PagedCollection(users, pageIndex, pageSize, out totalRecords);
328      }
329     
330    }
331
332    /// <summary>
333    /// Returns a paged collection of all users
334    /// TODO: Return a sorted result set
335    /// </summary>
336    /// <param name="pageIndex"></param>
337    /// <param name="pageSize"></param>
338    /// <param name="totalRecords"></param>
339    /// <returns></returns>
340    public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) {
341      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
342        // orderby u.UserName
343        // skipped ordering for now as the default comparator seems to be wrong
344        IQueryable<HeuristicLabUser> users = (from u in db.HeuristicLabUsers select u);
345        return PagedCollection(users, pageIndex, pageSize, out totalRecords);
346      }
347     
348    }
349
350    /// <summary>
351    /// Helper method that takes an IQueriable object and returns a paged part of it
352    /// </summary>
353    /// <param name="querySource"></param>
354    /// <param name="pageIndex"></param>
355    /// <param name="pageSize"></param>
356    /// <param name="totalRecords"></param>
357    /// <returns></returns>
358    private MembershipUserCollection PagedCollection(IQueryable<HeuristicLabUser> querySource, int pageIndex, int pageSize, out int totalRecords) {
359      totalRecords = querySource.Count();
360      MembershipUserCollection userCollection = new MembershipUserCollection();
361      int skip = (pageIndex == 0) ? 0 : (pageIndex * pageSize);
362      foreach (HeuristicLabUser u in querySource.Skip(skip).Take(pageSize)) {
363        userCollection.Add(u.getMembershipUser(this.Name));
364      }
365      return userCollection;
366    }
367
368    /// <summary>
369    /// not jet implemented returns 0 as default
370    /// </summary>
371    /// <returns></returns>
372    public override int GetNumberOfUsersOnline() {
373      return 0;
374    }
375
376    public override string GetPassword(string username, string answer) {
377      if (PasswordFormat == MembershipPasswordFormat.Hashed)
378        throw new NotSupportedException("Passwort is hashed");
379      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
380        HeuristicLabUser u =
381          db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.UserName == username);
382        if (EnablePasswordRetrieval && RequiresQuestionAndAnswer) {
383          if (u.PasswordAnswer.Equals(answer)) {
384            return u.Password;
385          } else {
386            throw new MembershipPasswordException();
387          }
388        } else throw new NotSupportedException("PasswortRetrival or RequiresQuestionAndAnswer not set");
389      }
390    }
391
392    public override MembershipUser GetUser(string username, bool userIsOnline) {
393      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
394        HeuristicLabUser u =
395            db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.UserName == username);
396        if (u != null)
397          return u.getMembershipUser(this.Name);
398        else
399          return null;
400      }
401    }
402
403    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) {
404      long ID = providerUserKey is long ? (long)providerUserKey : -1;
405      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
406        HeuristicLabUser u =
407            db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.ID == ID);
408        if (u != null)
409          return u.getMembershipUser(this.Name);
410        else
411          return null;
412      }
413    }
414
415    public override string GetUserNameByEmail(string email) {
416      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
417        HeuristicLabUser u =
418          db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.Email == email);
419        if (u != null)
420          return u.UserName;
421        else
422          return null;
423      }
424    }
425
426    public override string ResetPassword(string username, string answer) {
427      throw new NotSupportedException("Restet password not avaliable");
428    }
429
430    public bool LockUser(string userName) {
431      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
432        // check database connection
433        if (db == null) {
434          return false;
435        }
436        try {
437          // try to get user
438          HeuristicLabUser u =
439            db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.UserName == userName);
440
441          // unlock user
442          u.Locked = true;
443          db.SubmitChanges();
444          return true;
445        }
446        catch (Exception) {
447          return false;
448        }
449      }
450    }
451
452    public override bool UnlockUser(string userName) {
453      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
454        // check database connection
455        if (db == null) {
456          return false;
457        }
458        try {
459          // try to get user
460          HeuristicLabUser u =
461            db.HeuristicLabUsers.Single<HeuristicLabUser>(x => x.UserName == userName);
462
463          // unlock user
464          u.Locked = false;
465          db.SubmitChanges();
466          return true;
467        }
468        catch (Exception) {
469          return false;
470        }
471      }
472    }
473
474    public override void UpdateUser(MembershipUser user) {
475      throw new NotImplementedException();
476    }
477    /// <summary>
478    /// Validates a user
479    /// </summary>
480    /// <param name="username"></param>
481    /// <param name="password"></param>
482    /// <returns></returns>
483    public override bool ValidateUser(string username, string password) {
484      bool isValid = false;
485      using (DataClassesDataContext db = DatabaseUtil.createDataClassesDataContext()) {
486        if (db == null) {
487          return false;
488        }
489        if (db.HeuristicLabUsers.Count(x => x.UserName == username && x.Locked == false) > 0) {
490          HeuristicLabUser u = db.HeuristicLabUsers.Single(x => x.UserName == username && x.Locked == false);
491          isValid = CheckPassword(password, u.Password) && u.FailedLogins <= MaxInvalidPasswordAttempts;
492          if (!isValid) {
493            u.FailedLogins++;
494            if (u.FailedLogins > MaxInvalidPasswordAttempts) {
495              u.Locked = true;
496            }
497          } else {
498            u.FailedLogins = 0;
499          }
500
501          db.SubmitChanges();
502        }
503      }
504      return isValid;
505    }
506
507    /// <summary>
508    /// compaiers to passwords
509    /// </summary>
510    /// <param name="password"></param>
511    /// <param name="dbpassword"></param>
512    /// <returns></returns>
513    private bool CheckPassword(string password, string dbpassword) {
514      string pass1 = password;
515      string pass2 = dbpassword;
516
517      switch (PasswordFormat) {
518        case MembershipPasswordFormat.Encrypted:
519          pass2 = DecodePassword(dbpassword);
520          break;
521        case MembershipPasswordFormat.Hashed:
522          pass1 = EncodePassword(password);
523          break;
524        default:
525          break;
526      }
527
528      if (pass1 == pass2) {
529        return true;
530      }
531
532      return false;
533    }
534
535
536    /// <summary>
537    /// Encodes a password.
538    /// Public because needed for unit testing.
539    /// </summary>
540    /// <param name="password"></param>
541    /// <returns></returns>
542    public string EncodePassword(string password) {
543      string encodedPassword = password;
544
545      switch (PasswordFormat) {
546        case MembershipPasswordFormat.Clear:
547          break;
548        case MembershipPasswordFormat.Encrypted:
549          encodedPassword =
550            Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
551          break;
552        case MembershipPasswordFormat.Hashed:
553          SHA512 sha512 = SHA512.Create();
554          ASCIIEncoding encoder = new ASCIIEncoding();
555          byte[] combined = encoder.GetBytes(password);
556          sha512.ComputeHash(combined);
557          encodedPassword = Convert.ToBase64String(sha512.Hash);
558          break;
559        default:
560          throw new ProviderException("Unsupported password format.");
561      }
562
563      return encodedPassword;
564    }
565
566    private readonly byte[] _rgbKey = new byte[]
567                           {
568                             182, 140, 37, 101, 52, 157, 80, 17, 65, 35, 130, 208, 101, 68, 161, 45, 197, 102, 112, 190,
569                             187, 177, 37, 76, 63, 38, 190, 117, 247, 122, 94, 17
570                           };
571    private readonly byte[] _rgbIv = new byte[] { 60, 121, 178, 142, 50, 160, 226, 84, 41, 66, 158, 180, 26, 232, 42, 113 };
572
573    protected override byte[] EncryptPassword(byte[] password) {
574      SymmetricAlgorithm sa = Aes.Create();
575      MemoryStream msEncrypt = new MemoryStream();
576      CryptoStream csEncrypt = new CryptoStream(msEncrypt, sa.CreateEncryptor(_rgbKey, _rgbIv), CryptoStreamMode.Write);
577      csEncrypt.Write(password, 0, password.Length);
578      csEncrypt.Close();
579      byte[] encryptedTextBytes = msEncrypt.ToArray();
580      msEncrypt.Close();
581      return encryptedTextBytes;
582    }
583
584    protected override byte[] DecryptPassword(byte[] encodedPassword) {
585      SymmetricAlgorithm sa = Aes.Create();
586      MemoryStream msDecrypt = new MemoryStream(encodedPassword);
587      CryptoStream csDecrypt = new CryptoStream(msDecrypt, sa.CreateDecryptor(_rgbKey, _rgbIv), CryptoStreamMode.Read);
588      byte[] decryptedTextBytes = new Byte[encodedPassword.Length];
589      csDecrypt.Read(decryptedTextBytes, 0, encodedPassword.Length);
590      csDecrypt.Close();
591      msDecrypt.Close();
592      return decryptedTextBytes;
593    }
594
595    /// <summary>
596    /// Decodes a encoded Password
597    /// </summary>
598    /// <param name="encodedPassword"></param>
599    /// <returns></returns>
600    private string DecodePassword(string encodedPassword) {
601      string password = encodedPassword;
602
603      switch (PasswordFormat) {
604        case MembershipPasswordFormat.Clear:
605          break;
606        case MembershipPasswordFormat.Encrypted:
607          password =
608            Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password))).TrimEnd('\0');
609
610
611          break;
612        case MembershipPasswordFormat.Hashed:
613          throw new ProviderException("Cannot unencode a hashed password.");
614        default:
615          throw new ProviderException("Unsupported password format.");
616      }
617
618      return password;
619    }
620
621    /// <summary>
622    /// returns the configuration string, if the value is null or empty the default value is returned
623    /// </summary>
624    /// <param name="configValue"></param>
625    /// <param name="defaultValue"></param>
626    /// <returns></returns>
627    private string GetConfigValue(string configValue, string defaultValue) {
628      if (String.IsNullOrEmpty(configValue))
629        return defaultValue;
630
631      return configValue;
632    }
633  }
634}
Note: See TracBrowser for help on using the repository browser.