Line 1: Imports Microsoft.VisualBasic
Line 2: Imports System
Line 3: Imports System.IO
Line 4: Imports System.Web
Line 5: Imports System.Web.Security
Line 6: Imports System.Configuration
Line 7: Imports System.Configuration.Provider
Line 8: Imports System.Web.Security.MembershipProvider
Line 9: Imports System.Collections.Generic
Line 10: Imports System.Text
Line 11: Imports System.Text.RegularExpressions
Line 12: Imports System.Security.Permissions
Line 13: Imports System.Security.Cryptography
Line 14: Imports APress.ProAspNet.Providers.Store
Line 15:
Line 16: Namespace APress.ProAspNet.Providers
Line 17: Public Class XmlMembershipProvider : Inherits MembershipProvider
Line 18: Private _Name As String
Line 19: Private _FileName As String
Line 20: Private _CurrentStore As UserStore = Nothing
Line 21: Private _ApplicationName As String
Line 22: Private _EnablePasswordReset As Boolean
Line 23: Private _RequiresQuestionAndAnswer As Boolean
Line 24: Private _PasswordStrengthRegEx As String
Line 25: Private _MaxInvalidPasswordAttempts As Integer
Line 26: Private _MinRequiredNonAlphanumericChars As Integer
Line 27: Private _MinRequiredPasswordLength As Integer
Line 28: Private _PasswordFormat As MembershipPasswordFormat
Line 29:
Line 30: Private ReadOnly Property CurrentStore() As UserStore
Line 31: Get
Line 32: If _CurrentStore Is Nothing Then
Line 33: _CurrentStore = UserStore.GetStore(_FileName)
Line 34: End If
Line 35: Return _CurrentStore
Line 36: End Get
Line 37: End Property
Line 38:
Line 39: Public Overrides Sub Initialize(ByVal name As String, ByVal config As System.Collections.Specialized.NameValueCollection)
Line 40: If config Is Nothing Then
Line 41: Throw New ArgumentNullException("config")
Line 42: End If
Line 43: If String.IsNullOrEmpty(name) Then
Line 44: name = "XmlMembershipProvider"
Line 45: End If
Line 46: If String.IsNullOrEmpty(config("description")) Then
Line 47: config.Remove("description")
Line 48: config.Add("description", "XML Membership Provider")
Line 49: End If
Line 50:
Line 51: ' Initialize the base class
Line 52: MyBase.Initialize(name, config)
Line 53:
Line 54: ' Initialize default values
Line 55: _ApplicationName = "DefaultApp"
Line 56: _EnablePasswordReset = False
Line 57: _PasswordStrengthRegEx = "[\w| !§$%&/()=\-?\*]*"
Line 58: _MaxInvalidPasswordAttempts = 3
Line 59: _MinRequiredNonAlphanumericChars = 1
Line 60: _MinRequiredPasswordLength = 5
Line 61: _RequiresQuestionAndAnswer = False
Line 62: _PasswordFormat = MembershipPasswordFormat.Hashed
Line 63:
Line 64: ' Now go through the properties and initialize custom values
Line 65: For Each key As String In config.Keys
Line 66: Select Case key.ToLower()
Line 67: Case "name"
Line 68: _Name = config(key)
Line 69: Case "applicationname"
Line 70: _ApplicationName = config(key)
Line 71: Case "filename"
Line 72: _FileName = config(key)
Line 73: Case "enablepasswordreset"
Line 74: _EnablePasswordReset = Boolean.Parse(config(key))
Line 75: Case "passwordstrengthregex"
Line 76: _PasswordStrengthRegEx = config(key)
Line 77: Case "maxinvalidpasswordattempts"
Line 78: _MaxInvalidPasswordAttempts = Integer.Parse(config(key))
Line 79: Case "minrequirednonalphanumericchars"
Line 80: _MinRequiredNonAlphanumericChars = Integer.Parse(config(key))
Line 81: Case "minrequiredpasswordlength"
Line 82: _MinRequiredPasswordLength = Integer.Parse(config(key))
Line 83: Case "passwordformat"
Line 84: _PasswordFormat = CType(System.Enum.Parse(GetType(MembershipPasswordFormat), config(key)), MembershipPasswordFormat)
Line 85: Case "requiresquestionandanswer"
Line 86: _RequiresQuestionAndAnswer = Boolean.Parse(config(key))
Line 87: End Select
Line 88: Next key
Line 89: End Sub
Line 90:
Line 91: #Region "Properties"
Line 92:
Line 93: Public Overrides Property ApplicationName() As String
Line 94: Get
Line 95: Return _ApplicationName
Line 96: End Get
Line 97: Set
Line 98: _ApplicationName = Value
Line 99: _CurrentStore = Nothing
Line 100: End Set
Line 101: End Property
Line 102:
Line 103: Public Overrides ReadOnly Property EnablePasswordReset() As Boolean
Line 104: Get
Line 105: Return _EnablePasswordReset
Line 106: End Get
Line 107: End Property
Line 108:
Line 109: Public Overrides ReadOnly Property EnablePasswordRetrieval() As Boolean
Line 110: Get
Line 111: If Me.PasswordFormat = MembershipPasswordFormat.Hashed Then
Line 112: Return False
Line 113: Else
Line 114: Return True
Line 115: End If
Line 116: End Get
Line 117: End Property
Line 118:
Line 119: Public Overrides ReadOnly Property MaxInvalidPasswordAttempts() As Integer
Line 120: Get
Line 121: Return _MaxInvalidPasswordAttempts
Line 122: End Get
Line 123: End Property
Line 124:
Line 125: Public Overrides ReadOnly Property MinRequiredNonAlphanumericCharacters() As Integer
Line 126: Get
Line 127: Return _MinRequiredNonAlphanumericChars
Line 128: End Get
Line 129: End Property
Line 130:
Line 131: Public Overrides ReadOnly Property MinRequiredPasswordLength() As Integer
Line 132: Get
Line 133: Return _MinRequiredPasswordLength
Line 134: End Get
Line 135: End Property
Line 136:
Line 137: Public Overrides ReadOnly Property PasswordAttemptWindow() As Integer
Line 138: Get
Line 139: Return 20
Line 140: End Get
Line 141: End Property
Line 142:
Line 143: Public Overrides ReadOnly Property PasswordFormat() As MembershipPasswordFormat
Line 144: Get
Line 145: Return _PasswordFormat
Line 146: End Get
Line 147: End Property
Line 148:
Line 149: Public Overrides ReadOnly Property PasswordStrengthRegularExpression() As String
Line 150: Get
Line 151: Return _PasswordStrengthRegEx
Line 152: End Get
Line 153: End Property
Line 154:
Line 155: Public Overrides ReadOnly Property RequiresQuestionAndAnswer() As Boolean
Line 156: Get
Line 157: Return _RequiresQuestionAndAnswer
Line 158: End Get
Line 159: End Property
Line 160:
Line 161: Public Overrides ReadOnly Property RequiresUniqueEmail() As Boolean
Line 162: Get
Line 163: Return True
Line 164: End Get
Line 165: End Property
Line 166:
Line 167: #End Region
Line 168:
Line 169: #Region "Methods"
Line 170:
Line 171: Public Overrides Function CreateUser(ByVal username As String, ByVal password As String, ByVal email As String, ByVal passwordQuestion As String, ByVal passwordAnswer As String, ByVal isApproved As Boolean, ByVal providerUserKey As Object, <System.Runtime.InteropServices.Out()> ByRef status As MembershipCreateStatus) As MembershipUser
Line 172: Try
Line 173: ' Validate the username and email
Line 174: If (Not ValidateUsername(username, email, Guid.Empty)) Then
Line 175: status = MembershipCreateStatus.InvalidUserName
Line 176: Return Nothing
Line 177: End If
Line 178:
Line 179: ' Raise the event before validating the password
Line 180: MyBase.OnValidatingPassword(New ValidatePasswordEventArgs(username, password, True))
Line 181:
Line 182: ' Validate the password
Line 183: If (Not ValidatePassword(password)) Then
Line 184: status = MembershipCreateStatus.InvalidPassword
Line 185: Return Nothing
Line 186: End If
Line 187:
Line 188: ' Everything is valid, create the user
Line 189: Dim user As SimpleUser = New SimpleUser()
Line 190: user.UserKey = Guid.NewGuid()
Line 191: user.UserName = username
Line 192: user.PasswordSalt = String.Empty
Line 193: user.Password = Me.TransformPassword(password, user.PasswordSalt)
Line 194: user.Email = email
Line 195: user.PasswordQuestion = passwordQuestion
Line 196: user.PasswordAnswer = passwordAnswer
Line 197: user.CreationDate = DateTime.Now
Line 198: user.LastActivityDate = DateTime.Now
Line 199: user.LastPasswordChangeDate = DateTime.Now
Line 200:
Line 201: ' Add the user to the store
Line 202: CurrentStore.Users.Add(user)
Line 203: CurrentStore.Save()
Line 204:
Line 205: status = MembershipCreateStatus.Success
Line 206: Return CreateMembershipFromInternalUser(user)
Line 207: Catch
Line 208: Throw
Line 209: End Try
Line 210: End Function
Line 211:
Line 212: Public Overrides Function DeleteUser(ByVal username As String, ByVal deleteAllRelatedData As Boolean) As Boolean
Line 213: Try
Line 214: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 215: If Not user Is Nothing Then
Line 216: CurrentStore.Users.Remove(user)
Line 217: Return True
Line 218: End If
Line 219:
Line 220: Return False
Line 221: Catch
Line 222: Throw
Line 223: End Try
Line 224: End Function
Line 225:
Line 226: Public Overrides Overloads Function GetUser(ByVal username As String, ByVal userIsOnline As Boolean) As MembershipUser
Line 227: Try
Line 228: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 229: If Not user Is Nothing Then
Line 230: If userIsOnline Then
Line 231: user.LastActivityDate = DateTime.Now
Line 232: CurrentStore.Save()
Line 233: End If
Line 234: Return CreateMembershipFromInternalUser(user)
Line 235: Else
Line 236: Return Nothing
Line 237: End If
Line 238: Catch
Line 239: Throw
Line 240: End Try
Line 241: End Function
Line 242:
Line 243: Public Overrides Overloads Function GetUser(ByVal providerUserKey As Object, ByVal userIsOnline As Boolean) As MembershipUser
Line 244: Try
Line 245: Dim user As SimpleUser = CurrentStore.GetUserByKey(CType(providerUserKey, Guid))
Line 246: If Not user Is Nothing Then
Line 247: If userIsOnline Then
Line 248: user.LastActivityDate = DateTime.Now
Line 249: CurrentStore.Save()
Line 250: End If
Line 251: Return CreateMembershipFromInternalUser(user)
Line 252: Else
Line 253: Return Nothing
Line 254: End If
Line 255: Catch
Line 256: Throw
Line 257: End Try
Line 258: End Function
Line 259:
Line 260: Public Overrides Function GetUserNameByEmail(ByVal email As String) As String
Line 261: Try
Line 262: Dim user As SimpleUser = CurrentStore.GetUserByEmail(email)
Line 263:
Line 264: If Not user Is Nothing Then
Line 265: Return user.UserName
Line 266: Else
Line 267: Return Nothing
Line 268: End If
Line 269: Catch
Line 270: Throw
Line 271: End Try
Line 272: End Function
Line 273:
Line 274: Public Overrides Sub UpdateUser(ByVal user As MembershipUser)
Line 275: Try
Line 276: Dim suser As SimpleUser = CurrentStore.GetUserByKey(CType(user.ProviderUserKey, Guid))
Line 277:
Line 278: If Not suser Is Nothing Then
Line 279: If (Not ValidateUsername(suser.UserName, suser.Email, suser.UserKey)) Then
Line 280: Throw New ArgumentException("Username and / or email are not unique!")
Line 281: End If
Line 282:
Line 283: suser.Email = user.Email
Line 284: suser.LastActivityDate = user.LastActivityDate
Line 285: suser.LastLoginDate = user.LastLoginDate
Line 286: suser.Comment = user.Comment
Line 287:
Line 288: CurrentStore.Save()
Line 289: Else
Line 290: Throw New ProviderException("User does not exist!")
Line 291: End If
Line 292: Catch
Line 293: Throw
Line 294: End Try
Line 295: End Sub
Line 296:
Line 297: Public Overrides Function ValidateUser(ByVal username As String, ByVal password As String) As Boolean
Line 298: Try
Line 299: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 300: If user Is Nothing Then
Line 301: Return False
Line 302: End If
Line 303:
Line 304: If ValidateUserInternal(user, password) Then
Line 305: user.LastLoginDate = DateTime.Now
Line 306: user.LastActivityDate = DateTime.Now
Line 307: CurrentStore.Save()
Line 308: Return True
Line 309: Else
Line 310: Return False
Line 311: End If
Line 312: Catch
Line 313: Throw
Line 314: End Try
Line 315: End Function
Line 316:
Line 317: Public Overrides Function ChangePassword(ByVal username As String, ByVal oldPassword As String, ByVal newPassword As String) As Boolean
Line 318: Try
Line 319: ' Get the user from the store
Line 320: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 321: If user Is Nothing Then
Line 322: Throw New Exception("User does not exist!")
Line 323: End If
Line 324:
Line 325: If ValidateUserInternal(user, oldPassword) Then
Line 326: ' Raise the event before validating the password
Line 327: MyBase.OnValidatingPassword(New ValidatePasswordEventArgs(username, newPassword, False))
Line 328:
Line 329: If (Not ValidatePassword(newPassword)) Then
Line 330: Throw New ArgumentException("Password doesn't meet password strength requirements!")
Line 331: End If
Line 332:
Line 333: user.PasswordSalt = String.Empty
Line 334: user.Password = TransformPassword(newPassword, user.PasswordSalt)
Line 335: user.LastPasswordChangeDate = DateTime.Now
Line 336: CurrentStore.Save()
Line 337:
Line 338: Return True
Line 339: End If
Line 340:
Line 341: Return False
Line 342: Catch
Line 343: Throw
Line 344: End Try
Line 345: End Function
Line 346:
Line 347: Public Overrides Function ChangePasswordQuestionAndAnswer(ByVal username As String, ByVal password As String, ByVal newPasswordQuestion As String, ByVal newPasswordAnswer As String) As Boolean
Line 348: Try
Line 349: ' Get the user from the store
Line 350: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 351:
Line 352: If ValidateUserInternal(user, password) Then
Line 353: user.PasswordQuestion = newPasswordQuestion
Line 354: user.PasswordAnswer = newPasswordAnswer
Line 355: CurrentStore.Save()
Line 356:
Line 357: Return True
Line 358: End If
Line 359:
Line 360: Return False
Line 361: Catch
Line 362: Throw
Line 363: End Try
Line 364: End Function
Line 365:
Line 366: Public Overrides Function FindUsersByEmail(ByVal emailToMatch As String, ByVal pageIndex As Integer, ByVal pageSize As Integer, <System.Runtime.InteropServices.Out()> ByRef totalRecords As Integer) As MembershipUserCollection
Line 367: Try
Line 368: Dim matchingUsers As New List(Of SimpleUser)
Line 369: Dim nUserItem As Integer
Line 370: For nUserItem = 0 To CurrentStore.Users.Count - 1
Line 371: If CurrentStore.Users.Item(nUserItem).Email = emailToMatch Then
Line 372: matchingUsers.Add(CurrentStore.Users.Item(nUserItem))
Line 373:
Line 374: End If
Line 375:
Line 376: Next
Line 377: Return CreateMembershipCollectionFromInternalList(matchingUsers)
Line 378: Catch
Line 379: Throw
Line 380: End Try
Line 381: End Function
Line 382:
Line 383: Public Overrides Function FindUsersByName(ByVal usernameToMatch As String, ByVal pageIndex As Integer, ByVal pageSize As Integer, <System.Runtime.InteropServices.Out()> ByRef totalRecords As Integer) As MembershipUserCollection
Line 384: Try
Line 385: Dim matchingUsers As New List(Of SimpleUser)
Line 386: Dim nUserItem As Integer
Line 387: For nUserItem = 0 To CurrentStore.Users.Count - 1
Line 388: If CurrentStore.Users.Item(nUserItem).UserName = usernameToMatch Then
Line 389: matchingUsers.Add(CurrentStore.Users.Item(nUserItem))
Line 390:
Line 391: End If
Line 392:
Line 393: Next
Line 394: Return CreateMembershipCollectionFromInternalList(matchingUsers)
Line 395: Catch
Line 396: Throw
Line 397: End Try
Line 398: End Function
Line 399:
Line 400: Public Overrides Function GetAllUsers(ByVal pageIndex As Integer, ByVal pageSize As Integer, <System.Runtime.InteropServices.Out()> ByRef totalRecords As Integer) As MembershipUserCollection
Line 401: Try
Line 402: totalRecords = CurrentStore.Users.Count
Line 403: Return CreateMembershipCollectionFromInternalList(CurrentStore.Users)
Line 404: Catch
Line 405: Throw
Line 406: End Try
Line 407: End Function
Line 408:
Line 409: Public Overrides Function GetNumberOfUsersOnline() As Integer
Line 410: Dim ret As Integer = 0
Line 411:
Line 412: For Each user As SimpleUser In CurrentStore.Users
Line 413: If user.LastActivityDate.AddMinutes(Membership.UserIsOnlineTimeWindow) >= DateTime.Now Then
Line 414: ret += 1
Line 415: End If
Line 416: Next user
Line 417:
Line 418: Return ret
Line 419: End Function
Line 420:
Line 421: Public Overrides Function GetPassword(ByVal username As String, ByVal answer As String) As String
Line 422: Try
Line 423: If EnablePasswordRetrieval Then
Line 424: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 425:
Line 426: If answer.Equals(user.PasswordAnswer, StringComparison.OrdinalIgnoreCase) Then
Line 427: Return user.Password
Line 428: Else
Line 429: Throw New System.Web.Security.MembershipPasswordException()
Line 430: End If
Line 431: Else
Line 432: Throw New Exception("Password retrieval is not enabled!")
Line 433: End If
Line 434: Catch
Line 435: Throw
Line 436: End Try
Line 437: End Function
Line 438:
Line 439: Public Overrides Function ResetPassword(ByVal username As String, ByVal answer As String) As String
Line 440: Try
Line 441: Dim user As SimpleUser = CurrentStore.GetUserByName(username)
Line 442: If user.PasswordAnswer.Equals(answer, StringComparison.OrdinalIgnoreCase) Then
Line 443: Dim NewPassword As Byte() = New Byte(15){}
Line 444: Dim rng As RandomNumberGenerator = RandomNumberGenerator.Create()
Line 445: rng.GetBytes(NewPassword)
Line 446:
Line 447: Dim NewPasswordString As String = Convert.ToBase64String(NewPassword)
Line 448: user.PasswordSalt = String.Empty
Line 449: user.Password = TransformPassword(NewPasswordString, user.PasswordSalt)
Line 450: CurrentStore.Save()
Line 451:
Line 452: Return NewPasswordString
Line 453: Else
Line 454: Throw New Exception("Invalid answer entered!")
Line 455: End If
Line 456: Catch
Line 457: Throw
Line 458: End Try
Line 459: End Function
Line 460:
Line 461: Public Overrides Function UnlockUser(ByVal userName As String) As Boolean
Line 462: ' This provider doesn't support locking
Line 463: Return True
Line 464: End Function
Line 465:
Line 466: #End Region
Line 467:
Line 468: #Region "Private Helper Methods"
Line 469:
Line 470: Private Function TransformPassword(ByVal password As String, ByRef salt As String) As String
Line 471: Dim ret As String = String.Empty
Line 472:
Line 473: Select Case PasswordFormat
Line 474: Case MembershipPasswordFormat.Clear
Line 475: ret = password
Line 476:
Line 477: Case MembershipPasswordFormat.Hashed
Line 478:
Line 479: ' Generate the salt if not passed in
Line 480: If String.IsNullOrEmpty(salt) Then
Line 481: Dim saltBytes As Byte() = New Byte(15){}
Line 482: Dim rng As RandomNumberGenerator = RandomNumberGenerator.Create()
Line 483: rng.GetBytes(saltBytes)
Line 484: salt = Convert.ToBase64String(saltBytes)
Line 485: End If
Line 486: ret = FormsAuthentication.HashPasswordForStoringInConfigFile((salt & password), "SHA1")
Line 487:
Line 488: Case MembershipPasswordFormat.Encrypted
Line 489: Dim ClearText As Byte() = Encoding.UTF8.GetBytes(password)
Line 490: Dim EncryptedText As Byte() = MyBase.EncryptPassword(ClearText)
Line 491: ret = Convert.ToBase64String(EncryptedText)
Line 492: End Select
Line 493:
Line 494: Return ret
Line 495: End Function
Line 496:
Line 497: Private Function ValidateUsername(ByVal userName As String, ByVal email As String, ByVal excludeKey As Guid) As Boolean
Line 498: Dim IsValid As Boolean = True
Line 499:
Line 500: Dim store As UserStore = UserStore.GetStore(_FileName)
Line 501: For Each user As SimpleUser In store.Users
Line 502: If user.UserKey.CompareTo(excludeKey) <> 0 Then
Line 503: If String.Equals(user.UserName, userName, StringComparison.OrdinalIgnoreCase) Then
Line 504: IsValid = False
Line 505: Exit For
Line 506: End If
Line 507:
Line 508: If String.Equals(user.Email, email, StringComparison.OrdinalIgnoreCase) Then
Line 509: IsValid = False
Line 510: Exit For
Line 511: End If
Line 512: End If
Line 513: Next user
Line 514:
Line 515: Return IsValid
Line 516: End Function
Line 517:
Line 518: Private Function ValidatePassword(ByVal password As String) As Boolean
Line 519: Dim IsValid As Boolean = True
Line 520: Dim HelpExpression As Regex
Line 521:
Line 522: ' Validate simple properties
Line 523: IsValid = IsValid AndAlso (password.Length >= Me.MinRequiredPasswordLength)
Line 524:
Line 525: ' Validate non-alphanumeric characters
Line 526: HelpExpression = New Regex("\W")
Line 527: IsValid = IsValid AndAlso (HelpExpression.Matches(password).Count >= Me.MinRequiredNonAlphanumericCharacters)
Line 528:
Line 529: ' Validate regular expression
Line 530: HelpExpression = New Regex(Me.PasswordStrengthRegularExpression)
Line 531: IsValid = IsValid AndAlso (HelpExpression.Matches(password).Count > 0)
Line 532:
Line 533: Return IsValid
Line 534: End Function
Line 535:
Line 536: Private Function ValidateUserInternal(ByVal user As SimpleUser, ByVal password As String) As Boolean
Line 537: If Not user Is Nothing Then
Line 538: Dim passwordValidate As String = TransformPassword(password, user.PasswordSalt)
Line 539: If String.Compare(passwordValidate, user.Password) = 0 Then
Line 540: Return True
Line 541: End If
Line 542: End If
Line 543:
Line 544: Return False
Line 545: End Function
Line 546:
Line 547: Private Function CreateMembershipFromInternalUser(ByVal user As SimpleUser) As MembershipUser
Line 548: Dim muser As MembershipUser = New MembershipUser(MyBase.Name, user.UserName, user.UserKey, user.Email, user.PasswordQuestion, String.Empty, True, False, user.CreationDate, user.LastLoginDate, user.LastActivityDate, user.LastPasswordChangeDate, DateTime.MaxValue)
Line 549:
Line 550: Return muser
Line 551: End Function
Line 552:
Line 553: Private Function CreateMembershipCollectionFromInternalList(ByVal users As List(Of SimpleUser)) As MembershipUserCollection
Line 554: Dim ReturnCollection As MembershipUserCollection = New MembershipUserCollection()
Line 555:
Line 556: For Each user As SimpleUser In users
Line 557: ReturnCollection.Add(CreateMembershipFromInternalUser(user))
Line 558: Next user
Line 559:
Line 560: Return ReturnCollection
Line 561: End Function
Line 562:
Line 563: #End Region
Line 564: End Class
Line 565: End Namespace
Line 566:
|