Skip to content

Commit

Permalink
Change the behavior of User.Load based on the configured DomainUsageP…
Browse files Browse the repository at this point in the history
…olicy. (#1750)

* New api elements and acceptance tests (5/13 reds).

* Change the behavior of User.Load based on the configured DomainUsagePolicy.
  • Loading branch information
kavics authored Aug 24, 2022
1 parent 5197855 commit ac31b0d
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 12 deletions.
22 changes: 19 additions & 3 deletions src/ContentRepository/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace SenseNet.ContentRepository
public class User : GenericContent, IUser, IADSyncable, SenseNet.Security.ISecurityUser
{
private const string Profiles = "Profiles";
private const string AnyDomain = "*";

/// <summary>
/// Gets the Administrator user.
Expand Down Expand Up @@ -411,13 +412,25 @@ public static User Load(string domain, string name)
/// <returns></returns>
public static User Load(string domain, string name, ExecutionHint hint)
{
domain = string.IsNullOrWhiteSpace(domain) ? IdentityManagement.DefaultDomain : domain;
if (string.IsNullOrWhiteSpace(domain))
{
switch (IdentityManagement.DomainUsagePolicy)
{
case DomainUsagePolicy.NoDomain: domain = AnyDomain; break;
case DomainUsagePolicy.DefaultDomain: domain = IdentityManagement.DefaultDomain; break;
case DomainUsagePolicy.MandatoryDomain: return null;
default:
throw new ArgumentOutOfRangeException(
"Unknown DomainUsagePolicy: " + IdentityManagement.DomainUsagePolicy);
}
}

if (domain == null)
throw new ArgumentNullException(nameof(domain));
if (name == null)
throw new ArgumentNullException(nameof(name));

// look for the user ID in the cache by the doman-username key
// look for the user ID in the cache by the domain-username key
var ck = GetUserCacheKey(domain, name);
var userIdobject = Cache.Get(ck);
if (userIdobject != null)
Expand All @@ -428,7 +441,10 @@ public static User Load(string domain, string name, ExecutionHint hint)
return cachedUser;
}

var domainPath = string.Concat(RepositoryStructure.ImsFolderPath, RepositoryPath.PathSeparator, domain);
var domainPath = RepositoryStructure.ImsFolderPath;
if(domain != AnyDomain)
domainPath += RepositoryPath.PathSeparator + domain;

var type = Providers.Instance.StorageSchema.NodeTypes[typeof(User).Name];

User user;
Expand Down
64 changes: 57 additions & 7 deletions src/Services.Core/Operations/IdentityOperations.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Http;
Expand All @@ -8,6 +9,7 @@
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SenseNet.ApplicationModel;
using SenseNet.Configuration;
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.Email;
using SenseNet.ContentRepository.Security.Clients;
Expand All @@ -24,6 +26,17 @@ public class ClaimInfo
public string Value { get; set; }
}

[Serializable]
public class MissingDomainException : Exception
{
public static readonly string DefaultMessage = "Domain should be specified.";

public MissingDomainException() : base(DefaultMessage) { }
public MissingDomainException(string message) : base(message) { }
public MissingDomainException(string message, Exception inner) : base(message, inner) { }
protected MissingDomainException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

public static class IdentityOperations
{
/// <summary>Validates the provided user credentials.</summary>
Expand All @@ -46,10 +59,12 @@ public static class IdentityOperations
/// }
/// </code>
/// </example>
/// <exception cref="SenseNetSecurityException">Thrown when login is unsuccessful.</exception>
/// <exception cref="MissingDomainException">Thrown when the domain is missing but the login algorithm needs it.</exception>
[ODataAction]
[ContentTypes(N.CT.PortalRoot)]
[AllowedRoles(N.R.All)]
public static object ValidateCredentials(Content content, HttpContext context, string userName, string password)
public static CredentialValidationResult ValidateCredentials(Content content, HttpContext context, string userName, string password)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
Expand All @@ -65,6 +80,7 @@ public static object ValidateCredentials(Content content, HttpContext context, s
if (user == null)
{
SnTrace.Security.Write($"Could not find a user with the name: {userName}");
CheckDomainPolicy(userName);
}
else if (!user.Enabled)
{
Expand All @@ -76,13 +92,13 @@ public static object ValidateCredentials(Content content, HttpContext context, s
{
if (user.CheckPasswordMatch(password))
{
return new
return new CredentialValidationResult
{
id = user.Id,
email = user.Email,
username = user.Username,
name = user.Name,
loginName = user.LoginName
Id = user.Id,
Email = user.Email,
Username = user.Username,
Name = user.Name,
LoginName = user.LoginName
};
}

Expand All @@ -93,6 +109,40 @@ public static object ValidateCredentials(Content content, HttpContext context, s
throw new SenseNetSecurityException("Invalid username or password.");
}

private static void CheckDomainPolicy(string userName)
{
// return if the domain is specified
if (userName.IndexOf('\\') >= 0)
return;

// policy violation if not specified
if (IdentityManagement.DomainUsagePolicy == DomainUsagePolicy.MandatoryDomain)
throw new MissingDomainException();

// "default domain" policy is ok
if (IdentityManagement.DomainUsagePolicy != DomainUsagePolicy.NoDomain)
return;

// "no domain" policy: check user occurrence by name in all domains.
var users = Content.All.Where(c => c.InTree(Repository.ImsFolder) && c.Name == userName).ToArray();
if (users.Length > 1)
throw new MissingDomainException();
}

public class CredentialValidationResult
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("loginName")]
public string LoginName { get; set; }
}

/// <summary>Creates an external user who registered using one of the available
/// external providers.</summary>
/// <snCategory>Users and Groups</snCategory>
Expand Down
29 changes: 27 additions & 2 deletions src/Storage/Configuration/IdentityManagement.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
// ReSharper disable once CheckNamespace
namespace SenseNet.Configuration
{
/// <summary>
/// Determines domain handling in the login algorithms.
/// Can be configured under the key sensenet/identityManagement/DomainUsagePolicy.
/// The default is "NoDomain".
/// </summary>
public enum DomainUsagePolicy
{
/// <summary>
/// The domain is optional if the login name is system-wide unique.
/// If not, <see cref="MissingDomainException"/> will be thrown.
/// </summary>
NoDomain,
/// <summary>
/// The default domain will be used if the domain name is not present during logging in.
/// The default is optionally configured under the key sensenet/identityManagement/DefaultDomain.
/// The general default is "BuiltIn".
/// </summary>
DefaultDomain,
/// <summary>
/// This is the most strict policy: the domain name is always required.
/// </summary>
MandatoryDomain
}
public class IdentityManagement : SnConfig
{
private const string SectionName = "sensenet/identityManagement";

public static readonly string BuiltInDomainName = "BuiltIn";
public static string DefaultDomain { get; internal set; } = GetString(SectionName, "DefaultDomain", BuiltInDomainName);
public static bool UserProfilesEnabled { get; internal set; } = GetValue<bool>(SectionName, "UserProfilesEnabled");
public static DomainUsagePolicy DomainUsagePolicy { get; set; } =
GetValue<DomainUsagePolicy>(SectionName, nameof(DomainUsagePolicy), DomainUsagePolicy.NoDomain);
public static string DefaultDomain { get; set; } = GetString(SectionName, "DefaultDomain", BuiltInDomainName);
public static bool UserProfilesEnabled { get; set; } = GetValue<bool>(SectionName, "UserProfilesEnabled");
}
}
Loading

0 comments on commit ac31b0d

Please sign in to comment.