determine roles that have access to an item in Sitecore - in code - sitecore

Sitecore 6.5 system:
In code, is there a way to determine which roles have access to a specific item?
I have an extranet set up, some items are "protected" - meaning the anonymous account has had the inheritance broken, and certain roles have been granted read access. I already have a custom pipeline built, so I can determine when user attempts to view a protected item, but I need to determine what role(s) has access to view the item so I can direct them appropriately.
Thanks,
Thad
edit:
Here is the relevant code - there may be a better way of doing this (I inherited the system & code), but I am attempting to utilize what was already in place. The problem with the code below is that Sitecore.Context.Item is null when the user doesn't have permission.
public class NotFoundProcessor : Sitecore.Pipelines.HttpRequest.HttpRequestProcessor
{
public override void Process(Sitecore.Pipelines.HttpRequest.HttpRequestArgs args)
{
if (args.PermissionDenied)
{
//determine what role would give the user access
foreach(Sitecore.Security.Accounts.Role role in Sitecore.Security.Accounts.RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = Sitecore.Context.Item.Security.CanRead(role);
//... do stuff here
}
}
}
}

Just from the top of my head, you can check all the roles if they have read access to chosen item:
foreach (Role role in RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = item.Security.CanRead(role);
}
EDIT after code sample provided:
You need to try to resolve the item in the same way as ItemResolver does but wrapped it with SecurityDisabler and without setting it to Sitecore Context afterwards. This will allow you to find the requested item despite the fact the user doesn't have access to it:
public class NotFoundProcessor : HttpRequestProcessor
{
public override void Process(HttpRequestArgs args)
{
if (args.PermissionDenied)
{
Item item = GetItemUsingSecurityDisabler(args);
//determine what role would give the user access
foreach(Role role in RolesInRolesManager.GetAllRoles())
{
bool roleCanRead = item.Security.CanRead(role);
//... do stuff here
}
}
}
}
private Item GetItemUsingSecurityDisabler(HttpRequestArgs args)
{
using (new SecurityDisabler())
{
string path = MainUtil.DecodeName(args.Url.ItemPath);
Item item = args.GetItem(path);
if (item == null)
{
path = args.Url.ItemPath;
item = args.GetItem(path);
}
if (item == null)
{
path = args.LocalPath;
item = args.GetItem(path);
}
if (item == null)
{
path = MainUtil.DecodeName(args.LocalPath);
item = args.GetItem(path);
}
SiteContext site = Sitecore.Context.Site;
string rootPath = site != null ? site.RootPath : string.Empty;
if (item == null)
{
path = FileUtil.MakePath(rootPath, args.LocalPath, '/');
item = args.GetItem(path);
}
if (item == null)
{
path = MainUtil.DecodeName(FileUtil.MakePath(rootPath, args.LocalPath, '/'));
item = args.GetItem(path);
}
// I've ommited resolving item using DisplayName but you can add if necessary here
return item;
}
}

Related

how to collect sitecore information for anonymous

