SharePoint 2013/2010 根据当前用户的某个属性过滤搜索结果

本文讲述如何在SharePoint 2013/2010 中根据当前用户的某个属性过滤搜索结果。

最近客户有一个需求,就是根据用户所在的国家(User Info List里面有Country字段),在搜索时只显示该用户所在国家的记录(对应的list 有Country 字段)。

一般来说SharePoint 搜索是根据当前用户的权限来决定是否可以搜索到对应的记录,但是过是这样的话,需要将列表的所有记录都打破权限记录,这是非常损耗性能的,而且这样的权限结构维护起来很复杂。

本文将使用 ISecurityTrimmerPost 来实现,根据微软官方的文档说明,这个接口是用于在返回之前过滤查询结果的:

1. 在管理中心新建一个Crawl Rule, ISecurityTrimmerPost 必须挂接在某个Crawl
Rule,只有匹配这个Crawl Rule的查询结果才会调用ISecurityTrimmerPost 去检查:

2. 启动一个 Full Crawl (否则Crawl Rule不会生效)

3. 新建一个SharePoint farm solution, 命名为 CustomSecurityPostTrimmer

4. SharePoint List item搜索结果的地址格式为

sts4://msstoresp12013/siteurl=sites/ap/siteid={cb7ed81a-cce4-4b54-83a8-3b1eadcf2611}/weburl=/webid={199327ad-b16a-4c92-86cd-ec684c847234}/listid={39c30ef9-80c4-46bf-b391-028681191ca0}/folderurl=/itemid=22

这个地址不是程序可以理解的地址,因此需要创建一个STS4Adress类来解吸这个地址  STS4Adress.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CustomSecurityTrimmerSample
{
    class STS4Adress
    {
        public string SiteUrl { get; private set; }
        public string WebUrl { get; private set; }
        public Guid ListId { get; private set; }
        public int ItemId { get; private set; }
        public bool Matched { get; private set; }
        public STS4Adress(string adress)
        {
            string prefix = "sts4://";
            int prefixStartIndex = adress.IndexOf(prefix);
            if (prefixStartIndex >= 0)
            {
                try
                {
                    int firstEqual = adress.IndexOf("=");
                    int endHost = adress.Substring(0, firstEqual).LastIndexOf("/");
                    string host = adress.Substring(prefixStartIndex + prefix.Length, endHost - (prefixStartIndex + prefix.Length));

                    // siteurl=
                    string siteUrlPrefix = "siteurl=";
                    int siteUrlStartIndex = adress.IndexOf(siteUrlPrefix);
                    int nextEqual = adress.IndexOf("=", siteUrlStartIndex + siteUrlPrefix.Length);
                    int siteUrlEndIndex = adress.Substring(0, nextEqual).LastIndexOf("/");
                    this.SiteUrl = "http://" + host + "/" + adress.Substring(siteUrlStartIndex + siteUrlPrefix.Length, siteUrlEndIndex - (siteUrlStartIndex + siteUrlPrefix.Length));

                    // weburl=
                    string weburlPrefix = "weburl=";
                    int weburlStartIndex = adress.IndexOf(weburlPrefix);
                    nextEqual = adress.IndexOf("=", weburlStartIndex + weburlPrefix.Length);
                    int webUrlEndIndex = adress.Substring(0, nextEqual).LastIndexOf("/");
                    this.WebUrl = adress.Substring(weburlStartIndex + weburlPrefix.Length, webUrlEndIndex - (weburlStartIndex + weburlPrefix.Length));

                    // listid=
                    string listIdPrefix = "listid=";
                    int listIdStartIndex = adress.IndexOf(listIdPrefix);
                    nextEqual = adress.IndexOf("=", listIdStartIndex + listIdPrefix.Length);
                    int listIdEndIndex = adress.Substring(0, nextEqual).LastIndexOf("/");
                    this.ListId = new Guid(adress.Substring(listIdStartIndex + listIdPrefix.Length, listIdEndIndex - (listIdStartIndex + listIdPrefix.Length)));

                    // itemid=
                    string itemIdPrefix = "itemid=";
                    int itemIdStartIndex = adress.IndexOf(itemIdPrefix);
                    this.ItemId = int.Parse(adress.Substring(itemIdStartIndex + itemIdPrefix.Length));

                    Matched = true;
                }
                catch (Exception ex)
                {
                    this.Matched = false;
                }
            }
            else
            {
                this.Matched = false;
            }
        }
    }
}

4. 创建CustomSecurityPostTrimmer 来实现 ISecurityTrimmerPost, CustomSecurityPostTrimmer.cs:

using Microsoft.IdentityModel.Claims;
using Microsoft.Office.Server.Search.Administration;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration.Claims;
using Microsoft.SharePoint.Taxonomy;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;

namespace CustomSecurityTrimmerSample
{
    class CustomSecurityPostTrimmer : ISecurityTrimmerPost
    {
        private const string userInfoCaml = @"<Where> <Contains><FieldRef Name=‘Name‘/>
           <Value Type=‘Text‘>{0}</Value></Contains> </Where>";
        public void Initialize(NameValueCollection staticProperties, SearchServiceApplication searchApplication)
        {

        }

        public BitArray CheckAccess(IList<string> documentCrawlUrls, IList<string> documentAcls, IDictionary<string, object> sessionProperties, IIdentity passedUserIdentity)
        {
            var urlStatusArray = new BitArray(documentCrawlUrls.Count, true);
            try
            {
                //CheckAccess method implementation, see steps 3-5.
                if (documentCrawlUrls == null)
                {
                   // throw new ArgumentException("CheckAccess method is called with invalid URL list", "documentCrawlUrls");
                    return urlStatusArray;
                }
                if (documentAcls == null)
                {
                   //  throw new ArgumentException("CheckAccess method is called with invalid documentAcls list", "documentAcls");
                }
                if (passedUserIdentity == null)
                {
                    return urlStatusArray;
                }

                // Initialize the bit array with TRUE value which means all results are visible by default.
                var claimsIdentity = (IClaimsIdentity)passedUserIdentity;

                if (claimsIdentity != null)
                {
                    // var userGroups = GetGroupList(claimsIdentity.Claims);
                    string loginName = GetLonginName(claimsIdentity.Claims);
                    var numberDocs = documentCrawlUrls.Count;
                    for (var i = 0; i < numberDocs; ++i)
                    {
                        // try to parse the url
                        Helper.WriteLog("documentCrawlUrls:" + documentCrawlUrls[i], "CustomSecurityPostTrimmer");
                        STS4Adress sts4Adress = new STS4Adress(documentCrawlUrls[i]);
                        if (sts4Adress.Matched)
                        {

                            string template = "sts4Adress.SiteUrl{0}, sts4Adress.WebUrl{1},  sts4Adress.ListId{2}, sts4Adress.ItemId{3}, documentCrawlUrls:{4}";
                            string log = string.Format(template, sts4Adress.SiteUrl, sts4Adress.WebUrl, sts4Adress.ListId, sts4Adress.ItemId, documentCrawlUrls[i]);
                            Helper.WriteLog(log, "CustomSecurityPostTrimmer");
                            using (SPSite site = new SPSite(sts4Adress.SiteUrl))
                            {
                                if (site.RootWeb.SiteUserInfoList.Fields.ContainsFieldWithStaticName("Country"))
                                {
                                    SPWeb web = site.OpenWeb(sts4Adress.WebUrl);
                                    SPList list = web.Lists[sts4Adress.ListId];
                                    if ((list.Fields.ContainsFieldWithStaticName("Country"))
                                    {
                                        // get the current user‘s country and storetype
                                        SPQuery userInfoQuery = new SPQuery();
                                        userInfoQuery.ViewFields = @"<FieldRef Name=‘Country‘ />";
                                        userInfoQuery.Query = string.Format(userInfoCaml, loginName);

                                        SPListItemCollection userInfoItems = site.RootWeb.SiteUserInfoList.GetItems(userInfoQuery);
                                        if (userInfoItems.Count > 0)
                                        {
                                            string currentCountry = userInfoItems[0]["Country"] == null ? null : userInfoItems[0]["Region"].ToString().Trim();

                                            if ((currentCountry != null))
                                            {
                                                SPItem currentItem = list.GetItemById(sts4Adress.ItemId);
                                                List<string> countries = GetTaxonomyFieldValueCollection(currentItem, "Country");
                                              if (currentCountry != null && countries.Count != 0 && !countries.Contains(currentCountry))
                                                {
                                                    urlStatusArray[i] = false;
                                                }

                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            catch (Exception ex)
            {
                Helper.WriteException(ex, "CustomSecurityPostTrimmer");
            }

            return urlStatusArray;
        }

        public List<string> GetTaxonomyFieldValueCollection(SPItem item, string field)
        {
            List<string> result = new List<string>();
            if (item[field] != null)
            {
                if ((item[field] as TaxonomyFieldValue) != null)
                {
                    result.Add((item[field] as TaxonomyFieldValue).Label);
                }
                else if ((item[field] as TaxonomyFieldValueCollection) != null)
                {
                    TaxonomyFieldValueCollection taxonomyFieldValues = item[field] as TaxonomyFieldValueCollection;
                    foreach (TaxonomyFieldValue taxonomyFieldValue in taxonomyFieldValues)
                    {
                        result.Add(taxonomyFieldValue.Label);
                    }
                }
            }

            return result;
        }

        public string GetLonginName(ClaimCollection claims)
        {
            string loginName = string.Empty;
            foreach (var claim in claims)
            {
                if (SPClaimTypes.Equals(claim.ClaimType, SPClaimTypes.UserLogonName))
                {
                    loginName = claim.Value;
                    break;
                }
            }

            return loginName;
        }
    }
}

CheckAccess返回的urlStatusArray决定每条记录现实与否,true,表示现实, flase表示不现实

5. 部署改解决方案

6.注册CustomSecurityPostTrimmer,以管理员身份运行SharePoint
2013 Management Shell, 执行下列命令:

New-SPEnterpriseSearchSecurityTrimmer -SearchApplication "Search Service Application" -typeName "CustomSecurityTrimmerSample.CustomSecurityPostTrimmer, CustomSecurityTrimmerSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=472c45eb3e5aacc9" -RulePath "http://*"

注意替换 PublicKeyToken

CustomSecurityTrimmerSample 为dll 的名

CustomSecurityTrimmerSample.CustomSecurityPostTrimmer为实现了ISecurityTrimmerPost的类名

可以通过下列命令来查看客户化的SecurityTrimmer

$searchApp = Get-SPEnterpriseSearchServiceApplication
$searchApp | Get-SPEnterpriseSearchSecurityTrimmer

可以通过 Remove-SPEnterpriseSearchSecurityTrimmer 来删除SecurityTrimmer,如:

$searchApp = Get-SPEnterpriseSearchServiceApplication
$trimmer = $searchApp | Get-SPEnterpriseSearchSecurityTrimmer
Remove-SPEnterpriseSearchSecurityTrimmer -identity $trimmer

7. 以不同用户搜索不同的结果:

a. 没有指定Country 的用户可以搜索到所有记录:

b. 指定Country为US的用户,只能搜索到和US匹配的记录:

8.使用本方案的缺点

a. 用户通过搜索list view等显示有不匹配item的页面,点击进入这些页面后仍然可以看到不匹配item

b. SharePoint 2013 调用ISecurityTrimmerPost后不会重新分页,也就是说,本来没有调用ISecurityTrimmerPost之前当前页有10条记录,但有5条不匹配当前用户的属性(Country),当前用户只能看到五条记录,尽管可能下一页还有内容,SharePoint
2013不会把下也内容补齐到本页,也就是说可能有极端情况当前页的10条记录都不匹配,用户可能在当前页什么都看不到,但可以点下一页,看到有搜索结果。

c.
由于在返回搜索结果给用户之前,需要运行检查搜索记录是否匹配当前用户属性(Country)的代码,会降低搜索性能。

时间: 2024-10-03 22:00:30

SharePoint 2013/2010 根据当前用户的某个属性过滤搜索结果的相关文章

Powershell 功能函数大全(Sharepoint 2013/2010)

Powershell 功能函数大全 说明: 本文章讲述powershell操作大全,是笔者多时积累完成.一步步从底层网站架构搭建,到网页内容的呈现, 均由powershell完成. 考虑到网站内容框架的移植,比如从开发环境到测试环境,再到产品环境,底层框架内容可由Powershell一键部署,这样更加方便以及可维护性.考虑到项目架构搭建的异同,初步分为以下步骤: 1. Poweshell 对 Site Column的完整操作 2. Powershell 对 Content Type的完整操作 3

SharePoint 2013/2010 在一个列表或文档库内移动列表项,文档和目录位置而保持last modify by 等系统字段保持不变

本文讲述SharePoint 2013/2010 在一个列表或文档库内移动列表项.文档和目录位置而保持last modify by 等系统字段保持不变的解决方式. 近期遇到客户一个需求,在一个列表或文档库内移动列表项,文档和目录位置而保持last modify by 等系统字段保持不变. 研究出来了.不敢独享.特此共享出来给同鞋们做參考: using Microsoft.SharePoint; using System; using System.Collections.Generic; usi

在SharePoint 2013/2010 解决方案中使用一般处理程序

本文讲述如何在 在SharePoint 2013/2010 解决方案中添加 ashx (HttpHandler). 一般处理程序(HttpHandler)是·NET众多web组件的一种,ashx是其扩展名.一个httpHandler接受并处理一个http请求,类比于Java中的servlet.类比于在Java中需要继承HttpServlet类,在.net中需要实现IHttpHandler接口,这个接口有一个IsReusable成员,一个待实现的方法ProcessRequest(HttpConte

Sharepoint 2013 user permissions(用户权限)

 Read: This permission level gives you read-only access to the website. Contribute:  In addition to all the permissions included in the Read permission level, the Contribute permission level allows you to create, edit, and delete items in existing li

sharepoint 2013 Form认证 注销用户凭证

在网上找到一个关于sharepoint 2013 Form表单认证的sign out 方法,经过验证,有效.方法如下: private void RemoveCookiesAndSignOut() { // Clear sessionstate. if (Context.Session !=null) { Context.Session.Clear(); } string cookieValue = string.Empty; if(Context.Request.Browser["suppor

如何在 在SharePoint 2013/2010 解决方案中添加 ashx (HttpHandler)

本文讲述如何在 在SharePoint 2013/2010 解决方案中添加 ashx (HttpHandler). 一般处理程序(HttpHandler)是·NET众多web组件的一种,ashx是其扩展名.一个httpHandler接受并处理一个http请求,类比于Java中的servlet.类比于在Java中需要继承HttpServlet类,在.net中需要实现IHttpHandler接口,这个接口有一个IsReusable成员,一个待实现的方法ProcessRequest(HttpConte

SharePoint 2013 显示“以其他用户身份登录”菜单项

最近在SharePoint 2013的网站上发现,没有看到有切换不同用户登录的入口,在SharePoint 2010中是存在这样的菜单项能够很方便的进行用户切换的,不知道为什么,SharePoint 2013默认竟然没有.如下图: 这个功能我觉得还是有必要存在的,所以我们需要给它添加一个“以其他用户身份登录”的入口 在\15\TEMPLATE\CONTROLTEMPLATES目录下,找到Welcome.ascx文件 在这个文件的ID为ID_RequestAccess节点后,添加如下代码: 1 <

SharePoint 2013 使用 PowerShell 更新用户

在SharePoint开发中,经常会遇到网站部署,然而,当我们从开发环境,部署到正式环境以后,尤其是备份还原,所有用户组的用户,还依然是开发环境的,这时,我们就需要用PowerShell更新一下: PowerShell命令截图: Windows PowerShell ISE编辑工具编写PowerShell非常方便,头上的一句add是添加SharePoint的引用: PowerShell完整代码: PowerShell的编写和cmd命令.C#代码都有一些区别,尤其是比较字符,字符串连接,变量等等,

sharepoint 2013:活动目录迁移用户后,在sharepoint中move 用户 powershell

Get-SPUser -web http://wfe1  | fl    (查看该网站集中的账户) $user = Get-SPUser -web http://wfe1 -Identity 18   (将id为18的账户赋予变量$user ,不用变量下面一条语句会失败,18也可以用用户名取代) Move-SPUser -IgnoreSID -Identity $user -NewAlias 'test\sale02'   (将该账户迁移为新账户,可以在同一个域中,也可以不同域) 该操作在web