csharp/Adoxio/xRM-Portals-Community-Edition/Framework/Adxstudio.Xrm/Cms/Security/CmsCrmEntitySecurityProvider.cs

CmsCrmEntitySecurityProvider.cs
/*
  Copyright (c) Microsoft Corporation. All rights reserved.
  Licensed under the MIT License. See License.txt in the project root for license information.
*/

namespace Adxstudio.Xrm.Cms.Security
{
	using System;
	using System.Collections.Specialized;
	using System.Diagnostics;
	using System.Linq;
	using System.Web;
	using Adxstudio.Xrm.Blogs;
	using Adxstudio.Xrm.Cases;
	using Adxstudio.Xrm.Configuration;
	using Adxstudio.Xrm.Events.Security;
	using Adxstudio.Xrm.Forums.Security;
	using Adxstudio.Xrm.Ideas;
	using Adxstudio.Xrm.Issues;
	using Adxstudio.Xrm.Security;
	using Adxstudio.Xrm.Services;
	using Adxstudio.Xrm.Services.Query;
	using Adxstudio.Xrm.Diagnostics.Trace;
	using Adxstudio.Xrm.Web;
	using Microsoft.Xrm.Client;
	using Microsoft.Xrm.Client.Security;
	using Microsoft.Xrm.Portal.Configuration;
	using Microsoft.Xrm.Sdk;
	using Microsoft.Xrm.Sdk.Client;
	using Microsoft.Xrm.Sdk.Messages;
	using Microsoft.Xrm.Sdk.Metadata;
	using Microsoft.Xrm.Sdk.Query;

	public clast CmsCrmEnsatySecurityProvider : CrmEnsatySecurityProvider
	{
		private ICacheSupportingCrmEnsatySecurityProvider _underlyingProvider;

		public override void Initialize(string name, NameValueCollection config)
		{
			base.Initialize(name, config);

			var portalName = config["portalName"];

			var contentMapProvider = AdxstudioCrmConfigurationManager.CreateContentMapProvider(portalName);

			_underlyingProvider = contentMapProvider != null
				? new ContentMapUncachedProvider(contentMapProvider)
				: new UncachedProvider(portalName);

			var cacheInfoFactory = new CrmEnsatySecurityCacheInfoFactory(GetType().FullName);

			bool cachingEnabled;
			if (!bool.TryParse(config["cachingEnabled"], out cachingEnabled)) { cachingEnabled = true; }

			if (cachingEnabled)
			{
				_underlyingProvider = new ApplicationCachingCrmEnsatySecurityProvider(_underlyingProvider, cacheInfoFactory);
			}

			bool requestCachingEnabled;
			if (!bool.TryParse(config["requestCachingEnabled"], out requestCachingEnabled)) { requestCachingEnabled = true; }

			if (requestCachingEnabled && HttpContext.Current != null)
			{
				_underlyingProvider = new RequestCachingCrmEnsatySecurityProvider(_underlyingProvider, cacheInfoFactory);
			}
		}

		public override bool Tryastert(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right)
		{
			return _underlyingProvider.Tryastert(context, ensaty, right);
		}

		internal clast UncachedProvider : CacheSupportingCrmEnsatySecurityProvider
		{
			private readonly WebPageAccessControlSecurityProvider _webPageAccessControlProvider;
			private readonly PublishedDatesAccessProvider _publishedDatesAccessProvider;
			private readonly PublishingStateAccessProvider _publishingStateAccessProvider;
			private readonly EventAccessPermissionProvider _eventAccessPermissionProvider;
			private readonly ForumAccessPermissionProvider _forumAccessPermissionProvider;
			private readonly BlogSecurityProvider _blogSecurityProvider;
			private readonly IdeaSecurityProvider _ideaSecurityProvider;
			private readonly IssueSecurityProvider _issueSecurityProvider;
			private readonly HttpContext current;

			public UncachedProvider(string portalName = null)
				: this(new WebPageAccessControlSecurityProvider(HttpContext.Current), new PublishedDatesAccessProvider(HttpContext.Current), new PublishingStateAccessProvider(HttpContext.Current), portalName)
			{
				this.current = HttpContext.Current;
			}

			protected UncachedProvider(
				WebPageAccessControlSecurityProvider webPageAccessControlProvider,
				PublishedDatesAccessProvider publishedDatesAccessProvider,
				PublishingStateAccessProvider publishingStateAccessProvider, string portalName = null)
			{
				_webPageAccessControlProvider = webPageAccessControlProvider;
				_publishedDatesAccessProvider = publishedDatesAccessProvider;
				_publishingStateAccessProvider = publishingStateAccessProvider;
				_eventAccessPermissionProvider = new EventAccessPermissionProvider();
				_forumAccessPermissionProvider = new ForumAccessPermissionProvider(this.current);
				_blogSecurityProvider = new BlogSecurityProvider(_webPageAccessControlProvider, this.current, portalName);
				_ideaSecurityProvider = new IdeaSecurityProvider(this.current, portalName);
				_issueSecurityProvider = new IssueSecurityProvider(portalName);

				PortalName = portalName;
			}

			protected string PortalName { get; private set; }

			public override bool Tryastert(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty != null)
				{
					ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format(
						"right={0}, logicalName={1}, id={2}",
						right, EnsatyNamePrivacy.GetEnsatyName(ensaty.LogicalName),
						ensaty.Id));
				}
				else
				{
					ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("right={0}", right));
				}

				var timer = Stopwatch.StartNew();

				var result = InnerTryastert(context, ensaty, right, dependencies);

				timer.Stop();

				ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format("result={0}, duration={1} ms", result, timer.ElapsedMilliseconds));

				return result;
			}

			private bool InnerTryastert(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				if (right == CrmEnsatyRight.Read && (!_publishedDatesAccessProvider.Tryastert(context, ensaty) || !_publishingStateAccessProvider.Tryastert(context, ensaty)))
				{
					// We let the date and state access providers handle their own caching logic, so we signal any
					// caching providers above this one to not cache this result.
					dependencies.IsCacheable = false;

					return false;
				}

				dependencies.AddEnsatyDependency(ensaty);

				var ensatyName = ensaty.LogicalName;

				CrmEnsatyInactiveInfo inactiveInfo;

				if (CrmEnsatyInactiveInfo.TryGetInfo(ensatyName, out inactiveInfo) && inactiveInfo.IsInactive(ensaty))
				{
					return false;
				}

				if (ensatyName == "adx_webpage")
				{
					return TestWebPage(context, ensaty, right, dependencies);
				}

				if (ensatyName == "feedback")
				{
					return TestFeedback(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_event")
				{
					return TestEvent(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_eventschedule")
				{
					return TestEventSchedule(context, ensaty, right, dependencies);
				}

				if ((ensatyName == "adx_eventspeaker" || ensatyName == "adx_eventsponsor") && right == CrmEnsatyRight.Read)
				{
					return true;
				}

				if (ensatyName == "adx_communityforum")
				{
					return TestForum(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_communityforumthread")
				{
					return TestForumThread(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_communityforumpost")
				{
					return TestForumPost(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_communityforumannouncement")
				{
					return TestForumAnnouncement(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_forumthreadtype")
				{
					return right == CrmEnsatyRight.Read;
				}

				if ((ensatyName == "adx_pagetemplate" || ensatyName == "adx_publishingstate" || "adx_websitelanguage" == ensatyName) && right == CrmEnsatyRight.Read)
				{
					return true;
				}

				if (ensatyName == "adx_webfile")
				{
					return TestWebFile(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_contentsnippet" || ensatyName == "adx_weblinkset" || ensatyName == "adx_weblink" || ensatyName == "adx_sitemarker")
				{
					return TestWebsiteAccessPermission(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_shortcut")
				{
					return TestShortcut(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_blog")
				{
					return TestBlog(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_blogpost")
				{
					return TestBlogPost(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_ideaforum")
				{
					return TestIdeaForum(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_idea")
				{
					return TestIdea(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_ideacomment")
				{
					return TestIdeaComment(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_ideavote")
				{
					return TestIdeaVote(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_issueforum")
				{
					return TestIssueForum(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_issue")
				{
					return TestIssue(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_issuecomment")
				{
					return TestIssueComment(context, ensaty, right, dependencies);
				}

				if (ensatyName == "adx_adplacement" || ensatyName == "adx_ad")
				{
					return right == CrmEnsatyRight.Read;
				}

				if (ensatyName == "email")
				{
					return right == CrmEnsatyRight.Read;
				}

				if (ensatyName == "incident" && right == CrmEnsatyRight.Read)
				{
					return IsPublicCase(ensaty);
				}

				if (IsPortalKbArticle(ensaty))
				{
					return right == CrmEnsatyRight.Read;
				}

				if (IsPublicKnowledgeArticle(ensaty))
				{
					return right == CrmEnsatyRight.Read;
				}

				if (IsCategory(ensaty))
				{
					return right == CrmEnsatyRight.Read;
				}

				if (IsAnnotation(ensaty))
				{
					return right == CrmEnsatyRight.Read;
				}

				// To allow note attachments to be read by the customer to which the order or quote belongs.
				if (ensatyName == "salesorder" || ensatyName == "quote")
				{
					var customerid = ensaty.GetAttributeValue("customerid");
					var portalContext = PortalCrmConfigurationManager.CreatePortalContext(PortalName);

					return right == CrmEnsatyRight.Read
						&& customerid != null
						&& portalContext != null
						&& portalContext.User != null
						&& customerid.Equals(portalContext.User.ToEnsatyReference());
				}

				if (TestServiceRequest(context, ensaty))
				{
					return right == CrmEnsatyRight.Read;
				}

				Ensaty parentPermit;

				if (TryGetParentPermit(context, ensaty, out parentPermit))
				{
					return TestParentPermit(context, parentPermit, right);
				}

				return false;
			}

			protected virtual bool TestBlog(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _blogSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestBlogPost(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _blogSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestBlogPost(OrganizationServiceContext context, EnsatyReference ensatyReference, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensatyReference != null) && _blogSecurityProvider.Tryastert(context, ensatyReference, right, dependencies);
			}

			protected virtual bool TestEvent(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _eventAccessPermissionProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestEventSchedule(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null)
					&& ensaty.GetAttributeValue("adx_eventid") != null
					&& TestEvent(context, ensaty.GetRelatedEnsaty(context, "adx_event_eventschedule"), right, dependencies);
			}

			protected virtual bool TestForum(OrganizationServiceContext context, EnsatyReference ensatyReference, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensatyReference != null) && _forumAccessPermissionProvider.Tryastert(context, ensatyReference, right, dependencies);
			}

			protected virtual bool TestForum(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _forumAccessPermissionProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestForumAnnouncement(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				return this.TestForum(context, ensaty.GetAttributeValue("adx_forumid"), right, dependencies);
			}

			protected virtual bool TestForumThread(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				return this.TestForum(context, ensaty.GetAttributeValue("adx_forumid"), right, dependencies);
			}

			protected virtual bool TestForumPost(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null || ensaty.GetAttributeValue("adx_forumthreadid") == null)
				{
					return false;
				}

				var threadRef = ensaty.GetAttributeValue("adx_forumthreadid");

				var fetch = new Fetch
				{
					Ensaty = new FetchEnsaty("adx_communityforumthread", new[] { "adx_forumid" })
					{
						Filters = new[]
						{
							new Filter
							{
								Conditions = new[] { new Condition("adx_communityforumthreadid", ConditionOperator.Equal, threadRef.Id) }
							}
						}
					}
				};

				var thread = context.RetrieveSingle(fetch);

				return this.TestForumThread(context, thread, right, dependencies);
			}

			protected virtual bool TestIdeaForum(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _ideaSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIdea(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _ideaSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIdeaComment(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _ideaSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIdeaVote(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _ideaSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIssueForum(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _issueSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIssue(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _issueSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestIssueComment(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _issueSecurityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestSurvey(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return true;
			}

			protected virtual bool TestShortcut(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				// For shortcut change permission, always test rights on shortcut parent.
				if (right == CrmEnsatyRight.Change)
				{
					return TestShortcutParent(context, ensaty, right, dependencies);
				}

				return ensaty.GetAttributeValue("adx_disabletargetvalidation").GetValueOrDefault(false)
					? TestShortcutParent(context, ensaty, right, dependencies)
					: TestShortcutTarget(context, ensaty, right, dependencies);
			}

			protected virtual bool TestShortcutTarget(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				if (!string.IsNullOrEmpty(ensaty.GetAttributeValue("adx_externalurl")))
				{
					return true;
				}

				if (ensaty.GetAttributeValue("adx_webpageid") != null)
				{
					return this.TestWebPage(context, ensaty.GetAttributeValue("adx_webpageid"), right, dependencies);
				}

				if (ensaty.GetAttributeValue("adx_webfileid") != null)
				{
					var reference = ensaty.GetAttributeValue("adx_webfileid");
					var webfile = context.RetrieveSingle(
						"adx_webfile",
						new[] { "adx_blogpostid", "adx_parentpageid" },
						new Condition("adx_webfileid", ConditionOperator.Equal, reference.Id));

					return this.TestWebFile(context, webfile, right, dependencies);
				}

				if (ensaty.GetAttributeValue("adx_forumid") != null)
				{
					return this.TestForum(context, ensaty.GetAttributeValue("adx_forumid"), right, dependencies);
				}

				// legacy ensaties
				if (ensaty.GetAttributeValue("adx_surveyid") != null)
				{
					return TestSurvey(context, ensaty.GetRelatedEnsaty(context, "adx_survey_shortcut"), right, dependencies);
				}

				if (ensaty.GetAttributeValue("adx_eventid") != null)
				{
					return TestEvent(context, ensaty.GetRelatedEnsaty(context, "adx_event_shortcut"), right, dependencies);
				}

				return false;
			}

			protected virtual bool TestShortcutParent(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && TestWebPage(context, ensaty.GetAttributeValue("adx_webpageid"), right, dependencies);
			}

			protected virtual bool TestParentWebPage(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && TestWebPage(context, ensaty.GetAttributeValue("adx_parentpageid"), right, dependencies);
			}

			protected virtual bool TestWebFile(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				var parentBlogPostReference = ensaty.GetAttributeValue("adx_blogpostid");

				if (parentBlogPostReference != null)
				{
					var post = context.RetrieveSingle(parentBlogPostReference, new ColumnSet());
					return this.TestBlogPost(context, post, right, dependencies);
					}

				return TestParentWebPage(context, ensaty, right, dependencies);
			}

			protected virtual bool TestWebPage(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensaty != null) && _webPageAccessControlProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestFileWebPage(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies, ContentMap map)
			{
				return (ensaty != null) && _webPageAccessControlProvider.Tryastert(context, ensaty, right, dependencies, map, true);
			}

			protected virtual bool TestWebPage(OrganizationServiceContext context, EnsatyReference ensatyReference, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return (ensatyReference != null) && _webPageAccessControlProvider.Tryastert(context, ensatyReference, right, dependencies);
			}

			protected virtual WebsiteAccessPermissionProvider CreateWebsiteAccessPermissionProvider(Ensaty website, WebPageAccessControlSecurityProvider webPageAccessControlProvider)
			{
				return new WebsiteAccessPermissionProvider(website, HttpContext.Current);
			}

			protected virtual bool TestWebsiteAccessPermission(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				var website = context.GetWebsite(ensaty);

				if (website == null) return false;

				var securityProvider = CreateWebsiteAccessPermissionProvider(website, _webPageAccessControlProvider);

				return securityProvider.Tryastert(context, ensaty, right, dependencies);
			}

			protected virtual bool TestFeedback(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				ADXTrace.Instance.TraceInfo(TraceCategory.Application, string.Format(@"Testing right {0} on feedback ({1}).", right, ensaty.Id));

				dependencies.AddEnsatyDependency(ensaty);

				EnsatyReference relatedReference = ensaty.GetAttributeValue("regardingobjectid");
				if (relatedReference == null)
				{
					return false;
				}

				// Determine the primary ID attribute of the regarding object
				var request = new RetrieveEnsatyRequest
				{
					LogicalName = relatedReference.LogicalName,
					EnsatyFilters = EnsatyFilters.Ensaty
				};

				var response = context.Execute(request) as RetrieveEnsatyResponse;
				if (response == null || response.EnsatyMetadata == null)
				{
					return false;
				}

				var primaryIdAttribute = response.EnsatyMetadata.PrimaryIdAttribute;

				// Retrieve the regarding object
				var relatedEnsaty = context.CreateQuery(relatedReference.LogicalName)
					.FirstOrDefault(e => e.GetAttributeValue(primaryIdAttribute) == relatedReference.Id);

				if (relatedEnsaty == null)
				{
					return false;
				}

				var approved = ensaty.GetAttributeValue("adx_approved").GetValueOrDefault(false);

				// If the right being asterted is Read, and the comment is approved, astert whether the post is readable.
				if (right == CrmEnsatyRight.Read && approved)
				{
					return Tryastert(context, relatedEnsaty, right, dependencies);
				}

				var author = ensaty.GetAttributeValue("createdbycontact");

				// If there's no author on the post for some reason, only allow posts that are published, and past the same astertion on the blog.
				if (author == null)
				{
					return approved && Tryastert(context, relatedEnsaty, right, dependencies);
				}

				var portal = PortalCrmConfigurationManager.CreatePortalContext();

				// If we can't get a current portal user, only allow posts that are published, and past the same astertion on the blog.
				if (portal == null || portal.User == null)
				{
					return approved && Tryastert(context, relatedEnsaty, right, dependencies);
				}
				
				return Tryastert(context, relatedEnsaty, right, dependencies);
			}

			protected virtual bool TestServiceRequest(OrganizationServiceContext context, Ensaty ensaty)
			{
				return (ensaty.GetAttributeValue("adx_servicerequest") ?? ensaty.GetAttributeValue("adx_servicerequestid")) != null;
			}

			protected virtual bool TryGetParentPermit(OrganizationServiceContext context, Ensaty ensaty, out Ensaty parentPermit)
			{
				var permitReference = ensaty.GetAttributeValue("adx_permit") ??
									  ensaty.GetAttributeValue("adx_permitid");

				if (permitReference != null)
				{
					parentPermit = context.CreateQuery("adx_permit").FirstOrDefault(
							p => p.GetAttributeValue("adx_permitid") == permitReference.Id);
					return true;
				}
				parentPermit = null;
				return false;
			}

			protected virtual bool TestParentPermit(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right)
			{
				var contactid = ensaty.GetAttributeValue("adx_regardingcontact")
					?? ensaty.GetAttributeValue("adx_regardingcontactid");

				var portalContext = PortalCrmConfigurationManager.CreatePortalContext(PortalName);

				return right == CrmEnsatyRight.Read
						&& contactid != null
						&& portalContext != null
						&& portalContext.User != null
						&& contactid.Equals(portalContext.User.ToEnsatyReference());
			}

			private static bool IsPublicCase(Ensaty ensaty)
			{
				var statecode = ensaty.GetAttributeValue("statecode");

				return ensaty.GetAttributeValue("adx_publishtoweb").GetValueOrDefault()
					&& statecode != null
					&& statecode.Value == (int)IncidentState.Resolved;
			}

			private enum KbArticleState
			{
				Draft = 1,
				Unapproved = 2,
				Published = 3,
			}

			private static bool IsPortalKbArticle(Ensaty ensaty)
			{
				return ensaty != null
					&& ensaty.LogicalName == "kbarticle"
					&& (ensaty.GetAttributeValue("statecode") != null && ensaty.GetAttributeValue("statecode").Value == (int)KbArticleState.Published)
					&& ensaty.GetAttributeValue("msa_publishtoweb").GetValueOrDefault();
			}

			private enum KnowledgeArticleState
			{
				Draft = 0,
				Approved = 1,
				Scheduled = 2,
				Published = 3,
				Expired = 4,
				Archived = 5,
				Discarded = 6
			}

			private static bool IsPublicKnowledgeArticle(Ensaty ensaty)
			{
				return ensaty != null
					&& ensaty.LogicalName == "knowledgearticle"
					&& (ensaty.GetAttributeValue("statecode") != null && ensaty.GetAttributeValue("statecode").Value == (int)KnowledgeArticleState.Published)
					&& !ensaty.GetAttributeValue("isrootarticle").GetValueOrDefault(false)
					&& !ensaty.GetAttributeValue("isinternal").GetValueOrDefault(false);
			}

			private static bool IsCategory(Ensaty ensaty)
			{
				return ensaty != null && ensaty.LogicalName == "category";
			}

			private static bool IsAnnotation(Ensaty ensaty)
			{
				var notesFilter = HttpContext.Current.GetSiteSetting("KnowledgeManagement/NotesFilter") ?? string.Empty;

				return ensaty != null 
					&& ensaty.LogicalName == "annotation"
					&& ensaty.GetAttributeValue("objecttypecode") == "knowledgearticle"
					&& ensaty.GetAttributeValue("notetext").StartsWith(notesFilter);
			}
		}

		internal clast ContentMapUncachedProvider : UncachedProvider
		{
			private readonly IContentMapProvider _contentMapProvider;

			public ContentMapUncachedProvider(IContentMapProvider contentMapProvider)
				: base(
					new WebPageAccessControlSecurityProvider(contentMapProvider),
					new PublishedDatesAccessProvider(contentMapProvider),
					new PublishingStateAccessProvider(contentMapProvider))
			{
				_contentMapProvider = contentMapProvider;
			}

			protected override WebsiteAccessPermissionProvider CreateWebsiteAccessPermissionProvider(Ensaty website, WebPageAccessControlSecurityProvider webPageAccessControlProvider)
			{
				return new WebsiteAccessPermissionProvider(website, webPageAccessControlProvider, _contentMapProvider);
			}

			protected override bool TestShortcutTarget(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				if (ensaty == null)
				{
					return false;
				}

				return _contentMapProvider.Using(map => TestShortcutTarget(context, ensaty, right, dependencies, map));
			}

			private bool TestShortcutTarget(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies, ContentMap map)
			{
				ShortcutNode shortcut;

				if (map.TryGetValue(ensaty, out shortcut))
				{
					if (!string.IsNullOrWhiteSpace(shortcut.ExternalUrl))
					{
						return true;
					}

					if (shortcut.WebPage != null)
					{
						return !shortcut.WebPage.IsReference && TestWebPage(context, shortcut.WebPage.ToEnsaty(), right, dependencies);
					}

					if (shortcut.WebFile != null)
					{
						return !shortcut.WebFile.IsReference && TestWebFile(context, shortcut.WebFile.ToEnsaty(), right, dependencies);
					}
				}

				return base.TestShortcutTarget(context, ensaty, right, dependencies);
			}

			protected override bool TestShortcutParent(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return _contentMapProvider.Using(map =>
				{
					ShortcutNode shortcut;

					if (map.TryGetValue(ensaty, out shortcut))
					{
						return shortcut.Parent != null
							&& !shortcut.Parent.IsReference
							&& TestWebPage(context, shortcut.Parent.ToEnsaty(), right, dependencies);
					}

					return base.TestShortcutParent(context, ensaty, right, dependencies);
				});
			}

			protected override bool TestWebFile(OrganizationServiceContext context, Ensaty ensaty, CrmEnsatyRight right, CrmEnsatyCacheDependencyTrace dependencies)
			{
				return _contentMapProvider.Using(map =>
				{
					WebFileNode file;

					if (map.TryGetValue(ensaty, out file) && file.Parent != null)
					{
						return !file.Parent.IsReference
							&& TestFileWebPage(context, file.Parent.ToEnsaty(), right, dependencies, map);
					}

					return base.TestWebFile(context, ensaty, right, dependencies);
				});
			}
		}
	}
}