I need to get sitecore information that collected from anonymous users to give him availability to export it or opt out - [GDPR]
any idea about contact ID for anonymous !
The way of doing it is dependent on the sitecore version.
Sitcore 9 you can use right to be forgotten
Sitecore 8+ you have to implement the feature from scratch.
Regarding Anonymous user - If the user is really anonymous, then you done need to worry about the GDPR (my view). But sometimes we map user email and sensitive personal info to anonymous user by using forms or WFFM. You can use email address of that user to query xDB (Contact Identifiers) to get the contact and contactID. Then reset informations.
Also: please note that based on WFFFM save action config, anonymous user will store in Core DB and Contact List.
To forget a user, you can use the following code. It will execute the ExecuteRightToBeForgotten function on the contact and scrub their data.
Forget User
public bool ForgetUser()
{
var id = _contactIdentificationRepository.GetContactId();
if (id == null)
{
return false;
}
var contactReference = new IdentifiedContactReference(id.Source, id.Identifier);
using (var client = _contactIdentificationRepository.CreateContext())
{
var contact = client.Get(contactReference, new ContactExpandOptions());
if (contact != null)
{
client.ExecuteRightToBeForgotten(contact);
client.Submit();
}
}
return false;
}
Fake up some data
public void FakeUserInfo()
{
var contactReference = _contactIdentificationRepository.GetContactReference();
using (var client = SitecoreXConnectClientConfiguration.GetClient())
{
// we can have 1 to many facets
// PersonalInformation.DefaultFacetKey
// EmailAddressList.DefaultFacetKey
// Avatar.DefaultFacetKey
// PhoneNumberList.DefaultFacetKey
// AddressList.DefaultFacetKey
// plus custom ones
var facets = new List<string> { PersonalInformation.DefaultFacetKey };
// get the contact
var contact = client.Get(contactReference, new ContactExpandOptions(facets.ToArray()));
// pull the facet from the contact (if it exists)
var facet = contact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);
// if it exists, change it, else make a new one
if (facet != null)
{
facet.FirstName = $"Myrtle-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
facet.LastName = $"McSitecore-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, facet);
}
else
{
// make a new one
var personalInfoFacet = new PersonalInformation()
{
FirstName = "Myrtle",
LastName = "McSitecore"
};
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, personalInfoFacet);
}
if (contact != null)
{
// submit the changes to xConnect
client.Submit();
// reset the contact
_contactIdentificationRepository.Manager.RemoveFromSession(Analytics.Tracker.Current.Contact.ContactId);
Analytics.Tracker.Current.Session.Contact = _contactIdentificationRepository.Manager.LoadContact(Analytics.Tracker.Current.Contact.ContactId);
}
}
}
ContactIdentificationRepository
using System.Linq;
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Configuration;
using Sitecore.XConnect;
using Sitecore.XConnect.Client.Configuration;
namespace Sitecore.Foundation.Accounts.Repositories
{
public class ContactIdentificationRepository
{
private readonly ContactManager contactManager;
public ContactManager Manager => contactManager;
public ContactIdentificationRepository()
{
contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
}
public IdentifiedContactReference GetContactReference()
{
// get the contact id from the current contact
var id = GetContactId();
// if the contact is new or has no identifiers
var anon = Tracker.Current.Contact.IsNew || Tracker.Current.Contact.Identifiers.Count == 0;
// if the user is anon, get the xD.Tracker identifier, else get the one we found
return anon
? new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"))
: new IdentifiedContactReference(id.Source, id.Identifier);
}
public Analytics.Model.Entities.ContactIdentifier GetContactId()
{
if (Tracker.Current?.Contact == null)
{
return null;
}
if (Tracker.Current.Contact.IsNew)
{
// write the contact to xConnect so we can work with it
this.SaveContact();
}
return Tracker.Current.Contact.Identifiers.FirstOrDefault();
}
public void SaveContact()
{
// we need the contract to be saved to xConnect. It is only in session now
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
this.contactManager.SaveContactToCollectionDb(Tracker.Current.Contact);
}
public IXdbContext CreateContext()
{
return SitecoreXConnectClientConfiguration.GetClient();
}
}
}

Changing workflow state in Sitecore

I'm having an issue changing the workflow state for an item programmatically. The state isn't being changed no matter what I do to the field. I've tried using (new SecurityDisabler()){} and putting the item in editing mode then changing the field manually. I've noticed that the item itself has the Lock set to <r />, could this be causing an issue?
Here is some sample code of what I've tried to do:
[HttpPost]
[MultipleButton(Name = "action", Argument = "Submit")]
public ActionResult Submit(LoI model)
{
if (model.Submitted || !model.Signed)
{
return Redirect("/Profile/LoI");
}
ModifyCandidateInfo(model, true);
Session["message"] = Translate.Text("loi-submitted-message");
Session["messageClass"] = "success";
return Redirect("/Profile/LoI");
}
private static void ModifyCandidateInfo(LoI model, bool isSubmission)
{
using (new SecurityDisabler())
{
var candidateFolder = CBUtility.GetCandidateFolder();
var loi= candidateFolder.GetChildren().SingleOrDefault(loi => loi.TemplateID == LoITemplateId);
if (loi == null) return;
loi.Editing.BeginEdit();
EditFields(loi, model);
EditChildren(loi, model);
//Send emails upon submission
if (isSubmission)
{
loi.ExecuteCommand("Submit",
loi.Name + " submitted for " + model.CandidateName);
using (new SecurityDisabler())
{
loi.Editing.BeginEdit();
loi.Fields["__Workflow state"].Value = "{F352B651-341B-4CCF-89FE-BD77F5E4D540}";
loi.Editing.EndEdit();
}
}
loi.Editing.EndEdit();
}
}
I initalized the item's workflow with the following function:
public static void InitializeWorkflow(Item item, ID workflowId)
{
item.Editing.BeginEdit();
var workflow =
item.Database.WorkflowProvider.GetWorkflow(workflowId.ToString());
workflow.Start(item);
item.Editing.EndEdit();
}
The item starts at the default drafting state and executed a "Submit" command that fires off emails. Through the Sitecore UI if I hit submit it'll go to the next workflow state but not programmatically when I fire off the ExecuteCommand function. Below you'll find the ExecuteCommand function.
public static WorkflowResult ExecuteCommand(this Item item, string commandName, string comment)
{
using (new SecurityDisabler())
{
var workflow = item.Database.WorkflowProvider.GetWorkflow(item);
if (workflow == null)
{
return new WorkflowResult(false, "No workflow assigned to item");
}
var command = workflow.GetCommands(item[FieldIDs.WorkflowState])
.FirstOrDefault(c => c.DisplayName == commandName);
return command == null
? new WorkflowResult(false, "Workflow command not found")
: workflow.Execute(command.CommandID, item, comment, false);
}
}
The command fires off fine and the emails are sent but I can't figure out why the state won't change. Could someone provide me with other suggestions or a solution?
Am I reading the workflow state id correctly? I'm using the item ID for the workflow state.
I think your code is really similar to my implementation. This is my code's background.
All items have the same workflow named "WF" and it has three workflow states (Working, Awaiting Approval, and Approved). One page-item having "WF" has some rendering items and those datasource items. Suppose a content editor is ready to submit and approve the item with its related items. By hitting the "Submit" and "Approval" button in the page, all page-item's related items have the same workflow state as the page-item's one.
Most code are from Marek Musielak and this code is perfectly working in my side.
public class UpdateWorkflowState
{
// List all controls in page item
public RenderingReference[] GetListOfSublayouts(string itemId, Item targetItem)
{
RenderingReference[] renderings = null;
if (Sitecore.Data.ID.IsID(itemId))
{
renderings = targetItem.Visualization.GetRenderings(Sitecore.Context.Device, true);
}
return renderings;
}
// Return all datasource defined on one item
public IEnumerable<string> GetDatasourceValue(WorkflowPipelineArgs args, Item targetItem)
{
List<string> uniqueDatasourceValues = new List<string>();
Sitecore.Layouts.RenderingReference[] renderings = GetListOfSublayouts(targetItem.ID.ToString(), targetItem);
LayoutField layoutField = new LayoutField(targetItem.Fields[Sitecore.FieldIDs.FinalLayoutField]);
LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(Sitecore.Context.Device.ID.ToString());
foreach (var rendering in renderings)
{
if (!uniqueDatasourceValues.Contains(rendering.Settings.DataSource))
uniqueDatasourceValues.Add(rendering.Settings.DataSource);
}
return uniqueDatasourceValues;
}
// Check workflow state and update state
public WorkflowResult ChangeWorkflowState(Item item, ID workflowStateId)
{
using (new EditContext(item))
{
item[FieldIDs.WorkflowState] = workflowStateId.ToString();
}
Sitecore.Layouts.RenderingReference[] renderings = GetListOfSublayouts(item.ID.ToString(), item);
return new WorkflowResult(true, "OK", workflowStateId);
}
// Verify workflow state and update workflow state
public WorkflowResult ChangeWorkflowState(Item item, string workflowStateName)
{
IWorkflow workflow = item.Database.WorkflowProvider.GetWorkflow(item);
if (workflow == null)
{
return new WorkflowResult(false, "No workflow assigned to item");
}
WorkflowState newState = workflow.GetStates().FirstOrDefault(state => state.DisplayName == workflowStateName);
if (newState == null)
{
return new WorkflowResult(false, "Cannot find workflow state " + workflowStateName);
}
unlockItem(newState, item);
return ChangeWorkflowState(item, ID.Parse(newState.StateID));
}
// Unlock the item when it is on FinalState
public void unlockItem(WorkflowState newState, Item item)
{
if (newState.FinalState && item.Locking.IsLocked())
{
using (new EditContext(item, false, false))
{
item["__lock"] = "<r />";
}
}
}
}

Display items in bucket with Sitecore Data Provider

Hej Guys
I have a rather large problem, I've been tasked with creating a Custom Data Provider for extracting Stock Keeping Units(SKUs) from a SOLR database into sitecore, without actually populating the database with items.
I've created a data provider, which succesfully pulls data from the SOLR database a "creates" the items in sitecore, by using the following code:
public class SkuDataProvider : DataProvider, ISkuDataProvider
{
private readonly string _targetDatabaseName = "master";
private readonly string _idTablePrefix = "Skus";
private readonly ID _skuTemplateId = new ID("{F806B403-BDAF-4C60-959D-E706A82FC1DC}");
private readonly ID _skuRootTemplateId = new ID("{9767BC47-0A95-40E9-A2DE-3766FF241411}");
private readonly IEnumerable<SkuItemInfo> _skus;
public SkuDataProvider(/*IProductPageService productPageService*/)
{
_skus = new MockDataForSkuDataProvider().GetSimpleSkuCollection();
}
public override ItemDefinition GetItemDefinition(ID itemId, CallContext context)
{
Assert.ArgumentNotNull(itemId, "itemID");
// Retrieve the sku id from Sitecore's IDTable
var skuId = GetSkuIdFromIdTable(itemId);
if (!string.IsNullOrEmpty(skuId))
{
// Retrieve the sku data from the skus collection
var sku = _skus.FirstOrDefault(o => o.SkuId == skuId);
if (sku != null)
{
// Ensure the sku item name is valid for the Sitecore content tree
var itemName = ItemUtil.ProposeValidItemName($"{sku.SkuId}_{sku.Name}");
// Return a Sitecore item definition for the sku using the sku template
return new ItemDefinition(itemId, itemName, ID.Parse(_skuTemplateId), ID.Null);
}
}
return null;
}
private string GetSkuIdFromIdTable(ID itemId)
{
var idTableEntries = IDTable.GetKeys(_idTablePrefix, itemId);
if (idTableEntries.Any())
return idTableEntries[0].Key.ToString();
return null;
}
public override IDList GetChildIDs(ItemDefinition parentItem, CallContext context)
{
if (CanProcessParent(parentItem.ID))
{
var itemIdList = new IDList();
foreach (var sku in _skus)
{
var skuId = sku.SkuId;
// Retrieve the Sitecore item ID mapped to his sku
IDTableEntry mappedId = IDTable.GetID(_idTablePrefix, skuId) ??
IDTable.GetNewID(_idTablePrefix, skuId, parentItem.ID);
itemIdList.Add(mappedId.ID);
}
context.DataManager.Database.Caches.DataCache.Clear();
return itemIdList;
}
return base.GetChildIDs(parentItem, context);
}
private bool CanProcessParent(ID id)
{
var item = Factory.GetDatabase(_targetDatabaseName).Items[id];
bool canProcess = item.Paths.IsContentItem && item.TemplateID == _skuRootTemplateId && item.ID == new ID("{F37753A0-BC79-4FF7-B975-A8F142AACD76}");
return canProcess;
}
public override ID GetParentID(ItemDefinition itemDefinition, CallContext context)
{
var idTableEntries = IDTable.GetKeys(_idTablePrefix, itemDefinition.ID);
if (idTableEntries.Any())
{
return idTableEntries.First().ParentID;
}
return base.GetParentID(itemDefinition, context);
}
public override FieldList GetItemFields(ItemDefinition itemDefinition, VersionUri version, CallContext context)
{
var fields = new FieldList();
var idTableEntries = IDTable.GetKeys(_idTablePrefix, itemDefinition.ID);
if (idTableEntries.Any())
{
if (context.DataManager.DataSource.ItemExists(itemDefinition.ID))
{
ReflectionUtil.CallMethod(typeof(ItemCache), CacheManager.GetItemCache(context.DataManager.Database), "RemoveItem", true, true, new object[] { itemDefinition.ID });
}
var template = TemplateManager.GetTemplate(_skuTemplateId, Factory.GetDatabase(_targetDatabaseName));
if (template != null)
{
var skuId = GetSkuIdFromIdTable(itemDefinition.ID);
if (!string.IsNullOrEmpty(skuId))
{
var sku = _skus.FirstOrDefault(o => o.SkuId == skuId);
if (sku != null)
{
foreach (var field in GetDataFields(template))
{
fields.Add(field.ID, GetFieldValue(field, sku));
}
}
}
}
}
return fields;
}
protected virtual IEnumerable<TemplateField> GetDataFields(Template template)
{
return template.GetFields().Where(ItemUtil.IsDataField);
}
private string GetFieldValue(TemplateField field, SkuItemInfo sku)
{
string fieldValue = string.Empty;
switch (field.Name)
{
case "Name":
fieldValue = sku.Name;
break;
case "SkuId":
fieldValue = sku.SkuId;
break;
default:
break;
}
return fieldValue;
}
}
}
The problem emerges when accessing the Sitecore backend, where all items appears below the bucket item in a hierarchly-way.
I've checked that the Root item is set a bucket and that the template used is bucketable.
Furthermore when inserting manually in the backend, the item is correctly inserted in the bucket.
Do anyone got an idea for me, on how to fix this issue?
Best Regards
Nicolai
You need to set the Is Bucketable flag on the standard values of the template item rather than the template item itself.
Also, the way that items get "bucketed" is via events when the item is being created or saved. Sitecore then creates the bucket folders to store the items in. In your case as you have virtual items, you will need to handle their path via the data provider.
If you just want them hidden in the same way that they are in a standard bucket, then I would suggest creating a bucket folder under your SKU Root folder and using that item as the parent for all SKU virtual items. That way the bucket folder will be hidden by sitecore and you will get the same view as a standard bucket.
This is the template to use:

sitecore identify template type in treelist view

I have a treelist which allows an editor to define a list of links which is then output in a sublayout.
Depending upon which templates the selected pages use in that treelist field will determine the field values I need to write out.
I have a generic content page template where its own sitecore URL should be referenced but I then have a pseudo template which contains a field for a string URL and additional field params relating to to an external site.
In that scenario I don't want the sitecore URL I instead need its field values so as to concat a link to an external site along with the token details and present that to the user in the list of links.
Currently I have the code below but I need to include a condition that says if the template of the current GUID item is type 'SSO-Link' then don't retrieve its sitecore URL from linkmanager instead refer to a field called URL as well as a number of additional fields.
Thanks - current code below
Item[] selectedItems = treelistField.GetItems();
foreach (Item item in selectedItems)
{
string itemName = item.Name;
string displayName = item.DisplayName;
string url = LinkManager.GetItemUrl(item);
string linkName = "Undefined";
if (displayName != null)
{
linkName = displayName;
}
else if (itemName != null)
{
linkName = itemName;
}
if (linkName != "Undefined" && url != null)
{
htmlOutput.Text += "<a href=\"" + url + "\">";
htmlOutput.Text += linkName;
htmlOutput.Text += "</a>";
}
}
From what I understand, you need to add this simple condition at the beginning of your loop:
foreach (Item item in selectedItems)
{
string url = null;
if (item.TemplateName == "SSO-Link")
{
url = item["URL"];
// other fields
}
else
{
url = LinkManager.GetItemUrl(item);
}
// your code
I use an extension for my templates and template items. Then I call a constants class for the ID of the template I am comparing to.
public static class TemplateExtensions
{
public static bool IsDerived([NotNull] this Template template, [NotNull] ID templateId)
{
return template.ID == templateId || template.GetBaseTemplates().Any(baseTemplate => IsDerived(baseTemplate, templateId));
}
public static bool IsDerived([NotNull] this TemplateItem template, [NotNull] ID templateId)
{
return template.ID == templateId || template.BaseTemplates.Any(baseTemplate => IsDerived(baseTemplate, templateId));
}
}
Constants
public static class Products
{
public static TemplateID ProductSection = new TemplateID(new ID("{73400360-5935-40B6-88BC-350DC5B9BC90}"));
public static TemplateID ProductDetail = new TemplateID(new ID("{9CD3D5ED-E579-4611-88E0-6B44C9D56F16}"));
}
Use
if (item.IsDerived(Products.ProductDetail))
{ if code here }
Hope this helps.

How to use sitecore query in datasource location? (dynamic datasource)

Is it possible to set the datasource location (not the datasource) to be a sitecore query?
What I'm trying to do is to have the sublayout set its datasource location to a folder under the item containing it (current item).
The sublayout datasource location should point to a folder under the current item. So I tried setting the datasource location to query:./Items/* but that did not work.
You don't need the query -- the sublayout datasource location can simply use a relative path. e.g.
./Items
Obviously though, that folder needs to exist already. I've been meaning to blog this code, and it may be overkill but I'll post here since it may help you. The following can be added to the getRenderingDatasource pipeline to create a relative path datasource location if it doesn't exist already. Add it before the GetDatasourceLocation processor.
On the sublayout, you'll want to add a parameter contentFolderTemplate=[GUID] to specify the template of the item that gets created.
public class CreateContentFolder
{
protected const string CONTENT_FOLDER_TEMPLATE_PARAM = "contentFolderTemplate";
public void Process(GetRenderingDatasourceArgs args)
{
Assert.IsNotNull(args, "args");
Sitecore.Data.Items.RenderingItem rendering = new Sitecore.Data.Items.RenderingItem(args.RenderingItem);
UrlString urlString = new UrlString(rendering.Parameters);
var contentFolder = urlString.Parameters[CONTENT_FOLDER_TEMPLATE_PARAM];
if (string.IsNullOrEmpty(contentFolder))
{
return;
}
if (!ID.IsID(contentFolder))
{
Log.Warn(string.Format("{0} for Rendering {1} contains improperly formatted ID: {2}", CONTENT_FOLDER_TEMPLATE_PARAM, args.RenderingItem.Name, contentFolder), this);
return;
}
string text = args.RenderingItem["Datasource Location"];
if (!string.IsNullOrEmpty(text))
{
if (text.StartsWith("./") && !string.IsNullOrEmpty(args.ContextItemPath))
{
var itemPath = args.ContextItemPath + text.Remove(0, 1);
var item = args.ContentDatabase.GetItem(itemPath);
var contextItem = args.ContentDatabase.GetItem(args.ContextItemPath);
if (item == null && contextItem != null)
{
string itemName = text.Remove(0, 2);
//if we create an item in the current site context, the WebEditRibbonForm will see an ItemSaved event and think it needs to reload the page
using (new SiteContextSwitcher(SiteContextFactory.GetSiteContext("system")))
{
contextItem.Add(itemName, new TemplateID(ID.Parse(contentFolder)));
}
}
}
}
}
}