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>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
public class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
|
||||
private static readonly TEntity[] s_emptyEntities = new TEntity[0]; // const
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
|
||||
@@ -33,21 +33,29 @@ namespace Umbraco.Cms.Core.Cache
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected string GetEntityCacheKey(object id)
|
||||
protected string GetEntityCacheKey(int id) => EntityTypeCacheKey + id;
|
||||
|
||||
protected string GetEntityCacheKey(TId id)
|
||||
{
|
||||
if (id == null) throw new ArgumentNullException(nameof(id));
|
||||
return GetEntityTypeCacheKey() + id;
|
||||
if (EqualityComparer<TId>.Default.Equals(id, default))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (typeof(TId).IsValueType)
|
||||
{
|
||||
return EntityTypeCacheKey + id;
|
||||
}
|
||||
else
|
||||
{
|
||||
return EntityTypeCacheKey + id.ToString().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetEntityTypeCacheKey()
|
||||
{
|
||||
return $"uRepo_{typeof (TEntity).Name}_";
|
||||
}
|
||||
protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_";
|
||||
|
||||
protected virtual void InsertEntity(string cacheKey, TEntity entity)
|
||||
{
|
||||
Cache.Insert(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
=> Cache.Insert(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
|
||||
|
||||
protected virtual void InsertEntities(TId[] ids, TEntity[] entities)
|
||||
{
|
||||
@@ -56,7 +64,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
// getting all of them, and finding nothing.
|
||||
// if we can cache a zero count, cache an empty array,
|
||||
// for as long as the cache is not cleared (no expiration)
|
||||
Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
|
||||
Cache.Insert(EntityTypeCacheKey, () => s_emptyEntities);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -85,7 +93,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -95,7 +103,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -117,7 +125,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -127,7 +135,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
Cache.Clear(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -148,7 +156,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
var cacheKey = GetEntityCacheKey(entity.Id);
|
||||
Cache.Clear(cacheKey);
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.Clear(GetEntityTypeCacheKey());
|
||||
Cache.Clear(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,11 +168,16 @@ namespace Umbraco.Cms.Core.Cache
|
||||
|
||||
// if found in cache then return else fetch and cache
|
||||
if (fromCache != null)
|
||||
{
|
||||
return fromCache;
|
||||
}
|
||||
|
||||
var entity = performGet(id);
|
||||
|
||||
if (entity != null && entity.HasIdentity)
|
||||
{
|
||||
InsertEntity(cacheKey, entity);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
@@ -199,7 +212,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
else
|
||||
{
|
||||
// get everything we have
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(EntityTypeCacheKey)
|
||||
.ToArray(); // no need for null checks, we are not caching nulls
|
||||
|
||||
if (entities.Length > 0)
|
||||
@@ -222,7 +235,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
{
|
||||
// if none of them were in the cache
|
||||
// and we allow zero count - check for the special (empty) entry
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(EntityTypeCacheKey);
|
||||
if (empty != null) return empty;
|
||||
}
|
||||
}
|
||||
@@ -242,7 +255,7 @@ namespace Umbraco.Cms.Core.Cache
|
||||
/// <inheritdoc />
|
||||
public override void ClearAll()
|
||||
{
|
||||
Cache.ClearByKey(GetEntityTypeCacheKey());
|
||||
Cache.ClearByKey(EntityTypeCacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Handlers;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services.Notifications;
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Logging;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Runtime;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Extensions;
|
||||
@@ -85,21 +86,18 @@ namespace Umbraco.Cms.Infrastructure.HostedServices
|
||||
|
||||
using (_profilingLogger.DebugDuration<KeepAlive>("Keep alive executing", "Keep alive complete"))
|
||||
{
|
||||
var keepAlivePingUrl = _keepAliveSettings.KeepAlivePingUrl;
|
||||
var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString();
|
||||
if (umbracoAppUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.LogWarning("No umbracoApplicationUrl for service (yet), skip.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If the config is an absolute path, just use it
|
||||
string keepAlivePingUrl = WebPath.Combine(umbracoAppUrl, _hostingEnvironment.ToAbsolute(_keepAliveSettings.KeepAlivePingUrl));
|
||||
|
||||
try
|
||||
{
|
||||
if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}"))
|
||||
{
|
||||
var umbracoAppUrl = _hostingEnvironment.ApplicationMainUrl.ToString();
|
||||
if (umbracoAppUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.LogWarning("No umbracoApplicationUrl for service (yet), skip.");
|
||||
return;
|
||||
}
|
||||
|
||||
keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd(Constants.CharArrays.ForwardSlash));
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl);
|
||||
HttpClient httpClient = _httpClientFactory.CreateClient();
|
||||
_ = await httpClient.SendAsync(request);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Factories
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Cms.Infrastructure.Persistence.Mappers
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
Database.Update(dto);
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent>(entity.Id));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent, int>(entity.Id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -176,8 +176,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(IDictionaryItem entity)
|
||||
@@ -188,8 +188,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
|
||||
|
||||
entity.DeleteDate = DateTime.Now;
|
||||
}
|
||||
@@ -205,8 +205,8 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
|
||||
|
||||
//Clear the cache entries that exist by uniqueid/item key
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.UniqueId));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
|
||||
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1176,7 +1176,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Content)cached;
|
||||
|
||||
@@ -5,11 +5,11 @@ using Microsoft.Extensions.Logging;
|
||||
using NPoco;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Persistence;
|
||||
using Umbraco.Cms.Core.Persistence.Querying;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Dtos;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Factories;
|
||||
using Umbraco.Cms.Infrastructure.Persistence.Querying;
|
||||
|
||||
@@ -511,7 +511,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Core.Models.Media) cached;
|
||||
|
||||
@@ -615,7 +615,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
if (withCache)
|
||||
{
|
||||
// if the cache contains the (proper version of the) item, use it
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember, int>(dto.NodeId));
|
||||
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
|
||||
{
|
||||
content[i] = (Member)cached;
|
||||
|
||||
@@ -86,6 +86,14 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement
|
||||
|
||||
protected override IUser PerformGet(int id)
|
||||
{
|
||||
// This will never resolve to a user, yet this is asked
|
||||
// for all of the time (especially in cases of members).
|
||||
// Don't issue a SQL call for this, we know it will not exist.
|
||||
if (id == default || id < -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sql = SqlContext.Sql()
|
||||
.Select<UserDto>()
|
||||
.From<UserDto>()
|
||||
|
||||
@@ -5,7 +5,12 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// <summary>
|
||||
/// Umbraco back office specific <see cref="IdentityErrorDescriber"/>
|
||||
/// </summary>
|
||||
public class BackOfficeIdentityErrorDescriber : IdentityErrorDescriber
|
||||
public class BackOfficeErrorDescriber : IdentityErrorDescriber
|
||||
{
|
||||
// TODO: Override all the methods in order to provide our own translated error messages
|
||||
}
|
||||
|
||||
public class MembersErrorDescriber : IdentityErrorDescriber
|
||||
{
|
||||
// TODO: Override all the methods in order to provide our own translated error messages
|
||||
}
|
||||
@@ -41,17 +41,17 @@ namespace Umbraco.Cms.Core.Security
|
||||
services => new BackOfficePasswordHasher(
|
||||
new LegacyPasswordSecurity(),
|
||||
services.GetRequiredService<IJsonSerializer>()));
|
||||
services.TryAddScoped<IUserConfirmation<BackOfficeIdentityUser>, DefaultUserConfirmation<BackOfficeIdentityUser>>();
|
||||
services.TryAddScoped<IUserConfirmation<BackOfficeIdentityUser>, UmbracoUserConfirmation<BackOfficeIdentityUser>>();
|
||||
}
|
||||
|
||||
// override to add itself, by default identity only wants a single IdentityErrorDescriber
|
||||
public override IdentityBuilder AddErrorDescriber<TDescriber>()
|
||||
{
|
||||
if (!typeof(BackOfficeIdentityErrorDescriber).IsAssignableFrom(typeof(TDescriber)))
|
||||
if (!typeof(BackOfficeErrorDescriber).IsAssignableFrom(typeof(TDescriber)))
|
||||
{
|
||||
throw new InvalidOperationException($"The type {typeof(TDescriber)} does not inherit from {typeof(BackOfficeIdentityErrorDescriber)}");
|
||||
throw new InvalidOperationException($"The type {typeof(TDescriber)} does not inherit from {typeof(BackOfficeErrorDescriber)}");
|
||||
}
|
||||
|
||||
// Add as itself, by default identity only wants a single IdentityErrorDescriber
|
||||
Services.AddScoped<TDescriber>();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -14,8 +13,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// </summary>
|
||||
public class BackOfficeIdentityUser : UmbracoIdentityUser
|
||||
{
|
||||
private string _name;
|
||||
private string _passwordConfig;
|
||||
private string _culture;
|
||||
private IReadOnlyCollection<IReadOnlyUserGroup> _groups;
|
||||
private string[] _allowedSections;
|
||||
@@ -55,7 +52,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
user.Id = null;
|
||||
user.HasIdentity = false;
|
||||
user._culture = culture;
|
||||
user._name = name;
|
||||
user.Name = name;
|
||||
user.EnableChangeTracking();
|
||||
return user;
|
||||
}
|
||||
@@ -84,25 +81,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
public int[] CalculatedMediaStartNodeIds { get; set; }
|
||||
public int[] CalculatedContentStartNodeIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user's real name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password config
|
||||
/// </summary>
|
||||
public string PasswordConfig
|
||||
{
|
||||
get => _passwordConfig;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets content start nodes assigned to the User (not ones assigned to the user's groups)
|
||||
/// </summary>
|
||||
@@ -181,23 +159,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the user is locked out based on the user's lockout end date
|
||||
/// </summary>
|
||||
public bool IsLockedOut
|
||||
{
|
||||
get
|
||||
{
|
||||
bool isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
return isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the IUser IsApproved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
private static string UserIdToString(int userId) => string.Intern(userId.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
@@ -46,7 +44,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
IExternalLoginService externalLoginService,
|
||||
IOptions<GlobalSettings> globalSettings,
|
||||
UmbracoMapper mapper,
|
||||
BackOfficeIdentityErrorDescriber describer,
|
||||
BackOfficeErrorDescriber describer,
|
||||
AppCaches appCaches)
|
||||
: base(describer)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
@@ -7,6 +8,12 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// </summary>
|
||||
public interface IMemberManager : IUmbracoUserManager<MemberIdentityUser>
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the currently logged in member if there is one, else returns null
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<MemberIdentityUser> GetCurrentMemberAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current member is authorized based on the parameters provided.
|
||||
/// </summary>
|
||||
@@ -14,15 +21,38 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// <param name="allowGroups">Allowed groups.</param>
|
||||
/// <param name="allowMembers">Allowed individual members.</param>
|
||||
/// <returns>True or false if the currently logged in member is authorized</returns>
|
||||
bool IsMemberAuthorized(
|
||||
Task<bool> IsMemberAuthorizedAsync(
|
||||
IEnumerable<string> allowTypes = null,
|
||||
IEnumerable<string> allowGroups = null,
|
||||
IEnumerable<int> allowMembers = null);
|
||||
|
||||
// TODO: We'll need to add some additional things here that people will be using in their code:
|
||||
/// <summary>
|
||||
/// Check if a member is logged in
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool IsLoggedIn();
|
||||
|
||||
// bool MemberHasAccess(string path);
|
||||
// IReadOnlyDictionary<string, bool> MemberHasAccess(IEnumerable<string> paths)
|
||||
// Possibly some others from the old MembershipHelper
|
||||
/// <summary>
|
||||
/// Check if the current user has access to a document
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the document object to check</param>
|
||||
/// <returns>True if the current user has access or if the current document isn't protected</returns>
|
||||
Task<bool> MemberHasAccessAsync(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current user has access to the paths
|
||||
/// </summary>
|
||||
/// <param name="paths"></param>
|
||||
/// <returns></returns>
|
||||
Task<IReadOnlyDictionary<string, bool>> MemberHasAccessAsync(IEnumerable<string> paths);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a document object is protected by the "Protect Pages" functionality in umbraco
|
||||
/// </summary>
|
||||
/// <param name="path">The full path of the document object to check</param>
|
||||
/// <returns>True if the document object is protected</returns>
|
||||
Task<bool> IsProtectedAsync(string path);
|
||||
|
||||
Task<IReadOnlyDictionary<string, bool>> IsProtectedAsync(IEnumerable<string> paths);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Identity options specifically for the Umbraco members identity implementation
|
||||
/// </summary>
|
||||
public class MemberIdentityOptions : IdentityOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -13,9 +12,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// </summary>
|
||||
public class MemberIdentityUser : UmbracoIdentityUser
|
||||
{
|
||||
private string _name;
|
||||
private string _comments;
|
||||
private string _passwordConfig;
|
||||
private string _comments;
|
||||
private IReadOnlyCollection<IReadOnlyUserGroup> _groups;
|
||||
|
||||
// Custom comparer for enumerables
|
||||
@@ -53,20 +50,11 @@ namespace Umbraco.Cms.Core.Security
|
||||
user.MemberTypeAlias = memberTypeAlias;
|
||||
user.Id = null;
|
||||
user.HasIdentity = false;
|
||||
user._name = name;
|
||||
user.Name = name;
|
||||
user.EnableChangeTracking();
|
||||
return user;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the member's real name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the member's comments
|
||||
/// </summary>
|
||||
@@ -85,15 +73,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
// No change tracking because the persisted value is readonly
|
||||
public Guid Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password config
|
||||
/// </summary>
|
||||
public string PasswordConfig
|
||||
{
|
||||
get => _passwordConfig;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user groups
|
||||
/// </summary>
|
||||
@@ -121,23 +100,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the member is locked out
|
||||
/// </summary>
|
||||
public bool IsLockedOut
|
||||
{
|
||||
get
|
||||
{
|
||||
bool isLocked = LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
return isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the member is approved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the alias of the member type
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
@@ -11,7 +12,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// <summary>
|
||||
/// A custom user store that uses Umbraco member data
|
||||
/// </summary>
|
||||
public class MemberRoleStore : IRoleStore<UmbracoIdentityRole>
|
||||
public class MemberRoleStore : IRoleStore<UmbracoIdentityRole>, IQueryableRoleStore<UmbracoIdentityRole>
|
||||
{
|
||||
private readonly IMemberGroupService _memberGroupService;
|
||||
private bool _disposed;
|
||||
@@ -20,7 +21,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
//TODO: How revealing can the error messages be?
|
||||
private readonly IdentityError _intParseError = new IdentityError { Code = "IdentityIdParseError", Description = "Cannot parse ID to int" };
|
||||
private readonly IdentityError _memberGroupNotFoundError = new IdentityError { Code = "IdentityMemberGroupNotFound", Description = "Member group not found" };
|
||||
private const string genericIdentityErrorCode = "IdentityErrorUserStore";
|
||||
//private const string genericIdentityErrorCode = "IdentityErrorUserStore";
|
||||
|
||||
public MemberRoleStore(IMemberGroupService memberGroupService, IdentityErrorDescriber errorDescriber)
|
||||
{
|
||||
@@ -33,6 +34,8 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// </summary>
|
||||
public IdentityErrorDescriber ErrorDescriber { get; set; }
|
||||
|
||||
public IQueryable<UmbracoIdentityRole> Roles => _memberGroupService.GetAll().Select(MapFromMemberGroup).AsQueryable();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IdentityResult> CreateAsync(UmbracoIdentityRole role, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -268,5 +271,6 @@ namespace Umbraco.Cms.Core.Security
|
||||
throw new ObjectDisposedException(GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.ComponentModel;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.Identity
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
public class UmbracoIdentityRole : IdentityRole, IRememberBeingDirty
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.ComponentModel;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Umbraco.Cms.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.Identity
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,8 @@ namespace Umbraco.Cms.Core.Models.Identity
|
||||
/// </remarks>
|
||||
public abstract class UmbracoIdentityUser : IdentityUser, IRememberBeingDirty
|
||||
{
|
||||
private string _name;
|
||||
private string _passwordConfig;
|
||||
private string _id;
|
||||
private string _email;
|
||||
private string _userName;
|
||||
@@ -247,6 +249,42 @@ namespace Umbraco.Cms.Core.Models.Identity
|
||||
/// </summary>
|
||||
protected BeingDirty BeingDirty { get; } = new BeingDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the user is locked out based on the user's lockout end date
|
||||
/// </summary>
|
||||
public bool IsLockedOut
|
||||
{
|
||||
get
|
||||
{
|
||||
bool isLocked = LockoutEnabled && LockoutEnd.HasValue && LockoutEnd.Value.ToLocalTime() >= DateTime.Now;
|
||||
return isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the IUser IsApproved
|
||||
/// </summary>
|
||||
public bool IsApproved { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user's real name
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _name, nameof(Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password config
|
||||
/// </summary>
|
||||
public string PasswordConfig
|
||||
{
|
||||
// TODO: Implement this for members: AB#11550
|
||||
get => _passwordConfig;
|
||||
set => BeingDirty.SetPropertyValueAndDetectChanges(value, ref _passwordConfig, nameof(PasswordConfig));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDirty() => BeingDirty.IsDirty();
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Confirms whether a user is approved or not
|
||||
/// </summary>
|
||||
public class UmbracoUserConfirmation<TUser> : DefaultUserConfirmation<TUser>
|
||||
where TUser: UmbracoIdentityUser
|
||||
{
|
||||
public override Task<bool> IsConfirmedAsync(UserManager<TUser> manager, TUser user)
|
||||
=> Task.FromResult(user.IsApproved);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Cms.Core.Configuration;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Net;
|
||||
|
||||
namespace Umbraco.Cms.Core.Security
|
||||
|
||||
@@ -2,9 +2,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models.Identity;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.Scoping;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Cms.Core.Services.Implement
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user