Video Screencast Help
Symantec to Separate Into Two Focused, Industry-Leading Technology Companies. Learn more.

Developing ImageInvoker for DS6.9, Part 2

Created: 23 Dec 2010 • Updated: 14 Feb 2011
Language Translations
ianatkin's picture
+4 4 Votes
Login to vote

Welcome to the second in this fledgling series of articles on Developing ImageInvoker. For those who don't know what ImageInvoker is, here are a few links which will help,

In brief it's a fairly mature Deployment Server 6.9 add-on to which aims to simplify the image provisioning process by exposing specific imaging tasks through a client portal.

For version 0.4, I am taking a different development approach and recording on Connect my development progress and thoughts. This will give interested parties the chance to comment on my direction, and also perhaps assist others enhance their bespoke Deployment Server self-help systems which have been developed for their own environments. 

The first article on Developing ImageInvoker, pretty much set the groundwork for what I'm aiming to achieve on v0.4. In summary, I want to implement the following,

  1. AD Authentication and Authorisation
  2. On-the-fly menu creation for the client portal
  3. A new ImageInvoker job tree for scaling ImageInvoker to large job sets

In today's article I'm going to start tackling the authorisation piece of the puzzle, which is to say once a person is authenticated how do we figure out which of the ImageInvoker tagged jobs should be presented through the menu. And to do that, we must understand a little about how Deployment Server Console security works.

If you like the progress -please give these articles a thumbs up! If you don't -fire off critical comment or two, and we can mull over the options. Its a work in progress afterall.

For those interested in timescales, my aim is to be pretty code complete and in testing in February 2011. With luck that gives us a public release date sometime in March 2011.  If you are interested in getting dirty on the beta,  drop a comment here on one of the Developing ImageInvoker articles.

Deployment Server Security

In version 0.4, I want not only for the client menu to be unlockable via an AD credential, but I want the client menu to present only jobs which that user would see should they log into the console themselves.

This is tricky.

According to pg92 of the Deployment Server 6.9  Admin Guide, the rules for evaluating permissions are as follows,

  • Permissions cannot be used to deny the user with Administrator console rights access to use any console objects or features.
  • User permissions take precedence over Group permissions.
  • Deny overrides Allow. When a user is associated with multiple groups, one group could be allowed a permission at a particular level while the other group is denied the same permission. In this scenario, the permission to deny the privilege is enforced.
  • Permissions do not flow down an object tree. Instead, the object in question looks in the current location and up the tree, and uses the first permission it finds.
  • If a console user does not have permissions to run all tasks the job contains, the user cannot run the job.

To see how all this pans out in practice, let’s create some groups in the console and start assigning permissions.

Initial Group and User Setup

For my initial testing, I'm going to configure the DS Console Security with a few groups -AdministratorsTechnicians and LSG-Packages;

  1. The Administrators group is the default DS administrative group
  2. The Technicians group i've added as an AD group (although in fact its a local group on the server Altiris-DS)
  3. LSG-Packages is an Local Security Group on the Altiris-inc domain.

In the Manage Users tab, I've also configured the DS-Administrator account as full administrator.

 
It's interesting to see how this manifests in the eXpress database. Any users we declare are entered into the securityusers table,
 
 
 select * from securityuser 
 
And the output is as follows,
 
 
And groups are likewise entered into the securitygroups table,
 
 
select * from securitygroups
 
And the output is as follows,

A few things are interesting here,

  1. User IDs seem to occupy the range 14000001 -> 15000000
  2. Group IDs seem to occupy the range 15000001 -> 16000000
  3. In this table, there is no distinction between local groups on the server, and 'real' AD groups

As an aside, its useful to note that,

  1. Job IDs seem to occupy the range 2000001 -> 3000000
  2. Job Folder IDs seem to occupy the range 1000001 -> 2000000

At this point, I've not configured any permission on the jobs at this point.

Setting Security Permissions 

From rummaging through the database, there seem to be two tables critical to ascertaining job permissions,

  1. SecurityPermission
    This table has 3 columns; permission_id, name & description. It holds the permissions which can be ascribed to users and groups with the associated  permission IDs.  There are 81 permissions in total; 58 permissions for configuring on computers & groups, and 23 permissions which can be set on jobs & folders. 
     
  2. SecurityPermissionLink
    This table has 4 columns, permission_id,user_id, object_id and allow. For every console object which has had permissions configured, this table holds the object to which the permission has been applied (whether a job, folder, computer or group) and the user object associated with that permission and whether it's been allowed or denied.

So let's now take a look at what happens to these tables when I start allocating job permissions. 

Adding Permissions for LSG-Packagers

First, let's give the LSG-Packagers group the 'Create Job' and 'Create Job Folders' permissions in the Samples folder in the console. This isn't useful in itself, but I'd like to keep the permission allocation simple for now to illustrate how this all ties together.

The following SQL shows how this manifests in the securitypermissionslink table,

 select * from securitypermissionlink 

which gives the following output,

