Files
Umbraco-CMS/src/Umbraco.Core/Models/Membership/UserGroup.cs
Mole 5182f46bdb New Backoffice: User Groups Controller (#13811)
* Add key to UserGroupDto

* Fix renaming table in sqlite

The SqliteSyntaxProvider needed an overload to use the correct query

* Start work on user group GUID migration

* Add key index to UserGroupDto

* Copy over data when migrating sqlite

* Make sqlite column migration work

* Remove PostMigrations

These should be replaced with Notification usage

* Remove outer scope from Upgrader

* Remove unececary null check

* Add marker base class for migrations

* Enable scopeless migrations

* Remove unnecessary state check

The final state of the migration is no longer necessarily the final state of the plan.

* Extend ExecutedMigrationPlan

* Ensure that MigrationPlanExecutor.Execute always returns a result.

* Always save final state, regardless of errors

* Remove obsolete Execute

* Add Umbraco specific migration notification

* Publish notification after umbraco migration

* Throw the exception that failed a migration after publishing notification

* Handle notification publishing in DatabaseBuilder

* Fix tests

* Remember to complete scope

* Clean up MigrationPlanExecutor

* Run each package migration in a separate scope

* Add PartialMigrationsTests

* Add unhappy path test

* Fix bug shown by test

* Move PartialMigrationsTests into the correct folder

* Comment out refresh cache in data type migration

Need to add this back again as a notification handler or something.

* Start working on a notification test

* Allow migrations to request a cache rebuild

* Set RebuildCache from MigrateDataTypeConfigurations

* Clean MigrationPlanExecutor

* Add comment explaining the need to partial migration success

* Fix tests

* Allow overriding DefinePlan of UmbracoPlan

This is needed to test the DatabaseBuilder

* Fix notification test

* Don't throw exception to be immediately re-caught

* Assert that scopes notification are always published

* Ensure that scopes are created when requested

* Make test classes internal.

It doesn't really matter, but this way it doesn't show up in intellisense

* Add notification handler for clearing cookies

* Add CompatibilitySuppressions

* Use unscoped migration for adding GUID to user group

* Make sqlite migration work

It's really not pretty, square peg, round hole.

* Don't re-enable foreign keys

This will happen automatically next time a connection is started.

* Scope database when using SQLServer

* Don't call complete transaction

* Tidy up a couple of comment

* Only allow scoping the database from UnscopedMigrationBase

* Fix comment

* Remove remark in UnscopedMigrationBase as it's no longer true

* Add keys when creating default user groups

* Map database value from DTO to entity

* Fix migration

Rename also renamed the foreign keys, making it not work

* Make migration idempotent

* Fix unit test

* Update CompatibilitySuppressions.xml

* Add GetUserGroupByKey to UserService

* Add ByKey endpoint

* Add UniqueId to AppendGroupBy

Otherwise MSSQL grenades

* Ensure that languages are returned by PerformGetByQuery

* add POC displaying model

* Clean up by key controller

* Add GetAllEndpoint

* Add delete endpoint

* Use GetKey to get GUID from id

Instead of pulling up the entire entity.

* Add UserGroup2Permission table

* Fetch the new permissions when getting user groups

* Dont ToString int to parse it to a short

I'm pretty sure this is some way old migration type code that doesn't make any sense anymore

* Add new relation to GetDeleteClauses

* Persist the permissions

* Split UserGroupViewModel into multiple models

This is to make it possible to make endpoints more rest-ish

* Bootstrap create and update endpoints

* Make GetAllUserGroupController paged

* Add method to create IUserGroup from UserGroupSaveModel

* Add sanity check version of endpoint

* Fix persisting permissions

* Map section aliases to the name the frontend expects

This is a temporary fix till we find out how we really want to handle this

* Fix up post merge

* Make naming more consistent

* Implement initial update endpoint

* Fix media start node

* Clean name for XSS when mapping to IUserGroup

* Use a set instead of a list for permission names

We don't want dupes

* Make permission column nvarchar max

* Add UserGroupOperationStatuses

* Add IUserGroupAuthorizationService

* Add specific user group creation method to user service

* Move validating and authorizing into its own methods

* Add operation result to action result mapping

* Update create controller to use the create method

* Fix create end point

* Comment out getting current user untill we have auth

* Add usergroup service

* Obsolete usergroup things from IUserService

* Add update to UserGroupService interface

* User IUserGroupService in controllers

* User async notifications overloads

* Move authorize user group creation into its own service

* Add AuthorizeUserGroupUpdate method

* Make new service implementations internal and sealed

* Add update user

* Add GetAll to usergroup service

* Remove or obsolete usages of GetAllUserGroups

* Add usergroup service to DI

* Remove usage of GetGroupsByAlias

* Remove usages of GetUserGroupByAlias

* Remove usage of GetUserGroupById

* Add new table when creating a new database

* Implement Delete

* Add skip and take to getall

* Move skip take into the service

* Fixup suggestions in user group service

* Fixup unit tests

* Allow admins to change user groups they're not a part of

* Add CompatibilitySuppressions

* Update openapi

* Uppdate OpenApi.json

again

* Add missing compatibility suppression

* Added missing type info in ProducesResponseTypeAttribute

* Added INamedEntityViewModel and added on the relevant view models

* Fixed bug, resulting in serialization not being the same as swagger reported. Now all types objects implementing an interface, is serialized with the $type property

* updated OpenApi.json

* Added missing title in notfound response

* Typo

* .Result to .GetAwaiter().GetResult()

* Update comment to mention it should be implemented on CurrentUserController

* Validate that start nodes actually exists

* Handle not found consistently

* Use iso codes instead of ids

* Update OpenAPI

* Automatically infer statuscode in problemdetails

* Ensure that the language exists

* Fix usergroup 2 permission index

* Validate that group name and alias is not too long

* Only return status from validation

We're just returning the same usergroups, and this is less boilerplate code

* Handle empty and null group names

* Remove group prefix from statuses

* Add some basic validation tests

* Don't allow updating a usergroup to having a duplicate alias

---------

Co-authored-by: Bjarke Berg <mail@bergmania.dk>
2023-02-16 09:39:17 +01:00

191 lines
5.5 KiB
C#

using System.Collections;
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models.Membership;
/// <summary>
/// Represents a Group for a Backoffice User
/// </summary>
[Serializable]
[DataContract(IsReference = true)]
public class UserGroup : EntityBase, IUserGroup, IReadOnlyUserGroup
{
// Custom comparer for enumerable
private static readonly DelegateEqualityComparer<IEnumerable<string>> _stringEnumerableComparer =
new(
(enum1, enum2) => enum1.UnsortedSequenceEqual(enum2),
enum1 => enum1.GetHashCode());
private readonly IShortStringHelper _shortStringHelper;
private string _alias;
private string? _icon;
private string _name;
private bool _hasAccessToAllLanguages;
private IEnumerable<string>? _permissions;
private ISet<string> _permissionNames = new HashSet<string>();
private List<string> _sectionCollection;
private List<int> _languageCollection;
private int? _startContentId;
private int? _startMediaId;
/// <summary>
/// Constructor to create a new user group
/// </summary>
public UserGroup(IShortStringHelper shortStringHelper)
{
_alias = string.Empty;
_name = string.Empty;
_shortStringHelper = shortStringHelper;
_sectionCollection = new List<string>();
_languageCollection = new List<int>();
}
/// <summary>
/// Constructor to create an existing user group
/// </summary>
/// <param name="userCount"></param>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="permissions"></param>
/// <param name="icon"></param>
/// <param name="shortStringHelper"></param>
public UserGroup(
IShortStringHelper shortStringHelper,
int userCount,
string? alias,
string? name,
IEnumerable<string> permissions,
string? icon)
: this(shortStringHelper)
{
UserCount = userCount;
_alias = alias ?? string.Empty;
_name = name ?? string.Empty;
_permissions = permissions;
_icon = icon;
}
[DataMember]
public int? StartMediaId
{
get => _startMediaId;
set => SetPropertyValueAndDetectChanges(value, ref _startMediaId, nameof(StartMediaId));
}
[DataMember]
public int? StartContentId
{
get => _startContentId;
set => SetPropertyValueAndDetectChanges(value, ref _startContentId, nameof(StartContentId));
}
[DataMember]
public string? Icon
{
get => _icon;
set => SetPropertyValueAndDetectChanges(value, ref _icon, nameof(Icon));
}
[DataMember]
public string Alias
{
get => _alias;
set => SetPropertyValueAndDetectChanges(
value.ToCleanString(_shortStringHelper, CleanStringType.Alias | CleanStringType.UmbracoCase), ref _alias!,
nameof(Alias));
}
[DataMember]
public string? Name
{
get => _name;
set => SetPropertyValueAndDetectChanges(value, ref _name!, nameof(Name));
}
[DataMember]
public bool HasAccessToAllLanguages
{
get => _hasAccessToAllLanguages;
set => SetPropertyValueAndDetectChanges(value, ref _hasAccessToAllLanguages, nameof(HasAccessToAllLanguages));
}
/// <summary>
/// The set of default permissions for the user group
/// </summary>
/// <remarks>
/// By default each permission is simply a single char but we've made this an enumerable{string} to support a more
/// flexible permissions structure in the future.
/// </remarks>
[DataMember]
public IEnumerable<string>? Permissions
{
get => _permissions;
set => SetPropertyValueAndDetectChanges(value, ref _permissions, nameof(Permissions), _stringEnumerableComparer);
}
/// <inheritdoc />
public ISet<string> PermissionNames
{
get => _permissionNames;
set => SetPropertyValueAndDetectChanges(value, ref _permissionNames!, nameof(PermissionNames), _stringEnumerableComparer);
}
public IEnumerable<string> AllowedSections => _sectionCollection;
public int UserCount { get; }
public void RemoveAllowedSection(string sectionAlias)
{
if (_sectionCollection.Contains(sectionAlias))
{
_sectionCollection.Remove(sectionAlias);
}
}
public void AddAllowedSection(string sectionAlias)
{
if (_sectionCollection.Contains(sectionAlias) == false)
{
_sectionCollection.Add(sectionAlias);
}
}
public IEnumerable<int> AllowedLanguages
{
get => _languageCollection;
}
public void RemoveAllowedLanguage(int languageId)
{
if (_languageCollection.Contains(languageId))
{
_languageCollection.Remove(languageId);
}
}
public void AddAllowedLanguage(int languageId)
{
if (_languageCollection.Contains(languageId) == false)
{
_languageCollection.Add(languageId);
}
}
public void ClearAllowedLanguages() => _languageCollection.Clear();
public void ClearAllowedSections() => _sectionCollection.Clear();
protected override void PerformDeepClone(object clone)
{
base.PerformDeepClone(clone);
var clonedEntity = (UserGroup)clone;
// manually clone the start node props
clonedEntity._sectionCollection = new List<string>(_sectionCollection);
}
}