I have found an error in the code of the class com.haulmont.cuba.security.app.role.RolesHelper
:
package com.haulmont.cuba.security.app.role;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.app.ServerConfig;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.security.entity.*;
import com.haulmont.cuba.security.role.*;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.*;
import java.util.stream.Collectors;
import static com.haulmont.cuba.security.role.SecurityStorageMode.MIXED;
/**
* Class contains miscellaneous useful methods for working with predefined roles
*/
@Component("cuba_RolesHelper")
public class RolesHelper {
//...
/**
* Returns a collection of {@link RoleDefinition} objects assigned to the {@code User}. If the user role is
* associated with the database role, the database role will be converted to the {@code RoleDefinition}
*
* @param user the user
* @param reloadUser if set to true then the passed {@code user} will be reloaded with the proper view (contains
* roles with permissions). If the parameter value is false then this check won't be performed.
* Set the parameter to false when the method is invoked from within the opened transaction (i.e.
* from the {@link com.haulmont.cuba.security.sys.UserSessionManager}) - in this case all
* non-loaded fields will be fetched when they are accessed within the method.
* @return collection of {@link RoleDefinition} objects
*/
public Collection<RoleDefinition> getRoleDefinitionsForUser(User user, boolean reloadUser) {
if (reloadUser) {
View userWithRolesView = ViewBuilder.of(User.class)
.add("userRoles", userRoleViewBuilder -> {
userRoleViewBuilder
.add("roleName")
.add("role", roleViewBuilder ->
roleViewBuilder
.addView(View.LOCAL)
.add("permissions", View.LOCAL)
);
})
.build();
user = dataManager.reload(user, userWithRolesView);
}
List<UserRole> userRoles = user.getUserRoles();
Map<String, RoleDefinition> result = new HashMap<>();
List<UserRole> userRolesWithRoleDef = userRoles.stream()
.filter(ur -> ur.getRoleDefinition() != null)
.collect(Collectors.toList());
List<UserRole> userRolesWithRoleName = userRoles.stream()
.filter(ur -> ur.getRoleDefinition() == null && ur.getRoleName() != null)
.collect(Collectors.toList());
List<UserRole> userRolesWithRoleObject = userRoles.stream()
.filter(ur -> ur.getRoleDefinition() == null && ur.getRole() != null)
.collect(Collectors.toList());
userRolesWithRoleDef.stream()
.filter(ur -> ur.getRoleDefinition() != null)
.forEach(ur -> result.put(ur.getRoleDefinition().getName(), ur.getRoleDefinition()));
for (UserRole ur : userRolesWithRoleName) {
RoleDefinition role = predefinedRoleDefinitionRepository.getRoleDefinitionByName((ur.getRoleName()));
if (role != null) {
ur.setRoleDefinition(role);
result.put(role.getName(), role);
}
}
if (serverConfig.getRolesStorageMode() == MIXED) {
for (UserRole ur : userRolesWithRoleObject) {
Role role = ur.getRole();
if (predefinedRoleDefinitionRepository.getRoleDefinitionByName(role.getName()) != null) {
log.warn("User '{}' has link to the persisted role '{}', but this role name is used for some predefined role. " +
"Persisted role's permissions will not be taken into account.", ur.getUser().getLogin(), role.getName());
continue;
}
RoleDefinition roleDefinition = transformToRoleDefinition(role);
ur.setRoleDefinition(roleDefinition);
result.put(roleDefinition.getName(), roleDefinition);
}
}
return new ArrayList<>(result.values());
}
//...
}
The parameter ur.getUser().getLogin()
is passed to logging:
log.warn("User '{}' has link to the persisted role '{}', but this role name is used for some predefined role. " + "Persisted role's permissions will not be taken into account.", ur.getUser().getLogin(), role.getName());
Which was not specified in the View during the entity reload:
View userWithRolesView = ViewBuilder.of(User.class)
.add("userRoles", userRoleViewBuilder -> {
userRoleViewBuilder
.add("roleName")
.add("role", roleViewBuilder ->
roleViewBuilder
.addView(View.LOCAL)
.add("permissions", View.LOCAL)
);
})
.build();
As a result, we encounter an error:
java.lang.IllegalStateException: Cannot get unfetched attribute [login] from detached object com.haulmont.cuba.security.entity.User-9340869a-a327-95f3-68e7-48f2148b141c [detached].
at org.eclipse.persistence.internal.queries.EntityFetchGroup.onUnfetchedAttribute(EntityFetchGroup.java:100)
at com.haulmont.cuba.core.sys.persistence.CubaEntityFetchGroup.onUnfetchedAttribute(CubaEntityFetchGroup.java:74)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.processUnfetchedAttribute(EntityManagerImpl.java:2998)
at com.haulmont.chile.core.model.impl.AbstractInstance._persistence_checkFetched(AbstractInstance.java)
at com.haulmont.cuba.security.entity.User._persistence_get_login(User.java)
at com.haulmont.cuba.security.entity.User.getLogin(User.java:132)
at com.haulmont.cuba.security.app.role.RolesHelper.getRoleDefinitionsForUser(RolesHelper.java:146)
at com.haulmont.cuba.security.app.role.RolesServiceBean.getRoleDefinitionsForUser(RolesServiceBean.java:167)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:90)
at jdk.internal.reflect.GeneratedMethodAccessor450.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:624)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:72)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy130.getRoleDefinitionsForUser(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.haulmont.cuba.core.sys.remoting.LocalServiceInvokerImpl.invoke(LocalServiceInvokerImpl.java:94)
at com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:159)
at com.sun.proxy.$Proxy339.getRoleDefinitionsForUser(Unknown Source)
at com.haulmont.cuba.gui.app.security.user.browse.UserBrowser.buildJoinedRole(UserBrowser.java:383)
at com.haulmont.cuba.gui.app.security.user.browse.UserBrowser.showEffectiveRole(UserBrowser.java:374)
at com.haulmont.cuba.gui.app.security.user.browse.UserBrowser$ShowEffectiveRoleAction.actionPerform(UserBrowser.java:408)
at com.haulmont.cuba.web.gui.components.WebPopupButton.lambda$setPopupButtonAction$1(WebPopupButton.java:328)
at com.haulmont.cuba.web.widgets.CubaButton.fireClick(CubaButton.java:76)
at com.vaadin.ui.Button$1.click(Button.java:57)
at jdk.internal.reflect.GeneratedMethodAccessor656.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:153)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:115)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:442)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:407)
at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:275)
at com.vaadin.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:83)
at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40)
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1636)
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:465)
at com.haulmont.cuba.web.sys.CubaApplicationServlet.serviceAppRequest(CubaApplicationServlet.java:329)
at com.haulmont.cuba.web.sys.CubaApplicationServlet.service(CubaApplicationServlet.java:215)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799)
at org.eclipse.jetty.servlet.ServletHandler$ChainEnd.doFilter(ServletHandler.java:1656)
at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108)
at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74)
at com.haulmont.cuba.web.sys.CubaHttpFilter.doFilter(CubaHttpFilter.java:93)
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193)
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1626)
at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:292)
at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:193)
at org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1626)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:552)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:600)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1440)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:505)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1355)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
at org.eclipse.jetty.server.Server.handle(Server.java:516)
at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:487)
at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:732)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:479)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:409)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
at java.base/java.lang.Thread.run(Thread.java:829)
You need either to add the user to the View:
View userWithRolesView = ViewBuilder.of(User.class)
.add("userRoles", userRoleViewBuilder -> {
userRoleViewBuilder
.add("roleName")
.add("user", userViewBuilder -> userViewBuilder.addView(View.LOCAL))
.add("role", roleViewBuilder ->
roleViewBuilder
.addView(View.LOCAL)
.add("permissions", View.LOCAL)
);
})
.build();
Or, in the logging, pass user.getLogin()
instead of ur.getUser().getLogin()
, as the user
will be the same for all userRole
instances in the collection.