Skip to content

[NAE-2241] Anonymous access refactor - #408

Open
renczesstefan wants to merge 22 commits into
release/7.0.2from
NAE-2241
Open

[NAE-2241] Anonymous access refactor#408
renczesstefan wants to merge 22 commits into
release/7.0.2from
NAE-2241

Conversation

@renczesstefan

@renczesstefan renczesstefan commented Feb 3, 2026

Copy link
Copy Markdown
Member

Description

Refactor and reworks anonymous access to more common use concept, update the management of public endpoints, public resources and so.

Implements NAE-2241

Dependencies

No new dependencies were introduced.

Third party dependencies

No new dependencies were introduced.

Blocking Pull requests

There are no dependencies on other PR.

How Has Been This Tested?

This was tested manually and with unit tests.

Test Configuration

Name Tested on
OS macOS Tahoe 26.2
Runtime Java 21
Dependency Manager Maven 3.9.9n
Framework version Spring Boot 3.4.4
Run parameters
Other configuration

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes have been checked, personally or remotely, with @machacjozef
  • I have commented my code, particularly in hard-to-understand areas
  • I have resolved all conflicts with the target branch of the PR
  • I have updated and synced my code with the target branch
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes:
    • Lint test
    • Unit tests
    • Integration tests
  • I have checked my contribution with code analysis tools:
  • I have made corresponding changes to the documentation:
    • Developer documentation
    • User Guides
    • Migration Guides

Summary by CodeRabbit

  • New Features

    • Added anonymous access support for public realms, including configurable anonymous identities and realm-aware request handling.
    • Expanded public access to Petri net, workflow, task, user profile, and preferences endpoints.
    • Added authority checks for matching any of several permissions.
    • Added improved realm detection from request headers, bodies, or defaults.
  • Bug Fixes

    • Improved authentication and authorization resolution for authenticated and anonymous requests.
    • Corrected role-based process permission matching and public-access configuration handling.

- Introduced `anonymousAuthenticationKey` in `SecurityConfigurationProperties` for better anonymous user identification.
- Updated `PublicTaskController` to use `ActorTransformer.toLoggedUser` for cleaner authorization checks.
- Enhanced `LoggedUserConfiguration` to initialize the default user factory with `ActorTransformer.setUserFactory`.
- Refactored `TaskAuthorizationService` to replace manual user transformation with `ActorTransformer.toUser` for consistency.
- Overhauled `PrivateKeyReader` to use `Resource` and Apache Commons IO for improved file handling.
- Added `isAnonymous` method to `AbstractActor` for accurate anonymous user detection.
- Updated `UserServiceImpl` to handle anonymous users correctly using `ActorTransformer`.

These changes ensure better modularity, maintainability, and extend the functionality of authorization and user handling mechanisms.
Introduced `NetgrifHttpRequestTransformFilter` to wrap HTTP requests, enabling custom request handling. Further, added `NetgrifOncePerRequestFilter` as a base proxy filter and improved user authentication with `PublicAuthenticationFilter`. Updated various services to enable anonymous user support and public API access.
Introduced a new RealmFilter to extract the realm information from HTTP headers or request bodies, fallback to a default realm if available, and attach it to the request. Updated related utility functions and PublicAuthenticationFilter to use the extracted realm for better request handling.
Removed the @requiredargsconstructor annotation from RealmFilter and implemented an explicit constructor for dependency injection. Updated NaeSecurityConfiguration to autowire RealmFilter and included it in the security filter chain preceding NetgrifHttpRequestTransformFilter.
Removed the @requiredargsconstructor annotation from RealmFilter and implemented an explicit constructor for dependency injection. Updated NaeSecurityConfiguration to autowire RealmFilter and included it in the security filter chain preceding NetgrifHttpRequestTransformFilter.
…rity context and not auth parameter

- modified controller method properties to remove unnecessary auth param
- instead of auth param, the security context will be used, because of AnonymousAuthentication token
# Conflicts:
#	application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java
Replaced `LoggedUser` with `AbstractUser` across services for enhanced flexibility and alignment. Introduced `@RequiredArgsConstructor` annotation to eliminate redundant constructors and streamline dependency injection. Various private methods were reorganized for clarity and reusability, ensuring consistent coding patterns.
Removed deprecated authentication parameters from controllers, leveraging `ActorTransformer` for handling logged user context. Improved `AnonymousUser` handling by integrating `AnonymousUserRefService` to resolve missing users. Simplified method signatures and enhanced code maintainability.
Replaced specific authority checks with a broader `hasAnyAuthority` method, simplifying permission logic and allowing more flexible role-based access control. Added public endpoints to several APIs to extend functionality for anonymous or unauthorized users where appropriate. Removed unused imports for better code clarity and organization.
Extended `TaskController` to include a new "/public/case" endpoint path for retrieving tasks by cases. Refactored task search logic to leverage new `TaskSearchRequest` and `TaskSearchCaseRequest` builders, improving flexibility and maintainability. Adjusted `AbstractTaskController` to support the updated search functionality with enhanced parameters.
The AnonymousUserRefServiceImpl class was decoupled from the @service annotation and is now registered as a bean in AuthBeansConfiguration. This change also ensures the bean is only created if a UserFactory bean is missing, improving configurability and flexibility.
- Enhanced `AuthorizationService`:
  - Corrected the `hasAnyAuthority` method to use `equals` instead of `contains`.
  - Updated pre-authorization checks in the `TaskController` to include the `ANONYMOUS` role.

- Improved Anonymous User Support:
  - Modified `AnonymousUser` and `AnonymousUserRef` to streamline initialization and compatibility with `Serializable`.
  - Added a new factory method in `DefaultUserFactory` to create `AnonymousUser` instances based on `AnonymousUserRef`.

- Revised `ActorTransformer` Factories:
  - Injected `LoggedUserFactory` and `UserFactory` into `LoggedUserConfiguration` for better dependency management.
  - Updated `ActorTransformer` to support `LoggedUser` with improved user conversion logic.

- Miscellaneous:
  - Renamed constants and clarified field documentation for better readability and accuracy.
@renczesstefan renczesstefan self-assigned this Feb 3, 2026
@renczesstefan renczesstefan added bugfix A change that fixes a bug improvement A change that improves on an existing feature Extra Large labels Feb 3, 2026
@coderabbitai

coderabbitai Bot commented Feb 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change introduces realm-aware anonymous authentication, centralizes request user resolution, updates authorization services to use AbstractUser, and adds public route variants for Petri net, workflow, and task APIs.

Changes

Anonymous identity and security pipeline

