2017-12-07 16:45:25 +01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using NPoco;
|
|
|
|
|
|
using Umbraco.Core.Cache;
|
2018-03-22 11:24:12 +01:00
|
|
|
|
using Umbraco.Core.Configuration;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
using Umbraco.Core.Logging;
|
2018-01-15 11:32:30 +01:00
|
|
|
|
using Umbraco.Core.Models.Entities;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
using Umbraco.Core.Models.Membership;
|
2017-12-28 09:06:33 +01:00
|
|
|
|
using Umbraco.Core.Persistence.Dtos;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
using Umbraco.Core.Persistence.Factories;
|
|
|
|
|
|
using Umbraco.Core.Persistence.Mappers;
|
|
|
|
|
|
using Umbraco.Core.Persistence.Querying;
|
2017-12-12 15:04:13 +01:00
|
|
|
|
using Umbraco.Core.Scoping;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Persistence.Repositories.Implement
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Represents the UserRepository for doing CRUD operations for <see cref="IUser"/>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal class UserRepository : NPocoRepositoryBase<int, IUser>, IUserRepository
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IMapperCollection _mapperCollection;
|
2018-04-06 13:51:54 +10:00
|
|
|
|
private readonly IGlobalSettings _globalSettings;
|
2019-11-25 21:20:00 +11:00
|
|
|
|
private readonly IUserPasswordConfiguration _passwordConfiguration;
|
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>
|
2018-04-06 13:51:54 +10:00
|
|
|
|
/// <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>
|
2018-04-06 13:51:54 +10:00
|
|
|
|
/// <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>
|
2018-04-06 13:51:54 +10:00
|
|
|
|
/// <param name="globalSettings"></param>
|
2019-11-25 21:20:00 +11:00
|
|
|
|
public UserRepository(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger logger, IMapperCollection mapperCollection, IGlobalSettings globalSettings, IUserPasswordConfiguration passwordConfiguration)
|
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));
|
|
|
|
|
|
_globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings));
|
|
|
|
|
|
_passwordConfiguration = passwordConfiguration ?? throw new ArgumentNullException(nameof(passwordConfiguration));
|
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>
|
2017-12-14 17:04:44 +01:00
|
|
|
|
private string PasswordConfigJson
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_passwordConfigInitialized)
|
|
|
|
|
|
return _passwordConfigJson;
|
|
|
|
|
|
|
2019-11-25 21:20:00 +11:00
|
|
|
|
var passwordConfig = new Dictionary<string, string> { { "hashAlgorithm", _passwordConfiguration.HashAlgorithmType } };
|
2017-12-14 17:04:44 +01:00
|
|
|
|
_passwordConfigJson = passwordConfig == null ? null : JsonConvert.SerializeObject(passwordConfig);
|
|
|
|
|
|
_passwordConfigInitialized = true;
|
|
|
|
|
|
return _passwordConfigJson;
|
|
|
|
|
|
}
|
2017-12-07 16:45:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Overrides of RepositoryBase<int,IUser>
|
|
|
|
|
|
|
|
|
|
|
|
protected override IUser PerformGet(int id)
|
|
|
|
|
|
{
|
|
|
|
|
|
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()
|
|
|
|
|
|
{
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: I know this doesn't follow the normal repository conventions which would require us to create a UserSessionRepository
|
2018-03-22 11:24:12 +01:00
|
|
|
|
//and also business logic models for these objects but that's just so overkill for what we are doing
|
|
|
|
|
|
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
|
|
|
|
|
|
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>()
|
|
|
|
|
|
.From<UserLoginDto>()
|
|
|
|
|
|
.Where<UserLoginDto>(x => x.SessionId == SqlTemplate.Arg<Guid>("sessionId"))
|
|
|
|
|
|
.ForUpdate());
|
|
|
|
|
|
|
|
|
|
|
|
var sql = t.Sql(sessionId);
|
|
|
|
|
|
|
|
|
|
|
|
var found = Database.Query<UserLoginDto>(sql).FirstOrDefault();
|
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
|
2018-04-06 13:51:54 +10:00
|
|
|
|
if (DateTime.UtcNow - found.LastValidatedUtc > TimeSpan.FromMinutes(_globalSettings.TimeOutInMinutes))
|
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
|
|
|
|
|
|
|
|
|
|
|
|
#region Overrides of NPocoRepositoryBase<int,IUser>
|
|
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
{
|
|
|
|
|
|
"DELETE FROM umbracoUser2UserGroup WHERE userId = @id",
|
|
|
|
|
|
"DELETE FROM umbracoUser2NodeNotify WHERE userId = @id",
|
2018-12-21 08:58:20 +01:00
|
|
|
|
"DELETE FROM umbracoUserStartNode WHERE userId = @id",
|
2017-12-07 16:45:25 +01:00
|
|
|
|
"DELETE FROM umbracoUser WHERE id = @id",
|
|
|
|
|
|
"DELETE FROM umbracoExternalLogin WHERE id = @id"
|
|
|
|
|
|
};
|
|
|
|
|
|
return list;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override Guid NodeObjectTypeId => throw new NotImplementedException();
|
|
|
|
|
|
|
|
|
|
|
|
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 (((User) entity).AdditionalData.ContainsKey("IS_V7_ZERO"))
|
|
|
|
|
|
{
|
|
|
|
|
|
PersistUpdatedItem(entity);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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())
|
|
|
|
|
|
entity.SecurityStamp = Guid.NewGuid().ToString();
|
|
|
|
|
|
|
|
|
|
|
|
var userDto = UserFactory.BuildDto(entity);
|
|
|
|
|
|
|
|
|
|
|
|
// check if we have a known config, we only want to store config for hashing
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: This logic will need to be updated when we do http://issues.umbraco.org/issue/U4-10089
|
2017-12-14 17:04:44 +01:00
|
|
|
|
if (PasswordConfigJson != null)
|
|
|
|
|
|
userDto.PasswordConfig = PasswordConfigJson;
|
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())
|
|
|
|
|
|
entity.SecurityStamp = Guid.NewGuid().ToString();
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
|
|
|
|
|
|
|
|
// special case - when using ASP.Net identity the user manager will take care of updating the security stamp, however
|
|
|
|
|
|
// when not using ASP.Net identity (i.e. old membership providers), we'll need to take care of updating this manually
|
|
|
|
|
|
// so we can just detect if that property is dirty, if it's not we'll set it manually
|
|
|
|
|
|
if (entity.IsPropertyDirty("SecurityStamp") == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
|
|
|
|
|
changedCols.Add("securityStampToken");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// check if we have a known config, we only want to store config for hashing
|
2019-01-27 01:17:32 -05:00
|
|
|
|
// TODO: This logic will need to be updated when we do http://issues.umbraco.org/issue/U4-10089
|
2017-12-14 17:04:44 +01:00
|
|
|
|
if (PasswordConfigJson != null)
|
2017-12-07 16:45:25 +01:00
|
|
|
|
{
|
2017-12-14 17:04:44 +01:00
|
|
|
|
userDto.PasswordConfig = PasswordConfigJson;
|
2017-12-07 16:45:25 +01:00
|
|
|
|
changedCols.Add("passwordConfig");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString();
|
|
|
|
|
|
|
|
|
|
|
|
changedCols.Add("emailConfirmedDate");
|
|
|
|
|
|
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
|
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)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sql = SqlContext.Sql()
|
|
|
|
|
|
.SelectCount()
|
|
|
|
|
|
.From<UserDto>()
|
|
|
|
|
|
.Where<UserDto>(x => x.UserName == username);
|
|
|
|
|
|
|
|
|
|
|
|
return Database.ExecuteScalar<int>(sql) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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);
|
|
|
|
|
|
|
|
|
|
|
|
return ConvertFromDtos(Database.Fetch<UserDto>(sql));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|