Skip to content

Commit 2ed3005

Browse files
Copilotphrocker
andauthored
Consolidate attribute UI into fully integrated unified ABAC management interface with autocomplete and step-by-step workflow guide (#34)
* Initial plan * Add unified ABAC attribute management page with tabs Co-authored-by: phrocker <[email protected]> * Add security hardening for URL parameters and iframe sandboxing Co-authored-by: phrocker <[email protected]> * Replace iframe approach with page-based navigation and center content Co-authored-by: phrocker <[email protected]> * Create fully integrated unified ABAC page with all content embedded Co-authored-by: phrocker <[email protected]> * Add step-by-step workflow and practical examples to explain ABAC component relationships Co-authored-by: phrocker <[email protected]> * Add autocomplete functionality for username and attribute name lookups in User Assignments Co-authored-by: phrocker <[email protected]> * update --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: phrocker <[email protected]> Co-authored-by: Marc Parisi <[email protected]>
1 parent 76c951e commit 2ed3005

File tree

10 files changed

+1800
-14
lines changed

10 files changed

+1800
-14
lines changed

api/src/main/java/io/sentrius/sso/controllers/api/abac/AttributeManagementController.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.springframework.web.bind.annotation.*;
1616

1717
import java.time.LocalDateTime;
18+
import java.time.ZoneOffset;
1819
import java.util.HashMap;
1920
import java.util.List;
2021
import java.util.Map;
@@ -243,8 +244,13 @@ private AttributeAssignmentDTO toAssignmentDTO(AttributeAssignment assignment) {
243244
dto.setTargetId(assignment.getTargetId());
244245
dto.setAttributeName(assignment.getAttributeDefinition().getAttributeName());
245246
dto.setAttributeValue(assignment.getAttributeValue());
246-
dto.setValidFrom(LocalDateTime.from(assignment.getValidFrom()));
247-
dto.setValidUntil(LocalDateTime.from(assignment.getValidUntil()));
247+
if (assignment.getValidFrom() != null) {
248+
dto.setValidFrom(LocalDateTime.ofInstant(assignment.getValidFrom(), ZoneOffset.UTC));
249+
}
250+
if (assignment.getValidUntil() != null) {
251+
dto.setValidUntil(LocalDateTime.ofInstant(assignment.getValidUntil(), ZoneOffset.UTC));
252+
}
253+
248254
dto.setSyncedFromKeycloak(assignment.getSyncedFromKeycloak());
249255
dto.setActive(assignment.getIsActive());
250256
return dto;

api/src/main/java/io/sentrius/sso/controllers/api/users/UserApiController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.HashMap;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.Optional;
910
import com.fasterxml.jackson.core.JsonProcessingException;
1011
import com.fasterxml.jackson.databind.JsonNode;
1112
import com.fasterxml.jackson.databind.node.BooleanNode;
@@ -20,6 +21,7 @@
2021
import io.sentrius.sso.core.exceptions.ZtatException;
2122
import io.sentrius.sso.core.model.hostgroup.HostGroup;
2223
import io.sentrius.sso.core.model.security.UserType;
24+
import io.sentrius.sso.core.model.security.enums.ApplicationAccessEnum;
2325
import io.sentrius.sso.core.model.security.enums.UserAccessEnum;
2426
import io.sentrius.sso.core.model.users.User;
2527
import io.sentrius.sso.core.dto.UserDTO;
@@ -137,6 +139,29 @@ public ResponseEntity<List<UserDTO>> listusers(
137139
}
138140

139141

142+
@GetMapping("/search")
143+
@LimitAccess(userAccess = {UserAccessEnum.CAN_VIEW_USERS})
144+
public ResponseEntity<List<UserDTO>> findUsersByName(
145+
@RequestParam(name = "query") String userId) {
146+
147+
log.debug("Finding users with attribute userId={}", userId);
148+
149+
try {
150+
Optional<List<User>> userIds = userService.findByUsernameLike(userId);
151+
if (userIds.isPresent()) {
152+
log.info("Found users with attribute userId={}", userId);
153+
List<UserDTO> userDTOs = userIds.get().stream().map(User::toDto).toList();
154+
return ResponseEntity.ok(userDTOs);
155+
} else {
156+
log.info("No users with attribute userId={}", userId);
157+
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
158+
}
159+
160+
} catch (Exception e) {
161+
log.error("Error finding users with attribute", e);
162+
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
163+
}
164+
}
140165

141166
@PostMapping("add")
142167
@LimitAccess(userAccess = {UserAccessEnum.CAN_EDIT_USERS})

api/src/main/java/io/sentrius/sso/controllers/api/users/UserAttributeController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Map;
66
import java.util.Optional;
77
import java.util.stream.Collectors;
8+
import io.sentrius.sso.core.annotations.LimitAccess;
89
import io.sentrius.sso.core.config.SystemOptions;
910
import io.sentrius.sso.core.controllers.BaseController;
1011
import io.sentrius.sso.core.dto.users.UserAttributeDTO;
@@ -90,6 +91,7 @@ public ResponseEntity<List<UserAttributeDTO>> getUserAttributes(@PathVariable St
9091
* Get user attributes as a map
9192
*/
9293
@GetMapping("/{userId}/map")
94+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
9395
public ResponseEntity<Map<String, String>> getUserAttributesAsMap(@PathVariable String userId) {
9496
log.debug("Getting attributes map for user: {}", userId);
9597

@@ -107,6 +109,7 @@ public ResponseEntity<Map<String, String>> getUserAttributesAsMap(@PathVariable
107109
* Get a specific user attribute
108110
*/
109111
@GetMapping("/{userId}/{attributeName}")
112+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
110113
public ResponseEntity<UserAttributeDTO> getUserAttribute(
111114
@PathVariable String userId,
112115
@PathVariable String attributeName) {
@@ -133,6 +136,7 @@ public ResponseEntity<UserAttributeDTO> getUserAttribute(
133136
* Set a user attribute
134137
*/
135138
@PostMapping("/update")
139+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
136140
public ResponseEntity<UserAttributeDTO> setUserAttribute(
137141
@RequestParam("userId") String userId,
138142
@RequestBody @Valid UserAttributeDTO attributeDTO,
@@ -174,6 +178,7 @@ public ResponseEntity<UserAttributeDTO> setUserAttribute(
174178
* Set multiple user attributes at once
175179
*/
176180
@PostMapping("/{userId}/bulk")
181+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
177182
public ResponseEntity<List<UserAttributeDTO>> setUserAttributes(
178183
@PathVariable String userId,
179184
@RequestBody Map<String, String> attributes,
@@ -210,6 +215,7 @@ public ResponseEntity<List<UserAttributeDTO>> setUserAttributes(
210215
* Remove a user attribute
211216
*/
212217
@DeleteMapping("/{userId}/{attributeName}")
218+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
213219
public ResponseEntity<Map<String, Object>> removeUserAttribute(
214220
@PathVariable String userId,
215221
@PathVariable String attributeName,
@@ -245,6 +251,7 @@ public ResponseEntity<Map<String, Object>> removeUserAttribute(
245251
* Sync user attributes from Keycloak
246252
*/
247253
@PostMapping("/{userId}/sync/keycloak")
254+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
248255
public ResponseEntity<List<UserAttributeDTO>> syncFromKeycloak(
249256
@PathVariable String userId,
250257
HttpServletRequest request, HttpServletResponse response) {
@@ -278,6 +285,7 @@ public ResponseEntity<List<UserAttributeDTO>> syncFromKeycloak(
278285
* Check if user has specific attribute value
279286
*/
280287
@GetMapping("/{userId}/check")
288+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
281289
public ResponseEntity<Map<String, Boolean>> checkUserAttribute(
282290
@PathVariable String userId,
283291
@RequestParam String attributeName,
@@ -303,6 +311,7 @@ public ResponseEntity<Map<String, Boolean>> checkUserAttribute(
303311
* Find users with specific attribute (admin endpoint)
304312
*/
305313
@GetMapping("/search")
314+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
306315
public ResponseEntity<List<String>> findUsersWithAttribute(
307316
@RequestParam String attributeName,
308317
@RequestParam String attributeValue) {
@@ -323,6 +332,7 @@ public ResponseEntity<List<String>> findUsersWithAttribute(
323332
* Get all unique attribute names (admin endpoint)
324333
*/
325334
@GetMapping("/names")
335+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
326336
public ResponseEntity<List<String>> getAllAttributeNames() {
327337
log.debug("Getting all unique attribute names");
328338

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.sentrius.sso.controllers.view;
2+
3+
import io.sentrius.sso.core.annotations.LimitAccess;
4+
import io.sentrius.sso.core.config.SystemOptions;
5+
import io.sentrius.sso.core.controllers.BaseController;
6+
import io.sentrius.sso.core.model.security.enums.ApplicationAccessEnum;
7+
import io.sentrius.sso.core.services.ErrorOutputService;
8+
import io.sentrius.sso.core.services.UserService;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.stereotype.Controller;
11+
import org.springframework.ui.Model;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
15+
/**
16+
* Unified view controller for ABAC attribute management.
17+
* Provides a single entry point with tabs for:
18+
* - Attribute Definitions (schema)
19+
* - User Assignments (attribute values for users)
20+
* - Access Mappings (endpoint access control)
21+
*/
22+
@Slf4j
23+
@Controller
24+
@RequestMapping("/sso/v1/attributes")
25+
public class AttributeManagementViewController extends BaseController {
26+
27+
public AttributeManagementViewController(
28+
UserService userService,
29+
SystemOptions systemOptions,
30+
ErrorOutputService errorOutputService) {
31+
super(userService, systemOptions, errorOutputService);
32+
}
33+
34+
/**
35+
* Show unified ABAC attribute management page with tabs
36+
*/
37+
@GetMapping("/manage")
38+
@LimitAccess(applicationAccess = {ApplicationAccessEnum.CAN_MANAGE_APPLICATION})
39+
public String showAttributeManagement(Model model) {
40+
log.debug("Showing unified attribute management page");
41+
return "sso/attributes_unified";
42+
}
43+
}

api/src/main/resources/templates/fragments/sidebar.html

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,8 @@
7373
</a>
7474
</li>
7575
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
76-
<a href="/sso/v1/custom-attributes/mappings" class="nav-link px-0 align-middle">
77-
<i class="fas fa-user-tag"></i> <span class="ms-1 d-none d-sm-inline">Custom Attributes</span>
78-
</a>
79-
</li>
80-
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
81-
<a href="/sso/v1/user-attributes" class="nav-link px-0 align-middle">
82-
<i class="fas fa-id-badge"></i> <span class="ms-1 d-none d-sm-inline">User Attributes</span>
83-
</a>
84-
</li>
85-
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
86-
<a href="/sso/v1/attribute-definitions" class="nav-link px-0 align-middle">
87-
<i class="fas fa-list-alt"></i> <span class="ms-1 d-none d-sm-inline">Attribute Definitions</span>
76+
<a href="/sso/v1/attributes/manage" class="nav-link px-0 align-middle">
77+
<i class="fas fa-tags"></i> <span class="ms-1 d-none d-sm-inline">Attributes (ABAC)</span>
8878
</a>
8979
</li>
9080
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">

0 commit comments

Comments
 (0)