Disabling WebDAV

Do you use WebDAV when uploading media files to Sitecore? If not, why not disable WebDAV and reduce the number of log files and errors in the system log file? At least consider disabling it at your Content Delivery servers. It is easy and requires only five step and it is all done in the web.config file.

Step 1
Locate and remove (or out comment) the appender section named “WebDAVLogFileAppender” in within the setting (This section defines the location of the log files).
 
<appender name="LogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
      <file value="$(dataFolder)/logs/log.{date}.txt" />
      <appendToFile value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
      </layout>
    </appender>
<!--    <appender name="WebDAVLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
      <file value="$(dataFolder)/logs/WebDAV.log.{date}.txt" />
      <appendToFile value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
      </layout>
    </appender> -->
    <appender name="SearchLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
      <file value="$(dataFolder)/logs/Search.log.{date}.txt" />
      <appendToFile value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
      </layout>
    </appender>

Step 2
Locate the system.webServer section and remove (or out comment) setting the remove setting named “WebDAVModule”.
 
<system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
     <!-- <remove name="WebDAVModule" />-->
      <add type="Sitecore.Web.RewriteModule, Sitecore.Kernel" name="SitecoreRewriteModule" />
…
  </modules>

Step 3
At the handlers section of the system.webServer locate and remove (or comment out) the following three settings of WebDAV
 
    <handlers>
<!--      <add name="WebDAVRoot" path="*" verb="OPTIONS,PROPFIND" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
      <add name="WebDAVRoot64" path="*" verb="OPTIONS,PROPFIND" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
      <add verb="*" path="sitecore_webDAV.ashx" type="Sitecore.Resources.Media.WebDAVMediaRequestHandler, Sitecore.Kernel" name="Sitecore.WebDAVMediaRequestHandler" /> -->
…
</handlers>

Step 4
Locate the httpHandlers section at system.Web and remove (or comment out) the WebDAV setting.
 
<httpHandlers>
      <add verb="*" path="sitecore_webDAV.ashx" type="Sitecore.Resources.Media.WebDAVMediaRequestHandler, Sitecore.Kernel" />
      <add verb="*" path="sitecore_media.ashx" type="Sitecore.Resources.Media.MediaRequestHandler, Sitecore.Kernel" />

Step 5
When you have comment out all the web.config settings of WebDAV listed above, you can now disable the Sitecore.WebDAV.config file from App_Config/Include folder.

Cleanup strategy missing WebDAV, Search and Cragling

What about the cleanup strategy for WebDAV.log, Search.log and Crawling.log, Sitecore?


Whenever Sitecore version 7.X restarts, it creates four new log files. The naming and location of these log files are managed in the <log4net> setting in the web.config file. The first log file Sitecore creates is the system log file. This log file is named log.[yyyyMMdd].txt and is managed at the "LogFileAppender". The second log file is the WebDAV log file named WebDAV.log.[yyyyMMdd].txt handled by the "WebDAVLogFileAppender". The third handles the search indexing operations named Search.log.[yyyyMMdd].txt and fourth handles the logging for search query operations named Crawling.log.[yyyyMMdd].txt and are managed at the "SearchLogFileAppender" and "CrawlingLogFileAppender" respectively.

If Sitecore restarts more than once a day, the time of creation (HHmmss) will be added to the all subsequent log files for that specific day E.g. log.[yyyyMMdd].[HHmmss].txt.

The "Appender" section contains:
    
<appender name="LogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/log.{date}.txt">
        <appendtofile value="true">
            <layout type="log4net.Layout.PatternLayout">
                <conversionpattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
            </layout>
        </appendtofile>
    </file>
</appender>
<appender name="WebDAVLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/WebDAV.log.{date}.txt">
        <appendtofile value="true">
            <layout type="log4net.Layout.PatternLayout">
                <conversionpattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
            </layout>
        </appendtofile>
    </file>
</appender>
<appender name="SearchLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/Search.log.{date}.txt">
        <appendtofile value="true">
            <layout type="log4net.Layout.PatternLayout">
                <conversionpattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
            </layout>
        </appendtofile>
    </file>
</appender>
<appender name="CrawlingLogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/Crawling.log.{date}.txt">
        <appendtofile value="true">
            <layout type="log4net.Layout.PatternLayout">
                <conversionpattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
            </layout>
        </appendtofile>
    </file>
</appender>

If you compare the "appender" settings listed above with the "Sitecore.Tasks.CleanupAgent" placed at <sitecore> <scheduling> <agent> setting you will notice that the CleanupAgent does not contain any remove settings regarding the log files of WebDAV, Search or Crawling. Put in another way, by default Sitecore does not clean up this three log files, which results in a solution with an unmanageable number log files (If you take this into a Sitecore version 6.X, you get the same regarding the WebDAV log files).

It is easy to set up Sitecore to clean up the three additional log files. You could simply prefix "log.*.txt" with "*." at:
            
<remove folder="$(dataFolder)/logs" pattern="log.*.txt" maxcount="50" minage="30.00:00:00" />


Ending up with this:
            
<remove folder="$(dataFolder)/logs" pattern="*.log.*.txt" maxCount="50" minAge="30.00:00:00" />
In this case, the three additional log files will follow the same life cycle as the system log file. If you want specific life cycles for each of the log files you could add specific "remove" settings for each of the log files ending up with something like this:

<files hint="raw:AddCommand">
    <remove folder="$(dataFolder)/logs" pattern="log.*.txt" maxcount="50" minage="30.00:00:00" />
    <remove folder="$(dataFolder)/logs" pattern="WebDAV.log.*.txt" maxcount="20" minage="7.00:00:00" />
    <remove folder="$(dataFolder)/logs" pattern="Search.log.*.txt" maxcount="20" minage="7.00:00:00" />
    <remove folder="$(dataFolder)/logs" pattern="Crawling.log.*.txt" maxcount="20" minage="7.00:00:00" />
… 
</files>

In this case, Sitecore will store the system log file at least for 30 days, if the number of system log files does not exceed the number of 50, while Sitecore will only store the log files of WebDAV, Search and Crawling for seven days.

If you are not using WebDAV in your Sitecore solution, you can easily disable it. This is how you disable WebDAV.

CleanUpAgent and Log Files

Normally I have a checklist of different configurations, settings etc. I change, whenever I setup a new Sitecore solution. One of the items on the list is how long Sitecore saves the system log files. It is frustrating to investigate an error, if the system log file has been deleted.

Sitecore has an agent that handles the cleanup strategy, and by default, Sitecore deletes system log files when they are older than 7 days. It is easy to change and handled in the web.config. The cleanup strategy of the log files is managed by the Sitecore.Tasks.CleanupAgent placed at <sitecore><scheduling><agent> setting in the web.config file.


        
        
          
          
          
          
          
        
      

If you want Sitecore to store the system log files for E.g. the last 30 days, you should change the [minAge] from “7.00:00:00” attribute to “30.00:00:00”. Taking into account, the [maxCount] attribute by default is valued “20”, Sitecore will only store 20 of the system log files. Therefore, you should either change the [maxCount] to at least “30”, delete the attribute or add [rolling=”true”] to ignore the [maxCount] and [minCount] attribute.

It would look like these:
      

Disable SPEAK dialogs - Sitecore

Did you have trouble with Sitecore SPEAK dialogs in Sitecore? We had!

Because Sitecore SPEAK adds these dialogs by overriding the existing dialogs in Sitecore, you can easily disable the SPEAK dialogs and use the old standard dialogs instead.

Sitecore has added a new include file, managing whether Sitecore uses the SPEAK dialogs or the old classic dialogs. Among other sections, the include file contain sections concerning the dialogs of media item and links, respectively. The include file is named Sitecore.Speak.config (and is stored at /App_Config/Includes/ next to all the other include files). In the include file you can easily override/comment out the SPEAK dialogs, you do not want to use.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <overridexmlcontrols>
      <override with="/sitecore/client/applications/Dialogs/SelectMediaDialog" xmlcontrol="Sitecore.Shell.Applications.Media.MediaBrowser" />
    </overridexmlcontrols>
 
    <overridedialogs>
      <override dialogurl="/sitecore/shell/Applications/Dialogs/Internal%20link.aspx" with="/sitecore/client/applications/dialogs/InsertLinkViaTreeDialog" />
      <override dialogurl="/sitecore/shell/Applications/Dialogs/Mail%20link.aspx" with="/sitecore/client/applications/dialogs/InsertEmailDialog" />
      <override dialogurl="/sitecore/shell/Applications/Dialogs/Anchor%20link.aspx" with="/sitecore/client/applications/dialogs/InsertAnchorDialog" />
      <override dialogurl="/sitecore/shell/Applications/Item%20browser.aspx" with="/sitecore/client/applications/dialogs/InsertSitecoreItemViaTreeDialog" />
    </overridedialogs>
 
    ...
    ...
  </sitecore>
</configuration>


Remember Me Persistent login - Sitecore

If you have a web organization based at authors (student work etc.) helping creating content in a short period and afterwards leave the web organization, you might consider to change how long the “Remember me” at the login page should be persistent.

This is handled in the /sitecore/settings/setting element named “Authentication.ClientPersistentLoginDuration” which by default is set to 180 days.



Enforce Password Minimum Length Sitecore

Sitecore security relies on ASP.NET membership, role and profile providers. By standard, you can configure the membership options in the web.config file using the /configuration/system.web/membership/providers/add elements.

If you want to ensure the minimum length of a new user password, select the provider element named “slq” and change the attribute named “minRequiredPasswordLength” to be equal to the minimum length you required.



Enforce Password Expiration - Sitecore

To enforce the current user to change the password is straightforward and only needs a little coding. Following the following three step will take you through the implementation

  1. Defining when the user should change the password
  2. Create the functionality to handling the current user who tries to log into Sitecore
  3. At the loggingin processor add the processor setting to the functionality at step 2

Defining when the user should change the password

First, I created an item to handle the timespan from when users have to change their password. And also an checkbox to (de-)activate the “Force New Password” functionality.


Create the functionality

Sitecore security model uses the ASP.NET membership. The ASP.NET membership includes different information about authentications of the users – the creation date of the users, last login date, last logout date, but also last password changed date of each user. Knowing this, it is straightforward to implement the “Force New Password” functionality. It only requires a couple of lines of coding.

When authenticating the user logging into Sitecore, the trick is to check the “LastPasswordChangedDate”:

 currentUser.LastPasswordChangedDate

The Codings:


using System;
using System.Web;
using System.Web.Security;
using Sitecore.Data.Items;
using Sitecore.Pipelines.LoggingIn;

namespace MyProject.SitecoreExtensions.Pipelines.LoggingIn
{
    public class ForceNewPassword
    {
        private Item itmForceNewPasswordSetting =
            Sitecore.Configuration.Factory.GetDatabase("master").GetItem("/sitecore/system/Settings/Security/Security Settings/Force New Password");

        public void Process(LoggingInArgs args)
        {
            if (!IsActivated())
                return;

            int monthsToExpire = 0;
                int.TryParse(itmForceNewPasswordSetting["TimeToExpire"], out monthsToExpire);

            if(monthsToExpire.Equals(0))
                return;

            MembershipUser currentUser = Membership.GetUser(args.Username);

            if (currentUser != null)
            {
                if ((DateTime.Now.AddMonths(-monthsToExpire)) > currentUser.LastPasswordChangedDate)
                {
                    Sitecore.Diagnostics.Log.Audit(string.Format("Force New Password: User {0}, has been forced to change password", user.UserName), this);

                    //Redirect to Sitecore default Change Password Site
                    HttpContext.Current.Response.Redirect("/sitecore/login/changepassword.aspx");
                }
            }
        }

        private bool IsActivated()
        {
            if (itmForceNewPasswordSetting == null)
                return false;

            if (string.IsNullOrEmpty(itmForceNewPasswordSetting["ActivateForcePassword"]))
                return false;
            
            if (itmForceNewPasswordSetting["ActivateForcePassword"].Equals("0"))
                return false;

            return true;
        }
    }
}


Add a processor tag to the loggingin processor

The final step is to add the functionality checking whether the current user needs to change the password, into the loggingin processor.

You could do this by adding the processor tag manually directly into the web.config file, but best practice is to include the modifications into a separate include file (stored under “/App_Config/Include” folder):


Notice the changes should be included as the first processor in the loggingin processor (this is done by using the “patch:before” setting).


   
     
            
           
                     
     
   



Test the “Force New Password”

Implementing the above functionality should do the trick … Notice the “Last Password Changed” value.

Before changing the password:


After changing the password:





Alphanumeric Characters in "Generate New Password" - Sitecore

A couple of month ago, a customer asked me to change the way Sitecore normally handles the “Generate new password” in the Security Manager. Change it so it only use alphanumeric Characters. I tried convincing the customer not to do so, but I failed …

To change the “Generate new password” in the Security Manager you only need to follow these 5 steps:
  1. Create your own GeneratePassword().It should do exactly the same as System.Web.Security.SqlMembershipProvider.GeneratePassword(), but instead of using modulo 87 (line number 39), it should use modulo 62 or 87 depending on whether to use alphanumeric characters or nonalphanumeric characters, respectively (it will make sense later in this post).
  2. Create your own SqlMembershipProvider and let it inherit from System.Web.Security.SqlMembershipProvider. The only thing this provider should do is to return your own GeneratePassword() (from step 1) instead of returning from System.Web.Security.Membership.GenereatePassword().
  3. In the web.config fil, add your new SqlMembershipProvider (from step 2) into .
  4. In the web.config file, change the SitecoreMembershipProvider to use your own provider (from step 3) instead of Sitecore.Security.SitecoreMembershipProvider
  5. Test the Generate New Password from the Security Manager in Sitecore Desktop

Create your own GeneratePassword()

Your own GeneratePassword() should do almost the same as the System.Web.Security.Membership.GeneratePassword(). But, instead of using modulo 87 you should use modulo 62 in the case, where only alphanumeric characters should be used. This is done where the “int num2” is initiated (you will find the full code in the end of this post).
 

NOTE: Making it work, you should also add the following five methods from the System.Web.Security.Membership (you find the code in the bottom of this post):
  1. Private static char[] punctations
  2. Private static bool IsAtoZ(char c)
  3. Internal static bool IsDangerousUrl(string s)
  4. Private static char[] stratingChars = new char[]
  5. Internal static bool IsDangerousString(strings, out int matchIndex


Override the System.Web.Security.SqlMembershipProvider.GeneratePassword()

Your own SQLMembershipProvider should inherit from System.Web.Security.SqlMembershipProvider and only overriding the public string GeneratePassword() to return your own GeneratePassword() method.



Add your own SQLMembershipProvider in the web.config file

In the web.config file navigate to section and add the new provider. Let all the settings be as the original SQL provider, but changing the use of System.Web.Security.SqlMembershipProvider to your own MyProject.Security.MembershipExtensions.MySQLMembershipProvider. 

Instead of adding a new attribute defining whether to use alphanumeric or non alphanumeric characters, I let the [minRequiredNonalphanumericCharacters] do the trick. If this attribute is equal to zero only alphanumeric characters will be used, if greater than zero – the number of non alphanumeric characters will respect the specified number.



Change the SitecoreMembershipProvider to use your own

Changing the SitecoreeMembershipProvider to use your own SQL provider is a simple task. Navigate to the section in the web.config file and change the existing Sitecore provider to use your own.


Test the Generate New Password

Verify that the Generate New Password only uses alphanumeric characters if the [minRequiredNonalphanumericCharacters] is set to “0” and the use of non alphanumeric characters if the [minRequiredNonalphanumericCharacters] is set to greater than 0



The Code:

Create your own GeneratePassword()


 
using System;
using System.Security.Cryptography;

namespace MyProject.Security.MembershipExtensions
{
 public static class MyMembership
 {
  
  public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters)
  {
   if (length < 1 || length > 128)
   {
    throw new ArgumentException("Membership_password_length_incorrect");
   }
   if (numberOfNonAlphanumericCharacters > length || numberOfNonAlphanumericCharacters < 0)
   {
    throw new ArgumentException("Membership_min_required_non_alphanumeric_characters_incorrect");
   }
   string text;
   int num4;
   do
   {
    byte[] array = new byte[length];
    char[] array2 = new char[length];
    int num = 0;
    new RNGCryptoServiceProvider().GetBytes(array);

    //Start: Added to change the standard behavior
                int numModulo = 87;
                if (numberOfNonAlphanumericCharacters == 0)
                {
                    numModulo = 62;
                }
                //End: Added to change the standard behavior
 
       for (int i = 0; i < length; i++)
    {
     //int num2 = (int)(array[i] % 87);
                    int num2 = (int)(array[i] % numModulo);
     if (num2 < 10)
     {
      array2[i] = (char)(48 + num2);
     }
     else
     {
      if (num2 < 36)
      {
       array2[i] = (char)(65 + num2 - 10);
      }
      else
      {
       if (num2 < 62)
       {
        array2[i] = (char)(97 + num2 - 36);
       }
       else
       {
        array2[i] = AlphanumericCharacters[num2 - 62];
        num++;
       }
      }
     }
    }
    if (num < numberOfNonAlphanumericCharacters)
    {
     Random random = new Random();
     for (int j = 0; j < numberOfNonAlphanumericCharacters - num; j++)
     {
      int num3;
      do
      {
       num3 = random.Next(0, length);
      } while (!char.IsLetterOrDigit(array2[num3]));
 
      array2[num3] = punctuations[random.Next(0, punctuations.Length)];
     }
    }
    text = new string(array2);
   } while (IsDangerousString(text, out num4));
   return text;
 }
 private static char[] punctuations = "!@#$%^&*()_-+=[{]};:>|./?".ToCharArray();

  private static bool IsAtoZ(char c)
  {
   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
  }
 
  internal static bool IsDangerousUrl(string s)
  {
   if (string.IsNullOrEmpty(s))
   {
    return false;
   }
   s = s.Trim();
   int length = s.Length;
   if (length > 4 && (s[0] == 'h' || s[0] == 'H') && (s[1] == 't' || s[1] == 'T') &&
    (s[2] == 't' || s[2] == 'T') && (s[3] == 'p' || s[3] == 'P') &&
    (s[4] == ':' || (length > 5 && (s[4] == 's' || s[4] == 'S') && s[5] == ':')))
   {
    return false;
   }
   int num = s.IndexOf(':');
   return num != -1;
  }
 
  private static char[] startingChars = new char[]
              {
               '<',
               '&'
              };
 
  internal static bool IsDangerousString(string s, out int matchIndex)
  {
   matchIndex = 0;
   int startIndex = 0;
   while (true)
   {
    int num = s.IndexOfAny(startingChars, startIndex);
 
    if (num < 0)
    {
     break;
    }
    if (num == s.Length - 1)
    {
     return false;
    }
    matchIndex = num;
    char c = s[num];
    if (c != '&')
    {
     if (c == '<' && (IsAtoZ(s[num + 1]) || s[num + 1] == '!' || s[num + 1] == '/' || s[num + 1] == '?'))
     {
      return true;
     }
    }
    else
    {
     if (s[num + 1] == '#')
     {
      return true;
     }
    }
    startIndex = num + 1;
   }
   return false;
  }
 }


Overriding the System.Web.Security.SqlMembershipProvider.GeneratePassword


 
namespace MyProject.Security.MembershipExtensions
{
 internal class MySQLMembershipProvider : System.Web.Security.SqlMembershipProvider
 {
  public override string GeneratePassword()
  {
               return MyMembership.GeneratePassword(
                    (this.MinRequiredPasswordLength < 14) ? 14 : this.MinRequiredPasswordLength,
                    this.MinRequiredNonAlphanumericCharacters);
  }
 }
}





Sitecore ECM - Token replacement when full name is empty

$fullname$, $email$, $name$ and tokens like that can be used when creating emails via Sitecore Email Campaign Manager (ECM).


You can control the value Sitecore displays at the email, when the current email recipient has no full name added. The value is stored at the Subscriber profile item of the ECM, placed in the Core database at /Sitecore/system/Settings/Security/Profiles/Subscriber. By default, this profile item has to fields [Fullname] and [Phone], respectively. The value added to the [fullname] would be displayed if the current recipient has no full name.