Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs

934 lines
37 KiB
C#
Raw Normal View History

using System;
2017-12-07 16:45:25 +01:00
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
2020-09-17 09:42:55 +02:00
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
2017-12-07 16:45:25 +01:00
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Persistence.Querying;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
using Umbraco.Cms.Infrastructure.Persistence.Factories;
using Umbraco.Cms.Infrastructure.Persistence.Mappers;
using Umbraco.Cms.Infrastructure.Persistence.Querying;
using Umbraco.Extensions;
2017-12-07 16:45:25 +01:00
namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
2017-12-07 16:45:25 +01:00
{
/// <summary>
/// Represents the UserRepository for doing CRUD operations for <see cref="IUser"/>
/// </summary>
2020-12-22 10:30:16 +11:00
internal class UserRepository : EntityRepositoryBase<int, IUser>, IUserRepository
2017-12-07 16:45:25 +01:00
{
private readonly IMapperCollection _mapperCollection;
private readonly GlobalSettings _globalSettings;
private readonly UserPasswordConfigurationSettings _passwordConfiguration;
private readonly IJsonSerializer _jsonSerializer;
2017-12-14 17:04:44 +01:00
private string _passwordConfigJson;
private bool _passwordConfigInitialized;
2017-12-07 16:45:25 +01:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="scopeAccessor"></param>
2019-01-17 08:34:29 +01:00
/// <param name="appCaches"></param>
2017-12-07 16:45:25 +01:00
/// <param name="logger"></param>
/// <param name="mapperCollection">
2017-12-07 16:45:25 +01:00
/// A dictionary specifying the configuration for user passwords. If this is null then no password configuration will be persisted or read.
/// </param>
/// <param name="globalSettings"></param>
public UserRepository(
IScopeAccessor scopeAccessor,
AppCaches appCaches,
2020-09-18 12:53:06 +02:00
ILogger<UserRepository> logger,
IMapperCollection mapperCollection,
IOptions<GlobalSettings> globalSettings,
IOptions<UserPasswordConfigurationSettings> passwordConfiguration,
IJsonSerializer jsonSerializer)
2019-01-17 08:34:29 +01:00
: base(scopeAccessor, appCaches, logger)
2017-12-07 16:45:25 +01:00
{
_mapperCollection = mapperCollection ?? throw new ArgumentNullException(nameof(mapperCollection));
_globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings));
_passwordConfiguration = passwordConfiguration.Value ?? throw new ArgumentNullException(nameof(passwordConfiguration));
_jsonSerializer = jsonSerializer;
}
2017-12-14 17:04:44 +01:00
/// <summary>
/// Returns a serialized dictionary of the password configuration that is stored against the user in the database
/// </summary>
private string DefaultPasswordConfigJson
2017-12-14 17:04:44 +01:00
{
get
{
if (_passwordConfigInitialized)
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
{
2017-12-14 17:04:44 +01:00
return _passwordConfigJson;
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
}
2017-12-14 17:04:44 +01:00
Implement password config storage for members (#10170) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Reducing and removing published member cache * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry * cannot inject IPublishedMemberCache and cannot have IPublishedMember * splits out files, fixes build * fix tests * removes membership provider classes * removes membership provider classes * updates the identity map definition * reverts commented out lines * reverts commented out lines * Implements members Password config in db, fixes members cookie auth to not interfere with the back office cookie auth, fixes Startup sequence, fixes startup pipeline * commits change to Startup * Rename migration from `MemberTableColumns2` to `AddPasswordConfigToMemberTable` * Fix test * Fix tests, but adding default passwordConfig to members Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-22 23:59:13 +10:00
var passwordConfig = new PersistedPasswordSettings
{
HashAlgorithm = _passwordConfiguration.HashAlgorithmType
};
_passwordConfigJson = passwordConfig == null ? null : _jsonSerializer.Serialize(passwordConfig);
2017-12-14 17:04:44 +01:00
_passwordConfigInitialized = true;
return _passwordConfigJson;
}
2017-12-07 16:45:25 +01:00
}
#region Overrides of RepositoryBase<int,IUser>
protected override IUser PerformGet(int id)
{
Implements Public Access in netcore (#10137) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 15:11:45 +10:00
// This will never resolve to a user, yet this is asked
// for all of the time (especially in cases of members).
// Don't issue a SQL call for this, we know it will not exist.
if (id == default || id < -1)
{
return null;
}
2017-12-07 16:45:25 +01:00
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>()
.Where<UserDto>(x => x.Id == id);
var dtos = Database.Fetch<UserDto>(sql);
if (dtos.Count == 0) return null;
PerformGetReferencedDtos(dtos);
return UserFactory.BuildEntity(_globalSettings, dtos[0]);
2017-12-07 16:45:25 +01:00
}
/// <summary>
/// Returns a user by username
/// </summary>
/// <param name="username"></param>
/// <param name="includeSecurityData">
/// Can be used for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes).
/// This is really only used for a shim in order to upgrade to 7.6.
/// </param>
/// <returns>
/// A non cached <see cref="IUser"/> instance
/// </returns>
public IUser GetByUsername(string username, bool includeSecurityData)
{
return GetWith(sql => sql.Where<UserDto>(x => x.Login == username), includeSecurityData);
}
/// <summary>
/// Returns a user by id
/// </summary>
/// <param name="id"></param>
/// <param name="includeSecurityData">
/// This is really only used for a shim in order to upgrade to 7.6 but could be used
/// for slightly faster user lookups if the result doesn't require security data (i.e. groups, apps & start nodes)
/// </param>
/// <returns>
/// A non cached <see cref="IUser"/> instance
/// </returns>
public IUser Get(int id, bool includeSecurityData)
{
return GetWith(sql => sql.Where<UserDto>(x => x.Id == id), includeSecurityData);
}
public IProfile GetProfile(string username)
{
2018-11-20 14:11:35 +11:00
var dto = GetDtoWith(sql => sql.Where<UserDto>(x => x.Login == username), false);
2017-12-07 16:45:25 +01:00
return dto == null ? null : new UserProfile(dto.Id, dto.UserName);
}
public IProfile GetProfile(int id)
{
var dto = GetDtoWith(sql => sql.Where<UserDto>(x => x.Id == id), false);
return dto == null ? null : new UserProfile(dto.Id, dto.UserName);
}
public IDictionary<UserState, int> GetUserStates()
{
var sql = @"SELECT '1CountOfAll' AS colName, COUNT(id) AS num FROM umbracoUser
UNION
SELECT '2CountOfActive' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL
UNION
SELECT '3CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 1
UNION
SELECT '4CountOfLockedOut' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userNoConsole = 1
UNION
SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
2018-09-06 14:10:10 +02:00
UNION
2019-11-20 11:27:19 +01:00
SELECT '6CountOfDisabled' AS colName, COUNT(id) AS num FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL
2017-12-07 16:45:25 +01:00
ORDER BY colName";
var result = Database.Fetch<dynamic>(sql);
return new Dictionary<UserState, int>
{
2018-03-22 11:24:12 +01:00
{UserState.All, (int) result[0].num},
{UserState.Active, (int) result[1].num},
{UserState.Disabled, (int) result[2].num},
{UserState.LockedOut, (int) result[3].num},
2018-09-06 14:10:10 +02:00
{UserState.Invited, (int) result[4].num},
{UserState.Inactive, (int) result[5].num}
2017-12-07 16:45:25 +01:00
};
}
2018-03-22 11:24:12 +01:00
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
{
2018-03-22 11:24:12 +01:00
var now = DateTime.UtcNow;
var dto = new UserLoginDto
{
UserId = userId,
IpAddress = requestingIpAddress,
LoggedInUtc = now,
LastValidatedUtc = now,
LoggedOutUtc = null,
SessionId = Guid.NewGuid()
};
Database.Insert(dto);
if (cleanStaleSessions)
{
ClearLoginSessions(TimeSpan.FromDays(15));
}
return dto.SessionId;
}
public bool ValidateLoginSession(int userId, Guid sessionId)
{
// with RepeatableRead transaction mode, read-then-update operations can
// cause deadlocks, and the ForUpdate() hint is required to tell the database
// to acquire an exclusive lock when reading
// that query is going to run a *lot*, make it a template
var t = SqlContext.Templates.Get("Umbraco.Core.UserRepository.ValidateLoginSession", s => s
.Select<UserLoginDto>()
.SelectTop(1)
.From<UserLoginDto>()
.Where<UserLoginDto>(x => x.SessionId == SqlTemplate.Arg<Guid>("sessionId"))
.ForUpdate());
var sql = t.Sql(sessionId);
var found = Database.FirstOrDefault<UserLoginDto>(sql);
2018-03-22 11:24:12 +01:00
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
return false;
//now detect if there's been a timeout
Merge remote-tracking branch 'origin/v8/dev' into netcore/dev # Conflicts: # build/NuSpecs/UmbracoCms.Core.nuspec # build/NuSpecs/UmbracoCms.Web.nuspec # src/SolutionInfo.cs # src/Umbraco.Core/Cache/CacheKeys.cs # src/Umbraco.Core/Composing/TypeFinder.cs # src/Umbraco.Core/Configuration/GlobalSettings.cs # src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs # src/Umbraco.Core/Configuration/IGlobalSettings.cs # src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs # src/Umbraco.Core/Configuration/UmbracoSettings/ContentSectionExtensions.cs # src/Umbraco.Core/Constants-AppSettings.cs # src/Umbraco.Core/Editors/UserEditorAuthorizationHelper.cs # src/Umbraco.Core/Extensions/StringExtensions.cs # src/Umbraco.Core/Extensions/UriExtensions.cs # src/Umbraco.Core/IO/IOHelper.cs # src/Umbraco.Core/IO/PhysicalFileSystem.cs # src/Umbraco.Core/Media/Exif/MathEx.cs # src/Umbraco.Core/Media/UploadAutoFillProperties.cs # src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs # src/Umbraco.Core/Models/Membership/User.cs # src/Umbraco.Core/Models/UserExtensions.cs # src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs # src/Umbraco.Core/PropertyEditors/ListViewConfiguration.cs # src/Umbraco.Core/PropertyEditors/ValueConverters/MediaPickerValueConverter.cs # src/Umbraco.Core/PropertyEditors/ValueConverters/MultiNodeTreePickerValueConverter.cs # src/Umbraco.Core/Routing/AliasUrlProvider.cs # src/Umbraco.Core/Routing/DefaultUrlProvider.cs # src/Umbraco.Core/Routing/UriUtility.cs # src/Umbraco.Core/Routing/UrlProviderExtensions.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Core/RuntimeOptions.cs # src/Umbraco.Core/RuntimeState.cs # src/Umbraco.Core/Security/BackOfficeUserStore.cs # src/Umbraco.Core/Security/ContentPermissions.cs # src/Umbraco.Core/Sync/ApplicationUrlHelper.cs # src/Umbraco.Core/Trees/TreeNode.cs # src/Umbraco.Core/Udi.cs # src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs # src/Umbraco.Examine/Umbraco.Examine.csproj # src/Umbraco.Infrastructure/Examine/ContentValueSetValidator.cs # src/Umbraco.Infrastructure/Migrations/Install/DatabaseBuilder.cs # src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs # src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs # src/Umbraco.Infrastructure/Scoping/Scope.cs # src/Umbraco.Infrastructure/Search/ExamineComponent.cs # src/Umbraco.Infrastructure/Security/IdentityMapDefinition.cs # src/Umbraco.Infrastructure/Services/Implement/ContentService.cs # src/Umbraco.Infrastructure/Services/Implement/MediaService.cs # src/Umbraco.Infrastructure/Services/Implement/NotificationService.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/LocksTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Core/Models/UserExtensionsTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Editors/UserEditorAuthorizationHelperTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Examine/UmbracoContentValueSetValidatorTests.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs # src/Umbraco.Tests/Configurations/UmbracoSettings/umbracoSettings.config # src/Umbraco.Tests/TestHelpers/SettingsForTests.cs # src/Umbraco.Tests/Testing/TestDatabase.cs # src/Umbraco.Tests/Web/Controllers/ContentControllerUnitTests.cs # src/Umbraco.Tests/Web/Controllers/FilterAllowedOutgoingContentAttributeTests.cs # src/Umbraco.Tests/Web/Controllers/MediaControllerUnitTests.cs # src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs # src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentController.cs # src/Umbraco.Web.BackOffice/Controllers/EntityController.cs # src/Umbraco.Web.BackOffice/Controllers/MacrosController.cs # src/Umbraco.Web.BackOffice/Controllers/MediaController.cs # src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs # src/Umbraco.Web.BackOffice/Controllers/TourController.cs # src/Umbraco.Web.BackOffice/Controllers/UserGroupEditorAuthorizationHelper.cs # src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingContentAttribute.cs # src/Umbraco.Web.BackOffice/Filters/FilterAllowedOutgoingMediaAttribute.cs # src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs # src/Umbraco.Web.BackOffice/Services/IconService.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeController.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web.BackOffice/Trees/FileSystemTreeController.cs # src/Umbraco.Web.BackOffice/Trees/MediaTreeController.cs # src/Umbraco.Web.Common/Extensions/FormCollectionExtensions.cs # src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js # src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/editors/media.controller.js # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en.xml # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/en_us.xml # src/Umbraco.Web.UI/config/umbracoSettings.Release.config # src/Umbraco.Web/Cache/MemberCacheRefresher.cs # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/Binders/ContentModelBinderHelper.cs # src/Umbraco.Web/Editors/ContentTypeController.cs # src/Umbraco.Web/Editors/Filters/ContentSaveValidationAttribute.cs # src/Umbraco.Web/Editors/Filters/MediaItemSaveValidationAttribute.cs # src/Umbraco.Web/Editors/Filters/UserGroupAuthorizationAttribute.cs # src/Umbraco.Web/Editors/TinyMceController.cs # src/Umbraco.Web/Editors/UserGroupsController.cs # src/Umbraco.Web/Editors/UsersController.cs # src/Umbraco.Web/ImageCropperTemplateExtensions.cs # src/Umbraco.Web/Logging/WebProfiler.cs # src/Umbraco.Web/Logging/WebProfilerProvider.cs # src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs # src/Umbraco.Web/Mvc/EnsurePublishedContentRequestAttribute.cs # src/Umbraco.Web/Mvc/JsonNetResult.cs # src/Umbraco.Web/Mvc/MemberAuthorizeAttribute.cs # src/Umbraco.Web/Mvc/RenderRouteHandler.cs # src/Umbraco.Web/PropertyEditors/MediaPickerPropertyEditor.cs # src/Umbraco.Web/PropertyEditors/MultiNodeTreePickerPropertyEditor.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/RoutableDocumentFilter.cs # src/Umbraco.Web/Routing/ContentFinderByUrlAlias.cs # src/Umbraco.Web/Routing/NotFoundHandlerHelper.cs # src/Umbraco.Web/Routing/PublishedRouter.cs # src/Umbraco.Web/Runtime/WebInitialComposer.cs # src/Umbraco.Web/Scheduling/KeepAlive.cs # src/Umbraco.Web/Security/AppBuilderExtensions.cs # src/Umbraco.Web/Security/BackOfficeClaimsIdentityFactory.cs # src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs # src/Umbraco.Web/Trees/DictionaryTreeController.cs # src/Umbraco.Web/Trees/LanguageTreeController.cs # src/Umbraco.Web/Trees/LogViewerTreeController.cs # src/Umbraco.Web/Trees/PackagesTreeController.cs # src/Umbraco.Web/UmbracoApplication.cs # src/Umbraco.Web/UmbracoApplicationBase.cs # src/Umbraco.Web/UmbracoInjectedModule.cs # src/Umbraco.Web/WebApi/Filters/AdminUsersAuthorizeAttribute.cs # src/Umbraco.Web/WebApi/Filters/CheckIfUserTicketDataIsStaleAttribute.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForContentAttribute.cs # src/Umbraco.Web/WebApi/Filters/EnsureUserPermissionForMediaAttribute.cs # src/Umbraco.Web/WebApi/MemberAuthorizeAttribute.cs
2021-03-05 15:36:27 +01:00
if (DateTime.UtcNow - found.LastValidatedUtc > _globalSettings.TimeOut)
2018-03-22 11:24:12 +01:00
{
//timeout detected, update the record
ClearLoginSession(sessionId);
return false;
}
//update the validate date
found.LastValidatedUtc = DateTime.UtcNow;
Database.Update(found);
return true;
}
public int ClearLoginSessions(int userId)
{
return Database.Delete<UserLoginDto>(Sql().Where<UserLoginDto>(x => x.UserId == userId));
2018-03-22 11:24:12 +01:00
}
public int ClearLoginSessions(TimeSpan timespan)
{
var fromDate = DateTime.UtcNow - timespan;
return Database.Delete<UserLoginDto>(Sql().Where<UserLoginDto>(x => x.LastValidatedUtc < fromDate));
2018-03-22 11:24:12 +01:00
}
public void ClearLoginSession(Guid sessionId)
{
// TODO: why is that one updating and not deleting?
Database.Execute(Sql()
.Update<UserLoginDto>(u => u.Set(x => x.LoggedOutUtc, DateTime.UtcNow))
.Where<UserLoginDto>(x => x.SessionId == sessionId));
2018-03-22 11:24:12 +01:00
}
2017-12-07 16:45:25 +01:00
protected override IEnumerable<IUser> PerformGetAll(params int[] ids)
{
var dtos = ids.Length == 0
? GetDtosWith(null, true)
: GetDtosWith(sql => sql.WhereIn<UserDto>(x => x.Id, ids), true);
var users = new IUser[dtos.Count];
var i = 0;
foreach (var dto in dtos)
users[i++] = UserFactory.BuildEntity(_globalSettings, dto);
2017-12-07 16:45:25 +01:00
return users;
}
protected override IEnumerable<IUser> PerformGetByQuery(IQuery<IUser> query)
{
var dtos = GetDtosWith(sql => new SqlTranslator<IUser>(sql, query).Translate(), true)
.DistinctBy(x => x.Id)
.ToList();
var users = new IUser[dtos.Count];
var i = 0;
foreach (var dto in dtos)
users[i++] = UserFactory.BuildEntity(_globalSettings, dto);
2017-12-07 16:45:25 +01:00
return users;
}
private IUser GetWith(Action<Sql<ISqlContext>> with, bool includeReferences)
{
var dto = GetDtoWith(with, includeReferences);
return dto == null ? null : UserFactory.BuildEntity(_globalSettings, dto);
2017-12-07 16:45:25 +01:00
}
private UserDto GetDtoWith(Action<Sql<ISqlContext>> with, bool includeReferences)
{
var dtos = GetDtosWith(with, includeReferences);
return dtos.FirstOrDefault();
}
private List<UserDto> GetDtosWith(Action<Sql<ISqlContext>> with, bool includeReferences)
{
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>();
with?.Invoke(sql);
var dtos = Database.Fetch<UserDto>(sql);
if (includeReferences)
PerformGetReferencedDtos(dtos);
return dtos;
}
// NPoco cannot fetch 2+ references at a time
// plus it creates a combinatorial explosion
// better use extra queries
2019-01-17 12:07:31 +01:00
// unfortunately, SqlCe doesn't support multiple result sets
2017-12-07 16:45:25 +01:00
private void PerformGetReferencedDtos(List<UserDto> dtos)
{
if (dtos.Count == 0) return;
var userIds = dtos.Count == 1 ? new List<int> { dtos[0].Id } : dtos.Select(x => x.Id).ToList();
var xUsers = dtos.Count == 1 ? null : dtos.ToDictionary(x => x.Id, x => x);
// get users2groups
var sql = SqlContext.Sql()
.Select<User2UserGroupDto>()
.From<User2UserGroupDto>()
2019-01-21 15:39:19 +01:00
.WhereIn<User2UserGroupDto>(x => x.UserId, userIds);
2017-12-07 16:45:25 +01:00
var users2groups = Database.Fetch<User2UserGroupDto>(sql);
var groupIds = users2groups.Select(x => x.UserGroupId).ToList();
// get groups
sql = SqlContext.Sql()
.Select<UserGroupDto>()
.From<UserGroupDto>()
.WhereIn<UserGroupDto>(x => x.Id, groupIds);
var groups = Database.Fetch<UserGroupDto>(sql)
.ToDictionary(x => x.Id, x => x);
// get groups2apps
sql = SqlContext.Sql()
.Select<UserGroup2AppDto>()
.From<UserGroup2AppDto>()
.WhereIn<UserGroup2AppDto>(x => x.UserGroupId, groupIds);
var groups2apps = Database.Fetch<UserGroup2AppDto>(sql)
.GroupBy(x => x.UserGroupId)
.ToDictionary(x => x.Key, x => x);
// get start nodes
sql = SqlContext.Sql()
.Select<UserStartNodeDto>()
.From<UserStartNodeDto>()
.WhereIn<UserStartNodeDto>(x => x.UserId, userIds);
var startNodes = Database.Fetch<UserStartNodeDto>(sql);
// map groups
foreach (var user2group in users2groups)
{
if (groups.TryGetValue(user2group.UserGroupId, out var group))
{
var dto = xUsers == null ? dtos[0] : xUsers[user2group.UserId];
dto.UserGroupDtos.Add(group); // user2group is distinct
}
}
// map start nodes
foreach (var startNode in startNodes)
{
var dto = xUsers == null ? dtos[0] : xUsers[startNode.UserId];
dto.UserStartNodeDtos.Add(startNode); // hashset = distinct
}
// map apps
foreach (var group in groups.Values)
{
if (groups2apps.TryGetValue(group.Id, out var list))
group.UserGroup2AppDtos = list.ToList(); // groups2apps is distinct
}
}
#endregion
2020-12-22 10:30:16 +11:00
#region Overrides of EntityRepositoryBase<int,IUser>
2017-12-07 16:45:25 +01:00
protected override Sql<ISqlContext> GetBaseQuery(bool isCount)
{
if (isCount)
return SqlContext.Sql()
.SelectCount()
.From<UserDto>();
return SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>();
}
private static void AddGroupLeftJoin(Sql<ISqlContext> sql)
{
sql
.LeftJoin<User2UserGroupDto>()
.On<User2UserGroupDto, UserDto>(left => left.UserId, right => right.Id)
.LeftJoin<UserGroupDto>()
.On<UserGroupDto, User2UserGroupDto>(left => left.Id, right => right.UserGroupId)
.LeftJoin<UserGroup2AppDto>()
.On<UserGroup2AppDto, UserGroupDto>(left => left.UserGroupId, right => right.Id)
.LeftJoin<UserStartNodeDto>()
.On<UserStartNodeDto, UserDto>(left => left.UserId, right => right.Id);
}
private Sql<ISqlContext> GetBaseQuery(string columns)
{
return SqlContext.Sql()
.Select(columns)
.From<UserDto>();
}
protected override string GetBaseWhereClause()
{
return "umbracoUser.id = @id";
}
protected override IEnumerable<string> GetDeleteClauses()
{
var list = new List<string>
{
2021-07-22 16:34:30 -06:00
$"DELETE FROM {Constants.DatabaseSchema.Tables.UserLogin} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User2UserGroup} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User2NodeNotify} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.UserStartNode} WHERE userId = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.User} WHERE id = @id",
$"DELETE FROM {Constants.DatabaseSchema.Tables.ExternalLogin} WHERE id = @id"
2017-12-07 16:45:25 +01:00
};
return list;
}
protected override void PersistNewItem(IUser entity)
{
2019-05-07 19:44:41 +02:00
// the use may have no identity, ie ID is zero, and be v7 super
// user - then it has been marked - and we must not persist it
// as new, as we do not want to create a new user - instead, persist
// it as updated
// see also: UserFactory.BuildEntity
if (entity.FromUserCache<string>("IS_V7_ZERO") != null)
2019-05-07 19:44:41 +02:00
{
PersistUpdatedItem(entity);
return;
}
entity.AddingEntity();
2017-12-07 16:45:25 +01:00
// ensure security stamp if missing
if (entity.SecurityStamp.IsNullOrWhiteSpace())
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
{
2017-12-07 16:45:25 +01:00
entity.SecurityStamp = Guid.NewGuid().ToString();
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
}
2017-12-07 16:45:25 +01:00
Implement password config storage for members (#10170) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Reducing and removing published member cache * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry * cannot inject IPublishedMemberCache and cannot have IPublishedMember * splits out files, fixes build * fix tests * removes membership provider classes * removes membership provider classes * updates the identity map definition * reverts commented out lines * reverts commented out lines * Implements members Password config in db, fixes members cookie auth to not interfere with the back office cookie auth, fixes Startup sequence, fixes startup pipeline * commits change to Startup * Rename migration from `MemberTableColumns2` to `AddPasswordConfigToMemberTable` * Fix test * Fix tests, but adding default passwordConfig to members Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-22 23:59:13 +10:00
UserDto userDto = UserFactory.BuildDto(entity);
2017-12-07 16:45:25 +01:00
// check if we have a user config else use the default
userDto.PasswordConfig = entity.PasswordConfiguration ?? DefaultPasswordConfigJson;
2017-12-07 16:45:25 +01:00
var id = Convert.ToInt32(Database.Insert(userDto));
entity.Id = id;
if (entity.IsPropertyDirty("StartContentIds"))
{
AddingOrUpdateStartNodes(entity, Enumerable.Empty<UserStartNodeDto>(), UserStartNodeDto.StartNodeTypeValue.Content, entity.StartContentIds);
}
if (entity.IsPropertyDirty("StartMediaIds"))
{
AddingOrUpdateStartNodes(entity, Enumerable.Empty<UserStartNodeDto>(), UserStartNodeDto.StartNodeTypeValue.Media, entity.StartMediaIds);
}
if (entity.IsPropertyDirty("Groups"))
{
// lookup all assigned
var assigned = entity.Groups == null || entity.Groups.Any() == false
? new List<UserGroupDto>()
: Database.Fetch<UserGroupDto>("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups.Select(x => x.Alias) });
foreach (var groupDto in assigned)
{
var dto = new User2UserGroupDto
{
UserGroupId = groupDto.Id,
UserId = entity.Id
};
Database.Insert(dto);
}
}
entity.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IUser entity)
{
// updates Modified date
entity.UpdatingEntity();
2017-12-07 16:45:25 +01:00
// ensure security stamp if missing
if (entity.SecurityStamp.IsNullOrWhiteSpace())
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
{
2017-12-07 16:45:25 +01:00
entity.SecurityStamp = Guid.NewGuid().ToString();
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
}
2017-12-07 16:45:25 +01:00
var userDto = UserFactory.BuildDto(entity);
// build list of columns to check for saving - we don't want to save the password if it hasn't changed!
// list the columns to save, NOTE: would be nice to not have hard coded strings here but no real good way around that
var colsToSave = new Dictionary<string, string>
{
//TODO: Change these to constants + nameof
2017-12-07 16:45:25 +01:00
{"userDisabled", "IsApproved"},
{"userNoConsole", "IsLockedOut"},
{"startStructureID", "StartContentId"},
{"startMediaID", "StartMediaId"},
{"userName", "Name"},
{"userLogin", "Username"},
{"userEmail", "Email"},
{"userLanguage", "Language"},
{"securityStampToken", "SecurityStamp"},
{"lastLockoutDate", "LastLockoutDate"},
{"lastPasswordChangeDate", "LastPasswordChangeDate"},
{"lastLoginDate", "LastLoginDate"},
{"failedLoginAttempts", "FailedPasswordAttempts"},
{"createDate", "CreateDate"},
{"updateDate", "UpdateDate"},
{"avatar", "Avatar"},
{"emailConfirmedDate", "EmailConfirmedDate"},
2018-03-22 11:24:12 +01:00
{"invitedDate", "InvitedDate"},
{"tourData", "TourData"}
2017-12-07 16:45:25 +01:00
};
// create list of properties that have changed
var changedCols = colsToSave
.Where(col => entity.IsPropertyDirty(col.Value))
.Select(col => col.Key)
.ToList();
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
if (entity.IsPropertyDirty("SecurityStamp"))
{
changedCols.Add("securityStampToken");
}
2017-12-07 16:45:25 +01:00
// DO NOT update the password if it has not changed or if it is null or empty
if (entity.IsPropertyDirty("RawPasswordValue") && entity.RawPasswordValue.IsNullOrWhiteSpace() == false)
{
changedCols.Add("userPassword");
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
// If the security stamp hasn't already updated we need to force it
2017-12-07 16:45:25 +01:00
if (entity.IsPropertyDirty("SecurityStamp") == false)
{
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
changedCols.Add("securityStampToken");
}
// check if we have a user config else use the default
userDto.PasswordConfig = entity.PasswordConfiguration ?? DefaultPasswordConfigJson;
changedCols.Add("passwordConfig");
2017-12-07 16:45:25 +01:00
}
// If userlogin or the email has changed then need to reset security stamp
if (changedCols.Contains("userLogin") || changedCols.Contains("userEmail"))
{
userDto.EmailConfirmedDate = null;
changedCols.Add("emailConfirmedDate");
Security stamp implementation for members (#10140) * Getting new netcore PublicAccessChecker in place * Adds full test coverage for PublicAccessChecker * remove PublicAccessComposer * adjust namespaces, ensure RoleManager works, separate public access controller, reduce content controller * Implements the required methods on IMemberManager, removes old migrated code * Updates routing to be able to re-route, Fixes middleware ordering ensuring endpoints are last, refactors pipeline options, adds public access middleware, ensures public access follows all hops * adds note * adds note * Cleans up ext methods, ensures that members identity is added on both front-end and back ends. updates how UmbracoApplicationBuilder works in that it explicitly starts endpoints at the time of calling. * Changes name to IUmbracoEndpointBuilder * adds note * Fixing tests, fixing error describers so there's 2x one for back office, one for members, fixes TryConvertTo, fixes login redirect * fixing build * Updates user manager to correctly validate password hashing and injects the IBackOfficeUserPasswordChecker * Merges PR * Fixes up build and notes * Implements security stamp and email confirmed for members, cleans up a bunch of repo/service level member groups stuff, shares user store code between members and users and fixes the user identity object so we arent' tracking both groups and roles. * Security stamp for members is now working * Fixes keepalive, fixes PublicAccessMiddleware to not throw, updates startup code to be more clear and removes magic that registers middleware. * adds note * removes unused filter, fixes build * fixes WebPath and tests * Looks up entities in one query * remove usings * Fix test, remove stylesheet * Set status code before we write to response to avoid error * Ensures that users and members are validated when logging in. Shares more code between users and members. * merge changes * oops * Fixes RepositoryCacheKeys to ensure the keys are normalized * oops didn't mean to commit this * Fix casing issues with caching, stop boxing value types for all cache operations, stop re-creating string keys in DefaultRepositoryCachePolicy * oops didn't mean to comit this * bah, far out this keeps getting recommitted. sorry Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2021-04-20 17:13:40 +10:00
// If the security stamp hasn't already updated we need to force it
if (entity.IsPropertyDirty("SecurityStamp") == false)
{
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
changedCols.Add("securityStampToken");
}
}
2017-12-07 16:45:25 +01:00
//only update the changed cols
if (changedCols.Count > 0)
{
Database.Update(userDto, changedCols);
}
if (entity.IsPropertyDirty("StartContentIds") || entity.IsPropertyDirty("StartMediaIds"))
{
var assignedStartNodes = Database.Fetch<UserStartNodeDto>("SELECT * FROM umbracoUserStartNode WHERE userId = @userId", new { userId = entity.Id });
if (entity.IsPropertyDirty("StartContentIds"))
{
AddingOrUpdateStartNodes(entity, assignedStartNodes, UserStartNodeDto.StartNodeTypeValue.Content, entity.StartContentIds);
}
if (entity.IsPropertyDirty("StartMediaIds"))
{
AddingOrUpdateStartNodes(entity, assignedStartNodes, UserStartNodeDto.StartNodeTypeValue.Media, entity.StartMediaIds);
}
}
if (entity.IsPropertyDirty("Groups"))
{
//lookup all assigned
var assigned = entity.Groups == null || entity.Groups.Any() == false
? new List<UserGroupDto>()
: Database.Fetch<UserGroupDto>("SELECT * FROM umbracoUserGroup WHERE userGroupAlias IN (@aliases)", new { aliases = entity.Groups.Select(x => x.Alias) });
//first delete all
// TODO: We could do this a nicer way instead of "Nuke and Pave"
2017-12-07 16:45:25 +01:00
Database.Delete<User2UserGroupDto>("WHERE UserId = @UserId", new { UserId = entity.Id });
foreach (var groupDto in assigned)
{
var dto = new User2UserGroupDto
{
UserGroupId = groupDto.Id,
UserId = entity.Id
};
Database.Insert(dto);
}
}
entity.ResetDirtyProperties();
}
private void AddingOrUpdateStartNodes(IEntity entity, IEnumerable<UserStartNodeDto> current, UserStartNodeDto.StartNodeTypeValue startNodeType, int[] entityStartIds)
{
var assignedIds = current.Where(x => x.StartNodeType == (int)startNodeType).Select(x => x.StartNode).ToArray();
//remove the ones not assigned to the entity
var toDelete = assignedIds.Except(entityStartIds).ToArray();
if (toDelete.Length > 0)
Database.Delete<UserStartNodeDto>("WHERE UserId = @UserId AND startNode IN (@startNodes)", new { UserId = entity.Id, startNodes = toDelete });
//add the ones not currently in the db
var toAdd = entityStartIds.Except(assignedIds).ToArray();
foreach (var i in toAdd)
{
var dto = new UserStartNodeDto
{
StartNode = i,
StartNodeType = (int)startNodeType,
UserId = entity.Id
};
Database.Insert(dto);
}
}
#endregion
#region Implementation of IUserRepository
public int GetCountByQuery(IQuery<IUser> query)
{
var sqlClause = GetBaseQuery("umbracoUser.id");
var translator = new SqlTranslator<IUser>(sqlClause, query);
var subquery = translator.Translate();
//get the COUNT base query
var sql = GetBaseQuery(true)
.Append(new Sql("WHERE umbracoUser.id IN (" + subquery.SQL + ")", subquery.Arguments));
return Database.ExecuteScalar<int>(sql);
}
public bool Exists(string username)
{
return ExistsByUserName(username);
}
public bool ExistsByUserName(string username)
2017-12-07 16:45:25 +01:00
{
var sql = SqlContext.Sql()
.SelectCount()
.From<UserDto>()
.Where<UserDto>(x => x.UserName == username);
return Database.ExecuteScalar<int>(sql) > 0;
}
public bool ExistsByLogin(string login)
{
var sql = SqlContext.Sql()
.SelectCount()
.From<UserDto>()
.Where<UserDto>(x => x.Login == login);
return Database.ExecuteScalar<int>(sql) > 0;
}
2017-12-07 16:45:25 +01:00
/// <summary>
/// Gets a list of <see cref="IUser"/> objects associated with a given group
/// </summary>
/// <param name="groupId">Id of group</param>
public IEnumerable<IUser> GetAllInGroup(int groupId)
{
return GetAllInOrNotInGroup(groupId, true);
}
/// <summary>
/// Gets a list of <see cref="IUser"/> objects not associated with a given group
/// </summary>
/// <param name="groupId">Id of group</param>
public IEnumerable<IUser> GetAllNotInGroup(int groupId)
{
return GetAllInOrNotInGroup(groupId, false);
}
private IEnumerable<IUser> GetAllInOrNotInGroup(int groupId, bool include)
{
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>();
var inSql = SqlContext.Sql()
.Select<User2UserGroupDto>(x => x.UserId)
.From<User2UserGroupDto>()
.Where<User2UserGroupDto>(x => x.UserGroupId == groupId);
if (include)
sql.WhereIn<UserDto>(x => x.Id, inSql);
else
sql.WhereNotIn<UserDto>(x => x.Id, inSql);
var dtos = Database.Fetch<UserDto>(sql);
//adds missing bits like content and media start nodes
PerformGetReferencedDtos(dtos);
return ConvertFromDtos(dtos);
2017-12-07 16:45:25 +01:00
}
/// <summary>
/// Gets paged user results
/// </summary>
/// <param name="query"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="includeUserGroups">
/// A filter to only include user that belong to these user groups
/// </param>
/// <param name="excludeUserGroups">
/// A filter to only include users that do not belong to these user groups
/// </param>
2019-01-22 18:03:39 -05:00
/// <param name="userState">Optional parameter to filter by specified user state</param>
2017-12-07 16:45:25 +01:00
/// <param name="filter"></param>
/// <returns></returns>
/// <remarks>
/// The query supplied will ONLY work with data specifically on the umbracoUser table because we are using NPoco paging (SQL paging)
/// </remarks>
public IEnumerable<IUser> GetPagedResultsByQuery(IQuery<IUser> query, long pageIndex, int pageSize, out long totalRecords,
Expression<Func<IUser, object>> orderBy, Direction orderDirection = Direction.Ascending,
string[] includeUserGroups = null, string[] excludeUserGroups = null, UserState[] userState = null, IQuery<IUser> filter = null)
{
if (orderBy == null) throw new ArgumentNullException(nameof(orderBy));
Sql<ISqlContext> filterSql = null;
var customFilterWheres = filter?.GetWhereClauses().ToArray();
2017-12-07 16:45:25 +01:00
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
if (hasCustomFilter
|| includeUserGroups != null && includeUserGroups.Length > 0
|| excludeUserGroups != null && excludeUserGroups.Length > 0
|| userState != null && userState.Length > 0 && userState.Contains(UserState.All) == false)
filterSql = SqlContext.Sql();
if (hasCustomFilter)
{
foreach (var clause in customFilterWheres)
filterSql.Append($"AND ({clause.Item1})", clause.Item2);
}
if (includeUserGroups != null && includeUserGroups.Length > 0)
{
const string subQuery = @"AND (umbracoUser.id IN (SELECT DISTINCT umbracoUser.id
FROM umbracoUser
INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id
INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId
WHERE umbracoUserGroup.userGroupAlias IN (@userGroups)))";
filterSql.Append(subQuery, new { userGroups = includeUserGroups });
}
if (excludeUserGroups != null && excludeUserGroups.Length > 0)
{
2018-10-02 15:19:01 +02:00
const string subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
2017-12-07 16:45:25 +01:00
FROM umbracoUser
INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id
INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId
WHERE umbracoUserGroup.userGroupAlias IN (@userGroups)))";
filterSql.Append(subQuery, new { userGroups = excludeUserGroups });
}
if (userState != null && userState.Length > 0)
{
//the "ALL" state doesn't require any filtering so we ignore that, if it exists in the list we don't do any filtering
if (userState.Contains(UserState.All) == false)
{
var sb = new StringBuilder("(");
var appended = false;
if (userState.Contains(UserState.Active))
{
sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL)");
appended = true;
}
2018-09-06 14:10:10 +02:00
if (userState.Contains(UserState.Inactive))
{
if (appended) sb.Append(" OR ");
sb.Append("(userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL)");
appended = true;
}
2017-12-07 16:45:25 +01:00
if (userState.Contains(UserState.Disabled))
{
if (appended) sb.Append(" OR ");
sb.Append("(userDisabled = 1)");
appended = true;
}
if (userState.Contains(UserState.LockedOut))
{
if (appended) sb.Append(" OR ");
sb.Append("(userNoConsole = 1)");
appended = true;
}
if (userState.Contains(UserState.Invited))
{
if (appended) sb.Append(" OR ");
sb.Append("(lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL)");
appended = true;
}
sb.Append(")");
filterSql.Append("AND " + sb);
}
}
// create base query
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>();
// apply query
if (query != null)
sql = new SqlTranslator<IUser>(sql, query).Translate();
// get sorted and filtered sql
2019-02-20 15:00:23 +01:00
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderBy, orderDirection);
2017-12-07 16:45:25 +01:00
// get a page of results and total count
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
// map references
PerformGetReferencedDtos(pagedResult.Items);
return pagedResult.Items.Select(x => UserFactory.BuildEntity(_globalSettings, x));
2017-12-07 16:45:25 +01:00
}
2018-10-02 15:19:01 +02:00
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql, bool hasWhereClause)
2017-12-07 16:45:25 +01:00
{
if (filterSql == null) return sql;
2018-10-02 15:19:01 +02:00
//ensure we don't append a WHERE if there is already one
var args = filterSql.Arguments;
var sqlFilter = hasWhereClause
? filterSql.SQL
: " WHERE " + filterSql.SQL.TrimStart("AND ");
sql.Append(SqlContext.Sql(sqlFilter, args));
2017-12-07 16:45:25 +01:00
return sql;
}
2019-02-20 15:00:23 +01:00
private Sql<ISqlContext> ApplySort(Sql<ISqlContext> sql, Expression<Func<IUser, object>> orderBy, Direction orderDirection)
2017-12-07 16:45:25 +01:00
{
2019-02-20 15:00:23 +01:00
if (orderBy == null) return sql;
var expressionMember = ExpressionHelper.GetMemberInfo(orderBy);
var mapper = _mapperCollection[typeof(IUser)];
2019-03-29 08:30:51 +01:00
var mappedField = mapper.Map(expressionMember.Name);
2019-02-20 15:00:23 +01:00
if (mappedField.IsNullOrWhiteSpace())
throw new ArgumentException("Could not find a mapping for the column specified in the orderBy clause");
// beware! NPoco paging code parses the query to isolate the ORDER BY fragment,
// using a regex that wants "([\w\.\[\]\(\)\s""`,]+)" - meaning that anything
// else in orderBy is going to break NPoco / not be detected
// beware! NPoco paging code (in PagingHelper) collapses everything [foo].[bar]
// to [bar] only, so we MUST use aliases, cannot use [table].[field]
// beware! pre-2012 SqlServer is using a convoluted syntax for paging, which
// includes "SELECT ROW_NUMBER() OVER (ORDER BY ...) poco_rn FROM SELECT (...",
// so anything added here MUST also be part of the inner SELECT statement, ie
// the original statement, AND must be using the proper alias, as the inner SELECT
// will hide the original table.field names entirely
var orderByField = sql.GetAliasedField(mappedField);
2017-12-07 16:45:25 +01:00
if (orderDirection == Direction.Ascending)
2019-02-20 15:00:23 +01:00
sql.OrderBy(orderByField);
2017-12-07 16:45:25 +01:00
else
2019-02-20 15:00:23 +01:00
sql.OrderByDescending(orderByField);
2017-12-07 16:45:25 +01:00
return sql;
}
internal IEnumerable<IUser> GetNextUsers(int id, int count)
{
var idsQuery = SqlContext.Sql()
.Select<UserDto>(x => x.Id)
.From<UserDto>()
.Where<UserDto>(x => x.Id >= id)
.OrderBy<UserDto>(x => x.Id);
// first page is index 1, not zero
var ids = Database.Page<int>(1, count, idsQuery).Items.ToArray();
// now get the actual users and ensure they are ordered properly (same clause)
return ids.Length == 0 ? Enumerable.Empty<IUser>() : GetMany(ids).OrderBy(x => x.Id);
}
#endregion
private IEnumerable<IUser> ConvertFromDtos(IEnumerable<UserDto> dtos)
{
return dtos.Select(x => UserFactory.BuildEntity(_globalSettings, x));
2017-12-07 16:45:25 +01:00
}
}
}