MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Thursday, November 8, 2007

User Profile Privacy Policies

One of the new features with SharePoint 2007 is the ability to determine who can see different profile properties when looking at a users profile page. This is a great and necessary feature as it makes sure that various privacy concerns are addressed. My company's privacy policies state that no personal information is to be displayed on our intranet - that includes things such as the birth date and home phone number. By default when we ran the SharePoint upgrade it set some of these sensitive fields to be viewable by everyone.

As part of my upgrade script I needed to make sure that these fields were set appropriately. You can set these values by going to the SSP admin pages and clicking "Profile services policies" located under the "User Profiles and My Sites" section. In order to make these changes in batch during the upgrade I created two commands - the first was just to help me debug and is useful if you wish to get a dump of all the settings: gl-enumprofileprivacypolicies. The second command actually handles the settings of policies (edit only): gl-setprofileprivacypolicy.

In writing the code to handle all this there was one piece that threw me for a loop. You actually have to look in two different places for the policies. Most of the information will be obtained by getting a UserProfileConfigManager object and getting the collection of properties - the privacy policy information is associated with these individual properties which represent the actual profile data properties. The second place to find the data is by getting a collection of PrivacyPolicyItem objects via the PrivacyPolicyManager object. My first thought was that the later should be able to provide me with all the privacy policy information - unfortunately that's not the case - the later just provides those policies which don't have a direct profile property associated with them. Both the Property object and the PrivacyPolicyItem object implement the IPrivacyPolicyItem interface which is what enables the information to be stored in multiple locations. The commands I created are detailed below.

1. gl-enumprofileprivacypolicies

