2021-03-11 19:35:43 +11:00
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 ;
2020-08-20 22:18:50 +01:00
using Microsoft.Extensions.Options ;
2017-12-07 16:45:25 +01:00
using NPoco ;
2021-02-09 10:22:42 +01:00
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 ;
2021-02-15 11:41:12 +01:00
using Umbraco.Cms.Core.Scoping ;
2021-02-09 10:22:42 +01:00
using Umbraco.Cms.Core.Serialization ;
2021-09-07 12:10:58 +02:00
using Umbraco.Cms.Core.Services ;
2021-02-12 13:36:50 +01:00
using Umbraco.Cms.Infrastructure.Persistence.Dtos ;
using Umbraco.Cms.Infrastructure.Persistence.Factories ;
using Umbraco.Cms.Infrastructure.Persistence.Mappers ;
using Umbraco.Cms.Infrastructure.Persistence.Querying ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2017-12-07 16:45:25 +01:00
2021-02-12 13:36:50 +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 ;
2020-08-21 14:52:47 +01:00
private readonly GlobalSettings _globalSettings ;
2020-08-20 22:18:50 +01:00
private readonly UserPasswordConfigurationSettings _passwordConfiguration ;
2020-05-27 13:48:26 +10:00
private readonly IJsonSerializer _jsonSerializer ;
2021-09-02 14:11:13 +02:00
private readonly IRuntimeState _runtimeState ;
2017-12-14 17:04:44 +01:00
private string _passwordConfigJson ;
private bool _passwordConfigInitialized ;
2017-12-07 16:45:25 +01:00
/// <summary>
2021-09-07 12:10:58 +02:00
/// Initializes a new instance of the <see cref="UserRepository" /> class.
2017-12-07 16:45:25 +01:00
/// </summary>
2021-09-07 12:10:58 +02:00
/// <param name="scopeAccessor">The scope accessor.</param>
/// <param name="appCaches">The application caches.</param>
/// <param name="logger">The logger.</param>
/// <param name="mapperCollection">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">The global settings.</param>
/// <param name="passwordConfiguration">The password configuration.</param>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="runtimeState">State of the runtime.</param>
/// <exception cref="System.ArgumentNullException">mapperCollection
/// or
/// globalSettings
/// or
/// passwordConfiguration</exception>
2020-08-20 22:18:50 +01:00
public UserRepository (
IScopeAccessor scopeAccessor ,
AppCaches appCaches ,
2020-09-18 12:53:06 +02:00
ILogger < UserRepository > logger ,
2020-08-20 22:18:50 +01:00
IMapperCollection mapperCollection ,
2020-08-23 23:36:48 +02:00
IOptions < GlobalSettings > globalSettings ,
IOptions < UserPasswordConfigurationSettings > passwordConfiguration ,
2021-09-07 12:10:58 +02:00
IJsonSerializer jsonSerializer ,
IRuntimeState runtimeState )
2019-01-17 08:34:29 +01:00
: base ( scopeAccessor , appCaches , logger )
2017-12-07 16:45:25 +01:00
{
2019-11-25 23:10:54 +11:00
_mapperCollection = mapperCollection ? ? throw new ArgumentNullException ( nameof ( mapperCollection ) ) ;
2020-08-26 11:58:44 +02:00
_globalSettings = globalSettings . Value ? ? throw new ArgumentNullException ( nameof ( globalSettings ) ) ;
_passwordConfiguration = passwordConfiguration . Value ? ? throw new ArgumentNullException ( nameof ( passwordConfiguration ) ) ;
2020-05-27 13:48:26 +10:00
_jsonSerializer = jsonSerializer ;
2021-09-02 14:11:13 +02:00
_runtimeState = runtimeState ;
2019-12-09 14:12:06 +01:00
}
2017-12-14 17:04:44 +01:00
2019-11-25 21:20:00 +11:00
/// <summary>
/// Returns a serialized dictionary of the password configuration that is stored against the user in the database
/// </summary>
2020-05-27 13:48:26 +10:00
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
2020-05-27 13:48:26 +10:00
{
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.
2021-09-02 14:11:13 +02:00
if ( _runtimeState . Level = = RuntimeLevel . Upgrade )
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
{
2021-09-02 14:11:13 +02:00
// when upgrading people might come from version 7 where user 0 was the default,
// only in upgrade mode do we want to fetch the user of Id 0
if ( id < - 1 )
{
return null ;
}
}
else
{
if ( id = = default | | id < - 1 )
{
return null ;
}
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
}
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 ) ;
2019-12-09 14:12:06 +01:00
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 ( )
{
2021-08-31 05:10:50 +12:00
// These keys in this query map to the `Umbraco.Core.Models.Membership.UserState` enum
var sql = @ "SELECT -1 AS [Key], COUNT(id) AS [Value] FROM umbracoUser
2017-12-07 16:45:25 +01:00
UNION
2021-08-31 05:10:50 +12:00
SELECT 0 AS [ Key ] , COUNT ( id ) AS [ Value ] FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL
2017-12-07 16:45:25 +01:00
UNION
2021-08-31 05:10:50 +12:00
SELECT 1 AS [ Key ] , COUNT ( id ) AS [ Value ] FROM umbracoUser WHERE userDisabled = 1
2017-12-07 16:45:25 +01:00
UNION
2021-08-31 05:10:50 +12:00
SELECT 2 AS [ Key ] , COUNT ( id ) AS [ Value ] FROM umbracoUser WHERE userNoConsole = 1
2017-12-07 16:45:25 +01:00
UNION
2021-08-31 05:10:50 +12:00
SELECT 3 AS [ Key ] , COUNT ( id ) AS [ Value ] FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
2018-09-06 14:10:10 +02:00
UNION
2021-08-31 05:10:50 +12:00
SELECT 4 AS [ Key ] , COUNT ( id ) AS [ Value ] FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NULL ";
2017-12-07 16:45:25 +01:00
2021-08-31 05:10:50 +12:00
var result = Database . Dictionary < int , int > ( sql ) ;
2017-12-07 16:45:25 +01:00
2021-08-31 05:10:50 +12:00
return result . ToDictionary ( x = > ( UserState ) x . Key , x = > x . Value ) ;
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 )
2021-02-09 10:22:42 +01:00
{
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 )
{
2018-05-29 18:30:37 +02:00
// 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 > ( )
2020-08-19 13:33:49 +10:00
. SelectTop ( 1 )
2018-05-29 18:30:37 +02:00
. From < UserLoginDto > ( )
. Where < UserLoginDto > ( x = > x . SessionId = = SqlTemplate . Arg < Guid > ( "sessionId" ) )
. ForUpdate ( ) ) ;
var sql = t . Sql ( sessionId ) ;
2020-08-19 13:33:49 +10:00
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
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 )
{
2018-05-29 18:30:37 +02:00
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 ;
2018-05-29 18:30:37 +02:00
return Database . Delete < UserLoginDto > ( Sql ( ) . Where < UserLoginDto > ( x = > x . LastValidatedUtc < fromDate ) ) ;
2018-03-22 11:24:12 +01:00
}
public void ClearLoginSession ( Guid sessionId )
{
2019-01-26 09:42:14 -05:00
// TODO: why is that one updating and not deleting?
2018-05-29 18:30:37 +02:00
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 )
2019-12-09 14:12:06 +01:00
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 )
2019-12-09 14:12:06 +01:00
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 ) ;
2019-12-09 14:12:06 +01:00
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 ( )
{
2021-08-06 11:32:16 +02:00
return $"{Constants.DatabaseSchema.Tables.User}.id = @id" ;
2017-12-07 16:45:25 +01:00
}
protected override IEnumerable < string > GetDeleteClauses ( )
{
var list = new List < string >
{
2021-08-11 19:38:23 +02:00
$"DELETE FROM {Constants.DatabaseSchema.Tables.UserLogin} WHERE userId = @id" ,
2021-07-22 16:34:30 -06:00
$"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-06-28 09:19:11 +02:00
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
2020-05-27 13:48:26 +10: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
2019-06-28 09:19:11 +02:00
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 >
{
2019-11-26 12:49:57 +11:00
//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" ) ;
}
2020-05-27 13:48:26 +10:00
// 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
}
2020-02-12 10:41:31 +00: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" ) ;
2021-08-11 19:11:35 +02:00
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" ) ;
}
2020-02-12 10:41:31 +00:00
}
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
2019-01-27 01:17:32 -05:00
// 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 )
2020-02-15 09:20:59 +01:00
{
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 ;
}
2020-02-15 09:20:59 +01:00
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 ) ;
2020-08-26 08:05:15 +02:00
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 ;
2018-05-29 18:30:37 +02:00
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 ) ;
2019-12-09 14:12:06 +01:00
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 )
{
2019-12-09 14:12:06 +01:00
return dtos . Select ( x = > UserFactory . BuildEntity ( _globalSettings , x ) ) ;
2017-12-07 16:45:25 +01:00
}
}
}