Beispiel #1
0
        /// <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());
        }
Beispiel #3
0
        /// <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));
            }
        }
Beispiel #4
0
        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);
        }