XWiki Platform
  1. XWiki Platform
  2. XWIKI-8872

MessageTool can read from the wrong context in scheduler jobs or threads

    Details

    • Type: Bug Bug
    • Status: Closed Closed
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: 3.1, 3.5.1, 4.1.4, 4.2, 4.3, 4.4.1, 4.5
    • Fix Version/s: 4.5.2
    • Component/s: Localization
    • Labels:
      None
    • Difficulty:
      Unknown
    • Documentation:
      N/A
    • Documentation in Release Notes:
      N/A
    • Similar issues:
      XWIKI-7189URLs created by mailsender plugin called from a scheduler script can have a wrong context path
      XWIKI-6460External URLs generated from scheduler jobs can be wrong
      XWIKI-6461XWiki context mapped in scheduler jobs is not the API wrapped one
      XWIKI-3846Execution context should be initialized in Scheduler threads
      XWIKI-9062Scheduler space is incorrectly excluded from the Activity Stream
      XWIKI-8361IRC Bot is not restarted automatically by the scheduler job
      XWIKI-4405Remote Observation Manager can be in a state when the receiver thread is initialized before having the right context initialized
      XWIKI-8926WatchList should use the same Velocity Context as the one used by other Scheduler jobs
      XWIKI-5154Deleted execution context issue when forcing feed update thread update from a non-daemonized context
      XWIKI-8367The user sending Watchlist page modified events when jobs are triggered is the wrong one

      Description

      XWikiMessageTool has a context stored in it's data structure and uses that context when reading data from the wiki.

      In scheduler jobs or background threads (watchlist, extension manager, lucene, or user scheduler jobs) the context is cloned from the initial context that is passed to the initializations of the threads. If this context is from a child wiki (in multi wiki) then the message tool might read the preferences with a context from the wrong wiki while the ExecutionContext might be from another wiki. This can lead to reading the preferences from the wrong wiki or even worse lead to preferences read without objects because of a comparaison of references done in the loadXWikiDoc code. The preferences object will then be wrong in memeory and might be subsequently saved back to the wiki without the preferences object.

        Issue Links

          Activity

          Hide
          Ludovic Dubost added a comment -

          This issue might be related to this other issue which also has some discussions about preferences documents being lost.

          Show
          Ludovic Dubost added a comment - This issue might be related to this other issue which also has some discussions about preferences documents being lost.
          Hide
          Ludovic Dubost added a comment - - edited

          This code reproduces the issue:

          {{groovy}}
          xwiki.flushCache();
          
          def c = xcontext.getContext();
          def msg1 = c.getMessageTool();
          def execution = com.xpn.xwiki.web.Utils.getComponent(org.xwiki.context.Execution.class);
          c2 = c.clone()
          // Uncomment this line to fix
          //c2.remove("msg");
          try { 
            execution.getContext().setProperty("xwikicontext", c2);
            c.setDatabase("ressources")
            def msg2 = c2.getMessageTool()
            println "msg1.context.getDatabase() = " + msg1.context.getDatabase();
            println "msg2.context.getDatabase() = " + msg2.context.getDatabase();
            println "execution.getContext().getProperty('xwikicontext').getDatabase() = " + execution.getContext().getProperty("xwikicontext").getDatabase();
            println "msg2.getDocumentBundles() = " + msg2.getDocumentBundles();
          } catch ( e ) {
            println e;
          } finally {
            execution.getContext().setProperty("xwikicontext", c);
          }
          {{/groovy}}
          
          Show
          Ludovic Dubost added a comment - - edited This code reproduces the issue: {{groovy}} xwiki.flushCache(); def c = xcontext.getContext(); def msg1 = c.getMessageTool(); def execution = com.xpn.xwiki.web.Utils.getComponent(org.xwiki.context.Execution.class); c2 = c.clone() // Uncomment this line to fix //c2.remove( "msg" ); try {  execution.getContext().setProperty( "xwikicontext" , c2);  c.setDatabase( "ressources" )  def msg2 = c2.getMessageTool()  println "msg1.context.getDatabase() = " + msg1.context.getDatabase();  println "msg2.context.getDatabase() = " + msg2.context.getDatabase();  println "execution.getContext().getProperty('xwikicontext').getDatabase() = " + execution.getContext().getProperty( "xwikicontext" ).getDatabase();  println "msg2.getDocumentBundles() = " + msg2.getDocumentBundles(); } catch ( e ) {  println e; } finally {  execution.getContext().setProperty( "xwikicontext" , c); } {{/groovy}}
          Hide
          Ludovic Dubost added a comment - - edited

          The following code initializes MessageTool

          in XWikiContext
          
          public XWikiMessageTool getMessageTool()
              {
                  XWikiMessageTool msg = ((XWikiMessageTool) get("msg"));
                  if (msg == null) {
                      getWiki().prepareResources(this);
                      msg = ((XWikiMessageTool) get("msg"));
                  }
                  return msg;
              }
          
          in XWiki.java
          
          public void prepareResources(XWikiContext context)
              {
                  if (context.get("msg") == null) {
                      // String ilanguage = getInterfaceLanguagePreference(context);
                      String dlanguage = getLanguagePreference(context);
                      Locale locale = new Locale(dlanguage);
                      context.put("locale", locale);
                      if (context.getResponse() != null) {
                          context.getResponse().setLocale(locale);
                      }
                      ResourceBundle bundle = ResourceBundle.getBundle("ApplicationResources", locale);
                      if (bundle == null) {
                          bundle = ResourceBundle.getBundle("ApplicationResources");
                      }
                      XWikiMessageTool msg =
                          new XWikiMessageTool(Utils.getComponent(LocalizationManager.class), Utils.getComponentManager(),
                              context);
                      context.put("msg", msg);
                      VelocityContext vcontext = ((VelocityContext) context.get("vcontext"));
                      if (vcontext != null) {
                          vcontext.put("msg", msg);
                          vcontext.put("locale", locale);
                      }
                      @SuppressWarnings("unchecked")
                      Map<String, Object> gcontext = (Map<String, Object>) context.get("gcontext");
                      if (gcontext != null) {
                          gcontext.put("msg", msg);
                          gcontext.put("locale", locale);
                      }
                  }
              }
          
          In XWikiMessageTool
          
          public XWikiMessageTool(ResourceBundle bundle, XWikiContext context)
              {
                  this.bundle = bundle;
                  this.context = context;
              }
          
          

          The context is stored into the XWikiMessageTool object initially added to the context.

          If you are in a background thread or a scheduler job you use a cloned Context.
          Context cloning is the following which does not reset XWikiMessageTool and the XWikiMessageTool will stay in that cloned context. However the cloned context is not the one in the XWikiMessageTool object.

          @Override
              public synchronized XWikiContext clone()
              {
                  XWikiContext context = (XWikiContext) super.clone();
          
                  // Make sure to have unique instances of the various caches
                  context.displayedFields = Collections.synchronizedList(new ArrayList<String>(this.displayedFields));
                  context.classCache = Collections.synchronizedMap(new LRUMap(this.classCacheSize));
          
                  return context;
              }
          

          When the message tool is called the context stored is used, although in the mean time the database or other things might have been changed in the cloned context.

          public List<XWikiDocument> getDocumentBundles()
              {
                  String defaultLanguage = this.context.getWiki().getDefaultLanguage(this.context);
          

          The getDefaultLanguage call will need to read the preferences and these can be read with a context being from one wiki while the context in the Execution context might be from another wiki.

          Now in loadXWikiDoc there is a reference check between the object and the document. However these references are not generated using the same context. One is generated with the context from the MessageTool while the other is generated using the execution context. This leads to this check to fail and the objects will not be loaded. Additionally the preferneces objet read is not the one the MessageTool wanted to load but one from another wiki.

          Note: this check has been put in loadXWikiDoc, because mysql with certain configuration is doing case insensitive comparison in queries leading to potentially associating objects with the wrong document (suppose you have Test.MyDoc and Test.mydoc which both have Tag Objects. Then you could have the tags of both documents loading with both documents). Now this check could not include the wiki name in it but this would partially fix the issue as the document would still be a document from the wrong wiki or other problems might occur.

          // It seems to search before is case insensitive. And this would break the loading if we get an
                              // object which doesn't really belong to this document
                              if (!object.getDocumentReference().equals(doc.getDocumentReference())) {
                                  continue;
                              }
          

          The core issue here is that the XWiki context object stored in MessageTool and the one in the ExecutionContext are different and this should not happen. When reading documents this can lead to very bad things.

          It could be usefull to check that the context parameter in loadXWikiDoc is the same as the context object in the Execution context and issue a serious warning when this happens.

          Show
          Ludovic Dubost added a comment - - edited The following code initializes MessageTool in XWikiContext public XWikiMessageTool getMessageTool() { XWikiMessageTool msg = ((XWikiMessageTool) get( "msg" )); if (msg == null ) { getWiki().prepareResources( this ); msg = ((XWikiMessageTool) get( "msg" )); } return msg; } in XWiki.java public void prepareResources(XWikiContext context) { if (context.get( "msg" ) == null ) { // String ilanguage = getInterfaceLanguagePreference(context); String dlanguage = getLanguagePreference(context); Locale locale = new Locale(dlanguage); context.put( "locale" , locale); if (context.getResponse() != null ) { context.getResponse().setLocale(locale); } ResourceBundle bundle = ResourceBundle.getBundle( "ApplicationResources" , locale); if (bundle == null ) { bundle = ResourceBundle.getBundle( "ApplicationResources" ); } XWikiMessageTool msg = new XWikiMessageTool(Utils.getComponent(LocalizationManager.class), Utils.getComponentManager(), context); context.put( "msg" , msg); VelocityContext vcontext = ((VelocityContext) context.get( "vcontext" )); if (vcontext != null ) { vcontext.put( "msg" , msg); vcontext.put( "locale" , locale); } @SuppressWarnings( "unchecked" ) Map< String , Object > gcontext = (Map< String , Object >) context.get( "gcontext" ); if (gcontext != null ) { gcontext.put( "msg" , msg); gcontext.put( "locale" , locale); } } } In XWikiMessageTool public XWikiMessageTool(ResourceBundle bundle, XWikiContext context) { this .bundle = bundle; this .context = context; } The context is stored into the XWikiMessageTool object initially added to the context. If you are in a background thread or a scheduler job you use a cloned Context. Context cloning is the following which does not reset XWikiMessageTool and the XWikiMessageTool will stay in that cloned context. However the cloned context is not the one in the XWikiMessageTool object. @Override public synchronized XWikiContext clone() { XWikiContext context = (XWikiContext) super .clone(); // Make sure to have unique instances of the various caches context.displayedFields = Collections.synchronizedList( new ArrayList< String >( this .displayedFields)); context.classCache = Collections.synchronizedMap( new LRUMap( this .classCacheSize)); return context; } When the message tool is called the context stored is used, although in the mean time the database or other things might have been changed in the cloned context. public List<XWikiDocument> getDocumentBundles()    {         String defaultLanguage = this .context.getWiki().getDefaultLanguage( this .context); The getDefaultLanguage call will need to read the preferences and these can be read with a context being from one wiki while the context in the Execution context might be from another wiki. Now in loadXWikiDoc there is a reference check between the object and the document. However these references are not generated using the same context. One is generated with the context from the MessageTool while the other is generated using the execution context. This leads to this check to fail and the objects will not be loaded. Additionally the preferneces objet read is not the one the MessageTool wanted to load but one from another wiki. Note: this check has been put in loadXWikiDoc, because mysql with certain configuration is doing case insensitive comparison in queries leading to potentially associating objects with the wrong document (suppose you have Test.MyDoc and Test.mydoc which both have Tag Objects. Then you could have the tags of both documents loading with both documents). Now this check could not include the wiki name in it but this would partially fix the issue as the document would still be a document from the wrong wiki or other problems might occur. // It seems to search before is case insensitive. And this would break the loading if we get an                     // object which doesn't really belong to this document                     if (!object.getDocumentReference().equals(doc.getDocumentReference())) {                         continue ;                    } The core issue here is that the XWiki context object stored in MessageTool and the one in the ExecutionContext are different and this should not happen. When reading documents this can lead to very bad things. It could be usefull to check that the context parameter in loadXWikiDoc is the same as the context object in the Execution context and issue a serious warning when this happens.
          Hide
          Thomas Mortagne added a comment - - edited

          Modified XWikiMessageTool to use XWikiContext provider dynamically instead of keeping a XWikiContext instance forever. Lets see how it goes. Note that the same issue can probably still be found in other places, need a better protection at lower level.

          Show
          Thomas Mortagne added a comment - - edited Modified XWikiMessageTool to use XWikiContext provider dynamically instead of keeping a XWikiContext instance forever. Lets see how it goes. Note that the same issue can probably still be found in other places, need a better protection at lower level.

            People

            • Assignee:
              Thomas Mortagne
              Reporter:
              Ludovic Dubost
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Date of First Response: