Thursday, April 12, 2007

Security Wise User Manager


This applies to Sitecore 5.3. The problem. As you may know, by default it is not possible to restrict access to certain security items in the security or extranet databases. You can check it by going to the security or extranet database and trying to set permissions in the Security Editor, the application just won't let you deny access to a specific item. The reason is that these items do not have the necessary system field named "__Security" that stores the security assignments. The background. There could be a requirement for certain users to see only a subset of users and roles. For example, Biology department should not manage users from another departments. The solution. So the first step to make it happen is to modify the security templates to include the field which will be storing the security definitions. This file is stored under "/sitecore/shell/" and it is called "security templates.xml" and the purpose of this file is to define the data structure of the security templates which are created on the fly using these XML definitions. The following field definition should be added to the Folder, Role and User templates. The section you are adding to is not important but it is preferred to add the fields to the Data section (create it if it is not there yet). <field id="{DEC8D2D5-E3CF-48B6-A653-8E69E2716641}" name="__Security" icon="" shared="1" sortorder="" source="" style="" type="text" unversioned="1" /> The attributes in bold are important. The id attribute should be set to the "__Security" field ID and the name is important as well. After this change, IIS restart and browser reopen is required. You should see the following picture in the Content Editor when browsing the security and extranet databases: This means that now you can deny read access in the security or extranet databases: It is not all that you should do though, because the User Manager won't just respect this security settings. So the second step is to override this application. It is fairly simple: 1. Copy the XML control source file named Security manager.xml from "\sitecore\shell\Applications\Security\Security manager\" to "\sitecore\shell\override". 2. Don't forget about this when upgrading :-) 3. Compile the following code for this form and place the assembly to the bin folder. Here is the source: using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; using System.Collections; using Sitecore; using Sitecore.Data; using Sitecore.Data.Items; using Sitecore.SecurityModel; using Sitecore.Configuration; using Sitecore.Web.UI.HtmlControls; namespace WiseBusiness.Shell.Security { public class CustomSecurityManagerForm : Sitecore.Shell.Applications.Security.SecurityManager.SecurityManagerForm { private ArrayList deniedItems = new ArrayList(); protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (!Sitecore.Context.ClientPage.IsEvent) { // we should respect administrators if (!Sitecore.Context.User.IsAdministrator) { // fill the array list of item the current user does not have access to GetDeniedItems(); string deniedItemIDs = ArrayListToString(deniedItems, ","); if (deniedItemIDs != string.Empty) { // setting the data context excluding items that the current item does not have access to this.FilteredDataContext.Filter = "not(Contains('" + deniedItemIDs + "', @@id))"; this.DataContext.Filter = "not(Contains('" + deniedItemIDs + "', @@id))"; } } } } private string ArrayListToString(System.Collections.ArrayList ar, string delim) { return string.Join(delim, (string[])ar.ToArray(typeof(string))); } private void GetDeniedItems() { // getting the selected domain Database database = Factory.GetDomain(this.Domain).Database; if (database != null) { // checking security recursively ProcessItem(database.GetRootItem()); } } private void ProcessItem(Item parent) { foreach (Item child in parent.Children) { using (new SecurityDisabler()) { // check if the context user cannot read the current item if (child.SecurityField.GetRights(Sitecore.Context.User, true) == ItemRights.DenyRead) { deniedItems.Add(child.ID.ToString()); } } ProcessItem(child); } } } } The comments in the code should help to understand the concept. If not, shoot me a message. The key things: - Inheriting from the existing User Manager form. - Calling the base OnLoad method thanks to the power of inheritance. - Modifying the FilteredDataContext's and DataContext's filters to exclude security items that the current user does not have access to. 4. Make the following change in the XML control source file to reference the newly compiled class (as usual): <CodeBeside type="WiseBusiness.Shell.Security.CustomSecurityManagerForm,WiseBusiness" /> Here is the resulting picture in the User Manager for the user: As can be seen, this user cannot see the roles container, system users and user named dominic. Of course, the code might be far from perfect but I am open to suggestions! Update: this will be addressed in next major release.

