Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
edd4e72
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
ad5b3af
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
c12b104
modernize(users portlet): implement rest api to fetch permissions by…
hassandotcms Sep 24, 2025
16b12dd
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
ed2b99c
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
0f7b247
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
8a25c56
Merge branch 'main' of https://github.com/dotCMS/core into 33345-impl…
hassandotcms Sep 24, 2025
0e2df73
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 24, 2025
90b0991
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 26, 2025
e57b758
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 26, 2025
e30c863
modernize(users portlet): implement rest api to fetch permissions by…
hassandotcms Sep 26, 2025
334af95
modernize(users portlet): implement rest api to fetch permissions by…
hassandotcms Sep 26, 2025
dd74fa2
modernize(users portlet): implement rest api to fetch permissions by…
hassandotcms Sep 27, 2025
0544da9
Merge branch 'main' of https://github.com/dotCMS/core into 33345-impl…
hassandotcms Sep 27, 2025
fcd517c
modernize(users portlet): implement rest api to fetch permissions by …
hassandotcms Sep 27, 2025
079c33e
modernize(users portlet): implement rest api to fetch permissions me…
hassandotcms Sep 29, 2025
36decff
modernize(users portlet): implement rest api to fetch permissions met…
hassandotcms Sep 29, 2025
5420dec
modernize(users portlet): implement rest api to fetch permissions met…
hassandotcms Sep 29, 2025
f514ec8
modernize(users portlet): implement rest api to fetch permissions me…
hassandotcms Sep 29, 2025
9e6873a
modernize(users portlet): implement rest api to fetch permissions me…
hassandotcms Sep 29, 2025
a50e016
Merge branch 'main' of https://github.com/dotCMS/core into 33394-impl…
hassandotcms Oct 1, 2025
867b609
modernize(users-portlet) #33394
hassandotcms Oct 1, 2025
64ff4cc
modernize(users-portlet): rest api for saving permissions [#33393]
hassandotcms Oct 2, 2025
41aed50
modernize(users-portlet): rest api for saving permissions [#33393]
hassandotcms Oct 2, 2025
77bb2a1
Merge branch 'main' of https://github.com/dotCMS/core into 33393-impl…
hassandotcms Oct 2, 2025
9aab8a5
Merge branch 'main' of https://github.com/dotCMS/core into 33394-impl…
hassandotcms Oct 10, 2025
4106e61
Merge branch '33394-implement-rest-api-get-permission-metadata' into …
hassandotcms Oct 10, 2025
30efede
modernize(users-portlet): rest api to save user permissions #33393
hassandotcms Oct 13, 2025
02e17db
Merge branch 'main' of https://github.com/dotCMS/core into 33393-impl…
hassandotcms Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.dotcms.rest.api.v1.system.permission;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;
import java.util.Set;

/**
* Permission metadata response containing available permission levels and scopes.
* This bean represents the permission configuration options available in the dotCMS system.
* It provides information about what permission levels can be assigned (READ, WRITE, PUBLISH, etc.)
* and what asset types support permissions (HOST, FOLDER, CONTENT, etc.).
*/
@Schema(description = "Permission metadata containing available levels and scopes in the system")
public class PermissionMetadataView {

@JsonProperty("levels")
@Schema(
description = "Available permission levels that can be assigned to users and roles",
example = "[\"READ\", \"WRITE\", \"PUBLISH\", \"EDIT_PERMISSIONS\", \"CAN_ADD_CHILDREN\"]",
required = true
)
private final Set<String> levels;

@JsonProperty("scopes")
@Schema(
description = "Available permission scopes (asset types) that support permissions",
example = "[\"INDIVIDUAL\", \"HOST\", \"FOLDER\", \"CONTENT\", \"TEMPLATE\", \"PAGE\", \"CONTAINER\", \"STRUCTURE\", \"CATEGORY\"]",
required = true
)
private final Set<String> scopes;

/**
* Constructs permission metadata with available levels and scopes.
*
* @param levels The set of available permission levels (READ, WRITE, PUBLISH, EDIT_PERMISSIONS, CAN_ADD_CHILDREN)
* @param scopes The set of available permission scopes (asset types that support permissions)
*/
public PermissionMetadataView(final Set<String> levels, final Set<String> scopes) {
this.levels = levels;
this.scopes = scopes;
}

/**
* Gets the available permission levels.
*
* @return Set of permission level names
*/
public Set<String> getLevels() {
return levels;
}

/**
* Gets the available permission scopes.
*
* @return Set of permission scope names (asset types)
*/
public Set<String> getScopes() {
return scopes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.liferay.portal.model.User;
import com.liferay.util.StringPool;
import com.dotcms.rest.api.v1.user.UserPermissionHelper;
import javax.inject.Inject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -56,19 +58,25 @@ public class PermissionResource {
private final WebResource webResource;
private final PermissionHelper permissionHelper;
private final UserAPI userAPI;

public PermissionResource() {

this(new WebResource(), PermissionHelper.getInstance(), APILocator.getUserAPI());
private final UserPermissionHelper userPermissionHelper;

@Inject
public PermissionResource(final UserPermissionHelper userPermissionHelper) {
this(new WebResource(),
PermissionHelper.getInstance(),
APILocator.getUserAPI(),
userPermissionHelper);
}
@VisibleForTesting
public PermissionResource(final WebResource webResource,
final PermissionHelper permissionHelper,
final UserAPI userAPI) {
final UserAPI userAPI,
final UserPermissionHelper userPermissionHelper) {

this.webResource = webResource;
this.permissionHelper = permissionHelper;
this.userAPI = userAPI;
this.userPermissionHelper = userPermissionHelper;
}

/**
Expand Down Expand Up @@ -268,6 +276,47 @@ public Response getByContentletGroupByType(final @Context HttpServletRequest req
return Response.ok(new ResponseEntityPermissionGroupByTypeView(permissionsRoleGroupByTypeMap)).build();
}

@Operation(
summary = "Get permission metadata",
description = "Returns available permission levels and scopes that can be assigned to users and roles"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200",
description = "Permission metadata retrieved successfully",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = ResponseEntityPermissionMetadataView.class))),
@ApiResponse(responseCode = "401",
description = "Unauthorized - authentication required",
content = @Content(mediaType = "application/json"))
})
@GET
@Path("/metadata")
@JSONP
@NoCache
@Produces({MediaType.APPLICATION_JSON})
public ResponseEntityPermissionMetadataView getPermissionMetadata(
@Parameter(hidden = true) @Context HttpServletRequest request,
@Parameter(hidden = true) @Context HttpServletResponse response) {

Logger.debug(this, () -> "Retrieving permission metadata");

new WebResource.InitBuilder(webResource)
.requiredBackendUser(true)
.requiredFrontendUser(false)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.init();

final PermissionMetadataView permissionMetadata = new PermissionMetadataView(
userPermissionHelper.getAvailablePermissionLevels(),
userPermissionHelper.getAvailablePermissionScopes()
);

Logger.info(this, "Permission metadata retrieved successfully");

return new ResponseEntityPermissionMetadataView(permissionMetadata);
}

private boolean filter(final PermissionAPI.Type permissionType, final Permission permission) {

return null != permissionType?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.dotcms.rest.api.v1.system.permission;

import com.dotcms.rest.ResponseEntityView;

/**
* Response entity wrapper for permission metadata endpoint.
* Contains the available permission levels (READ, WRITE, PUBLISH, EDIT_PERMISSIONS,
* CAN_ADD_CHILDREN) and permission scopes (HOST, FOLDER, CONTENT, TEMPLATE, etc.)
* that can be assigned to users and roles in the dotCMS system.
*/
public class ResponseEntityPermissionMetadataView extends ResponseEntityView<PermissionMetadataView> {

/**
* Constructs a new response wrapper for permission metadata.
*
* @param metadata The permission metadata containing levels and scopes
*/
public ResponseEntityPermissionMetadataView(final PermissionMetadataView metadata) {
super(metadata);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.dotcms.rest.api.v1.user;

import com.dotcms.rest.ResponseEntityView;

/**
* Response entity wrapper for save user permissions endpoint.
* Follows dotCMS pattern of specific ResponseEntity*View classes for REST endpoints.
*
* @author Hassan
* @since 24.01
*/
public class ResponseEntitySaveUserPermissionsView extends ResponseEntityView<SaveUserPermissionsResponse> {

/**
* Constructs response wrapper for save operation.
*
* @param entity The save response containing updated permissions
*/
public ResponseEntitySaveUserPermissionsView(final SaveUserPermissionsResponse entity) {
super(entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

import com.dotcms.rest.ResponseEntityView;

import java.util.Map;

/**
* Response wrapper for user permission endpoint.
* Response entity wrapper for user permissions endpoint.
* Contains a user's individual role permissions organized by assets (hosts and folders).
* Each asset includes detailed permission information such as the permission assignments,
* whether the user can edit those permissions, and whether permissions are inherited.
*
* @see UserResource#getUserPermissions(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, String)
* @see UserPermissions
*/
public class ResponseEntityUserPermissionsView extends ResponseEntityView<Map<String, Object>> {
public ResponseEntityUserPermissionsView(Map<String, Object> entity) {
public class ResponseEntityUserPermissionsView extends ResponseEntityView<UserPermissions> {

/**
* Constructs a new response wrapper for user permissions.
*
* @param entity The user permissions data
*/
public ResponseEntityUserPermissionsView(final UserPermissions entity) {
super(entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.dotcms.rest.api.v1.user;

import com.dotcms.rest.api.Validated;
import com.dotcms.rest.exception.BadRequestException;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;

import javax.validation.constraints.NotNull;
import java.util.Map;
import java.util.Set;

/**
* Form for updating user permissions on a specific asset.
* Maps modern REST API structure to legacy RoleAjax.saveRolePermission() logic.
*
* @author Hassan
* @since 24.01
*/
public class SaveUserPermissionsForm extends Validated {

@JsonProperty("permissions")
@Schema(
description = "Permission assignments by scope (INDIVIDUAL, HOST, FOLDER, etc.) with permission levels (READ, WRITE, PUBLISH, EDIT_PERMISSIONS, CAN_ADD_CHILDREN)",
example = "{\"INDIVIDUAL\": [\"READ\", \"WRITE\"], \"HOST\": [\"READ\"]}",
required = true
)
@NotNull(message = "permissions is required")
private final Map<String, Set<String>> permissions;

@JsonProperty("cascade")
@Schema(
description = "Whether to cascade permissions to all children assets",
example = "false",
defaultValue = "false"
)
private final boolean cascade;

/**
* Constructs form for saving user permissions.
*
* @param permissions Map of permission scopes to permission levels
* @param cascade Whether to cascade permissions to children
*/
public SaveUserPermissionsForm(
@JsonProperty("permissions") final Map<String, Set<String>> permissions,
@JsonProperty("cascade") final boolean cascade
) {
this.permissions = permissions;
this.cascade = cascade;
}

/**
* Gets the permission assignments map.
*
* @return Map of scopes to permission levels
*/
public Map<String, Set<String>> getPermissions() {
return permissions;
}

/**
* Checks if cascade is enabled.
*
* @return true if permissions should cascade to children
*/
public boolean isCascade() {
return cascade;
}

@Override
public void checkValid() {
super.checkValid(); // JSR-303 validation

if (permissions == null || permissions.isEmpty()) {
throw new BadRequestException("permissions cannot be empty");
}

// Validate against metadata API
final UserPermissionHelper helper = new UserPermissionHelper();
final Set<String> validScopes = helper.getAvailablePermissionScopes();
final Set<String> validLevels = helper.getAvailablePermissionLevels();

for (final Map.Entry<String, Set<String>> entry : permissions.entrySet()) {
final String scope = entry.getKey();
if (!validScopes.contains(scope)) {
throw new BadRequestException("Invalid permission scope: " + scope);
}

final Set<String> levels = entry.getValue();

// Validate permission levels are not null or empty
if (levels == null) {
throw new BadRequestException("Permission levels for scope '" + scope + "' cannot be null");
}
if (levels.isEmpty()) {
throw new BadRequestException("Permission levels for scope '" + scope + "' cannot be empty");
}

// Validate each permission level
for (final String level : levels) {
if (level == null) {
throw new BadRequestException("Permission level cannot be null in scope '" + scope + "'");
}
if (!validLevels.contains(level)) {
throw new BadRequestException("Invalid permission level '" + level + "' in scope '" + scope + "'");
}
}
}
}
}
Loading