Skip to content

Commit cd5470f

Browse files
authored
V15: Current User Group Id Condition (#18011)
* feat: adds userGroupIds to the current user model * feat: generate new types * feat: adds function to check for two array intersections * feat: maps up userGroupIds * feat: adds new condition to verify user's groups * chore: add mocked data * chore: add generated consts * change structure of config to match other conditions match, oneOf, allOf, noneOf * rename condition from "group" to "groupId" * feat: inherit from base `UserPresentationBase` to have a shared foundation across user models
1 parent 0bcb3c5 commit cd5470f

File tree

16 files changed

+127
-11
lines changed

16 files changed

+127
-11
lines changed

src/Umbraco.Cms.Api.Management/Factories/UserPresentationFactory.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ public async Task<CurrentUserResponseModel> CreateCurrentUserResponseModelAsync(
216216
HasAccessToAllLanguages = hasAccessToAllLanguages,
217217
HasAccessToSensitiveData = user.HasAccessToSensitiveData(),
218218
AllowedSections = allowedSections,
219-
IsAdmin = user.IsAdmin()
219+
IsAdmin = user.IsAdmin(),
220+
UserGroupIds = presentationUser.UserGroupIds,
220221
});
221222
}
222223

src/Umbraco.Cms.Api.Management/ViewModels/User/Current/CurrentUserResponseModel.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,10 @@
22

33
namespace Umbraco.Cms.Api.Management.ViewModels.User.Current;
44

5-
public class CurrentUserResponseModel
5+
public class CurrentUserResponseModel : UserPresentationBase
66
{
77
public required Guid Id { get; init; }
88

9-
public required string Email { get; init; } = string.Empty;
10-
11-
public required string UserName { get; init; } = string.Empty;
12-
13-
public required string Name { get; init; } = string.Empty;
14-
159
public required string? LanguageIsoCode { get; init; }
1610

1711
public required ISet<ReferenceByIdModel> DocumentStartNodeIds { get; init; } = new HashSet<ReferenceByIdModel>();
@@ -35,5 +29,6 @@ public class CurrentUserResponseModel
3529
public required ISet<IPermissionPresentationModel> Permissions { get; init; }
3630

3731
public required ISet<string> AllowedSections { get; init; }
32+
3833
public bool IsAdmin { get; set; }
3934
}

src/Umbraco.Web.UI.Client/src/external/backend-api/src/types.gen.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,11 @@ export type CultureReponseModel = {
443443
};
444444