Friday, February 09, 2007

Get media path by file path


This code snippet applies to 5.1.1/5.2.

Here is the code snippet that does the job:

string fullPath = @"D:\wwwroot\Clean5.2.0.12\upload\images\Devil-Skype!.png";

// mapping the path

string mappedPath = Sitecore.Resources.Media.MediaPath.MapPath(fullPath);

// switching the context to shell

Sitecore.Context.SetActiveSite("shell");

// setting the actual media path

string mediaPath = Sitecore.Resources.Media.MediaPath.CreatePath(mappedPath).Paths.FullPath;

// next you may switch back to website

The site switch is necessary since the CreatePath method deals with the content database of the context site which is null for the website by default.

If you don’t want to change the site context, you should add the content database definition to the website:

<site

name="website"

...

content="master" or “web”

...

This way your code will look like:

string fullPath = @"D:\wwwroot\Clean5.2.0.12\upload\images\Devil-Skype!.png";

string mappedPath = Sitecore.Resources.Media.MediaPath.MapPath(fullPath);

string mediaPath = Sitecore.Resources.Media.MediaPath.CreatePath(mappedPath).Paths.FullPath;

Thursday, February 08, 2007

Publish at a specific time


The nature of the Sitecore scheduled operation is cyclic. The task’s execution time relies on the frequency and interval parameters in the web.config file. This approach has significant benefits. Since there is no way to either prevent the ASP.NET process from recycling or predict the recycle time, the cyclic approach makes it easier to guarantee that your scheduled task will be executed in next timeframe despite ASPNET process terminates the task execution. However, sometimes it is necessary to call a task at a specific time. The best example is the publishing task. This module approaches this scenario. The module’s architecture is depicted below: So the module consists of two components: 1. Console Windows application that invokes a web service. 2. Web Service that calls Sitecore publishing operation. Here you can download archive with the documentation and the module itself. Any feedback is really appreciated.

Monday, January 08, 2007

Proxy Items in 5.3


Here are some details on how the proxy items work in 5.3. Some sample code on how to create a proxy item: public void CreateProxyItem(string name) { Sitecore.Data.Database masterDB = Sitecore.Configuration.Factory.GetDatabase("master"); // getting a proxy template from the template repository Sitecore.Data.Items.TemplateItem proxyTemplate = masterDB.Templates[Sitecore.TemplateIDs.Proxy]; using (new Sitecore.SecurityModel.SecurityDisabler()) { // getting referece to the proxy container item Sitecore.Data.Items.Item proxyContainerItem = masterDB.Items["/sitecore/system/proxies/"]; if (proxyContainerItem != null && proxyTemplate != null) { Sitecore.Data.Items.Item proxyItem = proxyContainerItem.Add(name, proxyTemplate); proxyItem.Editing.BeginEdit(); proxyItem.Fields[Sitecore.FieldIDs.ProxySourceItem].Value = "{F5726884-BDBE-4DDD-9EBE-BB166E68E1EF}"; proxyItem.Fields[Sitecore.FieldIDs.ProxyTargetItem].Value = "{C3F3DBA9-87B3-4EB3-84CF-D75BD7FED626}"; proxyItem.Editing.EndEdit(); } } }

Tuesday, January 02, 2007

Cannot see the template folder?


This is applied to Sitecore 5.3 061102. If you create a folder (template container) for your custom templates in the Template Manager, you might not see this folder in the dialog when adding an item from a template or changing the template. The solution is to edit the \sitecore\shell\Controls\Data\TemplateDataContext.xml file to include the GUID of the template "Template Folder" that is placed under /sitecore/templates/system/templates/. The Filter attribute of the DataContext definition should be edited:
before: Filter="Contains('{E3E2D58C-DF95-4230-ADC9-279924CECE84}, {...}, {...}', @@templateid)"
after: Filter="Contains('{0437FEE2-44C9-46A6-ABE9-28858D9FEE8C},{...}, {...}', @@templateid)"
where {0437FEE2-44C9-46A6-ABE9-28858D9FEE8C} is the GUID of the "Template Folder" template.