Which isn't incredibly clear I admit. Perhaps we can make this a bit more lucid by joining up some tables to transform all these IDs into something a bit more human,

select sp.name 'Permission',sg.name 'User/Group', ef.name 'Job/Folder',allow
from securitypermissionlink spl
join securitypermission sp on spl.permission_id = sp.permission_id
join securitygroup sg on spl.user_id = sg.group_id
join event_folder ef on spl.object_id=ef.folder_id
 
which gives the following output,
 
 
So that's good -what we do in the console maps exactly to the securitypermissionlink table in the eXpress database.
 

Configuring permissions for the technicians group

 
Now lets add and remove some permissions for the technicians group,
  1. Granting Technicians the 'Schedule this Job' and 'Get Inventory' permissions on the folder Samples->HP,
  2. Grant Technicians the 'Shutdown' permission on the job Samples->HP->HP Backup Recovery
  3. Deny Technicians the 'Get Inventory' permission on the job Samples->HP->HP Backup Recovery

Now as we're meddling here with jobs and job folders, our SQL joins are going to get tricky. We now need to make the SQL distinguish between jobs and folders which as I've noted above we can do with the object_id as different objects occupy different numeric ranges,

 select permission,User_Group,Object,allow from (select
CASE
when Object_id BETWEEN 2000001 and 3000000 then (select 'Job - ' + name from event where event_id=object_id)
when Object_id BETWEEN 1000001 and 2000000 then (select 'Folder - ' + name from event_folder where folder_id=object_id)
end as Object,permission,User_Group,allow from (
select sp.name 'Permission',sg.name 'User_Group', spl.object_id 'Object_id',allow
from securitypermissionlink spl
join securitypermission sp on spl.permission_id = sp.permission_id
join securitygroup sg on spl.user_id = sg.group_id) xxx) yyy 
 
Here I've made use of the T-SQL CASE statement to create my little code fork. The result then is as follows,
 
 
 
Quite pleased with that. All looks neat.  And crikey, I've just created a script which will help document the job and folder permissions in the DS Console.
 
But there is an elephant in the room I'm avoiding. And that's the effective  permissions a user or group will have on any particular object.
 

Calculating Effective Permissions

 
Now I've done the easy bit, I need to tackle the harder piece of the puzzle -calculating the effective permissions. Now, when it comes to ImageInvoker, all I need to worry about is whether the "Schedule This Job" permission has been configured. My task therefore is to calculate for each ImageInvoker marked job (or folder) whether this schedule permission has been granted.
 
According to the documentation, permissions do not flow down the tree. The effective permission of any object is calculated by looking at the permissions in the current location and up the tree,  using the first permission it finds.
 
In my case the pseudo-code logic will therefore be,
  1. Set Allow=0
  2. Set CurrentPosition=Object
  3. Set UserObject
  4. Does Object have the  'Schedule this Job' permission granted for UserObject? If so, set Allow=1 and exit
  5. Does Object have the  'Schedule this Job' permission Denied for UserObject? If so, set Allow=0 and exit
  6. Set CurrentPosition=Object.Parent
  7. Is CurrentPosition top of tree? If so, exit
  8. Go to step 4
 
So then, let's see what this looks like in T-SQL,
 
 
 Declare @UserID integer
Declare @PermissionID integer
Declare @Allow integer
Declare @ObjectID integer
Declare @ParentObjectID integer
Declare @Perm integer


/* Permission 66 is the 'Schedule This Job Permission' */
Set @PermissionID=66


/* Set the UserID to the Technicians Group */
Set @UserID=15000001


/* Set the ObjectID to the HP Job */
Set @ObjectID=2000133


/* Ensure @Allow and @Perm Variables are Null */
Set @Allow= Null
Set @Perm = Null



/* Move up the folder tree in a While loop. Exit if we at the top of the job tree (nowhere else to go)
   or if we find the current tree object has the permission set as Allow or Deny */ 


WHILE @ObjectID is not Null and @Allow is Null

BEGIN

  /* Find if permission is set or not for current object */
  set @Perm = (select allow from securitypermissionlink 
               where permission_id=@PermissionID and object_id=@ObjectID and [User_id]=@UserID)

  
  /* If permission is set, set @Allow variable to trigger end of loop */
  if @Perm=0 set @Allow=0
  if @Perm=1 set @Allow=1


  /* Now move up the tree one level. If we are a job, the parent is a folder. If we are a folder, the parent is
     a folder */

  IF @ObjectID BETWEEN 2000001 AND 3000000 
     BEGIN
        SET @ParentObjectID=(select folder_id from event where event_id=@ObjectID)
     END
  ELSE IF @ObjectID BETWEEN 1000001 AND 2000000 
     BEGIN
        SET @ParentObjectID=(select parent_id from event_folder where folder_id=@ObjectID)
     END


  /* Configure the current object as the parent object */
  Set @ObjectID= @ParentObjectID

END


/* Output Effective Permission. If 1, then permission is allow. Otherwise it is Deny */