Layer / File(s) Summary
Identity and anonymous-user context
application-engine/.../auth/..., nae-object-library/.../auth/domain/*, nae-spring-core-adapter/.../auth/domain/*, nae-user-ce/.../auth/...
Anonymous users can be detected, created from realm references, converted through ActorTransformer, and resolved by UserService.
Realm and security filter pipeline
application-engine/.../configuration/security/*, application-engine/.../utils/*, nae-spring-core-adapter/.../configuration/filters/*, application-engine/src/main/resources/*
Requests are wrapped, realms are resolved, and public realms receive AnonymousAuthenticationToken authentication. Security patterns and configuration properties use the new flow.

Authorization and controllers

Layer / File(s) Summary
Abstract-user authorization contracts
application-engine/.../petrinet/service/*, application-engine/.../workflow/service/*
Authorization interfaces and implementations accept AbstractUser for process, workflow, and task checks.
Authorization-aware controller routes
application-engine/.../petrinet/web/*, application-engine/.../workflow/web/*, application-engine/.../responsebodies/*, application-engine/src/test/*
Controllers resolve users from services and security context, add public mappings and authorization rules, and update reflected links and test authentication setup.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the ticket and summarizes the primary change: refactoring anonymous access and public endpoint handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Removed 'ANONYMOUS' authority from task retrieval endpoint to tighten access control. Added `@NotNull` and `@Size` annotations to enforce input validation for improved reliability.
Deleted the `PublicPetriNetController`, `PublicTaskController`, `PublicUserController`, and `PublicWorkflowController` classes to streamline the codebase and eliminate unused public endpoints. Also updated test cases to reflect these changes by removing dependencies and ensuring existing functionality remains intact.
@coderabbitai coderabbitai Bot added breaking change Fix or feature that would cause existing functionality doesn't work as expected Large labels Feb 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 35

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java (1)

39-64: ⚠️ Potential issue | 🟠 Major

Inject Spring's managed ObjectMapper instead of creating a new instance.

JwtService currently creates its own ObjectMapper in the @PostConstruct method, bypassing Spring's auto-configured bean. This prevents the JWT serialization from using any custom Jackson configuration (such as the authConfigModule defined in JacksonConfiguration or other Spring-registered modules). Compare with RealmServiceImpl, which correctly injects the managed mapper via @Autowired.

Make objectMapper a constructor parameter (following the existing @RequiredArgsConstructor pattern), remove configureObjectMapper(), and let Spring provide the pre-configured instance.

🔧 Suggested refactor
-    private ObjectMapper objectMapper;
+    private final ObjectMapper objectMapper;

     `@PostConstruct`
     private void resolveSecret() {
-        configureObjectMapper();
         try {
             PrivateKeyReader reader = new PrivateKeyReader(properties.getAlgorithm());
             secret = reader.get(properties.getPrivateKey()).getEncoded();

-    private void configureObjectMapper() {
-        objectMapper = new ObjectMapper();
-        objectMapper.findAndRegisterModules();
-    }

Also applies to: 115-118

nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java (1)

348-357: ⚠️ Potential issue | 🟠 Major

Questionable fallback logic: findById returns anonymous user when requested ID not found.

When a user with the specified id is not found, the method falls back to returning an AnonymousUser based on the realm. This changes the semantic contract of findById:

  • Callers expect to find the user with the given ID or receive null
  • Now they may receive an anonymous user with a different ID than requested

This could cause subtle bugs where code expects a specific user but silently receives an anonymous user.

Consider whether this fallback belongs here or if it should be handled at a higher level where the intent to fall back to anonymous is explicit:

🔍 Suggested approach
 `@Override`
 public AbstractUser findById(String id, String realmId) {
     log.debug("Finding user by ID [{}]", id);
     String collectionName = collectionNameProvider.getCollectionNameForRealm(realmId);
     Optional<User> userOpt = userRepository.findById(new ObjectId(id), mongoTemplate, collectionName);
-    if (userOpt.isPresent()) {
-        return userOpt.get();
-    }
-    Optional<AnonymousUserRef> anonymousUserRefOptional = anonymousUserRefService.getRef(realmId);
-    return anonymousUserRefOptional.map(anonymousUserRef -> new AnonymousUser(anonymousUserRef, authorityService.getOrCreate(Authority.anonymous))).orElse(null);
+    return userOpt.orElse(null);
 }
+
+// Add a separate method if anonymous fallback is needed:
+public AbstractUser findByIdOrAnonymous(String id, String realmId) { ... }
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java (1)

72-82: ⚠️ Potential issue | 🟡 Minor

Duplicate Javadoc blocks.

There are duplicate Javadoc comment blocks for the processRoles field (lines 72-81 and the implicit preceding block). Similarly, duplicate Javadocs appear for groupIds (lines 84-89), sessionTimeout (lines 92-99), and groups (lines 102-112). Please remove the redundant comments.

application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java (1)

136-144: 🧹 Nitpick | 🔵 Trivial

Inconsistent authentication resolution pattern.

The search method at line 142 still uses direct cast (LoggedUser) auth.getPrincipal(), while getLoggedUser and preferences now use resolveAuthenticationToken. Similarly, assignRolesToUser (line 220) and savePreferences (line 319) use direct casts.

Consider applying the new helper consistently across all endpoints, or document why certain endpoints don't need it.

application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java (1)

3-4: 🧹 Nitpick | 🔵 Trivial

Remove unused import.

The LoggedUser import on line 4 is no longer used in this interface after the parameter type changes to AbstractUser.

🧹 Proposed cleanup
 import com.netgrif.application.engine.objects.auth.domain.AbstractUser;
-import com.netgrif.application.engine.objects.auth.domain.LoggedUser;
 import com.netgrif.application.engine.objects.petrinet.domain.PetriNet;
application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java (1)

9-9: 🧹 Nitpick | 🔵 Trivial

Remove unused import.

The Authentication import is no longer used after the link-building updates removed the Authentication parameter from method references.

🧹 Proposed cleanup
 import org.springframework.hateoas.Link;
 import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
-import org.springframework.security.core.Authentication;

 import java.io.FileNotFoundException;
application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java (1)

30-30: 🧹 Nitpick | 🔵 Trivial

Unused import: org.springframework.stereotype.Controller.

This import doesn't appear to be used in the class.

🧹 Remove unused import
-import org.springframework.stereotype.Controller;
application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java (1)

4-4: ⚠️ Potential issue | 🟡 Minor

Remove the unused LoggedUser import.

All method signatures in this interface use AbstractUser exclusively. The LoggedUser import on line 4 is not referenced anywhere and should be removed.

🤖 Fix all issues with AI agents
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java`:
- Around line 25-31: In AuthorizationService.hasAnyAuthority, convert the
incoming varargs (rename parameter from authority to authorities) into a
Set<String> once, then check membership against the logged user's authorities
using a contains lookup to avoid the nested Arrays.stream per-item scan;
specifically, build the Set from the authorities parameter and replace the
Arrays.stream(...).anyMatch(...) logic with a single pass that tests
loggedUser.getAuthoritySet() entries' getAuthority() against the Set (use
getLoggedUserFromContext() as currently used or restore impersonation call if
needed).

In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java`:
- Around line 342-351: The method resolveAuthenticationToken currently falls
back to SecurityContextHolder; remove that redundant fallback and simplify it to
just return the principal from the provided Authentication parameter (cast to
LoggedUser) or null if the injected auth is null. Update
resolveAuthenticationToken(Authentication auth) to only check auth and return
(LoggedUser) auth.getPrincipal() when non-null; remove any references to
SecurityContextHolder and its getContext() usage. This keeps callers like
getLoggedUser and preferences relying on the injected Authentication and removes
unnecessary defensive logic.

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java`:
- Around line 422-425: Add a Javadoc comment for the PublicProperties.enabled
field: describe that it toggles whether the public/anonymous access is enabled
for the application, state the default value (true), and briefly note the effect
(e.g., when false public endpoints/authentication for anonymous users are
disabled). Place the Javadoc immediately above the private boolean enabled =
true; declaration inside the PublicProperties class.

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java`:
- Around line 78-85: The JWT parsing code in JwtService uses
userMap.get("stringId") which will be null for older tokens; update the
LoggedUser population (in the block using LinkedHashMap<String,Object> userMap
and LoggedUser/LoggedUserImpl) to fall back to userMap.get("id") when "stringId"
is missing (convert to String if necessary) before calling user.setId(...), so
old tokens still populate user.id; keep the rest of fields the same.

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java`:
- Around line 22-24: The get method in PrivateKeyReader currently opens a
FileInputStream from resource.getFile() and doesn't close it; change it to call
resource.getInputStream() and read the bytes inside a try-with-resources block
so the InputStream is always closed; update the logic in PrivateKeyReader.get
(and any local variables like fileInputStream/keyBytes) to use the InputStream
from resource.getInputStream() and IOUtils.toByteArray(inputStream) inside the
try-with-resources, preserving the same exceptions (IOException,
NoSuchAlgorithmException, InvalidKeySpecException).

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java`:
- Around line 90-94: The AnonymousAuthenticationToken in
PublicAuthenticationFilter uses a hardcoded key "engine"; replace this with a
single source of truth by introducing either a static constant (e.g.
ANONYMOUS_AUTH_KEY) or a configurable property injected into
PublicAuthenticationFilter and use that value when constructing
AnonymousAuthenticationToken (reference AnonymousAuthenticationToken and
PublicAuthenticationFilter) so the key can be changed centrally and is not
hardcoded.
- Around line 98-100: Authentication failures in PublicAuthenticationFilter are
being logged at debug level; change the catch block that logs "Anonymous public
auth failed for realm {}: {}" (currently using log.debug in
PublicAuthenticationFilter) to use log.warn (or log.error if preferred) so
failures are visible in production, keeping the same message parameters and
passing the Exception object to preserve stacktrace.
- Around line 85-88: The code in PublicAuthenticationFilter unsafely casts the
result of ActorTransformer.toLoggedUser(anonymousUser) to LoggedUserImpl which
can throw ClassCastException; change to store the result in the LoggedUser
interface (e.g., LoggedUser userDetails =
ActorTransformer.toLoggedUser(anonymousUser)) and call setSessionTimeout via the
LoggedUser type if it exists, or add a runtime type check (instanceof) before
casting to LoggedUserImpl and handle the alternative case (log/wrap/create
fallback) if it is not an instance; consider adding setSessionTimeout to the
LoggedUser interface (or a helper setter) so PublicAuthenticationFilter no
longer relies on LoggedUserImpl.

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java`:
- Around line 71-76: The realmFilterFilterRegistrationBean `@Bean` is declared
inside a `@Component` (RealmFilter) which prevents CGLIB proxying and can cause
unexpected behavior; move the bean method out of the RealmFilter class into a
dedicated `@Configuration` class (or change the containing type to `@Configuration`)
and define the FilterRegistrationBean<RealmFilter> there (keep the method name
realmFilterFilterRegistrationBean and the FilterRegistrationBean
creation/registration logic intact) so Spring treats it as a full
`@Configuration-managed` bean definition.
- Line 32: The constant REALM_NAME_BODY in RealmFilter is likely misspelled
("realName"); update its value to "realmName" so it matches naming conventions
and aligns with REALM_ID_HEADER usage; locate REALM_NAME_BODY in class
RealmFilter and change the string literal accordingly and run tests to ensure
request parsing still works.
- Around line 115-117: The helper isJsonNodeValueEmpty has inverted semantics
(it returns true when a JsonNode has a non-blank value); rename it to reflect
actual behavior (e.g., hasNonEmptyJsonNodeValue or
isJsonNodeValuePresentAndNotBlank) and update all internal calls in RealmFilter
to use the new name (replace occurrences of isJsonNodeValueEmpty(...) in the
conditional checks around the existing usage sites and ensure negation is
removed/adjusted as appropriate); keep the implementation the same, only change
the method name and its call sites so semantics match the identifier.

In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java`:
- Around line 669-672: getProcessRolesCriteria fails when user.getProcessRoles()
is empty because Criteria.orOperator() cannot accept an empty array; guard it by
checking user.getProcessRoles().isEmpty() and in that case return a Criteria
that matches nothing (e.g. Criteria.where("_id").exists(false)); otherwise build
the existing stream mapping to
Criteria.where("permissions."+role.getStringId()).exists(true) and pass to new
Criteria().orOperator(...). Ensure you update the method getProcessRolesCriteria
and reference user.getProcessRoles() and the permission key construction
("permissions."+role.getStringId()) when making the change.

In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java`:
- Around line 162-166: The current ids.forEach(id -> id = decodeUrl(id)) in
getTransitionReferences does not mutate the list so encoded IDs are still
passed; replace that with a proper decoding step (e.g., use ids.replaceAll(id ->
decodeUrl(id)) or create a new List via
ids.stream().map(this::decodeUrl).collect(...)) and then pass the decoded list
into service.getTransitionReferences; update the code around
getTransitionReferences, the ids variable, and the decodeUrl usage accordingly.

In
`@application-engine/src/main/java/com/netgrif/application/engine/utils/HttpReqRespUtils.java`:
- Around line 57-67: The three extractor methods (extractRealmFromRequest,
extractBodyFromRequest, extractAuthReqTokenFromRequest) should guard against a
null request and avoid unchecked casts: first check that request != null,
retrieve Object param = request.getAdditionalParameter(...), then use instanceof
(pattern matching) to safely cast to Realm/JsonNode/NetgrifAuthenticationToken
and return the typed value or null when absent/incorrect type; this prevents
ClassCastException and NPEs while keeping the existing method signatures.
- Line 12: The HttpReqRespUtils class is a pure utility class and should not be
instantiable; add a private no-arg constructor (e.g., private HttpReqRespUtils()
{ throw new AssertionError("No instances."); }) to the HttpReqRespUtils class to
prevent construction and make intent explicit, keeping all existing static
methods unchanged.

In
`@application-engine/src/main/java/com/netgrif/application/engine/utils/HttpRequestParamConstants.java`:
- Around line 3-7: HttpRequestParamConstants is currently instantiable; make it
a proper constants utility by declaring the class final and adding a private
no-args constructor to prevent instantiation and subclassing (keep the existing
public static final String constants REQUEST_BODY, REALM, AUTH_REQ_TOKEN
unchanged); update the class declaration to final and add a private constructor
to enforce the utility pattern.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java`:
- Around line 81-87: The current return in TaskAuthorizationService (inside
isAssigned check) may NPE when unboxing user.getAttributeValue("anonymous");
change the anonymous check to a null-safe Boolean comparison (e.g. use
Boolean.TRUE.equals(user.getAttributeValue("anonymous"))) and keep the existing
task ownership check task.getUserId().equals(user.getStringId()); ensure this
replacement is applied where the code currently returns
task.getUserId().equals(user.getStringId()) || (Boolean)
user.getAttributeValue("anonymous") so it safely handles nulls.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java`:
- Around line 28-31: The code contains multiple TODOs and commented-out
impersonation-related code in WorkflowAuthorizationService (commented blocks
referencing userHasAtLeastOneRolePermission and userHasUserListPermission and
the return using getSelfOrImpersonated()); create a formal tracking issue in
your issue tracker for "implement impersonation support for authorization" and
replace each TODO/commented block with a single-line TODO that references that
issue ID (e.g., TODO: track impersonation work: ISSUE-1234) so the work is
discoverable; update all occurrences mentioned (the blocks around the current
commented code plus the other spots at 40-41, 62-63, 68-69) to reference the
same issue ID. Ensure the TODO text identifies the affected methods/classes
(WorkflowAuthorizationService and the
userHasAtLeastOneRolePermission/userHasUserListPermission usage) so reviewers
can find and complete the task later.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java`:
- Around line 182-189: Remove the dead, commented-out public search block in
AbstractTaskController: delete the entire commented method that defines
searchPublic (the multi-line commented code starting with “public
PagedModel<LocalisedTaskResource> searchPublic(...)” and ending with its closing
brace). This cleans up dead code and noise; ensure no other references to this
stub remain and run a compile to confirm nothing depended on the commented
method.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java`:
- Around line 15-18: The `@Builder` on TaskSearchRequest currently uses
builderMethodName = "from", causing inconsistency with TaskSearchCaseRequest;
change the annotation to use the conventional builder() entry (remove or set
builderMethodName to "builder") on the TaskSearchRequest class (the
`@Builder`(builderMethodName = "from") declaration), and update all call sites
that use TaskSearchRequest.from() to TaskSearchRequest.builder()...build() so
both request types use the same builder naming.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java`:
- Around line 210-274: The deleteFile and deleteNamedFile handlers currently
call super.deleteFile/deleteNamedFile with requestBody.getParentTaskId() instead
of the path variable taskId, allowing mismatched parentTaskId to bypass
authorization; update both methods (deleteFile and deleteNamedFile) to validate
that requestBody.getParentTaskId() equals the `@PathVariable` taskId and if not
throw new ResponseStatusException(HttpStatus.FORBIDDEN) (add imports for
org.springframework.web.server.ResponseStatusException and
org.springframework.http.HttpStatus), or alternatively ignore
requestBody.getParentTaskId() and pass the path taskId into the super calls to
ensure the operation always targets the path taskId.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java`:
- Around line 244-249: The method getCaseResources currently builds a PagedModel
named resources, calls ResourceLinkAssembler.addLinks(resources, Case.class,
selfLink.getRel().toString()) to enrich it, but then returns a newly constructed
PagedModel from cases.stream() which loses those links; change the return to
return the already prepared resources (from assembler.toModel) so the
link-enriched resources produced by ResourceLinkAssembler are returned by
getCaseResources.

In `@application-engine/src/main/resources/application-dev.yaml`:
- Line 31: The YAML exposes /api/users/me and /api/users/preferences in
server-patterns but those endpoints require authentication; either remove those
two paths from the server-patterns entry so they are protected by default, or
explicitly annotate the controller methods handling these endpoints (e.g., the
methods that serve /api/users/me and /api/users/preferences) with
`@PreAuthorize`("isAuthenticated()") to make intent clear; if you choose to keep
them in server-patterns because an external filter performs auth, add a short
comment in application-dev.yaml documenting that external authentication is
expected for these routes.

In `@application-engine/src/main/resources/application.yaml`:
- Line 88: The server-patterns entry in application.yaml currently includes
/api/users/me and /api/users/preferences which makes them publicly accessible
via anonymous authentication; verify whether this is intentional and either
remove those two patterns from the server-patterns list (so they require normal
authentication) or explicitly document the intended public behavior and add
explicit access checks in the handlers for getUser (or controller handling
/api/users/me) and getUserPreferences (or controller handling
/api/users/preferences) to safe-guard anonymous responses; update
application.yaml and the endpoint documentation accordingly.

In
`@application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy`:
- Around line 117-118: Replace the use of Groovy's coercion "as Set<Authority>"
and "as Set<ProcessRole>" with explicit Set constructors to improve clarity and
type safety: construct authoritySet with a new HashSet<Authority> containing the
results of authorityService.getOrCreate(Authority.user) and
authorityService.getOrCreate(Authority.admin) (refer to the authoritySet: line
and authorityService.getOrCreate(...) calls) and construct processRoles with an
explicit new HashSet<ProcessRole>() (or an immutable Collections.emptySet() if
appropriate) so the types are explicit and readability is improved.
- Around line 110-124: Extract the repeated ActorTransformer.toLoggedUser(user)
call into a local variable (e.g., loggedUser) before creating the authentication
token, then use that variable when constructing the
UsernamePasswordAuthenticationToken and when calling getPassword() and
getAuthoritySet(); ensure the variable replaces all three original calls and
keep the SecurityContextHolder.getContext().setAuthentication(token) usage
unchanged.

In `@nae-spring-core-adapter/pom.xml`:
- Around line 92-101: Add explicit <version>${spring.boot.version}</version>
entries to the two new dependencies to match the existing pattern: update the
spring-boot-starter-tomcat and spring-boot-starter-validation dependency
declarations to include a <version>${spring.boot.version}</version> element so
they are consistent with other starters (e.g., spring-boot-starter-security,
spring-boot-starter-data-mongodb).
- Around line 102-107: The org.jetbrains:annotations dependency currently
hardcodes version 23.0.0; add a new parent POM property named
jetbrains.annotations.version with value 26.0.2-1 and update the dependency in
nae-spring-core-adapter to use ${jetbrains.annotations.version} instead of
23.0.0 (locate the dependency block with groupId org.jetbrains and artifactId
annotations and replace the <version> value to reference the new property).

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java`:
- Around line 32-46: The AnonymousUser constructor currently calls new
ObjectId(ref.getId()) which will throw IllegalArgumentException if ref.getId()
is null; update the AnonymousUser(AnonymousUserRef ref) constructor to
defensively handle a null ref.getId() by validating ref and ref.getId() (or
throwing a clear IllegalArgumentException with a descriptive message) before
constructing the ObjectId, or supply a safe default behavior; reference the
constructor AnonymousUser(AnonymousUserRef) and the use of ObjectId to locate
where to add the null check and error handling.
- Around line 54-62: The two-arg constructor AnonymousUser(AnonymousUserRef ref,
Authority anonymousAuthority) redundantly reinitializes authoritySet after
calling this(ref); instead, remove the new HashSet assignment and reuse the
authoritySet populated by the single-arg constructor: call this(ref) as before,
then only if authoritySet is null or empty add anonymousAuthority (or add
ref.getAuthorities() if present), avoiding overwriting the field. Update
AnonymousUser(AnonymousUserRef, Authority) to rely on the single-arg
constructor's initialization of authoritySet and only mutate it when necessary.

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java`:
- Line 9: Remove the unused import org.bson.types.ObjectId from the
AnonymousUserRef class; update the imports in AnonymousUserRef.java (or run your
IDE's optimize/organize imports) so only used imports remain.

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java`:
- Around line 36-44: The code in NetgrifOncePerRequestFilter casts
ServletRequest to NetgrifHttpServletRequest without a type check which can throw
ClassCastException if NetgrifHttpRequestTransformFilter didn't run; update the
request handling in the method that calls requestNotMatches(...) and
doFilterInternal(...) to first check if request instanceof
NetgrifHttpServletRequest, and if not log a warning with context (include
this.requestMatcher) and call filterChain.doFilter(request, response) (or
alternatively wrap the request with the same transformation used by
NetgrifHttpRequestTransformFilter), ensuring you reference
NetgrifHttpServletRequest, NetgrifOncePerRequestFilter, requestNotMatches,
doFilterInternal and the NetgrifHttpRequestTransformFilter ordering requirement.

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java`:
- Around line 17-20: The constructor
NetgrifHttpServletRequest(HttpServletRequest request, Map<String, Object>
additionalParams) should defensively copy the provided Map instead of storing
the reference: in the NetgrifHttpServletRequest constructor, replace assigning
additionalParams directly with a safe copy (handle null by using
Collections.emptyMap() or new HashMap<>()) and consider wrapping it with
Collections.unmodifiableMap(...) if you want immutability; update any uses of
the field additionalParams accordingly.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java`:
- Around line 25-28: getOrCreateRef in AnonymousUserRefServiceImpl currently
calls processRoleService.getAnonymousRole which throws if the role is missing;
add defensive handling by catching that exception (or checking for null) and
invoking a safe "getOrCreate" fallback on processRoleService (or creating the
anonymous role via the service) before saving the AnonymousUserRef, and/or
change injection to a lazy/ObjectProvider pattern so the role lookup is
deferred; additionally add an explicit bean ordering or dependency between
AnonymousUserRefServiceImpl and the runner that creates the role
(AnonymousRoleRunner/DefaultRealmRunner) using `@DependsOn` or documented
initialization requirements and update docs to state that `@RunnerOrder` alone is
not relied upon.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java`:
- Around line 10-11: Remove the unused imports lombok.Setter and
org.springframework.beans.factory.annotation.Autowired from DefaultUserFactory;
the class already uses `@RequiredArgsConstructor` for constructor injection, so
simply delete those import lines to clean up unused imports and avoid
IDE/compiler warnings.

Comment on lines +25 to +31
@Override
public boolean hasAnyAuthority(String... authority) {
// TODO: impersonation
// LoggedUser loggedUser = userService.getLoggedUserFromContext().getSelfOrImpersonated();
LoggedUser loggedUser = userService.getLoggedUserFromContext();
return loggedUser.getAuthoritySet().stream().anyMatch(it -> Arrays.stream(authority).anyMatch(a -> it.getAuthority().equals(a)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider optimizing authority lookup with a Set.

The current implementation recreates Arrays.stream(authority) for each authority in the user's set, resulting in O(n×m) complexity. Converting the varargs to a Set first would improve this to O(n) with O(1) lookups.

Also, consider renaming the parameter to authorities (plural) for clarity with varargs.

♻️ Proposed optimization
 `@Override`
 public boolean hasAnyAuthority(String... authority) {
     // TODO: impersonation
 //        LoggedUser loggedUser = userService.getLoggedUserFromContext().getSelfOrImpersonated();
     LoggedUser loggedUser = userService.getLoggedUserFromContext();
-    return loggedUser.getAuthoritySet().stream().anyMatch(it -> Arrays.stream(authority).anyMatch(a -> it.getAuthority().equals(a)));
+    Set<String> authoritySet = Set.of(authority);
+    return loggedUser.getAuthoritySet().stream().anyMatch(it -> authoritySet.contains(it.getAuthority()));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public boolean hasAnyAuthority(String... authority) {
// TODO: impersonation
// LoggedUser loggedUser = userService.getLoggedUserFromContext().getSelfOrImpersonated();
LoggedUser loggedUser = userService.getLoggedUserFromContext();
return loggedUser.getAuthoritySet().stream().anyMatch(it -> Arrays.stream(authority).anyMatch(a -> it.getAuthority().equals(a)));
}
`@Override`
public boolean hasAnyAuthority(String... authority) {
// TODO: impersonation
// LoggedUser loggedUser = userService.getLoggedUserFromContext().getSelfOrImpersonated();
LoggedUser loggedUser = userService.getLoggedUserFromContext();
Set<String> authoritySet = Set.of(authority);
return loggedUser.getAuthoritySet().stream().anyMatch(it -> authoritySet.contains(it.getAuthority()));
}
🤖 Prompt for AI Agents
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java`
around lines 25 - 31, In AuthorizationService.hasAnyAuthority, convert the
incoming varargs (rename parameter from authority to authorities) into a
Set<String> once, then check membership against the logged user's authorities
using a contains lookup to avoid the nested Arrays.stream per-item scan;
specifically, build the Set from the authorities parameter and replace the
Arrays.stream(...).anyMatch(...) logic with a single pass that tests
loggedUser.getAuthoritySet() entries' getAuthority() against the Set (use
getLoggedUserFromContext() as currently used or restore impersonation call if
needed).

Comment on lines +342 to +351
private LoggedUser resolveAuthenticationToken(Authentication auth) {
if (auth != null) {
return (LoggedUser) auth.getPrincipal();
}
auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return (LoggedUser) auth.getPrincipal();
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all calls to resolveAuthenticationToken and their context
rg -n "resolveAuthenticationToken" --type java -B 5 -A 5

Repository: netgrif/application-engine

Length of output: 4910


Remove redundant fallback to SecurityContextHolder.

Both usages of resolveAuthenticationToken() are in controller methods (getLoggedUser at line 113 and preferences at line 295) where Authentication is auto-injected by Spring. Based on established patterns in this codebase, the fallback to SecurityContextHolder on lines 346-349 is unnecessary—the injected Authentication parameter will always be populated, and auth.getPrincipal() always returns a valid LoggedUser instance. Simplify the method to remove the defensive fallback logic.

🤖 Prompt for AI Agents
In
`@application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java`
around lines 342 - 351, The method resolveAuthenticationToken currently falls
back to SecurityContextHolder; remove that redundant fallback and simplify it to
just return the principal from the provided Authentication parameter (cast to
LoggedUser) or null if the injected auth is null. Update
resolveAuthenticationToken(Authentication auth) to only check auth and return
(LoggedUser) auth.getPrincipal() when non-null; remove any references to
SecurityContextHolder and its getContext() usage. This keeps callers like
getLoggedUser and preferences relying on the injected Authentication and removes
unnecessary defensive logic.

Comment on lines 422 to +425
public static class PublicProperties {

private boolean enabled = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add Javadoc for the enabled property.

Other properties in PublicProperties have Javadoc comments explaining their purpose. Consider adding documentation for the enabled field to maintain consistency.

📝 Suggested documentation
         `@Data`
         public static class PublicProperties {

+            /**
+             * Enables or disables all public web functionalities.
+             * Default value is true.
+             */
             private boolean enabled = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static class PublicProperties {
private boolean enabled = true;
public static class PublicProperties {
/**
* Enables or disables all public web functionalities.
* Default value is true.
*/
private boolean enabled = true;
🤖 Prompt for AI Agents
In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java`
around lines 422 - 425, Add a Javadoc comment for the PublicProperties.enabled
field: describe that it toggles whether the public/anonymous access is enabled
for the application, state the default value (true), and briefly note the effect
(e.g., when false public endpoints/authentication for anonymous users are
disabled). Place the Javadoc immediately above the private boolean enabled =
true; declaration inside the PublicProperties class.

import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole;
import lombok.Data;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.types.ObjectId;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Unused import.

The ObjectId import is not used in this file. Consider removing it.

-import org.bson.types.ObjectId;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import org.bson.types.ObjectId;
🤖 Prompt for AI Agents
In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java`
at line 9, Remove the unused import org.bson.types.ObjectId from the
AnonymousUserRef class; update the imports in AnonymousUserRef.java (or run your
IDE's optimize/organize imports) so only used imports remain.

Comment on lines +36 to +44
if (requestNotMatches(request)) {
log.trace("Request did not match the required URIs: {}", this.requestMatcher);
filterChain.doFilter(request, response);
return;
}

NetgrifHttpServletRequest typedRequest = (NetgrifHttpServletRequest) request;
doFilterInternal(typedRequest, response, filterChain);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unsafe cast to NetgrifHttpServletRequest without type check.

Line 42 performs a direct cast assuming the request has already been wrapped by NetgrifHttpRequestTransformFilter. If the filter ordering is misconfigured or the transform filter is disabled/bypassed, this will throw a ClassCastException at runtime.

Consider adding a defensive check or documenting the filter ordering requirement.

🛡️ Proposed defensive check
     `@Override`
     protected void doFilterInternal(`@NotNull` HttpServletRequest request,
                                     `@NotNull` HttpServletResponse response,
                                     `@NotNull` FilterChain filterChain) throws ServletException, IOException {
         if (requestNotMatches(request)) {
             log.trace("Request did not match the required URIs: {}", this.requestMatcher);
             filterChain.doFilter(request, response);
             return;
         }

-        NetgrifHttpServletRequest typedRequest = (NetgrifHttpServletRequest) request;
+        if (!(request instanceof NetgrifHttpServletRequest typedRequest)) {
+            log.warn("Request is not a NetgrifHttpServletRequest. Ensure NetgrifHttpRequestTransformFilter runs before this filter.");
+            filterChain.doFilter(request, response);
+            return;
+        }
         doFilterInternal(typedRequest, response, filterChain);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (requestNotMatches(request)) {
log.trace("Request did not match the required URIs: {}", this.requestMatcher);
filterChain.doFilter(request, response);
return;
}
NetgrifHttpServletRequest typedRequest = (NetgrifHttpServletRequest) request;
doFilterInternal(typedRequest, response, filterChain);
}
if (requestNotMatches(request)) {
log.trace("Request did not match the required URIs: {}", this.requestMatcher);
filterChain.doFilter(request, response);
return;
}
if (!(request instanceof NetgrifHttpServletRequest typedRequest)) {
log.warn("Request is not a NetgrifHttpServletRequest. Ensure NetgrifHttpRequestTransformFilter runs before this filter.");
filterChain.doFilter(request, response);
return;
}
doFilterInternal(typedRequest, response, filterChain);
}
🤖 Prompt for AI Agents
In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java`
around lines 36 - 44, The code in NetgrifOncePerRequestFilter casts
ServletRequest to NetgrifHttpServletRequest without a type check which can throw
ClassCastException if NetgrifHttpRequestTransformFilter didn't run; update the
request handling in the method that calls requestNotMatches(...) and
doFilterInternal(...) to first check if request instanceof
NetgrifHttpServletRequest, and if not log a warning with context (include
this.requestMatcher) and call filterChain.doFilter(request, response) (or
alternatively wrap the request with the same transformation used by
NetgrifHttpRequestTransformFilter), ensuring you reference
NetgrifHttpServletRequest, NetgrifOncePerRequestFilter, requestNotMatches,
doFilterInternal and the NetgrifHttpRequestTransformFilter ordering requirement.

Comment on lines +17 to +20
public NetgrifHttpServletRequest(HttpServletRequest request, Map<String, Object> additionalParams) {
super(request);
this.additionalParams = additionalParams;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider defensive copy of additionalParams Map.

The constructor stores the provided Map reference directly. If the caller modifies the Map after constructing the wrapper, it will affect the wrapper's state. Consider creating a defensive copy.

♻️ Proposed fix
 public NetgrifHttpServletRequest(HttpServletRequest request, Map<String, Object> additionalParams) {
     super(request);
-    this.additionalParams = additionalParams;
+    this.additionalParams = new HashMap<>(additionalParams);
 }
🤖 Prompt for AI Agents
In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java`
around lines 17 - 20, The constructor
NetgrifHttpServletRequest(HttpServletRequest request, Map<String, Object>
additionalParams) should defensively copy the provided Map instead of storing
the reference: in the NetgrifHttpServletRequest constructor, replace assigning
additionalParams directly with a safe copy (handle null by using
Collections.emptyMap() or new HashMap<>()) and consider wrapping it with
Collections.unmodifiableMap(...) if you want immutability; update any uses of
the field additionalParams accordingly.

Comment on lines 25 to 28
public AnonymousUserRef getOrCreateRef(String realmId) {
return repository.findByRealmId(realmId)
.orElseGet(() -> repository.save(new AnonymousUserRef(realmId)));
.orElseGet(() -> repository.save(new AnonymousUserRef(realmId, Set.of(authorityService.getOrCreate(Authority.anonymous)), Set.of(processRoleService.getAnonymousRole()))));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n 'ANONYMOUS_ROLE|anonymousRole|getAnonymousRole' --type java -A 3 -B 3 | head -100

Repository: netgrif/application-engine

Length of output: 13681


🏁 Script executed:

# Find the processRoleService implementation and the getAnonymousRole method
rg -n 'class.*ProcessRoleService|def.*getAnonymousRole|public.*getAnonymousRole' --type java -A 10 | head -150

Repository: netgrif/application-engine

Length of output: 5113


🏁 Script executed:

# Search for where AnonymousUserRef or getOrCreateRef is used/called
rg -n 'AnonymousUserRef|getOrCreateRef|AnonymousUserRefService' --type java -B 2 -A 2 | head -100

Repository: netgrif/application-engine

Length of output: 12595


🏁 Script executed:

# Look for initialization order, `@Component`, `@Bean`, `@PostConstruct`, etc.
rg -n '@PostConstruct|@Component|@Service|InitializingBean|ApplicationRunner' --type java -A 5 | grep -E 'class|@PostConstruct|@Component|@Service|InitializingBean|ApplicationRunner' | head -80

Repository: netgrif/application-engine

Length of output: 11764


🏁 Script executed:

# Check if AnonymousUserRefService is used anywhere in `@PostConstruct` or bean initialization
rg -n '@PostConstruct' --type java -A 10 | grep -E '@PostConstruct|enableAnonymUser|getOrCreateRef|getAnonymousRole' | head -50

Repository: netgrif/application-engine

Length of output: 756


🏁 Script executed:

# Look at the full AnonymousRoleRunner implementation to understand timing
cat -n application-engine/src/main/java/com/netgrif/application/engine/startup/runner/AnonymousRoleRunner.java

Repository: netgrif/application-engine

Length of output: 2328


🏁 Script executed:

# Check if AnonymousUserRefServiceImpl has any initialization methods
cat -n nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java

Repository: netgrif/application-engine

Length of output: 1757


🏁 Script executed:

# Check if RealmServiceImpl.enableAnonymUser is called during startup
rg -n 'enableAnonymUser' --type java -B 2 -A 2

Repository: netgrif/application-engine

Length of output: 2056


🏁 Script executed:

# Check the runner order of DefaultRealmRunner and other runners
rg -n '@RunnerOrder' --type java -B 2 -A 1 | head -80

Repository: netgrif/application-engine

Length of output: 7766


🏁 Script executed:

# Check if there are any `@PostConstruct` methods in beans that might call getOrCreateRef
rg -n 'getOrCreateRef|enableAnonymUser' --type java -B 5 | grep -E '@PostConstruct|@Bean|@Service|@Component|getOrCreateRef|enableAnonymUser' | head -60

Repository: netgrif/application-engine

Length of output: 989


🏁 Script executed:

# Find DefaultRealmRunner's RunnerOrder
rg -n 'class DefaultRealmRunner' --type java -B 5 -A 20 | grep -E '@RunnerOrder|class DefaultRealmRunner'

Repository: netgrif/application-engine

Length of output: 374


🏁 Script executed:

# Get the full DefaultRealmRunner to see its order
cat -n application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java | head -50

Repository: netgrif/application-engine

Length of output: 1985


🏁 Script executed:

# Check if there's any documentation or guarantee about runner order execution
rg -n 'RunnerOrder|ApplicationEngineStartupRunner' --type java nae-user-ce nae-user-common -A 2 | head -40

Repository: netgrif/application-engine

Length of output: 52


🏁 Script executed:

# See if there are any integration tests that verify initialization order
find . -name "*Test*.java" -type f | xargs rg -l 'AnonymousRoleRunner\|DefaultRealmRunner\|getOrCreateRef' | head -5

Repository: netgrif/application-engine

Length of output: 52


Add defensive handling or document initialization order guarantee.

processRoleService.getAnonymousRole() throws IllegalStateException if the anonymous role doesn't exist. While AnonymousRoleRunner (order 50) currently runs before DefaultRealmRunner (order 69) which calls this method, the initialization order is implicit and relies on the @RunnerOrder annotation rather than Spring's dependency system. If the runner order changes, execution is skipped, or the database state becomes corrupted, getOrCreateRef() will fail at startup.

Consider adding:

  • Defensive null-handling with lazy initialization fallback
  • Explicit Spring bean ordering (@Depends or ObjectProvider injection pattern)
  • Documentation of the initialization sequence requirement
🤖 Prompt for AI Agents
In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java`
around lines 25 - 28, getOrCreateRef in AnonymousUserRefServiceImpl currently
calls processRoleService.getAnonymousRole which throws if the role is missing;
add defensive handling by catching that exception (or checking for null) and
invoking a safe "getOrCreate" fallback on processRoleService (or creating the
anonymous role via the service) before saving the AnonymousUserRef, and/or
change injection to a lazy/ObjectProvider pattern so the role lookup is
deferred; additionally add an explicit bean ordering or dependency between
AnonymousUserRefServiceImpl and the runner that creates the role
(AnonymousRoleRunner/DefaultRealmRunner) using `@DependsOn` or documented
initialization requirements and update docs to state that `@RunnerOrder` alone is
not relied upon.

Comment on lines +10 to +11
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove unused imports.

@Setter and @Autowired are imported but not used in this class. Constructor injection is handled via @RequiredArgsConstructor.

🧹 Proposed cleanup
 import com.netgrif.application.engine.objects.auth.domain.User;
 import lombok.RequiredArgsConstructor;
-import lombok.Setter;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
🤖 Prompt for AI Agents
In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java`
around lines 10 - 11, Remove the unused imports lombok.Setter and
org.springframework.beans.factory.annotation.Autowired from DefaultUserFactory;
the class already uses `@RequiredArgsConstructor` for constructor injection, so
simply delete those import lines to clean up unused imports and avoid
IDE/compiler warnings.

… with `AbstractUser` and remove redundant methods and imports.
…th `AbstractUser`, optimize imports, and remove deprecated JWT service usage.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
Validation Failed: {"resource":"IssueComment","code":"custom","field":"body","message":"body is too long (maximum is 65536 characters)"} - https://docs.github.com/rest/issues/comments#update-an-issue-comment

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
Validation Failed: {"resource":"IssueComment","code":"custom","field":"body","message":"body is too long (maximum is 65536 characters)"} - https://docs.github.com/rest/issues/comments#update-an-issue-comment

…ame, relocate `LoggedUserConfiguration` and `DefaultLoggedUserFactory`, and clean up redundant code.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
Validation Failed: {"resource":"IssueComment","code":"custom","field":"body","message":"body is too long (maximum is 65536 characters)"} - https://docs.github.com/rest/issues/comments#update-an-issue-comment

@machacjozef
machacjozef changed the base branch from release/7.0.0-rev10 to release/7.0.2 August 3, 2026 08:24
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java (3)

237-252: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

The delete authorization check and the delete operation use different identifiers.

@PreAuthorize evaluates canCallProcessDelete(..., #processId) on the raw path variable. The method body then deletes decodeUrl(processId). If the caller sends a percent-encoded id, the authorization check inspects the encoded string while the service deletes the decoded process. The two identifiers can differ.

Decode once and authorize the decoded value, or reject any processId that changes after decoding.

🛡️ Proposed fix: reject encoded identifiers before the operation
     public MessageResource deletePetriNet(`@PathVariable`("id") String processId, `@RequestParam`(required = false) boolean force) {
         String decodedProcessId = decodeUrl(processId);
-        if (Objects.equals(decodedProcessId, "")) {
+        if (Objects.equals(decodedProcessId, "") || !Objects.equals(decodedProcessId, processId)) {
             log.error("Deleting Petri net [{}] failed: could not decode process ID from URL", processId);
             return MessageResource.errorMessage("Deleting Petri net " + processId + " failed!");
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java`
around lines 237 - 252, Align authorization with the identifier used for
deletion in deletePetriNet: decode processId before authorization, or reject
requests when decoding changes the value so `@PreAuthorize`’s `#processId` cannot
differ from the deleted process identifier. Preserve the existing
invalid-decoding response and ensure canCallProcessDelete and the delete
operation use the same validated identifier.

151-195: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

One handler serves both the private and the public route, so ANONYMOUS leaks onto the authenticated path. Across both controllers the same pattern appears: a method declares value = {"/x", "/public/x"} and a single @PreAuthorize that includes ANONYMOUS. Spring Security evaluates the expression per method invocation, not per mapping, so the authenticated route accepts anonymous callers as well. Several of these endpoints also have no resource-level check.

  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java#L151-L195: split getOne, getTransitionReferences, getDataFieldReferences, getRoles, and getTransactions into separate private and public handlers, and add a per-process check to the public handlers; getRoles currently exposes the full permission map of any process.
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java#L188-L281: split getData, getFile, getNamedFile, and getFilePreview into separate private and public handlers, and add a @taskAuthorizationService check to each, matching the pattern already used by setData and saveFile.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java`
around lines 151 - 195, In
application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java
lines 151-195, split getOne, getTransitionReferences, getDataFieldReferences,
getRoles, and getTransactions into distinct private and public handlers; keep
anonymous access only on public handlers and add a per-process authorization
check to those handlers, including protection for getRoles’ permission map. In
application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java
lines 188-281, split getData, getFile, getNamedFile, and getFilePreview into
private and public handlers, and add the `@taskAuthorizationService` checks used
by setData and saveFile to each relevant handler.

151-195: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Enforce process-level publication on public Petri net endpoints. PublicAuthenticationFilter grants ANONYMOUS only to /api/petrinet/public/** paths in realms with publicAccess; the non-public aliases remain protected. The public handlers perform no process-level publication check, so add one if anonymous access must be limited to explicitly published processes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java`
around lines 151 - 195, Add a process-level publication check to the public
Petri net endpoint handlers getOne, getTransitionReferences,
getDataFieldReferences, getRoles, and getTransactions, using the resolved
process identifier(s) before returning data. Ensure anonymous requests succeed
only for explicitly published processes, while authenticated USER and ADMIN
access retains existing behavior; keep the non-public route aliases protected as
currently configured.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java`:
- Around line 67-70: Update the non-public branch in PublicAuthenticationFilter
so it rejects the request with an appropriate 401/403 response instead of
continuing via filterChain.doFilter. Ensure public-route requests are terminated
before method security evaluates them, while preserving the existing behavior
for realms with public access enabled.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java`:
- Around line 80-86: The public alias in TaskController.getTasksOfCase is
blocked by the USER/ADMIN-only `@PreAuthorize` rule. Make the route behavior
consistent by either permitting ANONYMOUS in the authorization expression or
removing the /public/case/{id} mapping; preserve the protected behavior for the
non-public route.
- Around line 188-194: Add a read-specific authorization method to
ITaskAuthorizationService, then update the `@PreAuthorize` expressions on
TaskController methods getData, getFile, getNamedFile, and getFilePreview to
require that task-level check while preserving their existing authority
requirements and both route aliases.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java`:
- Around line 98-101: Update the exception handling in createCase to keep the
full exception details in log.error while returning only the fixed "Creating
case failed" message from EventOutcomeWithMessageResource.errorMessage; remove
the concatenation with e.getMessage() for anonymous callers.

In `@application-engine/src/main/resources/application-dev.yaml`:
- Line 44: Update the server-patterns configuration to remove the broad
/manage/** wildcard and allow only the required management probe endpoint, such
as /manage/health; keep all other listed public routes unchanged.

In `@application-engine/src/main/resources/application.yaml`:
- Line 89: Remove the /manage/** entry from the server-patterns configuration
while preserving the other explicitly permitted API patterns. Leave management
endpoints subject to their existing protected access rules, including health
endpoints unless separately configured as required.

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java`:
- Around line 28-35: Add an idempotent migration or temporary dual-read support
for legacy anonymous references stored in collection anonym_user, ensuring
public authentication can still resolve them after the collection changes to
anonymousUserRef; update AnonymousUserRef as the entry point or nearest relevant
persistence flow. In Authority, migrate stored ANONYMOUS_USER values to
ANONYMOUS, including authority references contained in anonymous-user records.
Apply changes at
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java
lines 28-35 and
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java
line 45.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java`:
- Line 71: Update the realm creation flow around RealmServiceImpl and its
public-access assignment so enabling public access also invokes
anonymousUserRefService.getOrCreateRef(...) for the new realm. Preserve the same
synchronization between public-access state and anonymous-reference creation
used by realm updates, including removing or retaining the reference when public
access changes.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java`:
- Around line 352-356: Update the anonymous fallback in the surrounding user
lookup method to compare the requested id with AnonymousUserRef.getId() before
constructing AnonymousUser; only the matching anonymous reference may resolve,
while deleted or unknown IDs must return null.

---

Outside diff comments:
In
`@application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java`:
- Around line 237-252: Align authorization with the identifier used for deletion
in deletePetriNet: decode processId before authorization, or reject requests
when decoding changes the value so `@PreAuthorize`’s `#processId` cannot differ from
the deleted process identifier. Preserve the existing invalid-decoding response
and ensure canCallProcessDelete and the delete operation use the same validated
identifier.
- Around line 151-195: In
application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java
lines 151-195, split getOne, getTransitionReferences, getDataFieldReferences,
getRoles, and getTransactions into distinct private and public handlers; keep
anonymous access only on public handlers and add a per-process authorization
check to those handlers, including protection for getRoles’ permission map. In
application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java
lines 188-281, split getData, getFile, getNamedFile, and getFilePreview into
private and public handlers, and add the `@taskAuthorizationService` checks used
by setData and saveFile to each relevant handler.
- Around line 151-195: Add a process-level publication check to the public Petri
net endpoint handlers getOne, getTransitionReferences, getDataFieldReferences,
getRoles, and getTransactions, using the resolved process identifier(s) before
returning data. Ensure anonymous requests succeed only for explicitly published
processes, while authenticated USER and ADMIN access retains existing behavior;
keep the non-public route aliases protected as currently configured.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f7b47d1c-9914-4f5e-8c0c-1d20466f755e

📥 Commits

Reviewing files that changed from the base of the PR and between a5d69f7 and 681711d.

📒 Files selected for processing (58)
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/service/interfaces/IAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/AuthenticationController.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/UserController.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/IJwtService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/PetriNetService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/service/interfaces/IPetriNetAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PublicPetriNetController.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/PetriNetReferenceResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/responsebodies/TransitionReferencesResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/startup/runner/DefaultRealmRunner.java
  • application-engine/src/main/java/com/netgrif/application/engine/utils/HttpReqRespUtils.java
  • application-engine/src/main/java/com/netgrif/application/engine/utils/HttpRequestParamConstants.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/TaskAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/WorkflowAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/ITaskAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/service/interfaces/IWorkflowAuthorizationService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/AbstractTaskController.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/TaskSearchRequest.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/requestbodies/taskSearch/TaskSearchCaseRequest.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/CaseResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/LocalisedTaskResource.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/responsebodies/ResourceLinkAssembler.java
  • application-engine/src/main/resources/application-dev.yaml
  • application-engine/src/main/resources/application.yaml
  • application-engine/src/test/groovy/com/netgrif/application/engine/TestHelper.groovy
  • application-engine/src/test/groovy/com/netgrif/application/engine/workflow/TaskControllerTest.groovy
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractActor.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/ActorTransformer.java
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java
  • nae-spring-core-adapter/pom.xml
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifHttpRequestTransformFilter.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/NetgrifOncePerRequestFilter.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/filters/requests/NetgrifHttpServletRequest.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/AuthBeansConfiguration.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/config/LoggedUserConfiguration.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/AnonymousUserRefServiceImpl.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultLoggedUserFactory.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/DefaultUserFactory.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java
  • nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java
💤 Files with no reviewable changes (9)
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/IJwtService.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtProperties.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicWorkflowController.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/JwtService.java
  • application-engine/src/main/java/com/netgrif/application/engine/workflow/web/PublicTaskController.java
  • application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PublicPetriNetController.java
  • application-engine/src/main/java/com/netgrif/application/engine/configuration/security/jwt/PrivateKeyReader.java
  • application-engine/src/main/java/com/netgrif/application/engine/auth/web/PublicUserController.java
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/configuration/LoggedUserConfiguration.java

Comment on lines +67 to +70
if (!realm.isPublicAccess()) {
log.debug("Public access disabled for realm {}; skipping anon auth", realm.getName());
filterChain.doFilter(request, response);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect public route handlers and their authorization paths.
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java \
  application-engine/src/main/java/com/netgrif/application/engine/auth/service/AuthorizationService.java
do
  ast-grep outline "$file" --items all
  rg -n -C 5 'public/|isPublicAccess|extractRealmFromRequest|AbstractUser|`@PreAuthorize`|authorize' "$file"
done

Repository: netgrif/application-engine

Length of output: 45859


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- candidate security/auth files ---'
fd -t f -i '.*(PublicAuthenticationFilter|NaeSecurityConfiguration|AuthorizationService|Realm|Authentication).*' application-engine/src/main/java

echo '--- filter and security configuration references ---'
rg -n -C 8 'class PublicAuthenticationFilter|isPublicAccess|server-patterns|permitAll|Anonymous|ANONYMOUS|SecurityFilterChain|PreAuthorize|EnableMethodSecurity|extractRealmFromRequest' \
  application-engine/src/main/java application-engine/src/test 2>/dev/null || true

echo '--- filter outline and source ---'
FILTER=$(fd -t f 'PublicAuthenticationFilter.java' application-engine/src/main/java | head -n 1)
ast-grep outline "$FILTER" --items all
cat -n "$FILTER"

echo '--- security configuration outlines ---'
for file in $(fd -t f -i 'NaeSecurityConfiguration.java|.*Security.*Configuration.*\.java' application-engine/src/main/java); do
  echo "### $file"
  ast-grep outline "$file" --items all
  cat -n "$file"
done

echo '--- authorization service source ---'
for file in $(fd -t f 'AuthorizationService.java' application-engine/src/main/java); do
  echo "### $file"
  cat -n "$file"
done

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- candidate security/auth files ---'
fd -t f -i '.*(PublicAuthenticationFilter|NaeSecurityConfiguration|AuthorizationService|Realm|Authentication).*' application-engine/src/main/java

echo '--- filter and security configuration references ---'
rg -n -C 8 'class PublicAuthenticationFilter|isPublicAccess|server-patterns|permitAll|Anonymous|ANONYMOUS|SecurityFilterChain|PreAuthorize|EnableMethodSecurity|extractRealmFromRequest' \
  application-engine/src/main/java application-engine/src/test 2>/dev/null || true

echo '--- filter and security source ---'
for file in $(fd -t f -i 'PublicAuthenticationFilter.java|NaeSecurityConfiguration.java|AuthorizationService.java' application-engine/src/main/java); do
  echo "### $file"
  ast-grep outline "$file" --items all
  cat -n "$file"
done

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- exact files ---'
fd -t f 'PublicAuthenticationFilter.java|NaeSecurityConfiguration.java|AuthorizationService.java' application-engine/src/main/java

echo '--- security-related Java files ---'
rg --files application-engine/src/main/java | rg '/(security|auth|realm)/|Security|Authentication|Realm' | head -n 200

echo '--- exact filter ---'
for file in $(fd -t f 'PublicAuthenticationFilter.java' application-engine/src/main/java); do
  wc -l "$file"
  cat -n "$file"
done

echo '--- security configuration candidates ---'
for file in $(rg -l 'server-patterns|SecurityFilterChain|authorizeHttpRequests|permitAll|addFilter' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 12 'server-patterns|SecurityFilterChain|authorizeHttpRequests|permitAll|addFilter|anonymous|Anonymous|EnableMethodSecurity|PreAuthorize' "$file"
done

echo '--- anonymous and authorization implementations ---'
for file in $(rg -l 'ANONYMOUS|AnonymousAuthenticationToken|hasAnyAuthority|class AuthorizationService|isPublicAccess|extractRealmFromRequest' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 12 'ANONYMOUS|AnonymousAuthenticationToken|hasAnyAuthority|class AuthorizationService|isPublicAccess|extractRealmFromRequest' "$file"
done

Repository: netgrif/application-engine

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- exact files ---'
fd -t f 'PublicAuthenticationFilter.java|NaeSecurityConfiguration.java|AuthorizationService.java' application-engine/src/main/java

echo '--- exact filter ---'
for file in $(fd -t f 'PublicAuthenticationFilter.java' application-engine/src/main/java); do
  wc -l "$file"
  cat -n "$file"
done

echo '--- security configuration candidates ---'
for file in $(rg -l 'server-patterns|SecurityFilterChain|authorizeHttpRequests|permitAll|addFilter' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 12 'server-patterns|SecurityFilterChain|authorizeHttpRequests|permitAll|addFilter|anonymous|Anonymous|EnableMethodSecurity|PreAuthorize' "$file"
done

echo '--- anonymous and authorization implementations ---'
for file in $(rg -l 'ANONYMOUS|AnonymousAuthenticationToken|hasAnyAuthority|class AuthorizationService|isPublicAccess|extractRealmFromRequest' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 12 'ANONYMOUS|AnonymousAuthenticationToken|hasAnyAuthority|class AuthorizationService|isPublicAccess|extractRealmFromRequest' "$file"
done

Repository: netgrif/application-engine

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- filter ordering and realm loading ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/security/SecurityContextFilter.java \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java
do
  echo "### $file"
  cat -n "$file" | sed -n '1,180p'
done

echo '--- user context methods ---'
rg -n -C 15 'getLoggedUserFromContext|getLoggedUser\(' application-engine/src/main/java/com/netgrif/application/engine/auth/service application-engine/src/main/java/com/netgrif/application/engine/security application-engine/src/main/java/com/netgrif/application/engine/configuration

echo '--- method-security configuration ---'
rg -n -C 12 'EnableMethodSecurity|EnableGlobalMethodSecurity|MethodSecurity|PreAuthorizeAuthorizationManager|`@PreAuthorize`' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine -g '*.java' | head -n 400

echo '--- pattern resolution ---'
rg -n -C 20 'getPatterns|serverPatterns|ServerPatterns|SecurityConfigurationProperties' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/properties -g '*.java'

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- filter ordering and realm loading ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/security/RealmFilter.java \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/security/SecurityContextFilter.java \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java
do
  echo "### $file"
  cat -n "$file" | sed -n '1,180p'
done

echo '--- user context methods ---'
rg -n -C 15 'getLoggedUserFromContext|getLoggedUser\(' \
  application-engine/src/main/java/com/netgrif/application/engine/auth/service \
  application-engine/src/main/java/com/netgrif/application/engine/security \
  application-engine/src/main/java/com/netgrif/application/engine/configuration

echo '--- method-security configuration ---'
rg -n -C 12 'EnableMethodSecurity|EnableGlobalMethodSecurity|MethodSecurity|PreAuthorizeAuthorizationManager|`@PreAuthorize`' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine -g '*.java' | head -n 400

echo '--- pattern resolution ---'
rg -n -C 20 'getPatterns|serverPatterns|ServerPatterns|SecurityConfigurationProperties' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/properties -g '*.java'

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UserService declaration and context methods ---'
USER_SERVICE=$(rg -l 'class UserService' application-engine/src/main/java -g '*.java' | head -n 1)
echo "$USER_SERVICE"
rg -n -C 25 'class UserService|getLoggedUserFromContext|getLoggedUser\(' "$USER_SERVICE"

echo '--- security context service ---'
for file in $(rg -l 'class SecurityContextService|isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 25 'class SecurityContextService|isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext' "$file"
done

echo '--- getPatterns implementation ---'
for file in application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java; do
  rg -n -C 30 'getPatterns|getStaticPatterns|getServerPatterns' "$file"
done

echo '--- controller base mappings ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  rg -n '^`@RequestMapping`|^public class|`@PreAuthorize`|@(Get|Post|Put|Delete|Request)Mapping' "$file"
done

Repository: netgrif/application-engine

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UserService declaration and context methods ---'
USER_SERVICE=$(rg -l 'class UserService' application-engine/src/main/java -g '*.java' | head -n 1)
echo "$USER_SERVICE"
rg -n -C 25 'class UserService|getLoggedUserFromContext|getLoggedUser\(' "$USER_SERVICE"

echo '--- security context service ---'
for file in $(rg -l 'class SecurityContextService|isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext' application-engine/src/main/java -g '*.java'); do
  echo "### $file"
  rg -n -C 25 'class SecurityContextService|isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext' "$file"
done

echo '--- getPatterns implementation ---'
rg -n -C 30 'getPatterns|getStaticPatterns|getServerPatterns' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java

echo '--- controller mappings ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  rg -n '^`@RequestMapping`|^public class|`@PreAuthorize`|@(Get|Post|Put|Delete|Request)Mapping' "$file"
done

Repository: netgrif/application-engine

Length of output: 216


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UserService files and declarations ---'
fd -t f -i 'UserService' application-engine/src
rg -n -C 20 'interface UserService|class UserService|UserService[[:space:]]*=|getLoggedUserFromContext|getLoggedUser\(' \
  application-engine/src/main application-engine/src/test -g '*.java' -g '*.groovy' -g '*.kt' 2>/dev/null || true

echo '--- security context implementation ---'
rg -l 'isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext' application-engine/src/main -g '*.java' -g '*.groovy' |
while read -r file; do
  echo "### $file"
  rg -n -C 25 'isAuthenticatedPrincipalLoggedUser|forceReloadSecurityContext|class |interface ' "$file"
done

echo '--- pattern configuration ---'
rg -n -C 30 'getPatterns|getStaticPatterns|getServerPatterns' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/SecurityConfigurationProperties.java

echo '--- controller class and request mappings ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  rg -n '^`@RequestMapping`|^public class|`@PreAuthorize`|@(Get|Post|Put|Delete|Request)Mapping' "$file"
done

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UserService filenames ---'
fd -t f -i 'UserService' application-engine/src | head -n 100

echo '--- exact UserService symbols ---'
rg -n 'UserService' application-engine/src/main/java -g '*.java' | \
  grep -E 'interface|class|enum|record|extends|implements|getLoggedUserFromContext' | head -n 120

echo '--- exact context method locations ---'
rg -l 'getLoggedUserFromContext' application-engine/src/main/java -g '*.java' |
while read -r file; do
  echo "### $file"
  rg -n -C 8 'getLoggedUserFromContext' "$file"
done

echo '--- exact pattern method ---'
rg -n -C 12 'protected String\[\] getPatterns|String\[\] getPatterns|getStaticPatterns|getServerPatterns' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java

echo '--- public route expressions only ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  awk '
    /@PreAuthorize|@(Get|Post|Put|Delete|Request)Mapping/ {
      line=NR ":" $0
      if ($0 ~ /public\// || hold ~ /public\//) print line
      hold=$0
      next
    }
    { if (hold ~ /public\// && $0 ~ /^    public /) { print NR ":" $0; hold="" } }
  ' "$file"
done

Repository: netgrif/application-engine

Length of output: 14237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- UserService filenames ---'
fd -t f -i 'UserService' application-engine/src | head -n 100

echo '--- exact UserService symbols ---'
rg -n 'UserService' application-engine/src/main/java -g '*.java' |
  grep -E 'interface|class|enum|record|extends|implements|getLoggedUserFromContext' |
  head -n 120

echo '--- exact context method locations ---'
rg -l 'getLoggedUserFromContext' application-engine/src/main/java -g '*.java' |
while read -r file; do
  echo "### $file"
  rg -n -C 8 'getLoggedUserFromContext' "$file"
done

echo '--- exact pattern method ---'
rg -n -C 12 'protected String\[\] getPatterns|String\[\] getPatterns|getStaticPatterns|getServerPatterns' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java

echo '--- public route expressions only ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  awk '
    /@PreAuthorize|@(Get|Post|Put|Delete|Request)Mapping/ {
      line=NR ":" $0
      if ($0 ~ /public\// || hold ~ /public\//) print line
      hold=$0
      next
    }
    { if (hold ~ /public\// && $0 ~ /^    public /) { print NR ":" $0; hold="" } }
  ' "$file"
done

Repository: netgrif/application-engine

Length of output: 14237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- security-context persistence configuration ---'
rg -n -C 20 'SecurityContextRepository|SecurityContextHolderFilter|SecurityContextPersistenceFilter|requireExplicitSave|securityContext\(|SessionCreationPolicy|HttpSessionSecurityContextRepository|NullSecurityContextRepository' \
  application-engine/src/main/java application-engine/src/test -g '*.java' -g '*.groovy' -g '*.yaml' -g '*.yml' || true

echo '--- anonymous/public security tests ---'
rg -n -C 12 'PublicAuthenticationFilter|AnonymousAuthenticationToken|publicAccess|server-patterns|ANONYMOUS|non-public|nonpublic|realm' \
  application-engine/src/test application-engine/src/main -g '*.java' -g '*.groovy' -g '*.yaml' -g '*.yml' | head -n 600

echo '--- relevant source verifier ---'
python3 - <<'PY'
from pathlib import Path
import re

filter_path = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java")
security_path = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java")
app_path = Path("application-engine/src/main/java/com/netgrif/application/engine/ApplicationEngine.java")

f = filter_path.read_text()
s = security_path.read_text()
a = app_path.read_text()

checks = {
    "filter accepts any authenticated token before realm check":
        f.find("if (current != null && current.isAuthenticated())") <
        f.find("Realm realm = HttpReqRespUtils.extractRealmFromRequest(request)"),
    "non-public branch occurs after current-token short circuit":
        f.find("if (!realm.isPublicAccess())") >
        f.find("if (current != null && current.isAuthenticated())"),
    "filter creates an AnonymousAuthenticationToken":
        "new AnonymousAuthenticationToken" in f,
    "session policy is ALWAYS":
        "SessionCreationPolicy.ALWAYS" in s,
    "method security is enabled":
        "`@EnableMethodSecurity`" in a,
}
for name, value in checks.items():
    print(f"{name}: {value}")

public_files = [
    Path("application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java"),
]
for p in public_files:
    text = p.read_text()
    matches = re.findall(r'`@PreAuthorize`\("([^"]+)"\)\s*.*?@(GetMapping|PostMapping|PutMapping|DeleteMapping|RequestMapping)\(([^)]*public[^)]*)\)', text, re.S)
    print(f"{p}: public mappings with ANONYMOUS in expression = "
          f"{sum('ANONYMOUS' in expr for expr, _, _ in matches)}, total matched = {len(matches)}")
PY

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- security-context persistence configuration ---'
rg -n -C 20 'SecurityContextRepository|SecurityContextHolderFilter|SecurityContextPersistenceFilter|requireExplicitSave|securityContext\(|SessionCreationPolicy|HttpSessionSecurityContextRepository|NullSecurityContextRepository' \
  application-engine/src/main/java application-engine/src/test -g '*.java' -g '*.groovy' -g '*.yaml' -g '*.yml' || true

echo '--- anonymous/public security tests ---'
rg -n -C 12 'PublicAuthenticationFilter|AnonymousAuthenticationToken|publicAccess|server-patterns|ANONYMOUS|non-public|nonpublic|realm' \
  application-engine/src/test application-engine/src/main -g '*.java' -g '*.groovy' -g '*.yaml' -g '*.yml' |
  head -n 600

echo '--- relevant source verifier ---'
python3 - <<'PY'
from pathlib import Path
import re

filter_path = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java")
security_path = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java")
app_path = Path("application-engine/src/main/java/com/netgrif/application/engine/ApplicationEngine.java")

f = filter_path.read_text()
s = security_path.read_text()
a = app_path.read_text()

checks = {
    "filter accepts any authenticated token before realm check":
        f.find("if (current != null && current.isAuthenticated())") <
        f.find("Realm realm = HttpReqRespUtils.extractRealmFromRequest(request)"),
    "non-public branch occurs after current-token short circuit":
        f.find("if (!realm.isPublicAccess())") >
        f.find("if (current != null && current.isAuthenticated())"),
    "filter creates an AnonymousAuthenticationToken":
        "new AnonymousAuthenticationToken" in f,
    "session policy is ALWAYS":
        "SessionCreationPolicy.ALWAYS" in s,
    "method security is enabled":
        "`@EnableMethodSecurity`" in a,
}
for name, value in checks.items():
    print(f"{name}: {value}")

public_files = [
    Path("application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java"),
]
for p in public_files:
    text = p.read_text()
    matches = re.findall(
        r'`@PreAuthorize`\("([^"]+)"\)\s*.*?'
        r'@(GetMapping|PostMapping|PutMapping|DeleteMapping|RequestMapping)\(([^)]*public[^)]*)\)',
        text,
        re.S,
    )
    print(
        f"{p}: public mappings with ANONYMOUS in expression = "
        f"{sum('ANONYMOUS' in expr for expr, _, _ in matches)}, "
        f"total matched = {len(matches)}"
    )
PY

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Spring Security and session dependency versions ---'
rg -n -C 3 'spring-boot|spring-security|spring-session|security.version|<parent>|<version>' \
  pom.xml application-engine/pom.xml gradle.properties build.gradle settings.gradle 2>/dev/null || true

echo '--- authentication/context writes ---'
rg -n -C 8 'setAuthentication|SecurityContextHolder|getContext\(\)|saveContext|SecurityContextRepository|X-Anonymous-Token' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine/security \
  application-engine/src/main/java/com/netgrif/application/engine/auth \
  -g '*.java' | head -n 800

echo '--- custom authentication filter classes ---'
rg -l 'extends .*Authentication|BasicAuthenticationFilter|OncePerRequestFilter|AuthenticationFilter|AuthenticationSuccessHandler' \
  application-engine/src/main/java/com/netgrif/application/engine/configuration \
  application-engine/src/main/java/com/netgrif/application/engine/auth \
  -g '*.java' |
while read -r file; do
  echo "### $file"
  rg -n -C 15 'class |setAuthentication|saveContext|SecurityContextHolder|AuthenticationSuccess' "$file"
done

echo '--- concise invariant verifier ---'
python3 - <<'PY'
from pathlib import Path

f = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java").read_text()
s = Path("application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java").read_text()
a = Path("application-engine/src/main/java/com/netgrif/application/engine/ApplicationEngine.java").read_text()

items = [
    ("early authenticated-token return precedes realm extraction",
     f.index("if (current != null && current.isAuthenticated())") <
     f.index("Realm realm = HttpReqRespUtils.extractRealmFromRequest(request)")),
    ("non-public check is after early return",
     f.index("if (!realm.isPublicAccess())") >
     f.index("if (current != null && current.isAuthenticated())")),
    ("anonymous token is created",
     "new AnonymousAuthenticationToken" in f),
    ("session policy is ALWAYS",
     "SessionCreationPolicy.ALWAYS" in s),
    ("method security is enabled",
     "`@EnableMethodSecurity`" in a),
    ("anonymous token is explicitly saved by this filter",
     "saveContext" in f),
]
for label, result in items:
    print(f"{label}: {result}")
PY

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- effective version properties ---'
rg -n -C 2 '<spring\.boot\.version>|<spring-boot.version>|<spring\.security|<spring-session|<spring\.session' pom.xml application-engine/pom.xml

echo '--- anonymous principal and security service ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUser.java \
  application-engine/src/main/java/com/netgrif/application/engine/security/service/SecurityContextService.java
do
  echo "### $file"
  if test -f "$file"; then
    cat -n "$file" | sed -n '1,180p'
  else
    echo "not found"
  fi
done

echo '--- all explicit context persistence references ---'
rg -n 'saveContext|SecurityContextRepository|SecurityContextHolderFilter|SecurityContextPersistenceFilter|requireExplicitSave|securityContext\(' \
  . -g '*.java' -g '*.groovy' -g '*.xml' -g '*.yaml' -g '*.yml' \
  --glob '!**/target/**' --glob '!**/build/**' || true

Repository: netgrif/application-engine

Length of output: 6332


🌐 Web query:

Spring Security 7 SecurityContextHolderFilter default explicit save SecurityContextPersistenceFilter SessionCreationPolicy ALWAYS custom filter setAuthentication persists session

💡 Result:

In Spring Security 7, the SecurityContextHolderFilter has replaced the deprecated SecurityContextPersistenceFilter as the standard mechanism for loading the SecurityContext [1][2][3]. Key behavioral changes and implementation details include: 1. Explicit Saving Requirement: Unlike its predecessor, SecurityContextHolderFilter only loads the SecurityContext from the SecurityContextRepository and does not automatically save it back [1][4]. When you set the SecurityContextHolder (e.g., via a custom filter), you must manually invoke the SecurityContextRepository to persist the context if needed [1][5]. 2. Configuration: To align with this behavior, you can configure explicit saving using the security context DSL [1][5]: http.securityContext(securityContext -> securityContext.requireExplicitSave(true)) 3. Custom Filter Implementation: If you are setting the authentication in a custom filter, you are now responsible for the save operation. The pattern is [1][5]: SecurityContextHolder.setContext(securityContext); securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse); 4. SessionCreationPolicy.ALWAYS: Using SessionCreationPolicy.ALWAYS ensures a session is created for every request, but it does not bypass the requirement for explicit saving [3]. While Spring Security previously handled automatic persistence through SecurityContextPersistenceFilter (which could be forced via session configuration), in modern Spring Security, you must ensure your custom logic explicitly persists the context using the repository to maintain session-based authentication [1][2]. Applications should use either SecurityContextHolderFilter or SecurityContextPersistenceFilter, never both [2][6]. Deprecating the automatic persistence filter improves performance and removes the ambiguity of implicit, often unnecessary, writes to the session [2][3].

Citations:


🌐 Web query:

github netgrif nae-user-ce UserService getLoggedUserFromContext

💡 Result:

In the NETGRIF Application Engine (NAE), the getLoggedUserFromContext method is part of the UserService class (and the IUserService interface) within the server-side Java backend [1][2]. It is defined with the signature public LoggedUser getLoggedUserFromContext [1][2]. This method retrieves the currently authenticated user from the security context of the application [1][3]. It is important to distinguish this from the client-side (Angular) UserService found in the @netgrif/components-core library [4]. The client-side UserService does not contain a getLoggedUserFromContext method; instead, it uses methods like loadUser, which interacts with the UserResourceService to fetch user data from the /api/user/me endpoint [5][6][7][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for url in \
  'https://engine.netgrif.com/javadoc/com/netgrif/application/engine/auth/service/UserService.html' \
  'https://engine.netgrif.com/javadoc/com/netgrif/application/engine/auth/service/interfaces/IUserService.html'
do
  echo "### $url"
  curl -LfsS "$url" |
    sed 's/<[^>]*>/ /g' |
    tr -s '[:space:]' ' ' |
    grep -o -i -E '.{0,500}getLoggedUserFromContext.{0,1000}' |
    head -n 5 || true
done

Repository: netgrif/application-engine

Length of output: 6132


🏁 Script executed:

#!/bin/bash
set -euo pipefail

base='https://repo1.maven.org/maven2/com/netgrif/nae-user-ce/7.0.2'
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

if curl -LfsS "$base/nae-user-ce-7.0.2-sources.jar" -o "$tmp/user.jar"; then
  unzip -l "$tmp/user.jar" | grep -E 'UserService|AbstractUserService'
  for entry in $(unzip -Z1 "$tmp/user.jar" | grep -E '(UserService|AbstractUserService)\.java$'); do
    echo "### $entry"
    unzip -p "$tmp/user.jar" "$entry" |
      grep -n -C 15 'getLoggedUserFromContext'
  done
else
  echo 'matching source JAR is not available'
fi

Repository: netgrif/application-engine

Length of output: 259


🏁 Script executed:

#!/bin/bash
set -euo pipefail

url='https://repo1.maven.org/maven2/com/netgrif/nae-user-ce/7.0.2/nae-user-ce-7.0.2-sources.jar'
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
curl -LfsS "$url" -o "$tmp/user.jar"

entry='com/netgrif/application/engine/auth/service/UserServiceImpl.java'
unzip -p "$tmp/user.jar" "$entry" |
  grep -n -C 30 'getLoggedUserFromContext'

Repository: netgrif/application-engine

Length of output: 4040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- configured server patterns ---'
rg -n -C 5 'server-patterns|public' \
  application-engine/src/main/resources application-engine/src/test/resources \
  -g '*.yaml' -g '*.yml' -g '*.properties' || true

echo '--- controller class mappings ---'
for file in \
  application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java \
  application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java
do
  echo "### $file"
  sed -n '55,90p' "$file"
done

echo '--- concise principal mismatch verifier ---'
python3 - <<'PY'
from pathlib import Path

filter_text = Path(
    "application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java"
).read_text()
security_text = Path(
    "application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java"
).read_text()

checks = [
    ("non-public branch skips custom anonymous token",
     "if (!realm.isPublicAccess())" in filter_text and
     "SecurityContextHolder.getContext().setAuthentication(token)" in filter_text and
     filter_text.index("if (!realm.isPublicAccess())") <
     filter_text.index("SecurityContextHolder.getContext().setAuthentication(token)")),
    ("framework anonymous authentication is not disabled",
     ".anonymous(" not in security_text and
     ".anonymous()" not in security_text),
    ("public HTTP patterns are permitted",
     ".requestMatchers(getPatterns()).permitAll()" in security_text),
    ("method security is enabled",
     "`@EnableWebSecurity`" in security_text and
     "`@EnableMethodSecurity`" in Path(
         "application-engine/src/main/java/com/netgrif/application/engine/ApplicationEngine.java"
     ).read_text()),
]
for label, result in checks:
    print(f"{label}: {result}")

for file in [
    Path("application-engine/src/main/java/com/netgrif/application/engine/petrinet/web/PetriNetController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java"),
    Path("application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java"),
]:
    text = file.read_text()
    print(f"{file}: public aliases={text.count('/public/')}, "
          f"ANONYMOUS expressions={text.count(\"'ANONYMOUS'\")}")
PY

Repository: netgrif/application-engine

Length of output: 13523


🌐 Web query:

Spring Security anonymous authentication default AnonymousAuthenticationToken principal anonymousUser ROLE_ANONYMOUS default enabled

💡 Result:

In Spring Security, anonymous authentication is enabled by default [1][2]. When no authentication object is present in the SecurityContextHolder, the AnonymousAuthenticationFilter automatically populates it with an AnonymousAuthenticationToken [3][4]. This default AnonymousAuthenticationToken is configured with the following characteristics: - Principal: "anonymousUser" [5] - Granted Authorities: "ROLE_ANONYMOUS" [5][1] The feature is managed by the AnonymousConfigurer, which integrates an AnonymousAuthenticationFilter and an AnonymousAuthenticationProvider into the security filter chain [1][2]. While enabled by default, you can customize or disable anonymous authentication via the HttpSecurity configuration (e.g., using.anonymous(anonymous -> anonymous.disable) in a lambda-based configuration) [4].

Citations:


Return 401/403 for non-public realms instead of evaluating public routes. When Realm.isPublicAccess() is false, Spring Security still installs its default anonymous token. UserServiceImpl.getLoggedUserFromContext() casts its "anonymousUser" principal to LoggedUser, causing a ClassCastException during @PreAuthorize on /api/petrinet/public/**, /api/workflow/public/**, and /api/task/public/**. Reject the request before method security or handle framework anonymous authentication explicitly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/configuration/security/PublicAuthenticationFilter.java`
around lines 67 - 70, Update the non-public branch in PublicAuthenticationFilter
so it rejects the request with an appropriate 401/403 response instead of
continuing via filterChain.doFilter. Ensure public-route requests are terminated
before method security evaluates them, while preserving the existing behavior
for realms with public access enabled.

Comment on lines 80 to 86
@Override
@PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN')")
@Operation(summary = "Get tasks of the case", security = {@SecurityRequirement(name = "BasicAuth")})
@GetMapping(value = "/case/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = {"/case/{id}", "/public/case/{id}"}, produces = MediaType.APPLICATION_JSON_VALUE)
public List<TaskReference> getTasksOfCase(@PathVariable("id") String caseId, Locale locale) {
return super.getTasksOfCase(caseId, locale);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The /public/case/{id} route cannot be reached anonymously.

getTasksOfCase maps both /case/{id} and /public/case/{id}, but @PreAuthorize allows only USER and ADMIN. Every anonymous request to the public alias returns 403. Either add ANONYMOUS to the expression or remove the public mapping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java`
around lines 80 - 86, The public alias in TaskController.getTasksOfCase is
blocked by the USER/ADMIN-only `@PreAuthorize` rule. Make the route behavior
consistent by either permitting ANONYMOUS in the authorization expression or
removing the /public/case/{id} mapping; preserve the protected behavior for the
non-public route.

Comment on lines 188 to 194
@Override
@PreAuthorize("@authorizationService.hasAnyAuthority('USER', 'ADMIN', 'ANONYMOUS')")
@Operation(summary = "Get all task data", security = {@SecurityRequirement(name = "BasicAuth")})
@GetMapping(value = "/{id}/data", produces = MediaTypes.HAL_JSON_VALUE)
@GetMapping(value = {"/{id}/data", "/public/{id}/data"}, produces = MediaTypes.HAL_JSON_VALUE)
public EntityModel<EventOutcomeWithMessage> getData(@PathVariable("id") String taskId, Locale locale) {
return super.getData(taskId, locale);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: List the task authorization contract to find a suitable read check.
set -euo pipefail

fd -e java 'ITaskAuthorizationService|TaskAuthorizationService' -x cat -n {}

Repository: netgrif/application-engine

Length of output: 10819


🏁 Script executed:

#!/bin/bash
set -euo pipefail

controller=$(fd -t f -e java 'TaskController.java' | head -n 1)
printf '%s\n' "=== controller: $controller ==="
ast-grep outline "$controller" || true
printf '%s\n' '=== controller authorization and inheritance ==='
rg -n -C 3 'class TaskController|extends |getData|getFile|getNamedFile|getFilePreview|canCallSave|PreAuthorize|public/' "$controller"

printf '%s\n' '=== superclass and read implementations ==='
rg -n -C 4 'getData|getFile|getNamedFile|getFilePreview|class .*Task.*Controller|canCallGet' --glob '*.java' .

printf '%s\n' '=== authorization expression and anonymous-user setup ==='
rg -n -C 4 'hasAnyAuthority|anonymous|Anonymous|PreAuthorize|SecurityExpressionHandler|LoggedUser|userService.getLoggedUser' --glob '*.java' --glob '*.xml' --glob '*.yml' --glob '*.yaml' .

Repository: netgrif/application-engine

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f -e java 'TaskController.java|.*Task.*Controller.*java' -x sh -c '
  for f do
    echo "=== $f ==="
    rg -n -C 8 "class .*Controller|extends |getData|getFile|getNamedFile|getFilePreview|`@PreAuthorize`|`@GetMapping`|`@PostMapping`|`@PutMapping`" "$f"
  done
' sh

echo '=== all read endpoint declarations and service calls ==='
rg -n -C 5 'getData|getFile|getNamedFile|getFilePreview' --glob '*.java' .

echo '=== security configuration and anonymous authority ==='
rg -n -C 6 'ANONYMOUS|anonymous|AnonymousAuthentication|GrantedAuthority|hasAnyAuthority|PreAuthorize' --glob '*.java' --glob '*.xml' --glob '*.yml' --glob '*.yaml' .

Repository: netgrif/application-engine

Length of output: 25862


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== data-service read implementations ==='
rg -n -C 12 'getDataGroups|getFileByTask|getFileByTaskAndName' \
  application-engine/src/main/java/com/netgrif/application/engine \
  --glob '*.java'

echo '=== public authentication matcher configuration ==='
rg -n -C 8 'serverPatterns|PublicAuthenticationFilter|security.*pattern|public.*pattern' \
  application-engine/src/main/java \
  --glob '*.java' --glob '*.yml' --glob '*.yaml' --glob '*.properties'

echo '=== anonymous authority representation ==='
rg -n -C 8 'class AnonymousUser|enum Authority|anonymous.*authority|Authority\.anonymous|ROLE_ANONYMOUS' \
  application-engine/src/main/java \
  --glob '*.java'

Repository: netgrif/application-engine

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

controller = next(Path('.').rglob('TaskController.java'))
source = controller.read_text()
methods = ('getData', 'getFile', 'getNamedFile', 'getFilePreview',
           'setData', 'saveFile', 'saveFiles')

for method in methods:
    match = re.search(
        rf'(?P<annotations>(?:\s*@[^;\n]+\n)+)\s*public\s+[^;{{]+?\s+{method}\s*\(',
        source,
    )
    if not match:
        raise SystemExit(f'missing method: {method}')
    annotations = match.group('annotations')
    print(f'{method}: task_check={("`@taskAuthorizationService`." in annotations)}')
PY

python3 - <<'PY'
from pathlib import Path

source = next(Path('.').rglob('AbstractTaskController.java')).read_text()
for method, call in {
    'getData': 'dataService.getDataGroups(taskId, locale)',
    'getFile': 'dataService.getFileByTask(taskId, fieldId, false)',
    'getNamedFile': 'dataService.getFileByTaskAndName(taskId, fieldId, name)',
    'getFilePreview': 'dataService.getFileByTask(taskId, fieldId, true)',
}.items():
    start = source.find(f' {method}(')
    if start < 0:
        raise SystemExit(f'missing method: {method}')
    end = source.find('\n    }', start)
    body = source[start:end]
    print(f'{method}: direct_data_call={call in body}')
PY

Repository: netgrif/application-engine

Length of output: 304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

controller = next(Path('.').rglob('TaskController.java'))
source = controller.read_text()

for method in ('getData', 'getFile', 'getNamedFile', 'getFilePreview',
               'setData', 'saveFile', 'saveFiles'):
    match = re.search(
        rf'(?P<annotations>(?:\s*@[^;\n]+\n)+)\s*public\s+[^;{{]+?\s+{method}\s*\(',
        source,
    )
    if not match:
        raise SystemExit(f'missing method: {method}')
    print(f'{method}: task_check={("`@taskAuthorizationService`." in match.group("annotations"))}')
PY

Repository: netgrif/application-engine

Length of output: 304


Add task-level authorization to the read endpoints.

getData, getFile, getNamedFile, and getFilePreview accept ANONYMOUS without a task-level check. Their data-service calls perform no additional authorization. A caller with ANONYMOUS authority can read data or download files for any existing task ID through both route aliases. Add a read-specific method to ITaskAuthorizationService and require it in all four @PreAuthorize expressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/TaskController.java`
around lines 188 - 194, Add a read-specific authorization method to
ITaskAuthorizationService, then update the `@PreAuthorize` expressions on
TaskController methods getData, getFile, getNamedFile, and getFilePreview to
require that task-level check while preserving their existing authority
requirements and both route aliases.

Comment on lines +98 to 101
} catch (Exception e) {
log.error("Creating case failed:", e);
return EventOutcomeWithMessageResource.errorMessage("Creating case failed" + e.getMessage());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

The public case-creation route returns raw exception text to anonymous callers.

createCase now serves /api/workflow/public/case and accepts ANONYMOUS. The catch block appends e.getMessage() to the response. Internal messages from the workflow layer, for example missing-process or database errors, become visible to unauthenticated clients. Log the exception and return a fixed message.

🛡️ Proposed fix
         } catch (Exception e) {
             log.error("Creating case failed:", e);
-            return EventOutcomeWithMessageResource.errorMessage("Creating case failed" + e.getMessage());
+            return EventOutcomeWithMessageResource.errorMessage("Creating case failed");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (Exception e) {
log.error("Creating case failed:", e);
return EventOutcomeWithMessageResource.errorMessage("Creating case failed" + e.getMessage());
}
} catch (Exception e) {
log.error("Creating case failed:", e);
return EventOutcomeWithMessageResource.errorMessage("Creating case failed");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@application-engine/src/main/java/com/netgrif/application/engine/workflow/web/WorkflowController.java`
around lines 98 - 101, Update the exception handling in createCase to keep the
full exception details in log.error while returning only the fixed "Creating
case failed" message from EventOutcomeWithMessageResource.errorMessage; remove
the concatenation with e.getMessage() for anonymous callers.

server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs,/v3/api-docs/**,/swagger-ui.html,/swagger-ui/**,/api/public/**,/manage/health
auth:
admin-password: password
server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs,/v3/api-docs/**,/swagger-ui.html,/swagger-ui/**,/api/petrinet/public/**,/api/workflow/public/**,/api/task/public/**,/manage/**,/api/users/me,/api/users/preferences

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not permit all management routes.

NaeSecurityConfiguration applies permitAll() to server-patterns. This /manage/** entry makes every current and future management endpoint unauthenticated. Restrict this entry to the required probe endpoint, such as /manage/health, or protect management endpoints in a separate security chain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application-engine/src/main/resources/application-dev.yaml` at line 44,
Update the server-patterns configuration to remove the broad /manage/** wildcard
and allow only the required management probe endpoint, such as /manage/health;
keep all other listed public routes unchanged.

server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/api/public/**,/v3/api-docs/public,/manage/health
anonymous-exceptions: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/manage/health
private-key: classpath:certificates/private.der
server-patterns: /api/auth/signup,/api/auth/token/verify,/api/auth/reset,/api/auth/recover,/v3/api-docs/public,/manage/**,/api/users/me,/api/users/preferences,/api/petrinet/public/**,/api/workflow/public/**,/api/task/public/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline application-engine/src/main/java/com/netgrif/application/engine/configuration/NaeSecurityConfiguration.java --type class

rg -n -C 5 \
  'serverPatterns|getServerPatterns|server-patterns|requestMatchers|permitAll|authorizeHttpRequests|/manage' \
  application-engine/src/main/java

rg -n -C 3 \
  'heapdump|logfile|env|management\.endpoints|access:\s*unrestricted' \
  application-engine/src/main/resources

Repository: netgrif/application-engine

Length of output: 17180


Remove /manage/** from server-patterns.

getPatterns() applies permitAll() to every server-patterns entry. This exposes sensitive management endpoints, including env, logfile, and unrestricted heapdump, to anonymous requests. Restrict the pattern to required health endpoints and protect all other management endpoints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@application-engine/src/main/resources/application.yaml` at line 89, Remove
the /manage/** entry from the server-patterns configuration while preserving the
other explicitly permitted API patterns. Leave management endpoints subject to
their existing protected access rules, including health endpoints unless
separately configured as required.

Comment on lines +28 to +35
@Document(collection = "anonymousUserRef")
public class AnonymousUserRef implements Serializable {

/**
* Serial version UID for ensuring compatibility during deserialization.
*/
@Serial
private static final long serialVersionUID = 1239812903890129012L;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Migrate persisted anonymous-access data before this release.

Existing deployments store anonymous references in anonym_user and can store the ANONYMOUS_USER authority value. The new code reads anonymousUserRef and recognizes only ANONYMOUS. Public authentication can then fail to find the realm reference or fail to recognize its authority.

  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java#L28-L35: Add an idempotent migration or a temporary dual-read path for documents in anonym_user.
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java#L45-L45: Migrate stored ANONYMOUS_USER authority names to ANONYMOUS, including references in anonymous-user records.
📍 Affects 2 files
  • nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java#L28-L35 (this comment)
  • nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java#L45-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java`
around lines 28 - 35, Add an idempotent migration or temporary dual-read support
for legacy anonymous references stored in collection anonym_user, ensuring
public authentication can still resolve them after the collection changes to
anonymousUserRef; update AnonymousUserRef as the entry point or nearest relevant
persistence flow. In Authority, migrate stored ANONYMOUS_USER values to
ANONYMOUS, including authority references contained in anonymous-user records.
Apply changes at
nae-spring-core-adapter/src/main/java/com/netgrif/application/engine/adapter/spring/auth/domain/AnonymousUserRef.java
lines 28-35 and
nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/Authority.java
line 45.

com.netgrif.application.engine.adapter.spring.auth.domain.Realm realm = new com.netgrif.application.engine.adapter.spring.auth.domain.Realm(createRequest.getName());
realm.setDescription(createRequest.getDescription());
realm.setAdminRealm(createRequest.isAdminRealm());
realm.setPublicAccess(createRequest.isPublicAccess());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Create the anonymous reference when a realm is created with public access.

A realm created with publicAccess=true does not call anonymousUserRefService.getOrCreateRef(...). PublicAuthenticationFilter skips anonymous authentication when that reference is absent. Public routes for the new realm then fail authorization instead of receiving the anonymous principal.

Keep public-access state and anonymous-reference creation consistent for realm creation and updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/RealmServiceImpl.java`
at line 71, Update the realm creation flow around RealmServiceImpl and its
public-access assignment so enabling public access also invokes
anonymousUserRefService.getOrCreateRef(...) for the new realm. Preserve the same
synchronization between public-access state and anonymous-reference creation
used by realm updates, including removing or retaining the reference when public
access changes.

Comment on lines +352 to +356
if (userOpt.isPresent()) {
return userOpt.get();
}
Optional<AnonymousUserRef> anonymousUserRefOptional = anonymousUserRefService.getRef(realmId);
return anonymousUserRefOptional.map(anonymousUserRef -> new AnonymousUser(anonymousUserRef, authorityService.getOrCreate(Authority.anonymous))).orElse(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restrict the anonymous fallback to the anonymous reference ID.

This method returns the realm anonymous user for every missing user ID. A deleted or unknown actor ID can therefore resolve to the anonymous actor. Compare id with AnonymousUserRef.getId() before constructing AnonymousUser; otherwise return null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java`
around lines 352 - 356, Update the anonymous fallback in the surrounding user
lookup method to compare the requested id with AnonymousUserRef.getId() before
constructing AnonymousUser; only the matching anonymous reference may resolve,
while deleted or unknown IDs must return null.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Fix or feature that would cause existing functionality doesn't work as expected bugfix A change that fixes a bug Extra Large improvement A change that improves on an existing feature Large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant