While walking another IT user through the SSP admin interface the other day I discovered that even though the user was a Farm Administrator that user was not able to get into certain pages within the SSP admin site (such as the profile properties page). Turns out that there’s additional permissions that must be granted via the “Personalization Service Permissions” page located under the “User Profiles and My Sites” section. As my goal is to make all changes necessary for our upgrade scriptable I ended up having to create a new command to give our admin group the appropriate permissions: gl-setsspacl. The challenge with this one is that the API to manipulate this information is not a public interface – almost everything is marked internal. I have no idea why this is the case – it’s really annoying (what should have been about 40 lines of code turned into about 110 lines because of all the reflection calls). I continue to be frustrated when trying to programmatically manipulate the SSP – why on earth they made so much internal is beyond me. The code to set the permissions is below (avert your eyes if you’re easily scared – using reflection is such a pain!):

  1public override int Run(string command, StringDictionary keyValues, out string output)
  2{
  3    output = string.Empty;
  4
  5    InitParameters(keyValues);
  6
  7    string sspname = Params["sspname"].Value;
  8    string username = Params["user"].Value;
  9    SharedServiceRights rights = GetUserRights(Params["rights"].Value.Split(','));
 10
 11    ServerContext current = ServerContext.GetContext(sspname);
 12
 13    //SharedServiceAccessControlList acl = SharedServiceAccessControlList.GetInstance(current);
 14    Type sharedServiceAccessControlListType = Type.GetType("Microsoft.Office.Server.Infrastructure.SharedServiceAccessControlList, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
 15
 16    MethodInfo sharedServiceAccessControlListMethodInfo =
 17    sharedServiceAccessControlListType.GetMethod("GetInstance",
 18            BindingFlags.NonPublic | 
 19            BindingFlags.Public |
 20            BindingFlags.Instance | 
 21            BindingFlags.InvokeMethod |
 22            BindingFlags.FlattenHierarchy |
 23            BindingFlags.Static, 
 24            null,
 25            new Type[] { typeof(ServerContext) }, null);
 26    object acl = sharedServiceAccessControlListMethodInfo.Invoke(null, new object[] { current });
 27
 28    //SharedServiceAccessControlEntry aclEntry = acl[username];
 29    PropertyInfo itemProp = acl.GetType().GetProperty("Item",
 30                BindingFlags.NonPublic |
 31                BindingFlags.Instance |
 32                BindingFlags.InvokeMethod |
 33                BindingFlags.GetProperty |
 34                BindingFlags.Public);
 35    SharedServiceAccessControlEntry aclEntry = (SharedServiceAccessControlEntry)itemProp.GetValue(acl, new object[] { username });
 36
 37    if (aclEntry == null)
 38    {
 39        if (rights == SharedServiceRights.None)
 40        {
 41            output += "User does not currently have any rights assiged - nothing to do.";
 42            return 0;
 43        }
 44
 45        // Adding a new user
 46        aclEntry = new SharedServiceAccessControlEntry(username, rights);
 47
 48        MethodInfo add =
 49        acl.GetType().GetMethod("Add",
 50                BindingFlags.NonPublic |
 51                BindingFlags.Public |
 52                BindingFlags.Instance |
 53                BindingFlags.InvokeMethod |
 54                BindingFlags.FlattenHierarchy,
 55                null,
 56                new Type[] { typeof(SharedServiceAccessControlEntry) }, null);
 57
 58        //acl.Add(aclEntry);
 59        add.Invoke(acl, new object[] { aclEntry });
 60    }
 61    else
 62    {
 63        if (rights == SharedServiceRights.None)
 64        {
 65            // Remove an existing user
 66            MethodInfo remove =
 67            acl.GetType().GetMethod("Remove",
 68                    BindingFlags.NonPublic |
 69                    BindingFlags.Public |
 70                    BindingFlags.Instance |
 71                    BindingFlags.InvokeMethod |
 72                    BindingFlags.FlattenHierarchy,
 73                    null,
 74                    new Type[] {typeof (string)}, null);
 75
 76            //acl.Remove(username);
 77            remove.Invoke(acl, new object[] { username });
 78        }
 79        else
 80        {
 81            // Modifying an existing user
 82            aclEntry = new SharedServiceAccessControlEntry(username, rights);
 83
 84            MethodInfo updateAccessControlEntry =
 85            acl.GetType().GetMethod("UpdateAccessControlEntry",
 86                    BindingFlags.NonPublic |
 87                    BindingFlags.Public |
 88                    BindingFlags.Instance |
 89                    BindingFlags.InvokeMethod |
 90                    BindingFlags.FlattenHierarchy,
 91                    null,
 92                    new Type[] {typeof (SharedServiceAccessControlEntry)},
 93                    null);
 94
 95            //acl.UpdateAccessControlEntry(username, aclEntry);
 96            updateAccessControlEntry.Invoke(acl, new object[] {aclEntry});
 97        }
 98    }
 99
100    MethodInfo update =
101    acl.GetType().GetMethod("Update",
102        BindingFlags.NonPublic |
103        BindingFlags.Public |
104        BindingFlags.Instance |
105        BindingFlags.InvokeMethod |
106        BindingFlags.FlattenHierarchy,
107        null,
108        new Type[] {}, null);
109    // acl.Update();
110    update.Invoke(acl, null);
111
112    return 1;
113}

And just to show how much simpler this would have been if the classes were marked as public:

 1public override int Run(string command, StringDictionary keyValues, out string output)
 2{
 3    output = string.Empty;
 4
 5    InitParameters(keyValues);
 6
 7    string sspname = Params["sspname"].Value;
 8    string username = Params["user"].Value;
 9    SharedServiceRights rights = GetUserRights(Params["rights"].Value.Split(','));
10
11    ServerContext current = ServerContext.GetContext(sspname);
12
13    SharedServiceAccessControlList acl = SharedServiceAccessControlList.GetInstance(current);
14    SharedServiceAccessControlEntry aclEntry = acl[username];
15
16    if (aclEntry == null)
17    {
18        if (rights == SharedServiceRights.None)
19        {
20            output += "User does not currently have any rights assiged - nothing to do.";
21            return 0;
22        }
23
24        // Adding a new user
25        aclEntry = new SharedServiceAccessControlEntry(username, rights);
26        acl.Add(aclEntry);
27    }
28    else
29    {
30        if (rights == SharedServiceRights.None)
31        {
32            // Remove an existing user
33            acl.Remove(username);
34        }
35        else
36        {
37            // Modifying an existing user
38            aclEntry = new SharedServiceAccessControlEntry(username, rights);
39            acl.UpdateAccessControlEntry(username, aclEntry);
40        }
41    }
42    acl.Update();
43
44    return 1;
45}

The syntax of the command can be seen below:

C:\>stsadm -help gl-setsspacl

stsadm -o gl-setsspacl

Set the personalization services permissions for an SSP.  Specify 'None' for rights to remove an existing user.

Parameters:
        -sspname <SSP name>
        -rights <comma separated: All | None | CreatePersonalSite, ManageAnalytics, ManageAudiences, ManageUserProfiles, SetPermissions, UsePersonalFeatures>
        -user <DOMAIN\name>

Note that the user parameter can refer to a group or a user. Here’s an example of how to give a group all permissions:

stsadm -o gl-setsspacl -sspname SSP1 -rights All -user "domain\group1"

If you wish to remove a user or group then simply specify “None” for the rights. You can specify multiple rights by comma separating the values:

stsadm -o gl-setsspacl -sspname SSP1 -rights "UsePersonalFeatures, CreatePersonalSite" -user "domain\group1"