If @Perm is Null Set @Perm=0
select @Perm 
 
This appears to work quite well. But what I really want for programming is for this to be turned into a nice function. Ideally what I'd like is a general function which would take as input the permission_id being sought after, the user_id that the permission is being check against, and the object_id for the job or folder in question. The return value should be 1 should an allow permission be found or 0 otherwise (as the DS console permissions default to a deny).
 
The following code should therefore do the trick to create the necessary function,
 
 USE [eXpress]

GO
/* Specify ISO compliant behavior comparison operators when they have null values
   and allow data objects to be referenced using double quotes */
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

CREATE FUNCTION [dbo].[Custom_Effective_Permission]
(
  @PermissionID INTEGER,
  @UserID INTEGER,
  @ObjectID INTEGER
)
RETURNS INTEGER
AS
BEGIN


Declare @Allow integer
Declare @ParentObjectID integer
Declare @Perm integer

/* Ensure @Allow and @Perm Variables are Null */
Set @Allow= Null
Set @Perm = Null


/* Move up the folder tree in a While loop. Exit if we at the top of the 
  job tree (nowhere else to go) or if we find the current tree object 
   has the permission set as Allow or Deny */ 


WHILE @ObjectID is not Null and @Allow is Null
BEGIN

  /* Find if permission is set or not for current object */
  set @Perm = (select allow from securitypermissionlink 
               where permission_id=@PermissionID and object_id=@ObjectID 
               and [User_id]=@UserID)

  /* If permission is set, set @Allow variable to trigger end of loop */
  if @Perm=0 set @Allow=0
  if @Perm=1 set @Allow=1

  /* Now move up the tree one level. If we are a job, the parent is a folder. 
     If we are a folder, the parent is a folder */

  IF @ObjectID BETWEEN 2000001 AND 3000000 
     BEGIN
        SET @ParentObjectID=(select folder_id from event where event_id=@ObjectID)
     END
  ELSE IF @ObjectID BETWEEN 1000001 AND 2000000 
     BEGIN
        SET @ParentObjectID=(select parent_id from event_folder where folder_id=@ObjectID)
     END


  /* Configure the current object as the parent object */
  Set @ObjectID= @ParentObjectID

END


/* Output Effective Permission. If 1, then permission is allow. Otherwise it is Deny */
If @Perm is Null Set @Perm=0

RETURN @PERM

END
 
And to call it, the following should work. I've embellished the code with comments to help me navigate my way through testing the permission scenario set out in this article,
 
 Declare @ObjectID integer
Declare @PermissionID integer
Declare @UserID integer
Declare @OUTPUT varchar(256)
Declare @Allow integer
Set @OUTPUT=''


/* UserID for Administrators Group: 15000001
   UserID for Technicians Group   : 15000002
   UserID for LSG_Packages        : 15000003
*/

Set @UserID=15000003


/* ObjectID for Samples folder : 1000027
   ObjectID for HP folder      : 1000063
   ObjectID for HP Backup job  : 2000133
*/

Set @ObjectID=1000027



/* Loop through all permissions from 1 to 81 recording which have an effective allow 
   recording the collective allows in @OUTPUT*/


Set @PermissionID=1

WHILE @PermissionID < 82
BEGIN
  set @Allow = (select [dbo].[Custom_Effective_Permission] (@PermissionID,@UserID,@ObjectID))

  IF @Allow = 1 
    BEGIN
    set @OUTPUT=@OUTPUT + (select name from securitypermission 
                           where permission_id=@PermissionID) + ','
    END
  set @PermissionID=@PermissionID+1
END


/* Now present the results in a nice human readable form */
IF @ObjectID BETWEEN 2000001 AND 3000000 
   BEGIN
        select 'Job: ' + (select name from event where event_id=@ObjectID)
   END
ELSE IF @ObjectID BETWEEN 1000001 AND 2000000 
   BEGIN
        select 'Folder: ' + (select name from event_folder where folder_id=@ObjectID)
   END

select 'User/Group: ' + (select name from securitygroup where group_id=@UserID) 
select 'Permissions: ' + @OUTPUT
 
This gives me a quick way to test the code as I can just enter the Job/Folder IDs and the group IDs. The output will then give me the collective allow permissison as shown below,
 
 
Executing this on a number folders and jobs seems to do the right thing, so at first glance I'm happy about this.
 

Logic Fork for Administrator Groups

There is one case where the above SQL fails to ennumerate the correct permissions. The Administrators group does not have ANY permissions according to the above logic. This is because you can't deny rights to members of this group -they by default have allows.
 
In my programming therefore, if I find that a user account is administrative (or that they belong to an administrative group) then I should just fork the code declaring that they are 'allowed'. I could do that in the above SQL, or before it.
 
Either way, the back is broken on this one, so now its time to move onto getting my code to locate AD users and groups, so that I can ennumerate group memberships and then tally this with the authorisations revealed through the above code.
 
That's it for now... back soon with Part 3.
 
Kind Regards,
Ian./
 
Return to Index