介绍
Windows SharePoint Services 3.0 和 Microsoft Office SharePoint Server 2007 支持丰富的安全模型,允许管理员把特定安全对象(比如站点、列表、库、文件夹甚至独立的文档和条目)的权限分配给用户和用户组,来控制对站点和内容的访问。
然而,在某些情形下,需要对列表和文档库中单独的列进行安全控制。目前,SharePoint 没有为列或视图的安全控制提供内置支持。一个需要这种功能的典型场景就是,在一个包含了大量雇员或客户的信息的列表当中,某些特定的列(薪资、工资发放日期、晋升机会等等)最好只被这个门户里的特定用户组查看到。
为了满足这些场景的要求,本文描述了一个利用 SharePoint 扩展性和内置条目级别安全性的方法,允许在自定义字段类型上实现列基本权限控制。这是通过使用一个查询字段作为列来实现的,在这种方式的背后实际上紧密关联着另外一个列表,其中包含了安全值和只为具有有效权限的用户返回这些值的方法。
其结果就是,在视图模式中经授权的用户将看到安全列的内容,就像它是一个普通列一样;相反,未经授权的用户就根本看不到这个列的内容。在图 1 中显示了不同的行为。类似的,只有经授权的用户能够以新建和编辑模式访问安全列的内容。
图 1:经授权和未经授权用户查看安全列的效果
列级安全性:解决方案架构
为了设计自定义列级安全性解决方案,需解决如下问题:
- 以安全的方式存储数据,而不会暴露安全数据给未经授权的用户
- 只向获得授权的用户呈现安全数据
- 只允许获得授权的用户编辑安全数据
为了解决这些问题,我们决定使用自定义字段类型(Custom Field Type)用于数据的呈现和处理,并使用条目级别安全性(Item Level Security)的功能用于数据存储。
自定义字段类型同内容类型(Content Types)和列表窗体(List Forms)是 Windows SharePoint Services 的主要扩展机制,允许自定义数据的访问、呈现和处理。你能在 Windows SharePoint Services 3.0 SDK 中阅读关于自定义字段类型的内容。
我们的自定义字段类型的主要角色是保证只有具有正确授权的用户才能查看或编辑数据。我们实现了一个名为 Secure Column 的自定义字段类型,能用于任何 SharePoint 列表中。
对于存储数据,我们考虑过的一种选择是在单独的数据库中使用单独的数据表。这对于为了其他目而使用单独数据库的情况来说,是一种较好的 SharePoint 实现选择。然而,考虑到大部分 SharePoint 解决方案都未使用非 SharePoint 数据库,所以针对这篇文章和示例,我们决定使用 SharePoint 列表(List)作为存储机制,而不引入对单独数据库的依赖。
使用 SharePoint 列表作为安全数据的后端存储方式,能够为每条需要进行安全控制的数据创建一个列表条目(List Item)。为了提供适当的安全保证级别,条目级别安全(Item-Level Security)特性也要被使用到。我们在这里承诺,列级安全性应该和内置的 SharePoint 条目级别安全性一样安全。
对于实现决策,要着重注意的一点就是,使用 SharePoint 列表和查询(Lookup)列功能同样具有一些固有的限制。这些限制是:在非常大的列表中进行查找会影响到性能,并且查询列也不能直接访问位于不同站点集合(Site Collection)中的列表。
实现细节
为了实现列级安全性,需要几个自定义组件:
- 数据存储列表(Data Storage List)
- 自定义字段类型,包括自定义字段和编辑器控件
- 部署解决方案包(Deployment Solution Package)
这些组件如图 2 所描述的那样,协同工作在一起,以允许用户创建、查看和编辑存储在安全列中的数据。
图 2 实现概要
对于这个示例实现,一个单独的数据存储列表(Data Storage List)用于保存这个站点集中的所有数据。这样可以让这个解决方案更简单,更易维护。不过一个缺点就是,所有的安全字段都依赖于单一的存储点。在非常庞大的解决方案中,这可能会引起性能问题。如果出现这种情况,那么示例解决方案也能够扩展成为每个安全字段创建一个新的数据存储列表。
数据存储列表
虽然使用 SharePoint 列表作为安全数据的存储方式带来了很多好处,如条目级别安全性和完整的 API 支持。但也需要面对几个挑战:
- 可见性——普通的 SharePoint 列表对于所有具有访问权限的用户来说都是可见的。
- 可伸缩性——如果在系统中,单一列表用于存储来自所有安全列的数据,那么在列表中的条目数量可能会非常巨大而超出了 SharePoint 建议的限制,性能可能会出现明显的下降。
- 权限维护——如果条目级别安全性用于保证每个条目的安全性,那么更新列的权限可能会变得非常困难。
- 创建——当用户添加一个新列时,并不一定意味着列表已经存在了。
这些挑战中的每一个都需要用标准的 SharePoint 列表功能或通过添加自定义字段类型中的自定义代码来解决。
为了解决可见性的问题,我们创建了“目录”列表。这类似于创建标准 SharePoint 实现中的那些系统列表。目录列表包括:Web 部件库、站点模板库和母版页库。另外,SPList 对象的如下属性也进行了设置以进一步隐藏列表:
- Hidden——设置该值为 False 将从所有标准 UI 元素中删除列表。
- NoCrawl——设置该值为 True 将保证 SharePoint crawl 引擎不包括列表中的数据。
- OnQuickLaunch——设置该值为 False 将保证列表不会被添加到快速启动导航栏中。
为了解决可伸缩性和权限维护问题,我们决定不使用平面表,而是转而创建一个两级文件夹结构来分组这些条目。这个文件夹结构的第一级和给定的安全列所属的列表相对应。第二级和安全列本身相对应。最终,在一个单独文件夹中,条目数量就不会超过给定安全列所属列表的条目数量,即使对于那种包含了多个安全列的列表也是如此。这种方式能够解决可扩展性的问题。拥有这样的文件夹结构,我们就可以使用第二级文件夹的权限来表示和给定的安全列关联的权限。这样也去除了为每个独立条目维护权限的需要。
图 3:文件夹结构
最后一个问题是创建出的这个列表应该能作为自定义字段类型的一部分被轻易地处理。为了达到这个目的,假如当新字段创建的时候不存在这个列表,我们就用代码来创建它。
自定义字段类型
在上面的解决方案架构一节提到,数据不是真正的存储在这个列表中,而是通过添加到 SharePoint 列表中的安全列在逻辑上的表示。实际情况是,把数据安全地存储在独立的、专门的 SharePoint 列表中,就是我们提到的数据存储列表(这个列表具有一个叫做 _SecureFieldStorage 的内部名称,并且具有叫做 _SecureFieldStorage 的站点相对 URL)。获取存储在这个独立的数据存储列表中的数据,并把其显示在宿主列表的上下文中,则是自定义字段类型的主要用处。
当我们在研究如何实现这个功能的时候,我们希望让我们的自定义字段类型从基础的 SPField 类继承以避免从零开始。我们应尽可能地利用 SharePoint 现有的功能。如果我们考虑把一些可用的 SharePoint 现有的字段类型用作我们自定义字段类型的基类时,查询字段类型(SPFieldLookup)会是最好的选择。
对于查询字段,我们打算使用的主要特性是指向其他 SharePoint 列表中的字段,并基于特定的列表条目编号获取这个字段的值,这就是我们所需要的功能。而且,使用查询字段功能允许充分利用内部实现的一些优点——它们得利于 SQL Join 的功能,在提供很好的伸缩性的同时还能具备很好的安全性。
虽然查询字段类型的核心功能为我们所需功能提供了很好的开端,不过在标准查询字段特性和我们的需求之间还存在很多不匹配的地方。这就是我们的自定义代码的用武之处——用于扩展 SPLookupField:
- 用户一定不能改变关联到查询列的列表。
- 用户必须能够设定什么权限能够在和这个列关联的数据上设置。
- 当新列创建的时候,它应该能自动地把相关的列表和列设置到我们自定义的数据存储列表上。
- 如果自定义数据存储列表不存在,那么它应该能被自动地创建。
- 当显示这个字段的值时,值不应该呈现为一个指向相关列表条目的超链接。
- 当处于编辑或新建模式的时候,这个字段应该呈现为一个标准的文本列表,且任何改变都应该能持久保存到目标列表中。
- 当处于编辑或新建模式的时候,如果用户不具有适当的权限,那么这个字段的数据值应该被隐藏。
自定义类型 XML
任何自定义字段类型实现的一个重要组件就是自定义 fldtypes.xml 文件。在我们的这个例子中,这个文件被定制为指向我们的自定义字段类型类:
<<span color="#a31515">Field</span><span color="#ff0000">Name</span>="<span color="#0000ff">FieldTypeClass"> SecureField.SecureField, SecureField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=48a15d1316dd0f7d</span> Field>
我们也包含了一个自定义显示模式。这个显示模式采用如下方式被识别,并且保证它不会被呈现为超链接,它默认用于 Lookup 列。
<<span color="#a31515">LookupColumn</span> <span color="#ff0000">HTMLEncode</span> <span color="#0000ff">=</span><span color="#0000ff">"TRUE"</span> "<span color="#ff0000">AutoHyperLink</span>=<span color="#0000ff">"FALSE"</span>"<span color="#0000ff">/></span>
我们仅仅定义的另外一个是设定一个自定义字段编辑器控件。
<<span color="#a31515">Field</span><span color="#ff0000">Name</span><span color="#0000ff">=</span>"<span color="#0000ff">FieldEditorUserControl</span>">/_controltemplates/SecureFieldEditor.ascx Field>
自定义字段类型类
自定义字段类型类包含了我们自定义的核心查询字段功能的主要代码。这些功能包括:
- 创建数据存储列表
- 创建数据存储列表结果
- 在数据存储列表上设置权限
- 关联我们的自定义字段控件
- 当字段被删除的时候,清除数据存储列表
这个类从 SPFieldLookup 继承,并重写了 Update 方法以便在安全列被创建或编辑的任何时候,背后的数据存储列表都能被创建并以适当的权限进行设置。
<span color="#0000ff">public override void Update()<br></br> {<br></br><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(EnsureSecureFieldStorageListExists);<p><span color="#2b91af">SPWeb</span> web = <span color="#2b91af">SPContext</span>.Current.Site.RootWeb;</p><p> this.LookupWebId = web.ID;</p><br></br> this.LookupField = secureFieldStorageFieldName;<br></br> this.LookupList = web.Lists[secureFieldStorageListName].ID.ToString();<p> RetrieveCustomProperties();</p><p><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(ApplyPermissions);</p><p> base.Update();</p><p> }</p></span>
所有这些动作都在严格权限的级别中执行。这就保证标准用户不会创建一个具有错误的新安全列。
另外,为了达到这种定制的效果,我们也需要重写 FieldRenderingControl 属性以便我们的自定义字段控件能被使用。
<span color="#0000ff"> public override <span color="#2b91af">BaseFieldControl</span> FieldRenderingControl<br></br> {<br></br> get<br></br> {<br></br><span color="#2b91af">BaseFieldControl</span> control = new <span color="#2b91af">SecuredFieldControl</span>();<br></br> control.FieldName = this.InternalName;<br></br> return control;<br></br> }<br></br> }</span>
最后需要定制的是重写 OnDeleting 方法以保证我们能在列被删除的时候清楚任何相关的数据。
<span color="#0000ff"> public override void OnDeleting()<br></br> {<br></br> base.OnDeleting();<p><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(removeFieldFolder);</p><br></br> }</span>
#### 自定义字段编辑器控件
除了核心的字段类型类之外,还需要一个自定义字段编辑器控件,才能让用户设置安全列上的权限。权限不仅能在列被添加到列表的时候设置,也可以之后随时更新。这个功能是用一个包含了 SharePoint PeopleEditor 控件的用户控件来实现的。这个控件能让用户搜索和选择安全主体。
<<span color="#a31515">sharepoint:PeopleEditor</span> <span color="#ff0000">ID</span>=<span color="#0000ff">"AllowedPrincipalsPeoplePicker"</span> <span color="#ff0000">runat</span>=<span color="#0000ff">"server"</span> <span color="#ff0000">AutoPostBack</span>=<span color="#0000ff">"false"</span> <span color="#ff0000">PlaceButtonsUnderEntityEditor</span>=<span color="#0000ff">"true"</span> <span color="#ff0000">SelectionSet</span>=<span color="#0000ff">"SPGroup"</span> <span color="#ff0000">MultiSelect</span>=<span color="#0000ff">"true"</span> />
为了设置并获取安全字段类型的信息,用户控件的后置代码类实现了 IFieldEditor 接口。特别地,InitializeWithField 方法用于从字段中获取任何现有的安全设置。
<span color="#0000ff">if (Page.IsPostBack)<br></br> return;<p><span color="#008000">// Initialize the people picker control using comma separated account list from the secure field</span><span color="#2b91af">SecureField</span> secureField = <span color="#2b91af">(SecureField)</span>field;</p><p> if (secureField != null && secureField.AllowedPrincipals != null)</p><br></br> {<br></br><span color="#2b91af">StringBuilder</span> accounts = new <span color="#2b91af">StringBuilder</span>();<br></br> foreach (object entity in secureField.AllowedPrincipals)<br></br> {<br></br> accounts.Append((entity as <span color="#2b91af">PickerEntity</span>).Key);<br></br> accounts.Append(',');<br></br> }<p> this.AllowedPrincipalsPeoplePicker.CommaSeparatedAccounts = accounts.ToString();</p><br></br> this.AllowedPrincipalsPeoplePicker.Validate();<br></br> }</span>
此外,OnSaveChange 方法会用于更新字段安全设置。
<span color="#0000ff">AllowedPrincipalsPeoplePicker.Validate();<p><span color="#2b91af">SecureField</span> secureField = (<span color="#2b91af">SecureField</span>)field;</p><p> secureField.AllowedPrincipals = AllowedPrincipalsPeoplePicker.ResolvedEntities;</p><br></br> secureField.SaveCustomProperties();</span>
#### 自定义字段控件
自定义字段类型的最后一部分是自定义字段控件,它实现了创建和维护存储在数据存储列表中的数据的逻辑。为了达到这个目的,且依旧保持现存查询字段的功能,我们让自定义类从 LookupField 类继承。在字段处于显示模式的时候,这个基类处理所有的功能。然而,在新建和编辑模式下,我们要修改一些地方让这个控件符合我们的需要。目前,我们使用基类的 ControlMode 属性确定字段控件处于什么模式下。
当字段控件处于新建或编辑模式下,需要对默认的功能进行几个改变以实现如下行为:
- 如果用户不具备这个列的权限,那么隐藏控件。
- 显示文本框,并用任何存储在后台列表条目中的当前值来填充它。
- 当值被更改时,创建和更新后台列表条目的值。
为了控制控件的可见性,最好的方法是重写 Visible 属性。在属性 getter 的实现中,我们要检查用户是否有权对数据进行访问,并在不能访问的时候返回 False。
为了给可以输入或编辑数据的用户显示文本框,我们可以使用现存具有 id 为 TextField 的 SharePoint 模板。这个模板同样也被标准文本字段使用。为了实现这个功能,我们只需简单地重写 DefaultTemplateName 属性的 Get 方法,实现代码如下:
<span color="#0000ff">// If the mode is Display default to Lookup Field functionality<br></br> if (ControlMode == <span color="#2b91af">SPControlMode</span>.Display || ControlMode == <span color="#2b91af">SPControlMode</span>.Invalid)<br></br> {<br></br> return base.DefaultTemplateName;<br></br> }<br></br> return <span color="#a31515">@"TextField";</span></span>
为了实现创建或更新数据存储列表的余下功能,我们需要重写 Value 属性的 Get 和 Set 方法。Get 方法将被 SharePoint 框架用于更新字段值。我们对于这个逻辑的自定义,是基于用户输入的值创建或编辑位于数据存储列表中的后台条目。为了避免没有权限的用户编辑列表,在必要时,该功能将运行在比较严格的安全级别下。我们也实现了保证用户不会对不能编辑的字段进行编辑的逻辑。在代码中,我们引用了多个辅助方法。这些方法的代码在本节的最后也能找到。
<span color="#008000">// If the mode is Display default to Lookup Field functionality</span> <span color="#0000ff">if (ControlMode == <span color="#2b91af">SPControlMode</span>.Display || ControlMode == <span color="#2b91af">SPControlMode</span>.Invalid)<br></br> {<br></br> return base.Value;<br></br> }<p> this.EnsureChildControls();</p><p> // Validate the current users permissions.</p><br></br> if (!DoesUserHavePermissions())<br></br> {<br></br> return lookupListItemId;<br></br> }<p> // Check for an existing value to determine if we create new or edit.</p><br></br> if (lookupListItemId == null)<br></br> {<br></br><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(createLookupListItem);<br></br> }<br></br> else<br></br> {<br></br><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(updateLookupListItem);<br></br> }<p> return lookupListItemId;</p></span>
Value 属性的 Set 方法用于为当前列表条目设置字段的值。我们针对这个功能的定制需要从数据存储列表中获取当前值,并显示到文本框中。它也实现了保证数据安全性的功能。
<span color="#0000ff">// If the mode is Display default to Lookup Field functionality<br></br> if (ControlMode == SPControlMode.Display || ControlMode == SPControlMode.Invalid)<br></br> {<br></br> base.Value = value;<br></br> return;<br></br> }<p> this.EnsureChildControls();</p><p> // Validate the current users permissions.</p><br></br> if (!DoesUserHavePermissions())<br></br> {<br></br> return;<br></br> }<p> if( value != null)</p><br></br> {<br></br> if (value is SPFieldLookupValue)<br></br> {<br></br> SPFieldLookupValue fullValue = value as SPFieldLookupValue;<br></br> lookupListItemId = fullValue.LookupId;<br></br> this.TextBoxValue.Text = fullValue.LookupValue;<br></br> }<br></br> else<br></br> {<br></br> if (!(value is string))<br></br> {<br></br> throw new ArgumentException();<br></br> }<br></br> try<br></br> {<br></br> SPFieldLookupValue fullValue = new SPFieldLookupValue(value as string);<br></br> lookupListItemId = fullValue.LookupId;<br></br> this.TextBoxValue.Text = fullValue.LookupValue;<br></br> }<br></br> catch (ArgumentException ex)<br></br> {<br></br> this.TextBoxValue.Text = string.Empty;<br></br> }<br></br> }</span>
接下来的方法是我们之前用到的一些辅助方法。
<span color="#0000ff">private bool DoesUserHavePermissions()<br></br> {<br></br> bool doesUserHavePermissions = false;<p><span color="#2b91af">SPSecurity</span>.RunWithElevatedPrivileges(delegate()</p><br></br> {<br></br><span color="#2b91af">SPFieldLookup</span> lookupField = this.Field as <span color="#2b91af">SPFieldLookup</span>;<br></br> using (<span color="#2b91af">SPSite</span> site = new SPSite(<span color="#2b91af">SPContext</span>.Current.Site.ID))<br></br> {<br></br> using (<span color="#2b91af">SPWeb</span> web = site.OpenWeb(lookupField.LookupWebId))<br></br> {<br></br><span color="#2b91af">SPList</span> list = web.Lists[new <span color="#2b91af">Guid</span>(lookupField.LookupList)];<p><span color="#2b91af">SPListItem</span> subFolderItem = <span color="#2b91af">SecureField</span>.GetOrCreateSubFolderItem(web, list, ListId, Field, false);</p><p> if (subFolderItem == null)</p><br></br> {<br></br> throw new <span color="#2b91af">Exception</span>("Cannot find the List folder or Field folder.");<br></br> }<p> doesUserHavePermissions = subFolderItem.DoesUserHavePermissions(<span color="#2b91af">SPContext</span>.Current.Web.CurrentUser, <span color="#2b91af">SPBasePermissions</span>.ViewListItems);</p><br></br> }<br></br> }<br></br> });<p> return doesUserHavePermissions;</p><br></br> }<p> private void createLookupListItem()</p><br></br> {<br></br><span color="#2b91af">SPFieldLookup</span> lookupField = this.Field as <span color="#2b91af">SPFieldLookup</span>;<br></br> using (<span color="#2b91af">SPSite</span> site = new <span color="#2b91af">SPSite</span>(SPContext.Current.Site.ID))<br></br> {<br></br> using (<span color="#2b91af">SPWeb</span> web = site.OpenWeb(lookupField.LookupWebId))<br></br> {<br></br><span color="#2b91af">SPList</span> list = web.Lists[new <span color="#2b91af">Guid</span>(lookupField.LookupList)];<p><span color="#2b91af">SPListItem</span> subFolderItem = <span color="#2b91af">SecureField</span>.GetOrCreateSubFolderItem(web, list, ListId, Field, false);</p><p> if (subFolderItem == null)</p><br></br> {<br></br> throw new <span color="#2b91af">Exception</span>(<span color="#a31515">"Cannot find the List folder or Field folder."</span>);<br></br> }<p><span color="#008000">// Create the list item.</span><span color="#2b91af">SPListItem</span> listItem = list.Items.Add(subFolderItem.Folder.ServerRelativeUrl, <span color="#2b91af">SPFileSystemObjectType</span>.File, this.TextBoxValue.Text);</p><br></br> listItem[<span color="#2b91af">SecureField</span>.secureFieldStorageFieldName] = this.TextBoxValue.Text;<p> web.AllowUnsafeUpdates = true;</p><p> listItem.Update();</p><p> lookupListItemId = listItem.ID;</p><br></br> }<br></br> }<br></br> }<p> private void updateLookupListItem()</p><br></br> {<br></br> if (lookupListItemId == null)<br></br> {<br></br> return;<br></br> }<p><span color="#2b91af">SPFieldLookup</span> lookupField = this.Field as <span color="#2b91af">SPFieldLookup</span>;</p><br></br> using (<span color="#2b91af">SPSite</span> site = new SPSite(<span color="#2b91af">SPContext</span>.Current.Site.ID))<br></br> {<br></br> using (<span color="#2b91af">SPWeb</span> web = site.OpenWeb(lookupField.LookupWebId))<br></br> {<br></br><span color="#2b91af">SPList</span> list = web.Lists[new <span color="#2b91af">Guid</span>(lookupField.LookupList)];<br></br><span color="#2b91af">SPListItem</span> listItem = list.GetItemById((int)lookupListItemId);<br></br> listItem[<span color="#2b91af">SecureField</span>.secureFieldStorageFieldName] = this.TextBoxValue.Text;<p> web.AllowUnsafeUpdates = true;</p><br></br> listItem.Update();<br></br> }<br></br> }<br></br> }</span>
### 部署
对于扩展 SharePoint 核心功能的大部分自定义开发,SharePoint Solution 包是最佳的部署工具。这个框架允许我们创建部署安装包,以中心的、一致的和可检测的方式部署到服务器群集中的所有服务器上。我们所实现的自定义安全功能包含了一个程序集,一个 XML 配置文件和一个控件模板文件,以及一个大小适中的 Solution 包。
最终解决方案
把这些组件彼此结合,用户就可以使用如图 4 所示的标准 SharePoint 界面来添加安全列了。
图4:创建安全列
作为添加列的一部分,用户能够选择哪些用户和用户组能够对这个列进行访问。这一功能使用了标准的SharePoint“People Picker”控件,如图5 所示。
图5:为安全列配置权限
一旦列添加好,并且配置了具有相应的访问权限的用户,那么列就可以使用标准的SharePoint 新建和编辑窗体来添加数据了,如图6 所示。
图6:编辑安全列的值
对这个列不具有权限的用户就不允许编辑或查看这个列当中的数据。从图7 中可以看到。
图7:当用户不具有编辑安全列的权限时编辑模式的显示效果
当这个列添加到SharePoint 中的一个视图中时,只有拥有该权限的用户能够访问。可以从图8 和图9 中看到。
图8:具有安全列权限的用户查看列表的效果
图9:不具有安全列权限的用户查看列表的效果
结论
本文展示了如何扩展SharePoint 以包含列级别安全性的功能,并在SharePoint 内部保存所有数据。在本文中被验证的对策能够让这个功能无缝地附加到任何SharePoint 环境中。另外,我们也在 MSDN Code Gallery 上发布了一个可以运行的例子的完整的源代码及部署文件。
尽管我们相信本文所演示的方式能充分地保证可伸缩性,并且是安全的,但我们没有进行广泛的测试;并且我们非常希望,如果微软能在未来的 SharePoint 版本中包含内置的列基本安全性功能,那么它将会比我们的示例具有更好的 UI、更好的性能和可伸缩性,而且它也可以有售后支持,而对于这一点,很显然我们的示例无法做到。
在我们的例子中未被解决但已得到验证的其他事情还有:
- 当父对象被删除的时候,要清除安全记录
- 让用户可以使用除了文本外的其它数据类型
- 把列的编辑权限从查看权限中分离出来
应该考虑一下在数据表视图中,如何改善列的功能。
额外资源
要了解更多信息,可以查看如下资源:
- Custom Field Types
- Users, Groups, and Authorization
- Plan site and content security (Windows SharePoint Services)
- Plan site and content security (Office SharePoint Server)
- Microsoft Office Developer Center
给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
活动推荐:
2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。
评论