diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticatedEventNotifier.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticationEventNotifier.java similarity index 60% rename from xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticatedEventNotifier.java rename to xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticationEventNotifier.java index b0d1a295f816..99808e05e097 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticatedEventNotifier.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/internal/user/UserAuthenticationEventNotifier.java @@ -21,16 +21,22 @@ package com.xpn.xwiki.internal.user; import javax.inject.Inject; +import javax.inject.Provider; import javax.inject.Singleton; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.observation.ObservationManager; +import org.xwiki.observation.event.Event; import org.xwiki.security.authentication.UserAuthenticatedEvent; +import org.xwiki.security.authentication.UserUnauthenticatedEvent; import org.xwiki.user.UserReference; import org.xwiki.user.UserReferenceResolver; +import com.xpn.xwiki.XWikiContext; + /** * This notifier helps dealing with events triggered when a user is authenticated through XWiki Oldcore's * authenticators. It wraps an {@code ObservationManager} and a {@code UserReferenceResolver} to @@ -40,9 +46,9 @@ * @version $Id$ * @since 13.3RC1 */ -@Component(roles = UserAuthenticatedEventNotifier.class) +@Component(roles = UserAuthenticationEventNotifier.class) @Singleton -public class UserAuthenticatedEventNotifier +public class UserAuthenticationEventNotifier { @Inject @@ -54,6 +60,9 @@ public class UserAuthenticatedEventNotifier @Inject private UserReferenceResolver userReferenceResolver; + @Inject + private Provider contextProvider; + /** * Resolve a string as a {@code UserReference} and notify a {@code UserAuthenticatedEvent} created with that user * reference. @@ -61,10 +70,31 @@ public class UserAuthenticatedEventNotifier * @param stringUserReference string form of the reference of user that will be resolved as a {@code * UserReference} and passed to the {@code UserAuthenticatedEvent} instance creation */ - public void notify(String stringUserReference) + public void notifyUserAuthenticated(String stringUserReference) + { + if (!StringUtils.isBlank(stringUserReference)) { + UserReference userReference = this.userReferenceResolver.resolve(stringUserReference); + if (this.logger.isDebugEnabled()) { + this.logger.debug("User [{}] authenticated", userReference); + } + this.notify(new UserAuthenticatedEvent(userReference)); + } + } + + /** + * Notify that the given user is now logged out. + * + * @param stringUserReference the user for whom to fire a {@link UserUnauthenticatedEvent}. + */ + public void notifyUserUnauthenticated(String stringUserReference) { - UserReference userReference = this.userReferenceResolver.resolve(stringUserReference); - this.notify(new UserAuthenticatedEvent(userReference)); + if (!StringUtils.isBlank(stringUserReference)) { + UserReference userReference = this.userReferenceResolver.resolve(stringUserReference); + if (this.logger.isDebugEnabled()) { + this.logger.debug("User [{}] unauthenticated", userReference); + } + this.notify(new UserUnauthenticatedEvent(userReference)); + } } /** @@ -72,11 +102,8 @@ public void notify(String stringUserReference) * * @param event {@code UserAuthenticatedEvent} */ - private void notify(UserAuthenticatedEvent event) + private void notify(Event event) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("User authenticated for [{}]", event.getUserReference()); - } - this.observationManager.notify(event, null); + this.observationManager.notify(event, null, this.contextProvider.get()); } } diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java index c69235f3f5d0..0796787e7e01 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java @@ -64,7 +64,6 @@ import org.hibernate.query.Query; import org.slf4j.Logger; import org.suigeneris.jrcs.rcs.Version; -import org.xwiki.bridge.event.ActionExecutingEvent; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentLookupException; import org.xwiki.component.manager.ComponentManager; @@ -86,7 +85,9 @@ import org.xwiki.observation.event.Event; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; +import org.xwiki.security.authentication.UserUnauthenticatedEvent; import org.xwiki.store.UnexpectedException; +import org.xwiki.user.UserReferenceSerializer; import org.xwiki.wiki.descriptor.WikiDescriptorManager; import org.xwiki.wiki.manager.WikiManagerException; @@ -176,6 +177,10 @@ public class XWikiHibernateStore extends XWikiHibernateBaseStore implements XWik @Named("local") private EntityReferenceSerializer localEntityReferenceSerializer; + @Inject + @Named("document") + private UserReferenceSerializer userReferenceSerializer; + @Inject private ComponentManager componentManager; @@ -2055,7 +2060,7 @@ private void registerLogoutListener() { this.observationManager.addListener(new EventListener() { - private final Event ev = new ActionExecutingEvent(); + private static final List EVENT_LIST = List.of(new UserUnauthenticatedEvent()); @Override public String getName() @@ -2066,17 +2071,17 @@ public String getName() @Override public List getEvents() { - return Collections.singletonList(this.ev); + return EVENT_LIST; } @Override public void onEvent(Event event, Object source, Object data) { - if ("logout".equals(((ActionExecutingEvent) event).getActionName())) { - final XWikiContext ctx = (XWikiContext) data; - if (ctx.getUserReference() != null) { - releaseAllLocksForCurrentUser(ctx); - } + UserUnauthenticatedEvent userUnauthenticatedEvent = (UserUnauthenticatedEvent) event; + if (userUnauthenticatedEvent.getUserReference() != null) { + DocumentReference userDoc = XWikiHibernateStore.this.userReferenceSerializer.serialize( + userUnauthenticatedEvent.getUserReference()); + releaseAllLocksForUser(userDoc, (XWikiContext) data); } } }); @@ -2084,18 +2089,16 @@ public void onEvent(Event event, Object source, Object data) /** * Release all of the locks held by the currently logged in user. - * - * @param ctx the XWikiContext, used to start the connection and get the user name. */ - private void releaseAllLocksForCurrentUser(final XWikiContext ctx) + private void releaseAllLocksForUser(final DocumentReference userDoc, final XWikiContext context) { try { - executeWrite(ctx, session -> { + executeWrite(context, session -> { final Query query = session.createQuery("delete from XWikiLock as lock where lock.userName=:userName"); // Using deprecated getUser() because this is how locks are created. // It would be a maintainibility disaster to use different code paths // for calculating names when creating and removing. - query.setParameter("userName", ctx.getUser()); + query.setParameter("userName", this.compactWikiEntityReferenceSerializer.serialize(userDoc)); query.executeUpdate(); return null; @@ -2103,22 +2106,25 @@ private void releaseAllLocksForCurrentUser(final XWikiContext ctx) } catch (Exception e) { String msg = "Error while deleting active locks held by user."; try { - this.endTransaction(ctx, false); + this.endTransaction(context, false); } catch (Exception utoh) { msg += " Failed to commit OR rollback [" + utoh.getMessage() + "]"; } throw new UnexpectedException(msg, e); } - // If we're in a non-main wiki & the user is global, - // switch to the global wiki and delete locks held there. - if (!ctx.isMainWiki() && ctx.isMainWiki(ctx.getUserReference().getWikiReference().getName())) { - final String cdb = ctx.getWikiId(); + if (this.wikiDescriptorManager.isMainWiki(userDoc.getWikiReference().getName())) { try { - ctx.setWikiId(ctx.getMainXWiki()); - this.releaseAllLocksForCurrentUser(ctx); - } finally { - ctx.setWikiId(cdb); + String currentWiki = context.getWikiId(); + for (String wikiId : this.wikiDescriptorManager.getAllIds()) { + if (!currentWiki.equals(wikiId)) { + context.setWikiReference(new WikiReference(wikiId)); + this.releaseAllLocksForUser(userDoc, context); + context.setWikiReference(new WikiReference(currentWiki)); + } + } + } catch (WikiManagerException e) { + this.logger.error("Error for getting list of wikis to release locks for user [{}]", userDoc, e); } } } diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyBasicAuthenticator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyBasicAuthenticator.java index 19e03c6b29e7..f70823a6c479 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyBasicAuthenticator.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyBasicAuthenticator.java @@ -35,20 +35,20 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.internal.user.UserAuthenticatedEventNotifier; +import com.xpn.xwiki.internal.user.UserAuthenticationEventNotifier; import com.xpn.xwiki.web.Utils; public class MyBasicAuthenticator extends BasicAuthenticator implements XWikiAuthenticator { - private UserAuthenticatedEventNotifier userAuthenticatedEventNotifier; + private UserAuthenticationEventNotifier userAuthenticationEventNotifier; - private UserAuthenticatedEventNotifier getUserAuthenticatedEventNotifier() + private UserAuthenticationEventNotifier getUserAuthenticatedEventNotifier() { - if ( this.userAuthenticatedEventNotifier == null ) { - this.userAuthenticatedEventNotifier = Utils.getComponent(UserAuthenticatedEventNotifier.class); + if ( this.userAuthenticationEventNotifier == null ) { + this.userAuthenticationEventNotifier = Utils.getComponent(UserAuthenticationEventNotifier.class); } - return this.userAuthenticatedEventNotifier; + return this.userAuthenticationEventNotifier; } @Override @@ -89,7 +89,7 @@ public boolean processLogin(String username, String password, String rememberme, request.setUserPrincipal(principal); - this.getUserAuthenticatedEventNotifier().notify(principal.getName()); + this.getUserAuthenticatedEventNotifier().notifyUserAuthenticated(principal.getName()); return false; } else { @@ -135,8 +135,8 @@ public static Principal checkLogin(SecurityRequestWrapper request, HttpServletRe // Since this scope is static, no UserAuthenticatedEventNotifier is available // So we create one here - UserAuthenticatedEventNotifier notifier = Utils.getComponent(UserAuthenticatedEventNotifier.class); - notifier.notify(principal.getName()); + UserAuthenticationEventNotifier notifier = Utils.getComponent(UserAuthenticationEventNotifier.class); + notifier.notifyUserAuthenticated(principal.getName()); return principal; } else { diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java index 9d2f64f92a0c..89e7fe257c32 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/java/com/xpn/xwiki/user/impl/xwiki/MyFormAuthenticator.java @@ -38,21 +38,21 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; -import com.xpn.xwiki.internal.user.UserAuthenticatedEventNotifier; +import com.xpn.xwiki.internal.user.UserAuthenticationEventNotifier; import com.xpn.xwiki.web.Utils; public class MyFormAuthenticator extends FormAuthenticator implements XWikiAuthenticator { private static final Logger LOGGER = LoggerFactory.getLogger(MyFormAuthenticator.class); - private UserAuthenticatedEventNotifier userAuthenticatedEventNotifier; + private UserAuthenticationEventNotifier userAuthenticationEventNotifier; - private UserAuthenticatedEventNotifier getUserAuthenticatedEventNotifier() + private UserAuthenticationEventNotifier getUserAuthenticatedEventNotifier() { - if ( this.userAuthenticatedEventNotifier == null ) { - this.userAuthenticatedEventNotifier = Utils.getComponent(UserAuthenticatedEventNotifier.class); + if ( this.userAuthenticationEventNotifier == null ) { + this.userAuthenticationEventNotifier = Utils.getComponent(UserAuthenticationEventNotifier.class); } - return this.userAuthenticatedEventNotifier; + return this.userAuthenticationEventNotifier; } /** @@ -168,7 +168,7 @@ public boolean processLogin(SecurityRequestWrapper request, HttpServletResponse request.setUserPrincipal(principal); - this.getUserAuthenticatedEventNotifier().notify(principal.getName()); + this.getUserAuthenticatedEventNotifier().notifyUserAuthenticated(principal.getName()); } else { // Failed to authenticate, better cleanup the user stored in the session @@ -240,7 +240,7 @@ public boolean processLogin(String username, String password, String rememberme, request.setUserPrincipal(principal); - this.getUserAuthenticatedEventNotifier().notify(principal.getName()); + this.getUserAuthenticatedEventNotifier().notifyUserAuthenticated(principal.getName()); Boolean bAjax = (Boolean) context.get("ajax"); if ((bAjax == null) || (!bAjax.booleanValue())) { @@ -292,7 +292,8 @@ public boolean processLogout(SecurityRequestWrapper securityRequestWrapper, HttpServletResponse httpServletResponse, URLPatternMatcher urlPatternMatcher) throws Exception { boolean result = super.processLogout(securityRequestWrapper, httpServletResponse, urlPatternMatcher); - if (result == true) { + if (result) { + this.getUserAuthenticatedEventNotifier().notifyUserUnauthenticated(securityRequestWrapper.getRemoteUser()); if (this.persistentLoginManager != null) { this.persistentLoginManager.forgetLogin(securityRequestWrapper, httpServletResponse); } diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt index 656ff8ff6927..e4b77f0adec8 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/main/resources/META-INF/components.txt @@ -159,7 +159,7 @@ com.xpn.xwiki.internal.render.DefaultOldRendering com.xpn.xwiki.internal.render.OldRenderingProvider com.xpn.xwiki.internal.render.groovy.ParseGroovyFromString com.xpn.xwiki.internal.user.MyPersistentLoginManagerProvider -com.xpn.xwiki.internal.user.UserAuthenticatedEventNotifier +com.xpn.xwiki.internal.user.UserAuthenticationEventNotifier com.xpn.xwiki.internal.user.UserCreatedEventListener com.xpn.xwiki.internal.velocity.DefaultVelocityEvaluator org.xwiki.internal.web.EffectiveAuthorSetterListener diff --git a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java index 92136a116dcd..8f4ee70811ea 100644 --- a/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java +++ b/xwiki-platform-core/xwiki-platform-oldcore/src/test/java/com/xpn/xwiki/store/XWikiHibernateStoreTest.java @@ -41,7 +41,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.xwiki.bridge.event.ActionExecutingEvent; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.model.reference.DocumentReference; @@ -51,12 +50,15 @@ import org.xwiki.observation.EventListener; import org.xwiki.observation.ObservationManager; import org.xwiki.query.QueryManager; +import org.xwiki.security.authentication.UserUnauthenticatedEvent; import org.xwiki.test.LogLevel; import org.xwiki.test.junit5.LogCaptureExtension; import org.xwiki.test.junit5.mockito.ComponentTest; import org.xwiki.test.junit5.mockito.InjectMockComponents; import org.xwiki.test.junit5.mockito.MockComponent; import org.xwiki.test.mockito.MockitoComponentManager; +import org.xwiki.user.UserReference; +import org.xwiki.user.UserReferenceSerializer; import org.xwiki.wiki.descriptor.WikiDescriptorManager; import org.xwiki.wiki.manager.WikiManagerException; @@ -143,6 +145,10 @@ public class XWikiHibernateStoreTest @Named("compactwiki") private EntityReferenceSerializer compactWikiEntityReferenceSerializer; + @MockComponent + @Named("document") + private UserReferenceSerializer userReferenceSerializer; + @MockComponent private WikiDescriptorManager wikiDescriptorManager; @@ -240,14 +246,17 @@ void locksAreReleasedOnLogout() throws Exception Query query = mock(Query.class); when(session.createQuery("delete from XWikiLock as lock where lock.userName=:userName")).thenReturn(query); - when(xcontext.getUserReference()).thenReturn(new DocumentReference("xwiki", "XWiki", "LoggerOutter")); - when(xcontext.getUser()).thenReturn("XWiki.LoggerOutter"); + UserReference userReference = mock(UserReference.class); + String username = "XWiki.LoggerOutter"; + DocumentReference userDoc = new DocumentReference("xwiki", "XWiki", "LoggerOutter"); + when(this.userReferenceSerializer.serialize(userReference)).thenReturn(userDoc); + when(this.compactWikiEntityReferenceSerializer.serialize(userDoc)).thenReturn(username); when(this.hibernateStore.beginTransaction()).thenReturn(true); // Fire the logout event. - eventListenerCaptor.getValue().onEvent(new ActionExecutingEvent("logout"), null, xcontext); + eventListenerCaptor.getValue().onEvent(new UserUnauthenticatedEvent(userReference), null, xcontext); - verify(query).setParameter("userName", "XWiki.LoggerOutter"); + verify(query).setParameter("userName", username); verify(query).executeUpdate(); verify(this.hibernateStore).beginTransaction(); verify(this.hibernateStore).endTransaction(true); diff --git a/xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authentication/xwiki-platform-security-authentication-api/src/main/java/org/xwiki/security/authentication/UserUnauthenticatedEvent.java b/xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authentication/xwiki-platform-security-authentication-api/src/main/java/org/xwiki/security/authentication/UserUnauthenticatedEvent.java new file mode 100644 index 000000000000..bcf04a643954 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-security/xwiki-platform-security-authentication/xwiki-platform-security-authentication-api/src/main/java/org/xwiki/security/authentication/UserUnauthenticatedEvent.java @@ -0,0 +1,75 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.xwiki.security.authentication; + +import org.xwiki.observation.event.Event; +import org.xwiki.stability.Unstable; +import org.xwiki.user.UserReference; + +/** + * Event triggered whenever a user is logged out. + * + * @version $Id$ + * @since 16.8.0RC1 + * @since 16.4.3 + * @since 15.10.13 + */ +@Unstable +public class UserUnauthenticatedEvent implements Event +{ + /** + * The reference related to an authenticated user for whom a {@link UserUnauthenticatedEvent} has been triggered. + */ + private final UserReference userReference; + + /** + * Default constructor without user reference for matching. + */ + public UserUnauthenticatedEvent() + { + this(null); + } + + /** + * Default constructor. + * + * @param userReference The reference related to an authenticated user for whom a {@link UserUnauthenticatedEvent} + * has been triggered. + */ + public UserUnauthenticatedEvent(UserReference userReference) + { + this.userReference = userReference; + } + + /** + * @return the {@link UserReference} of the authenticated user. + */ + public UserReference getUserReference() + { + return this.userReference; + } + + @Override + public boolean matches(Object other) + { + return other instanceof UserUnauthenticatedEvent; + } +}