/// <summary> /// Adds Content Access Level and Product Filtering to fetch /// </summary> /// <param name="annotation">Annotation</param> /// <param name="context">Context</param> /// <param name="contentAccessLevelProvider">content Access Level Provider</param> /// <param name="productAccessProvider">product Access Provider</param> private bool AssertKnowledgeArticleCalAndProductFiltering(Entity annotation, OrganizationServiceContext context, ContentAccessLevelProvider contentAccessLevelProvider, ProductAccessProvider productAccessProvider) { if (!contentAccessLevelProvider.IsEnabled() & !productAccessProvider.IsEnabled()) { // If CAL and Product Filtering is not enabled then we must not restrict access to the article. This will also eliminate an unnecessary knowledge article query. return(true); } var entityReference = annotation.GetAttributeValue <EntityReference>("objectid"); var fetch = new Fetch(); var knowledgeArticleFetch = new FetchEntity("knowledgearticle") { Filters = new List <Filter> { new Filter { Type = LogicalOperator.And, Conditions = new List <Condition> { new Condition("knowledgearticleid", ConditionOperator.Equal, entityReference.Id) } } }, Links = new List <Link>() }; fetch.Entity = knowledgeArticleFetch; // Apply Content Access Level filtering. If it is not enabled the fetch will not be modified contentAccessLevelProvider.TryApplyRecordLevelFiltersToFetch(CrmEntityPermissionRight.Read, fetch); // Apply Product filtering. If it is not enabled the fetch will not be modified. productAccessProvider.TryApplyRecordLevelFiltersToFetch(CrmEntityPermissionRight.Read, fetch); var kaResponse = (RetrieveMultipleResponse)context.Execute(fetch.ToRetrieveMultipleRequest()); var isValid = kaResponse.EntityCollection.Entities.Any(); if (isValid) { if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage)) { PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "TryCreateHandler CAL/PF passed", 1, annotation.ToEntityReference(), "read"); } return(true); } if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.TelemetryFeatureUsage)) { PortalFeatureTrace.TraceInstance.LogFeatureUsage(FeatureTraceCategory.Note, HttpContext.Current, "TryCreateHandler CAL/PF failed", 1, annotation.ToEntityReference(), "read"); } return(false); }
/// <summary> /// Validates the content access level and product filtering. /// </summary> /// <param name="serviceContext">The service context.</param> /// <param name="result">The result.</param> /// <returns>Boolean</returns> private bool ValidateContentAccessLevelAndProducts(OrganizationServiceContext serviceContext, CrmEntitySearchResult result, ContentAccessLevelProvider contentAccessLevelProvider, ProductAccessProvider productAccessProvider) { if (result == null || result.EntityID == null) { return(false); } // Content access levels/products will only filter knowledge articles if (result.EntityLogicalName != "knowledgearticle") { return(true); } var baseFetch = string.Format(@" <fetch mapping='logical'> <entity name='knowledgearticle'> <filter type='and'> <condition attribute='knowledgearticleid' operator='eq' value='{0}' /> </filter> </entity> </fetch>" , result.EntityID); if (!contentAccessLevelProvider.IsEnabled() && !productAccessProvider.IsEnabled()) { return(true); } Fetch filterCheckFetch = Fetch.Parse(baseFetch); contentAccessLevelProvider.TryApplyRecordLevelFiltersToFetch(CrmEntityPermissionRight.Read, filterCheckFetch); productAccessProvider.TryApplyRecordLevelFiltersToFetch(CrmEntityPermissionRight.Read, filterCheckFetch); // If there are no results, user didn't have access to products or CALs associated to article var response = (RetrieveMultipleResponse)serviceContext.Execute(filterCheckFetch.ToRetrieveMultipleRequest()); return(response.EntityCollection != null && response.EntityCollection.Entities.Any()); }
/// <summary> /// Overrides Search behavior to do faceted search with BoboBrowse.Net /// </summary> /// <param name="query"> /// The search query. /// </param> /// <returns> /// The <see cref="Query"/>. /// </returns> protected override Query CreateQuery(ICrmEntityQuery query) { if (FeatureCheckHelper.IsFeatureEnabled(FeatureNames.CmsEnabledSearching)) { var baseQuery = base.CreateQuery(query); var compositeQuery = new BooleanQuery() { { baseQuery, Occur.MUST } }; var contentAccessLevelProvider = new ContentAccessLevelProvider(); compositeQuery.Add(new TermQuery(new Term("_logicalname", "annotation")), Occur.MUST_NOT); if (contentAccessLevelProvider.IsEnabled()) { var calQuery = new BooleanQuery(); var userCals = contentAccessLevelProvider.GetContentAccessLevels(); ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding User CALs to Lucene query"); foreach (var cal in userCals) { ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User CAL {0}", cal.Id)); calQuery.Add(new TermQuery(new Term(FixedFacetsConfiguration.ContentAccessLevel, cal.Id.ToString())), Occur.SHOULD); } calQuery.Add(new TermQuery(new Term(FixedFacetsConfiguration.ContentAccessLevel, "public")), Occur.SHOULD); compositeQuery.Add(calQuery, Occur.MUST); } var productAccessProvider = new ProductAccessProvider(); if (productAccessProvider.IsEnabled()) { var productFilteringQuery = new BooleanQuery { { new TermQuery( new Term(FixedFacetsConfiguration.ProductFieldFacetName, this.Index.ProductAccessNonKnowledgeArticleDefaultValue)), Occur.SHOULD } }; var userProducts = productAccessProvider.GetProducts(); ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding User products to Lucene query"); foreach (var product in userProducts) { ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User product {0}", product)); productFilteringQuery.Add( new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, product.ToString())), Occur.SHOULD); } if (PortalContext.Current.User != null) { if (productAccessProvider.DisplayArticlesWithoutAssociatedProductsEnabled()) { productFilteringQuery.Add( new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, this.Index.ProductAccessDefaultValue)), Occur.SHOULD); } } else { productFilteringQuery.Add( new TermQuery(new Term(FixedFacetsConfiguration.ProductFieldFacetName, "unauthenticatedUser")), Occur.SHOULD); } compositeQuery.Add(productFilteringQuery, Occur.MUST); } ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("Adding User WebRoleDefaultValue to Lucene query: {0}", this.Index.WebRoleDefaultValue)); var cmsQuery = new BooleanQuery { { new TermQuery( new Term(this.Index.WebRoleFieldName, this.Index.WebRoleDefaultValue)), Occur.SHOULD } }; // Windows Live ID Server decided to return null for an unauthenticated user's name // A null username, however, breaks the Roles.GetRolesForUser() because it expects an empty string. var currentUsername = (HttpContext.Current != null && HttpContext.Current.User != null && HttpContext.Current.User.Identity != null) ? HttpContext.Current.User.Identity.Name ?? string.Empty : string.Empty; var userRoles = Roles.GetRolesForUser(currentUsername); ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, "Adding user role to Lucene query"); foreach (var role in userRoles) { ADXTrace.Instance.TraceInfo(TraceCategory.Monitoring, string.Format("User role: {0}", role)); cmsQuery.Add(new TermQuery(new Term(this.Index.WebRoleFieldName, role)), Occur.SHOULD); } compositeQuery.Add(cmsQuery, Occur.MUST); // Add the Url Defined Part to the Query. var urlDefinedQuery = new BooleanQuery { { new TermQuery( new Term(this.Index.IsUrlDefinedFieldName, bool.TrueString)), Occur.SHOULD } }; compositeQuery.Add(urlDefinedQuery, Occur.MUST); // Add knowledgearticle to the query compositeQuery.Add( new TermQuery(new Term(FixedFacetsConfiguration.RecordTypeFacetFieldName, FixedFacetsConfiguration.KnowledgeArticleConstraintName)), Occur.SHOULD); return(compositeQuery); } else { return(base.CreateQuery(query)); } }
protected override bool TryCreateHandler(OrganizationServiceContext context, string logicalName, Guid id, out IHttpHandler handler) { if (string.Equals(logicalName, "annotation", StringComparison.InvariantCulture)) { var annotation = context.CreateQuery(logicalName).FirstOrDefault(e => e.GetAttributeValue <Guid>("annotationid") == id); if (annotation != null) { var regarding = annotation.GetAttributeValue <EntityReference>("objectid"); if (regarding != null && string.Equals(regarding.LogicalName, "knowledgearticle", StringComparison.InvariantCulture)) { // Access to a note associated to a knowledge article requires the CMS Security to grant read of the annotation and the related knowledge article. // Additionally, if CAL or Product filtering is enabled and the CAL and/or Product providers reject access to the knowledge article // then access to the note is denied. If CAL and Product filtering are NOT enabled or CAL and/or Product Provider assertion passed, // we must continue to check the Entity Permissions. If the Entity Permission Provider grants read to the knowledge article then the // note can be accessed, otherwise access will be denied. // Assert CMS Security on the annotation and knowledge article. if (TryAssertByCrmEntitySecurityProvider(context, annotation.ToEntityReference()) && TryAssertByCrmEntitySecurityProvider(context, regarding)) { // Assert CAL and/or Product Filtering if enabled. var contentAccessLevelProvider = new ContentAccessLevelProvider(); var productAccessProvider = new ProductAccessProvider(); if (contentAccessLevelProvider.IsEnabled() || productAccessProvider.IsEnabled()) { if (!AssertKnowledgeArticleCalAndProductFiltering(annotation, context, contentAccessLevelProvider, productAccessProvider)) { ADXTrace.Instance.TraceInfo(TraceCategory.Application, $"Access to {EntityNamePrivacy.GetEntityName(annotation.LogicalName)} was denied. Id:{id} RegardingId={regarding.Id} RegardingLogicalName={EntityNamePrivacy.GetEntityName(regarding.LogicalName)}"); handler = null; return(false); } } // Assert Entity Permissions on the knowledge article. if (TryAssertByCrmEntityPermissionProvider(context, regarding)) { ADXTrace.Instance.TraceInfo(TraceCategory.Application, $"Access to {EntityNamePrivacy.GetEntityName(annotation.LogicalName)} was granted. Id:{id} RegardingId={regarding.Id} RegardingLogicalName={EntityNamePrivacy.GetEntityName(regarding.LogicalName)}"); handler = CreateAnnotationHandler(annotation); return(true); } } ADXTrace.Instance.TraceInfo(TraceCategory.Application, $"Access to {EntityNamePrivacy.GetEntityName(annotation.LogicalName)} was denied. Id:{id} RegardingId={regarding.Id} RegardingLogicalName={EntityNamePrivacy.GetEntityName(regarding.LogicalName)}"); handler = null; return(false); } // Assert CMS security on the regarding entity or assert entity permission on the annotation and the regarding entity. if (TryAssertByCrmEntitySecurityProvider(context, regarding) || TryAssertByCrmEntityPermissionProvider(context, annotation, regarding)) { ADXTrace.Instance.TraceInfo(TraceCategory.Application, $"Access to {EntityNamePrivacy.GetEntityName(annotation.LogicalName)} was granted. Id={id} RegardingId={regarding?.Id} RegardingLogicalName={EntityNamePrivacy.GetEntityName(regarding?.LogicalName)}"); handler = CreateAnnotationHandler(annotation); return(true); } } } if (string.Equals(logicalName, "salesliteratureitem", StringComparison.InvariantCulture)) { var salesliteratureitem = context.CreateQuery(logicalName).FirstOrDefault(e => e.GetAttributeValue <Guid>("salesliteratureitemid") == id); if (salesliteratureitem != null) { //Currently salesliteratureitem.iscustomerviewable is not exposed to CRM UI, therefore get the parent and check visibility. //var isCustomerViewable = salesliteratureitem.GetAttributeValue<bool?>("iscustomerviewable").GetValueOrDefault(); var salesliterature = context.CreateQuery("salesliterature") .FirstOrDefault( e => e.GetAttributeValue <Guid>("salesliteratureid") == salesliteratureitem.GetAttributeValue <EntityReference>("salesliteratureid").Id); if (salesliterature != null) { var isCustomerViewable = salesliterature.GetAttributeValue <bool?>("iscustomerviewable").GetValueOrDefault(); if (isCustomerViewable) { handler = CreateSalesAttachmentHandler(salesliteratureitem); return(true); } } } } if (string.Equals(logicalName, "sharepointdocumentlocation", StringComparison.InvariantCulture)) { var location = context.CreateQuery(logicalName).FirstOrDefault(e => e.GetAttributeValue <Guid>("sharepointdocumentlocationid") == id); if (location != null) { var httpContext = HttpContext.Current; var regardingId = location.GetAttributeValue <EntityReference>("regardingobjectid"); // assert CMS access to the regarding entity or assert entity permission on the entity if (TryAssertByCrmEntitySecurityProvider(context, regardingId) || TryAssertByCrmEntityPermissionProvider(context, location, location.GetAttributeValue <EntityReference>("regardingobjectid"))) { var locationUrl = context.GetDocumentLocationUrl(location); var fileName = httpContext.Request["file"]; // Ensure safe file URL - it cannot begin or end with dot, contain consecutive dots, or any of ~ " # % & * : < > ? \ { | } fileName = Regex.Replace(fileName, @"(\.{2,})|([\~\""\#\%\&\*\:\<\>\?\/\\\{\|\}])|(^\.)|(\.$)", string.Empty); // also removes solidus var folderPath = httpContext.Request["folderPath"]; Uri sharePointFileUrl; if (!string.IsNullOrWhiteSpace(folderPath)) { // Ensure safe folder URL - it cannot begin or end with dot, contain consecutive dots, or any of ~ " # % & * : < > ? \ { | } folderPath = Regex.Replace(folderPath, @"(\.{2,})|([\~\""\#\%\&\*\:\<\>\?\\\{\|\}])|(^\.)|(\.$)", string.Empty).Trim('/'); sharePointFileUrl = new Uri("{0}/{1}/{2}".FormatWith(locationUrl.OriginalString, folderPath, fileName)); } else { sharePointFileUrl = new Uri("{0}/{1}".FormatWith(locationUrl.OriginalString, fileName)); } handler = CreateSharePointFileHandler(sharePointFileUrl, fileName); return(true); } if (!httpContext.Request.IsAuthenticated) { httpContext.Response.ForbiddenAndEndResponse(); } else { // Sending Forbidden gets caught by the Application_EndRequest and throws an error trying to redirect to the Access Denied page. // Send a 404 instead with plain text indicating Access Denied. httpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; httpContext.Response.ContentType = "text/plain"; httpContext.Response.Write("Access Denied"); httpContext.Response.End(); } } } if (string.Equals(logicalName, "activitymimeattachment", StringComparison.InvariantCulture)) { var attachment = context.CreateQuery(logicalName).FirstOrDefault(e => e.GetAttributeValue <Guid>("attachmentid") == id); if (attachment != null) { // retrieve the parent object for the annoation var objectId = attachment.GetAttributeValue <EntityReference>("objectid"); // assert CMS access to the regarding entity or assert entity permission on the entity if (TryAssertByCrmEntitySecurityProvider(context, objectId) || TryAssertByCrmEntityPermissionProvider(context, attachment, attachment.GetAttributeValue <EntityReference>("objectid"))) { handler = CreateActivityMimeAttachmentHandler(attachment); return(true); } } } handler = null; return(false); }