445445
export type CurrentUserResponseModel = {
446-
id: string;
447446
email: string;
448447
userName: string;
449448
name: string;
449+
userGroupIds: Array<(ReferenceByIdModel)>;
450+
id: string;
450451
languageIsoCode: (string) | null;
451452
documentStartNodeIds: Array<(ReferenceByIdModel)>;
452453
hasDocumentRootAccess: boolean;
@@ -867,6 +868,8 @@ export type DocumentVariantResponseModel = {
867868
updateDate: string;
868869
state: DocumentVariantStateModel;
869870
publishDate?: (string) | null;
871+
scheduledPublishDate?: (string) | null;
872+
scheduledUnpublishDate?: (string) | null;
870873
};
871874

872875
export enum DocumentVariantStateModel {

src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class UmbUserMockDB extends UmbEntityMockDbBase<UmbMockUserModel> {
8686
permissions,
8787
allowedSections,
8888
isAdmin: firstUser.isAdmin,
89+
userGroupIds: firstUser.userGroupIds,
8990
};
9091
}
9192

src/Umbraco.Web.UI.Client/src/packages/core/utils/string/string-or-string-array-contains/string-or-string-array-contains.function.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,17 @@
77
export function stringOrStringArrayContains(value: string | Array<string>, search: string): boolean {
88
return Array.isArray(value) ? value.indexOf(search) !== -1 : value === search;
99
}
10+
11+
/**
12+
* Check if a string or array of strings intersects with another array of strings
13+
* @param value The string or array of strings to search in
14+
* @param search The array of strings to search for
15+
* @returns {boolean} Whether the string or array of strings intersects with the search array
16+
*/
17+
export function stringOrStringArrayIntersects(value: string | Array<string>, search: Array<string>): boolean {
18+
if (Array.isArray(value)) {
19+
return value.some((v) => search.indexOf(v) !== -1);
20+
} else {
21+
return search.indexOf(value) !== -1;
22+
}
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export * from './group-id/constants.js';
12
export * from './is-admin/constants.js';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const UMB_CURRENT_USER_GROUP_ID_CONDITION_ALIAS = 'Umb.Condition.CurrentUser.GroupId';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { UMB_CURRENT_USER_GROUP_ID_CONDITION_ALIAS } from './constants.js';
2+
import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';
3+
4+
export interface UmbCurrentUserGroupIdConditionConfig
5+
extends UmbConditionConfigBase<typeof UMB_CURRENT_USER_GROUP_ID_CONDITION_ALIAS> {
6+
/**
7+
* The user group that the current user must be a member of to pass the condition.
8+
* @examples ['guid1']
9+
*/
10+
match?: string;
11+
12+
/**
13+
* The user group(s) that the current user must be a member of to pass the condition.
14+
* @examples [['guid1', 'guid2']]
15+
*/
16+
oneOf?: Array<string>;
17+
18+
/**
19+
* The user groups that the current user must be a member of to pass the condition.
20+
* @examples [['guid1', 'guid2']]
21+
*/
22+
allOf?: Array<string>;
23+
24+
/**
25+
* The user group(s) that the current user must not be a member of to pass the condition.
26+
* @examples [['guid1', 'guid2']]
27+
*/
28+
noneOf?: Array<string>;
29+
}
30+
31+
declare global {
32+
interface UmbExtensionConditionConfigMap {
33+
UmbCurrentUserGroupIdConditionConfig: UmbCurrentUserGroupIdConditionConfig;
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { UMB_CURRENT_USER_GROUP_ID_CONDITION_ALIAS } from './constants.js';
2+
import type { ManifestCondition } from '@umbraco-cms/backoffice/extension-api';
3+
4+
export const manifest: ManifestCondition = {
5+
type: 'condition',
6+
name: 'Current user group id Condition',
7+
alias: UMB_CURRENT_USER_GROUP_ID_CONDITION_ALIAS,
8+
api: () => import('./group-id.condition.js'),
9+
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { UMB_CURRENT_USER_CONTEXT } from '../../current-user.context.token.js';
2+
import type { UmbCurrentUserModel } from '../../types.js';
3+
import type { UmbCurrentUserGroupIdConditionConfig } from './types.js';
4+
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
5+
import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';
6+
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
7+
8+
export class UmbCurrentUserGroupCondition
9+
extends UmbConditionBase<UmbCurrentUserGroupIdConditionConfig>
10+
implements UmbExtensionCondition
11+
{
12+
constructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbCurrentUserGroupIdConditionConfig>) {
13+
super(host, args);
14+
15+
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
16+
this.observe(context.currentUser, this.observeCurrentUser, 'umbCurrentUserGroupConditionObserver');
17+
});
18+
}
19+
20+
private observeCurrentUser = async (currentUser: UmbCurrentUserModel) => {
21+
// Idea: This part could be refactored to become a shared util, to align these matching feature across conditions. [NL]
22+
// Notice doing so it would be interesting to invistigate if it makes sense to combine some of these properties, to enable more specific matching. (But maybe it is only relevant for the combination of match + oneOf) [NL]
23+
const { match, oneOf, allOf, noneOf } = this.config;
24+
25+
if (match) {
26+
this.permitted = currentUser.userGroupUniques.includes(match);
27+
return;
28+
}
29+
30+
if (oneOf) {
31+
this.permitted = oneOf.some((v) => currentUser.userGroupUniques.includes(v));
32+
return;
33+
}
34+
35+
if (allOf) {
36+
this.permitted = allOf.every((v) => currentUser.userGroupUniques.includes(v));
37+
return;
38+
}
39+
40+
if (noneOf) {
41+
if (noneOf.some((v) => currentUser.userGroupUniques.includes(v))) {
42+
this.permitted = false;
43+
return;
44+
}
45+
}
46+
47+
this.permitted = true;
48+
};
49+
}
50+
51+
export { UmbCurrentUserGroupCondition as api };

0 commit comments

Comments
 (0)