This command is really quite simple. It basically has two loops, one for each collection type, and a helper method which converts the objects to XML:

   1: public override int Run(string command, StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   4:  
   5:  InitParameters(keyValues);
   6:  
   7:  ServerContext serverContext = ServerContext.GetContext(Params["sspname"].Value);
   8:  UserProfileConfigManager manager = new UserProfileConfigManager(serverContext);
   9:  PropertyCollection properties = manager.GetProperties();
  10:  
  11:  StringBuilder sb = new StringBuilder();
  12:  
  13:  XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
  14:  xmlWriter.Formatting = Formatting.Indented;
  15:  
  16:  xmlWriter.WriteStartElement("PrivacyPolicies");
  17:  xmlWriter.WriteAttributeString("ssp", Params["sspname"].Value);
  18:  
  19:  foreach (IPrivacyPolicyItem item in properties)
  20:  {
  21:   AddXml(xmlWriter, item);
  22:  }
  23:  
  24:  PrivacyPolicyManager privacyPolicyManager = ProfileLoader.GetProfileLoader(serverContext).GetUserProfileManager().GetPrivacyPolicy();
  25:  foreach (PrivacyPolicyItem item in privacyPolicyManager.GetAllItems())
  26:  {
  27:   AddXml(xmlWriter, item);
  28:  }
  29:  
  30:  xmlWriter.WriteEndElement();
  31:  xmlWriter.Flush();
  32:  
  33:  output += sb.ToString();
  34:  
  35:  if (Params["outputfile"].UserTypedIn)
  36:  {
  37:   File.WriteAllText(Params["outputfile"].Value, output);
  38:   output = string.Empty;
  39:  }
  40:  
  41:  return 1;
  42: }
  43:  
  44: private static void AddXml(XmlTextWriter xmlWriter, IPrivacyPolicyItem item)
  45: {
  46:  xmlWriter.WriteStartElement("Policy");
  47:  
  48:  xmlWriter.WriteAttributeString("isProperty", (item is Property).ToString());
  49:  
  50:  if (item is PrivacyPolicyItem)
  51:  {
  52:   xmlWriter.WriteElementString("Id", ((PrivacyPolicyItem)item).Id.ToString());
  53:   xmlWriter.WriteElementString("Group", ((PrivacyPolicyItem)item).Group);
  54:   xmlWriter.WriteElementString("FilterPrivacyItems", ((PrivacyPolicyItem)item).FilterPrivacyItems.ToString());
  55:  }
  56:  else if (item is Property)
  57:  {
  58:   xmlWriter.WriteElementString("Name", ((Property)item).Name);
  59:   xmlWriter.WriteElementString("Description", ((Property)item).Description);
  60:   xmlWriter.WriteElementString("DisplayOrder", ((Property)item).DisplayOrder.ToString());
  61:   xmlWriter.WriteElementString("IsReplicable", ((Property)item).IsReplicable.ToString());
  62:   xmlWriter.WriteElementString("IsSection", ((Property)item).IsSection.ToString());
  63:   xmlWriter.WriteElementString("URI", ((Property)item).URI);
  64:  }
  65:  
  66:  xmlWriter.WriteElementString("DisplayName", item.DisplayName);
  67:  xmlWriter.WriteElementString("AllowPolicyOverride", item.AllowPolicyOverride.ToString());
  68:  xmlWriter.WriteElementString("DefaultPrivacy", item.DefaultPrivacy.ToString());
  69:  xmlWriter.WriteElementString("PrivacyPolicy", item.PrivacyPolicy.ToString());
  70:  xmlWriter.WriteElementString("UserOverridePrivacy", item.UserOverridePrivacy.ToString());
  71:  
  72:  xmlWriter.WriteEndElement();
  73: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-enumprofileprivacypolicies

stsadm -o gl-enumprofileprivacypolicies

Lists the user profile privacy policies associated with the SSP.

Parameters:
        -sspname <name of the SSP>
        [-outputfile <file to output results to>]

Here's an example of how to output all the privacy policy information to a file:

stsadm –o gl-enumprofileprivacypolicies -sspname "SSP1" -outputfile "c:\privacypolicies.xml"

Running the above command will produce results similar to the following (I've abbreviated the output for the sake of space):

<PrivacyPolicies ssp="SSP1">  
  ...
  <Policy isProperty="True">
    <Name>CollegeMajor</Name>
    <Description />
    <DisplayOrder>5202</DisplayOrder>
    <IsReplicable>False</IsReplicable>
    <IsSection>False</IsSection>
    <URI>urn:schemas-microsoft-com:sharepoint:portal:profile:CollegeMajor</URI>
    <DisplayName>College Major</DisplayName>
    <AllowPolicyOverride>True</AllowPolicyOverride>
    <DefaultPrivacy>Public</DefaultPrivacy>
    <PrivacyPolicy>OptIn</PrivacyPolicy>
    <UserOverridePrivacy>False</UserOverridePrivacy>
  </Policy>
  <Policy isProperty="False">
    <Id>861d8fb6-7012-4cd9-a7a0-a615aed038b3</Id>
    <Group>My Links</Group>
    <FilterPrivacyItems>True</FilterPrivacyItems>
    <DisplayName>Links on My Site</DisplayName>
    <AllowPolicyOverride>True</AllowPolicyOverride>
    <DefaultPrivacy>Public</DefaultPrivacy>
    <PrivacyPolicy>Mandatory</PrivacyPolicy>
    <UserOverridePrivacy>True</UserOverridePrivacy>
  </Policy>
  ...
</PrivacyPolicies>

2. gl-setprofileprivacypolicy

This command's code logic is similar to that of the enum command but includes more logic to handle the different types of changes that can be made. I probably could have cleaned the code up a bit to reduce some of the redundancy but in the end I decided it made more sense to keep the handling of the two object types separate (that and time isn't something I have a lot of at the moment so I didn't see the need to spend a lot of time on something that was working).

   1: public override int Run(string command, StringDictionary keyValues, out string output)
   2: {
   3:  output = string.Empty;
   4:  
   5:  InitParameters(keyValues);
   6:  
   7:  string name = Params["name"].Value;
   8:  
   9:  ServerContext serverContext = ServerContext.GetContext(Params["sspname"].Value);
  10:  UserProfileConfigManager manager = new UserProfileConfigManager(serverContext);
  11:  PropertyCollection properties = manager.GetProperties();
  12:  IPrivacyPolicyItem property = properties.GetPropertyByName(name);
  13:  
  14:  if (property == null)
  15:  {
  16:   // We couldn't find using the system name so try using the display name.
  17:   foreach (IPrivacyPolicyItem item in properties)
  18:   {
  19:    if (item.DisplayName.ToLowerInvariant() == name.ToLowerInvariant())
  20:    {
  21:     if (property != null)
  22:      throw new ArgumentException(string.Format("Duplicate properties found matching the name {0}.  Use the system name instead (use enumprofileprivacypolicies to get system names).", name));
  23:     property = item;
  24:    }
  25:   }
  26:  }
  27:  
  28:  if (property == null)
  29:  {
  30:   PrivacyPolicyManager privacyPolicyManager = ProfileLoader.GetProfileLoader(serverContext).GetUserProfileManager().GetPrivacyPolicy();
  31:  
  32:   foreach (PrivacyPolicyItem item in privacyPolicyManager.GetAllItems())
  33:   {
  34:    if (item.DisplayName.ToLowerInvariant() == name.ToLowerInvariant())
  35:    {
  36:     property = item;
  37:     break;
  38:    }
  39:   }
  40:   if (property == null)
  41:    throw new ArgumentException(
  42:     string.Format("Could not find a policy for the User Profile property '{0}'", name));
  43:  
  44:   
  45:   if (Params["privacy"].UserTypedIn)
  46:    property.PrivacyPolicy = (PrivacyPolicy)Enum.Parse(typeof(PrivacyPolicy), Params["privacy"].Value, true);
  47:  
  48:   if (((PrivacyPolicyItem)property).Id != PrivacyPolicyIdConstants.MyColleaguesRecommendations)
  49:   {
  50:    if (Params["defaultprivacy"].UserTypedIn)
  51:     property.DefaultPrivacy = (Privacy)Enum.Parse(typeof(Privacy), Params["defaultprivacy"].Value, true);
  52:  
  53:    if (Params["allowuseroverride"].UserTypedIn)
  54:     property.UserOverridePrivacy = bool.Parse(Params["allowuseroverride"].Value);
  55:   }
  56:  
  57:   if (Params["replicable"].UserTypedIn)
  58:    Console.WriteLine("WARNING: Property does not support replication. \"replicable\" parameter ignored.\r\n");
  59:  
  60:   property.Commit();
  61:  }
  62:  else
  63:  {
  64:   if (Params["privacy"].UserTypedIn)
  65:   {
  66:    if (property.AllowPolicyOverride)
  67:     property.PrivacyPolicy =(PrivacyPolicy) Enum.Parse(typeof (PrivacyPolicy), Params["privacy"].Value, true);
  68:    else 
  69:     Console.WriteLine("WARNING: Property does not allow policy override. \"privacy\" parameter ignored.\r\n");
  70:   }
  71:  
  72:   if (Params["defaultprivacy"].UserTypedIn)
  73:    property.DefaultPrivacy = (Privacy)Enum.Parse(typeof(Privacy), Params["defaultprivacy"].Value, true);
  74:  
  75:   if (Params["replicable"].UserTypedIn)
  76:    ((Property)property).IsReplicable = bool.Parse(Params["replicable"].Value);
  77:  
  78:   if (Params["allowuseroverride"].UserTypedIn)
  79:   {
  80:    if (property.AllowPolicyOverride && !((Property)property).IsReplicable)
  81:     property.UserOverridePrivacy = bool.Parse(Params["allowuseroverride"].Value);
  82:    else
  83:    {
  84:     if (((Property)property).IsReplicable)
  85:      Console.WriteLine("WARNING: Property does not allow policy override because it is marked as Replicable. \"allowuseroverride\" parameter ignored.\r\n");
  86:     else
  87:      Console.WriteLine("WARNING: Property does not allow policy override. \"allowuseroverride\" parameter ignored.\r\n");
  88:    }
  89:   }
  90:  
  91:   property.Commit();
  92:  }
  93:  return 1;
  94: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-setprofileprivacypolicy

stsadm -o gl-setprofileprivacypolicy

Sets the privacy policy settings for a profile property.

Parameters:
        -sspname <name of the SSP>
        -name <name of the property to edit>
        [-defaultprivacy <public | contacts | organization | manager | private | notset>]
        [-privacy <mandatory | optin | optout | disabled>]
        [-allowuseroverride <true | false>]
        [-replicable <true | false>]

Here's an example of how to set the privacy policy of the CollegeMajor profile property identified above:

stsadm –o gl-setprofileprivacypolicy -sspname "SSP1" -name "CollegeMajor" -defaultprivacy manager -privacy optin -allowuseroverride true -replicable

No comments: