/// <summary> /// Subscribe a user to one or more security policies. /// </summary> public async Task SubscribeAsync(User user, IUserSecurityPolicySubscription subscription) { if (user == null) { throw new ArgumentNullException(nameof(user)); } if (subscription == null) { throw new ArgumentNullException(nameof(subscription)); } if (IsSubscribed(user, subscription)) { Diagnostics.Information($"User '{user.Username}' is already subscribed to '{subscription.SubscriptionName}'."); } else { foreach (var policy in subscription.Policies) { user.SecurityPolicies.Add(new UserSecurityPolicy(policy)); } await subscription.OnSubscribeAsync(new UserSecurityPolicySubscriptionContext(this, user)); await Auditing.SaveAuditRecordAsync( new UserAuditRecord(user, AuditedUserAction.SubscribeToPolicies, subscription.Policies)); await EntitiesContext.SaveChangesAsync(); Diagnostics.Information($"User '{user.Username}' is now subscribed to '{subscription.SubscriptionName}'."); } }
/// <summary> /// Look up and evaluation of security policies for the specified action. /// </summary> public async Task <SecurityPolicyResult> EvaluateAsync(SecurityPolicyAction action, HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException(nameof(httpContext)); } var user = httpContext.GetCurrentUser(); foreach (var handler in UserHandlers.Where(h => h.Action == action)) { var foundPolicies = user.SecurityPolicies.Where(p => p.Name.Equals(handler.Name, StringComparison.OrdinalIgnoreCase)); if (foundPolicies.Any()) { var result = handler.Evaluate(new UserSecurityPolicyEvaluationContext(httpContext, foundPolicies)); await Auditing.SaveAuditRecordAsync(new UserSecurityPolicyAuditRecord( user.Username, GetAuditAction(action), foundPolicies, result.Success, result.ErrorMessage)); if (!result.Success) { Diagnostics.Information( $"Security policy '{handler.Name}' failed for user '{user.Username}' with error '{result.ErrorMessage}'."); return(result); } } } return(SecurityPolicyResult.SuccessResult); }
private async Task MigrateCredentials(User user, List <Credential> creds, string password) { var toRemove = creds.Where(c => !string.Equals( c.Type, CredentialBuilder.LatestPasswordType, StringComparison.OrdinalIgnoreCase)) .ToList(); // Remove any non latest credentials foreach (var cred in toRemove) { creds.Remove(cred); user.Credentials.Remove(cred); Entities.DeleteOnCommit(cred); } await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.RemoveCredential, toRemove)); // Now add one if there are no credentials left if (creds.Count == 0) { var newCred = _credentialBuilder.CreatePasswordCredential(password); await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, newCred)); user.Credentials.Add(newCred); } // Save changes, if any await Entities.SaveChangesAsync(); }
public async Task CancelChangeEmailAddress(User user) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.CancelChangeEmail, user.UnconfirmedEmailAddress)); user.CancelChangeEmailAddress(); await UserRepository.CommitChangesAsync(); }
public virtual async Task AddCredential(User user, Credential credential) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.AddCredential, credential)); user.Credentials.Add(credential); await Entities.SaveChangesAsync(); }
private async Task ReplaceCredentialInternal(User user, Credential credential) { // Find the credentials we're replacing, if any var toRemove = user.Credentials .Where(cred => // If we're replacing a password credential, remove ALL password credentials (credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase) && cred.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) || cred.Type == credential.Type) .ToList(); foreach (var cred in toRemove) { user.Credentials.Remove(cred); Entities.DeleteOnCommit(cred); } if (toRemove.Any()) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord( user, AuditedUserAction.RemoveCredential, toRemove)); } user.Credentials.Add(credential); await Auditing.SaveAuditRecordAsync(new UserAuditRecord( user, AuditedUserAction.AddCredential, credential)); }
public async Task <bool> ConfirmEmailAddress(User user, string token) { if (user == null) { throw new ArgumentNullException(nameof(user)); } if (String.IsNullOrEmpty(token)) { throw new ArgumentNullException(nameof(token)); } if (user.EmailConfirmationToken != token) { return(false); } var conflictingUsers = FindAllByEmailAddress(user.UnconfirmedEmailAddress); if (conflictingUsers.AnySafe(u => u.Key != user.Key)) { throw new EntityException(Strings.EmailAddressBeingUsed, user.UnconfirmedEmailAddress); } await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.ConfirmEmail, user.UnconfirmedEmailAddress)); user.ConfirmEmailAddress(); await UserRepository.CommitChangesAsync(); return(true); }
public async Task <User> DeleteMemberAsync(Organization organization, string memberName) { organization = organization ?? throw new ArgumentNullException(nameof(organization)); var membership = FindMembershipByUsername(organization, memberName); if (membership == null) { throw new EntityException(string.Format(CultureInfo.CurrentCulture, Strings.UpdateOrDeleteMember_MemberNotFound, memberName)); } var memberToRemove = membership.Member; // block removal of last admin if (membership.IsAdmin && organization.Administrators.Count() == 1) { throw new EntityException(Strings.DeleteMember_CannotRemoveLastAdmin); } organization.Members.Remove(membership); await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.RemoveOrganizationMember, membership)); await EntitiesContext.SaveChangesAsync(); return(memberToRemove); }
private async Task <SecurityPolicyResult> EvaluateInternalAsync(IEnumerable <UserSecurityPolicy> policies, HttpContextBase httpContext, SecurityPolicyAction action, bool auditSuccess) { var relevantHandlers = UserHandlers.Where(h => h.Action == action).ToList(); foreach (var handler in relevantHandlers) { var foundPolicies = policies.Where(p => p.Name.Equals(handler.Name, StringComparison.OrdinalIgnoreCase)).ToList(); if (foundPolicies.Any()) { var user = httpContext.GetCurrentUser(); var result = handler.Evaluate(new UserSecurityPolicyEvaluationContext(httpContext, foundPolicies)); if (auditSuccess || !result.Success) { await Auditing.SaveAuditRecordAsync(new UserSecurityPolicyAuditRecord( user.Username, GetAuditAction(action), foundPolicies, result.Success, result.ErrorMessage)); } if (!result.Success) { Diagnostics.Information( $"Security policy from subscription '{foundPolicies.First().Subscription}' - '{handler.Name}' failed for user '{user.Username}' with error '{result.ErrorMessage}'."); return(result); } } } return(SecurityPolicyResult.SuccessResult); }
public virtual async Task RemoveCredential(User user, Credential cred) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.RemoveCredential, cred)); user.Credentials.Remove(cred); Entities.Credentials.Remove(cred); await Entities.SaveChangesAsync(); }
public virtual async Task ChangeMultiFactorAuthentication(User user, bool enableMultiFactor) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, enableMultiFactor ? AuditedUserAction.EnabledMultiFactorAuthentication : AuditedUserAction.DisabledMultiFactorAuthentication)); user.EnableMultiFactorAuthentication = enableMultiFactor; await UserRepository.CommitChangesAsync(); TelemetryService.TrackUserChangedMultiFactorAuthentication(user, enableMultiFactor); }
private async Task <AuthenticatedUser> AuthenticateInternal(Func <Credential, Credential> matchCredential, Credential credential) { if (credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)) { // Password credentials cannot be used this way. throw new ArgumentException(Strings.PasswordCredentialsCannotBeUsedHere, nameof(credential)); } using (_trace.Activity("Authenticate Credential: " + credential.Type)) { var matched = matchCredential(credential); if (matched == null) { _trace.Information("No user matches credential of type: " + credential.Type); await Auditing.SaveAuditRecordAsync( new FailedAuthenticatedOperationAuditRecord(null, AuditedAuthenticatedOperationAction.FailedLoginNoSuchUser, attemptedCredential : credential)); return(null); } if (matched.HasExpired) { _trace.Verbose("Credential of type '" + matched.Type + "' for user '" + matched.User.Username + "' has expired on " + matched.Expires.Value.ToString("O", CultureInfo.InvariantCulture)); return(null); } if (matched.Type == CredentialTypes.ApiKey.V1 && !matched.HasBeenUsedInLastDays(_config.ExpirationInDaysForApiKeyV1)) { // API key credential was last used a long, long time ago - expire it await Auditing.SaveAuditRecordAsync( new UserAuditRecord(matched.User, AuditedUserAction.ExpireCredential, matched)); matched.Expires = _dateTimeProvider.UtcNow; await Entities.SaveChangesAsync(); _trace.Verbose( "Credential of type '" + matched.Type + "' for user '" + matched.User.Username + "' was last used on " + matched.LastUsed.Value.ToString("O", CultureInfo.InvariantCulture) + " and has now expired."); return(null); } // update last used timestamp matched.LastUsed = _dateTimeProvider.UtcNow; await Entities.SaveChangesAsync(); _trace.Verbose("Successfully authenticated '" + matched.User.Username + "' with '" + matched.Type + "' credential"); return(new AuthenticatedUser(matched.User, matched)); } }
public virtual async Task EditCredentialScopes(User user, Credential cred, ICollection <Scope> newScopes) { foreach (var oldScope in cred.Scopes.ToArray()) { Entities.Scopes.Remove(oldScope); } cred.Scopes = newScopes; await Entities.SaveChangesAsync(); await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.EditCredential, cred)); }
public virtual async Task CreateSessionAsync(IOwinContext owinContext, AuthenticatedUser user) { // Create a claims identity for the session ClaimsIdentity identity = CreateIdentity(user.User, AuthenticationTypes.LocalUser); // Issue the session token and clean up the external token if present owinContext.Authentication.SignIn(identity); owinContext.Authentication.SignOut(AuthenticationTypes.External); // Write an audit record await Auditing.SaveAuditRecordAsync( new UserAuditRecord(user.User, AuditedUserAction.Login, user.CredentialUsed)); }
public async Task <bool> TransformUserToOrganization(User accountToTransform, User adminUser, string token) { await SubscribeOrganizationToTenantPolicyIfTenantIdIsSupported(accountToTransform, adminUser); var result = await EntitiesContext.TransformUserToOrganization(accountToTransform, adminUser, token); if (result) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(accountToTransform, AuditedUserAction.TransformOrganization, adminUser, affectedMemberIsAdmin : true)); } return(result); }
public async Task <Organization> AddOrganizationAsync(string username, string emailAddress, User adminUser) { var existingUserWithIdentity = EntitiesContext.Users .FirstOrDefault(u => u.Username == username || u.EmailAddress == emailAddress); if (existingUserWithIdentity != null) { if (existingUserWithIdentity.Username.Equals(username, StringComparison.OrdinalIgnoreCase)) { throw new EntityException(Strings.UsernameNotAvailable, username); } if (string.Equals(existingUserWithIdentity.EmailAddress, emailAddress, StringComparison.OrdinalIgnoreCase)) { throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress); } } var organization = new Organization(username) { EmailAllowed = true, UnconfirmedEmailAddress = emailAddress, EmailConfirmationToken = Crypto.GenerateToken(), NotifyPackagePushed = true, CreatedUtc = DateTimeProvider.UtcNow, Members = new List <Membership>() }; if (!Config.ConfirmEmailAddresses) { organization.ConfirmEmailAddress(); } var membership = new Membership { Organization = organization, Member = adminUser, IsAdmin = true }; organization.Members.Add(membership); adminUser.Organizations.Add(membership); OrganizationRepository.InsertOnCommit(organization); await SubscribeOrganizationToTenantPolicyIfTenantIdIsSupported(organization, adminUser, commitChanges : false); await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.AddOrganization, membership)); await EntitiesContext.SaveChangesAsync(); return(organization); }
public async Task ChangeEmailAddress(User user, string newEmailAddress) { var existingUsers = FindAllByEmailAddress(newEmailAddress); if (existingUsers.AnySafe(u => u.Key != user.Key)) { throw new EntityException(Strings.EmailAddressBeingUsed, newEmailAddress); } await Auditing.SaveAuditRecordAsync(new UserAuditRecord(user, AuditedUserAction.ChangeEmail, newEmailAddress)); user.UpdateEmailAddress(newEmailAddress, Crypto.GenerateToken); await UserRepository.CommitChangesAsync(); }
public virtual async Task <AuthenticatedUser> Register(string username, string emailAddress, Credential credential) { if (_config.FeedOnlyMode) { throw new FeedOnlyModeException(FeedOnlyModeException.FeedOnlyModeError); } var existingUser = Entities.Users .FirstOrDefault(u => u.Username == username || u.EmailAddress == emailAddress); if (existingUser != null) { if (string.Equals(existingUser.Username, username, StringComparison.OrdinalIgnoreCase)) { throw new EntityException(Strings.UsernameNotAvailable, username); } else { throw new EntityException(Strings.EmailAddressBeingUsed, emailAddress); } } var newUser = new User(username) { EmailAllowed = true, UnconfirmedEmailAddress = emailAddress, EmailConfirmationToken = CryptographyService.GenerateToken(), NotifyPackagePushed = true, CreatedUtc = _dateTimeProvider.UtcNow }; // Add a credential for the password newUser.Credentials.Add(credential); if (!_config.ConfirmEmailAddresses) { newUser.ConfirmEmailAddress(); } // Write an audit record await Auditing.SaveAuditRecordAsync(new UserAuditRecord(newUser, AuditedUserAction.Register, credential)); Entities.Users.Add(newUser); await Entities.SaveChangesAsync(); return(new AuthenticatedUser(newUser, credential)); }
public virtual async Task GeneratePasswordResetToken(User user, int expirationInMinutes) { if (user == null) { throw new ArgumentNullException(nameof(user)); } if (expirationInMinutes < 1) { throw new ArgumentException( Strings.TokenExpirationShouldGiveUser1MinuteToChangePassword, nameof(expirationInMinutes)); } if (!user.Confirmed) { throw new InvalidOperationException(Strings.UserIsNotYetConfirmed); } if (!string.IsNullOrEmpty(user.PasswordResetToken) && !user.PasswordResetTokenExpirationDate.IsInThePast()) { return; } user.PasswordResetToken = CryptographyService.GenerateToken(); user.PasswordResetTokenExpirationDate = _dateTimeProvider.UtcNow.AddMinutes(expirationInMinutes); var passwordCredential = user.Credentials.FirstOrDefault( credential => credential.Type.StartsWith(CredentialTypes.Password.Prefix, StringComparison.OrdinalIgnoreCase)); UserAuditRecord auditRecord; if (passwordCredential == null) { auditRecord = new UserAuditRecord(user, AuditedUserAction.RequestPasswordReset); } else { auditRecord = new UserAuditRecord(user, AuditedUserAction.RequestPasswordReset, passwordCredential); } await Auditing.SaveAuditRecordAsync(auditRecord); await Entities.SaveChangesAsync(); }
public async Task <bool> TransformUserToOrganization(User accountToTransform, User adminUser, string token) { if (accountToTransform.OrganizationMigrationRequest == null || !accountToTransform.OrganizationMigrationRequest.AdminUser.MatchesUser(adminUser) || accountToTransform.OrganizationMigrationRequest.ConfirmationToken != token) { return(false); } await SubscribeOrganizationToTenantPolicyIfTenantIdIsSupported(accountToTransform, adminUser); var result = await EntitiesContext.TransformUserToOrganization(accountToTransform, adminUser, token); if (result) { await Auditing.SaveAuditRecordAsync(new UserAuditRecord(accountToTransform, AuditedUserAction.TransformOrganization, adminUser, affectedMemberIsAdmin : true)); } return(result); }
/// <summary> /// Unsubscribe a user from one or more security policies. /// </summary> public async Task UnsubscribeAsync(User user, IUserSecurityPolicySubscription subscription, bool commitChanges = true) { if (user == null) { throw new ArgumentNullException(nameof(user)); } if (subscription == null) { throw new ArgumentNullException(nameof(subscription)); } var matches = FindPolicies(user, subscription).ToList(); if (matches.Any()) { foreach (var policy in matches) { user.SecurityPolicies.Remove(policy); EntitiesContext.UserSecurityPolicies.Remove(policy); } await subscription.OnUnsubscribeAsync(new UserSecurityPolicySubscriptionContext(this, user)); await Auditing.SaveAuditRecordAsync( new UserAuditRecord(user, AuditedUserAction.UnsubscribeFromPolicies, subscription.Policies)); if (commitChanges) { await EntitiesContext.SaveChangesAsync(); } Diagnostics.Information($"User is now unsubscribed from '{subscription.SubscriptionName}'."); } else { Diagnostics.Information($"User is already unsubscribed from '{subscription.SubscriptionName}'."); } }
public virtual async Task <PasswordAuthenticationResult> Authenticate(string userNameOrEmail, string password) { using (_trace.Activity("Authenticate:" + userNameOrEmail)) { var user = FindByUserNameOrEmail(userNameOrEmail); // Check if the user exists if (user == null) { var ldapUser = this.Ldap.ValidateUsernameAndPassword(userNameOrEmail, password); if (ldapUser != null) { _trace.Information($"Creating user from LDAP credentials: {userNameOrEmail}"); var ldapCredential = _credentialBuilder.CreateExternalCredential(AuthenticationTypes.LdapUser, ldapUser.Username, ldapUser.Identity); var authUser = await this.Register(ldapUser.Username, ldapUser.Email, ldapCredential); return(new PasswordAuthenticationResult(PasswordAuthenticationResult.AuthenticationResult.Success, authUser)); } _trace.Information("No such user: "******"Login failed. User account {userNameOrEmail} is locked for the next {remainingMinutes} minutes."); return(new PasswordAuthenticationResult(PasswordAuthenticationResult.AuthenticationResult.AccountLocked, authenticatedUser: null, lockTimeRemainingMinutes: remainingMinutes)); } // Validate the password Credential matched; if (!ValidatePasswordCredential(user.Credentials, password, out matched)) { var isValid = this.Ldap.ValidateCredentials(user.Credentials, password, out matched); if (isValid) { _trace.Verbose($"Successfully authenticated '{user.Username}' with '{matched.Type}' credential"); return(new PasswordAuthenticationResult(PasswordAuthenticationResult.AuthenticationResult.Success, new AuthenticatedUser(user, matched))); } _trace.Information($"Password validation failed: {userNameOrEmail}"); await UpdateFailedLoginAttempt(user); await Auditing.SaveAuditRecordAsync( new FailedAuthenticatedOperationAuditRecord( userNameOrEmail, AuditedAuthenticatedOperationAction.FailedLoginInvalidPassword)); return(new PasswordAuthenticationResult(PasswordAuthenticationResult.AuthenticationResult.BadCredentials)); } var passwordCredentials = user .Credentials .Where(c => CredentialTypes.IsPassword(c.Type)) .ToList(); if (passwordCredentials.Count > 1 || !passwordCredentials.Any(c => string.Equals(c.Type, CredentialBuilder.LatestPasswordType, StringComparison.OrdinalIgnoreCase))) { await MigrateCredentials(user, passwordCredentials, password); } // Reset failed login count upon successful login await UpdateSuccessfulLoginAttempt(user); // Return the result _trace.Verbose("Successfully authenticated '" + user.Username + "' with '" + matched.Type + "' credential"); return(new PasswordAuthenticationResult(PasswordAuthenticationResult.AuthenticationResult.Success, new AuthenticatedUser(user, matched))); } }
private async Task <SecurityPolicyResult> EvaluatePackagePoliciesInternalAsync( SecurityPolicyAction action, Package package, User sourceAccount, User targetAccount, HttpContextBase httpContext, IEnumerable <UserSecurityPolicy> policies = null, bool auditSuccess = true) { policies = policies ?? targetAccount.SecurityPolicies; var relevantHandlers = PackageHandlers.Where(h => h.Action == action).ToList(); var packagePoliciesResult = SecurityPolicyResult.SuccessResult; foreach (var handler in relevantHandlers) { var foundPolicies = policies.Where(p => p.Name.Equals(handler.Name, StringComparison.OrdinalIgnoreCase)).ToList(); if (foundPolicies.Any()) { var context = new PackageSecurityPolicyEvaluationContext( _userService.Value, _packageOwnershipManagementService.Value, _telemetryService, foundPolicies, package, sourceAccount, targetAccount, httpContext); var result = await handler.EvaluateAsync(context); if (auditSuccess || !result.Success) { await Auditing.SaveAuditRecordAsync(new UserSecurityPolicyAuditRecord( context.TargetAccount.Username, GetAuditAction(action), foundPolicies, result.Success, result.ErrorMessage)); } if (!result.Success) { Diagnostics.Information( $"Security policy from subscription '{foundPolicies.First().Subscription}' - '{handler.Name}' failed with error '{result.ErrorMessage}'."); return(result); } if (result.HasWarnings) { if (packagePoliciesResult == SecurityPolicyResult.SuccessResult) { packagePoliciesResult = result; } else { packagePoliciesResult.AddWarnings(result.WarningMessages); } } } } return(packagePoliciesResult); }
public async Task <Membership> AddMemberAsync(Organization organization, string memberName, string confirmationToken) { organization = organization ?? throw new ArgumentNullException(nameof(organization)); var request = FindMembershipRequestByUsername(organization, memberName); if (request == null || request.ConfirmationToken != confirmationToken) { throw new EntityException(string.Format(CultureInfo.CurrentCulture, Strings.AddMember_MissingRequest, memberName)); } var member = request.NewMember; organization.MemberRequests.Remove(request); if (!member.Confirmed) { throw new EntityException(string.Format(CultureInfo.CurrentCulture, Strings.AddMember_UserNotConfirmed, memberName)); } if (member is Organization) { throw new EntityException(string.Format(CultureInfo.CurrentCulture, Strings.AddMember_UserIsOrganization, memberName)); } var membership = FindMembershipByUsername(organization, memberName); if (membership == null) { // Ensure that the new member meets the AAD tenant policy for this organization. var policyResult = await SecurityPolicyService.EvaluateOrganizationPoliciesAsync( SecurityPolicyAction.JoinOrganization, organization, member); if (policyResult != SecurityPolicyResult.SuccessResult) { throw new EntityException(string.Format(CultureInfo.CurrentCulture, Strings.AddMember_PolicyFailure, policyResult.ErrorMessage)); } membership = new Membership() { Member = member, IsAdmin = request.IsAdmin }; organization.Members.Add(membership); await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.AddOrganizationMember, membership)); } else { // If the user is already a member, update the existing membership. // If the request grants admin but this member is not an admin, grant admin to the member. membership.IsAdmin = membership.IsAdmin || request.IsAdmin; await Auditing.SaveAuditRecordAsync(new UserAuditRecord(organization, AuditedUserAction.UpdateOrganizationMember, membership)); } await EntitiesContext.SaveChangesAsync(); return(membership); }