Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / algorithm

Jazz Up Your C# Code

4.95/5 (11 votes)
3 Jul 2012CPOL28 min read 53.4K   369  
Maintaining code with complex permissions tends to be difficult, because the code can be distributed across multiple classes. By embedding permissions directly on methods and properties within a class, code is reduced and maintainability is simplified.

Introduction

Maintaining code with complex permissions tends to be difficult, because the code can be distributed across multiple classes. By embedding permissions directly on methods and properties within a class, code is reduced and maintainability is simplified. 

Table of Contents

  • Introduction
  • Architecture
  • Jazz Features
  • Applying Jazz to your Project
  • Example with Code walk thru
  • Windows Form Project
  • What is Jazz?

    Jazz is an open source project available from codeplex. It represents a compact, modular framework that can easily be added to your development projects to build secure applications. Jazz allows complete workflows to be implemented in a small footprint of C# or VB code.

    Jazz may be used as an add-on to your project that implements secure access to objects, properties and methods.

    How easy is it to use Jazz?

    Here are the steps to adding Jazz to your project:

    1. Ensure that the targetObject class inherits from jObject, where MyFirstJazzClass is the name of your class.
      C#
      /// <summary>
       /// This is a minimalist 'Jazz Class'.
       /// The '[Jazz]' attribute and inheriting from jObject are required.
       /// </summary>
       [Jazz]
       public class MyFirstJazzClass : jObject
       {
           // insert standard C# code here
       }
      
    2. Decorate your code with any of the Grant construct attributes, where: "Started" is the name of a state in MyFirstJazzClass, "Manager" is the name of a Role in MyFirstJazzClass, "IsValid" is the name of a property in MyFirstJazzClass.
      C#
      /// <summary>
      /// The ‘Complete’ command is only allowed to be executed in the
      /// ‘Started’ state by the Manager when the jObject is valid.
      /// </summary>
      [GrantStates("Started"), GrantRoles("Manager"), GrantProperty("IsValid")]
      publicvoid Complete()
      {
          this.EnterState("Completed");
      }
      
    3. Create a Client Nexus to perform secure operations, such as method calls and property access on the myObject., where "myUser" is the name of the user.
      C#
      NexusConfig config = newNexusConfig()
      {
          LoginProfile = jUser.Create("myUser"),
          BindUnits = newjObject[] { new MyFirstJazzClass() }
      };
      // Instantiate the Client Nexus where secure operations are performed
      using (ClientNexus nexus = ClientNexus.Create(config))
      {
          // Perform your secure operations on the jObjects
      }
      

    Who would use Jazz?

    Jazz is targeted at applications where:
    1. Security Permissions are a key requirement
    2. It is important that only specific Users and/or Roles are permitted to access specific members in the business objects.
      • Security rules are dynamic – i.e. rules are a function of the data that itself is changing.
    3. The developer wants improved code maintainability by embedding permission statements directly on the properties and methods.
    4. The application has many users, roles and complex actions.
    5. The application has workflow attributes. It is required that certain methods and properties are only available in specific states.
    6. The user interface is cleanly separated from business objects.

    The name: where did it come from?

    John Hansen and Charles Wiebe developed Jetfire, a persistent scripting language, and built many applications based on it from 2007 to the present. "We wanted to build the evolution of Jetfire based on.NET 4.0 and all the ‘what-ifs’ that we had encountered in the past two years into a lighter, more flexible platform." That amounted to a lot of ‘jazz’.

    For further information, check out the wiki at http://wiki.jazzfire.net.

    Architecture

    The architecture consists of:

    • jClass and jObject
    • Built-in members of jObject
    • First Class Objects
    • The Client Nexus
    • Permission attributes

    jClass

    The jClass is an object that provides schema information about the jObject type.

    jObject

    The jObject, also known as the Jazz object, is the common building block object in Jazz providing many of the features. All first class objects inherit from jObject.

    Built-in Members of jObject

    • The jObject class includes the following public members:
    • Class: Returns the Jazz Workflow Class that provides Jazz workflow type operations and data. The Class is set during the Bind process.
    • ClientNexus: Returns the nexus object.EnterState: Causes 'this' jObject to enter the state as specified by 'stateName'.
    • Flags: The following flags provide information about the jObject.
      • IsAccessible: Returns true when this instance is accessible. False may be returned because access is not allowed due to the ACL or the jObject with this.ID does not exist. An 'AccessViolationException' will be generated any access is attempted when IsAccessible returns false.
      • IsBound: Returns true if this object has been bound ("Bind" operation) to the client nexus. See http://wiki.jazzfire.net/Bind.ashx.
      • IsEmpty: Returns true if this is the empty object for this 'jObject' class. Empty objects are 'ReadOnly'.
      • IsReadOnly: Returns true when this instance is readonly. When the instance is readonly public properties and fields can be read. Fields and properties cannot be set. Methods cannot be executed. An 'AccessViolationException' is generated if write or execute access is attempted.
      • IsRoot: Returns true if this object is a 'root' object. 'IsRoot' is only applicable when Jazz objects are persisted A Root object, even if it has no references, is not removed by the Server GC unless it is deleted.
    • FullName: Returns a name in the format of 'server name'.'workspace name'.'this name'.
    • ID: Gets the Guid value for jObjects from the nexus. IDs are assigned during the 'Bind' process. Before the initial bind, the ID is empty.
    • IsNameUnique: indicates that the name is unique for all instances of this 'Class'. For example all 'jRoleBase' instances will have unique names.
    • Name: Returns the name of this 'jObject'. If your 'jObject' class requires a unique name then use 'jNameUnique' as a base class.
    • StorageStatus: Indicates whether an object's data is pending or confirmed. 'Pending' indicates that an object is changed but the server. Nexus has not yet 'Confirmed' the operation.
    • Status: Indicates whether this jObject instance 'Valid', 'Deleted', 'Broken' or 'Disconnected'.
      • IsDeleted: indicates that the jObject is deleted.
      • IsValid: indicates that the jObject is valid.
    • StorageType: type of storage unit
    • Timestamp: Returns the timeStamp that is issued by server nexus. When blank the object has been created but not written for the first time. The time stamp is only valid if 'StorageStatus’ is confirmed. Time stamps are unique within a single server nexus.
    • Workspace: Returns the 'Workspace' that this ‘jObject’ resides in. All jObject instances must reside in 1 workspace and only 1 workspace.

    Protected Members include:

    • _ACL: The ACL (Access Control List) controls how this jObject instance is accessed. Note: If the ACL is empty then there are no access restrictions.
    • _Add(jAclItem): Add a 'jAclItem' to this jObject's ACL.
    • _Delete: Deletes the jObject.
    • _Remove(jAclItem): Remove a 'jAclItem' from this jObject's ACL.
    • OnAssign: Allows the assignment of predefined IDs. This is typically used for defining empty objects for the class.
    • OnBind: Executed before this instance is bound to the client nexus. This method will be executed every time this instance is bound to a client nexus. When this method is executed the 'Grant' attributes are not active.
    • OnInitialBind: Executed before this instance is bound to the client nexus for the first time. When this method is executed the base Jazz object fields will have been set (except the Flag 'Bound' and 'Initialized' bits). When this method is executed the 'Grant' constructs are not active nor is the ACL active.

    Jazz First class objects

    First class objects can be fully customized by the developer. Within Jazz, first class objects include:

    • jRole
    • jUser
    • jProfile
    • jWorkspace

    jRole

    Roles provide the logged-in user with permissions for accessing workflows, workspace contents, executing methods, accessing properties and changing permissions. Roles can contain other roles.

    • jRoles are independent from Roles found in .NET such as Forms Authentication and Active Directory. However, developers can customize jRole to wrap .NET roles.

    Roles can be assigned to a Jazz user. Methods and properties can be permitted based on Roles by using the 'GrantRole construct'.

    jObjects can be assigned roles programmatically (see ACL). Roles, used in ACL items, work by restricting access to the jObjects. Within the client nexus, jObjects that are not accessible to the user are marked as inaccessible.

    Role(s) can give a user special abilities that may be denied to other users such as:

    • The ability to execute methods that have been declared with the 'GrantRole' construct.
    • The ability to set and get properties that have been declared with the 'GrantRole' construct.
    • The ability to set a property that has private getter and public setter.
    • Restricting access to workspaces (and their objects) when the workspace has its 'Access Control List' set.
    • Restricting access to a jObject when its 'Access Control List' is set.

    Roles are identified by a unique name which is assigned to the role when it is created. Once created the name of role may not change.

    jUser

    A Jazz user class, jUser, describes a physical user, typically a person. The jazz user consists of a unique name and a collection of profiles. A Jazz user must have at least 1 profile. The jazz user can not contain roles directly; profiles contain roles.

    jUsers are independent from Users found in .NET such as Forms Authentication and Active Directory. However, developers can customize the jUser class to wrap .NET users.

    jProfile

    A jUser may have one or more profiles. These are used to login as different aspects of the user’s life.

    • e.g. Joe, the Plumber, Joe, the soccer coach, Joe, the dad.
    • e.g. Jane, the coach, Jane, the player, Jane, the referee

    jWorkspace

    jWorkspace is a Jazz directory that allows the user to organize objects. When an Access Control List (ACL) is assigned to a jWorkspace, all objects assigned to this workspace are secured the same way as the workspace.

    If a user cannot access the workspace, then the user cannot access the objects within the workspace.

    If a user can only read the workspace, then the user can only read the objects within the workspace.

    ClientNexus

    Wikipedia defines Nexus as a connection, usually where multiple elements meet, as for example spokes at a hub, originally from a Latin verb meaning "connect, bind."

    The Client Nexus encapsulates objects for a user and performs security operations on bound objects.

    What does ‘fully customized’ mean?

    The developer can inherit from the base classes to customize all or any of the first class objects.

    This is done by inheriting the ClientNexusGeneric class and passing in the inherited first classes.

    Note: jProfile, jUser, jRole and jWorkspace are classes designed by the developer. The developer may choose any name for these classes as long as they meet each interface.

    The first class objects contain the features required to implement security and the code used matches standard classes used in the development project. Customization allows the developer to write user and role classes based on Windows or Forms Authentication.

    Jazz Features

    States

    States are built into jObject.

    jObjects can define a set of states allowing a jObject instance to be in one given state. The jObject changes state by executing the 'EnterState' method. Operations can be performed upon entering a state by inserting code in the state method. Instance methods and properties can have their accessibility changed as the state of the jObject changes by using the 'GrantState' construct.

    Defining a State

    A state is declared in Jazz as a method with a void return type. For example:

    C#
    /// <summary>
     /// Using the 'State' attribute the 'Completed' method is declared a 'State'.
     /// </summary>
     [State]
     void Completed()
     {
     }
    

    When the 'StartState' attribute is used then the jObject is placed in that state when instantiated.

    C#
    /// <summary>
     /// The state named 'Started' is entered automatically upon instantiation.
     /// </summary>
     [StartState]
     void Started()
     {
     }
    

    Entering a State

    To enter a state the "EnterState" method is used.

    C#
    this.EnterState("Completed");
    

    ACL

    Every jObject has an Access Control List (ACL). If the ACL is empty, then there are no access restrictions on the jObject. ACL items can be added programmatically, such that:

    • Certain roles have read only access to the jObject.
    • Certain roles have full access to the jObject.\
    • The jObject is totally inaccessible to certain roles.
    • Certain users have read-only access to the jObject.
    • Certain users have full access to the jObject.
    • The jObject is totally inaccessible to certain users.

    The ACL is enforced by internal mechanisms in Jazz.

    ACL Examples

    In this example, "Manager" and "Boss" are Roles defined in the code.

    Logged in User Roles Object ACL Items Actions Allowed
    "Manager" empty ACL Allows full access of the object.
    no roles empty ACL Allows full access of the object.
    "Manager" [1] "Manager" - 'Accessible' Allows full access of the object.
    no roles [1] "Manager" - 'Accessible' No access of the object allowed.
    "Manager" [1] "Manager" - 'ReadOnly' ReadOnly access of the object.
    no roles [1] "Manager" - 'ReadOnly' No access of the object allowed.
    "Manager" [1] "Manager" - 'ReadOnly'
    [2] "Boss" - 'Accessible'
    ReadOnly access of the object.
    "Manager", "Boss" [1] "Manager" - 'ReadOnly'
    [2] "Boss" - 'Accessible'
    Allows full access of the object.

    Access Enforcement

    The grant construct consists of three C# attributes:

    GrantProperty

    The ‘GrantProperty’ attribute grants access to a instance method, property or property setter when the ‘Property’ value:

    is a boolean type returning ‘true’, or is a role that the logged in user possesses, orreturns a state that is the current state.

    GrantState

    The ‘GrantState’ attribute grants access to an instance method, property or property setter only when the jObject's current state is one of the states specified by the attribute.

    GrantRole

    The ‘GrantRole’ attribute grants access to an instance method, property or property setter only when the logged in profile has one of the roles specified by the attribute.

    When multiple Grant constructs are used, then the access granted will be a logical And of the 'grant constructs'. For example if both a 'GrantState' and a 'GrantRole' are used on a member, then access is granted when the jObject's current state is specified by the 'GrantState' attribute and the user's login roles are specified by 'GrantRole' attribute, otherwise no access is permitted.

    Access Violations

    Applies To: Access Violation Exception occurs when:
    GrantState Instance Method
    Instance Property
    Instance Setter
    Attempting an operation when the jObject's current state is not a state declared by ‘GrantState’
    GrantRole Instance Method
    Instance Property
    Instance Setter
    Attempting an operation when the logged in user/profile does not possess a role specified by ‘GrantRole’.
    GrantProperty Instance Method
    Instance Property
    Instance Setter

    Attempting an operation when the ‘GrantProperty’ value

    is ‘false’ oris a role that the logged in user does not possess orreturns a state that is not the current state.
    ReadOnly ACL All methods of an Jazz object Attempting to execute a method when the logged in profile only possesses a role that supports ‘ReadOnly’ ACL access.
    No Access ACL All methods, fields and properties of a Jazz object. Attempting any access when the logged in profile does not possess a role that supports access (i.e. ‘Accessible’ ACL item).

    The Difference

    How is Jazz different than .NET security?

    Let’s look at how Jazz security is different from .NET security.

    Function

    .NET Role-based security

    Jazz security

    Access to methods

    PrincipalPermission attribute.

    Unauthorized access to the method results in an exception.

    Use Grant constructs.

    Unauthorized access to the method results in an exception.

    In-line assertions

    .NET imperative pattern implements in-line assertions for access to a specific portion of a method.

    Use ‘IsAccessible’ for the object.

    Use ‘IsExecutable’ property to see if method is accessible.

    Use ‘CanRead’ and/or ‘CanWrite’ for properties.

    Access attributes

    PrincipalPermission supports:

    Members of a Role(s) Named UserAny Authenticated user

    GrantRoles supports:

    Members of a Role(s)

    GrantStates supports:

    Any state in the object

    GrantProperty supports:

    Any instance property in the object Named User Any Authenticated user

    Access to Constructors

    Not supported

    Future

    Access to Properties

    Not supported

    Use Grant construct to decorate properties.

    Access to Property Setter

    Not supported

    Use Grant construct to decorate property setters.

    A Second Level of Security

    A second level of security included in Jazz is Access Control Lists.

    Function

    .NET Role-based security

    Jazz security

    Access Control List for an object

    Not supported

    jObject have an Access Control List that allow Roles, Read-only Roles, Users and Read-only Users.

    Access Control List for a workspace

    Not supported

    jWorkspace has an Access Control List that allow Roles, Read-only Roles, Users and Read-only Users. jWorkspace is a container for jObjects.

    Nexus Binding

    jObjects may be used with or without the Nexus. When used without the Nexus, no security operations take place.

    Let’s look at the life of a jObject:
    • Instantiation of the jObject occurs when a new object is created or data retrieved from the database is converted into jObjects. The object is a typical .NET object.
      • ToDoItem item = new ToDoItem();
    • Bind the jObject to the Nexus. The object now belongs to the nexus. Secure operations can now be performed on the jObject and changes stored.
      • nexus.Bind(item);

    The jObject features lie dormant until the 'Bind' operation binds it to client nexus. A single physical 'jObject' may only be contained in a single Client Nexus; however an instance of jObject, copied through serialization, may be in multiple client nexuses simultaneously.

    jObjects are standard .NET objects. jObjects are not activated until a 'Bind' operation binds them to client nexus. Steps 1 and 2 can be combined:

    • ToDoItem item = new ToDoItem(nexus);

    jObjects may be bound to the nexus at any time. Another approach is to create the nexus when it is required – this is shown in the code snippet in the introduction, where a profile is defined as the login profile and a role and target object are passed into the BindUnits array. Once the Nexus is instantiated, secure operations can be performed on the target object(s).

    Postsharp attributes

    Postsharp is used to implement Jazz attributes. To use the source code ‘Postsharp’ is required. –It is available at: http://www.sharpcrafters.com/postsharp/download

    Applying Jazz to your Project

    To use Jazz in your project, objects that are Jazz targets must inherit from the jObject and have the Jazz attribute decorate the class. To do this, make sure that projects referenced include

    TrackerRealm.Jazz.ClientTrackerRealm.Jazz.CommonPostsharp

    Note: TrackerRealm.Jazz.Client.Custom is shown in the using statements, because jUser, jRole, jProfile, jWorkspace and ClientNexus use these classes in this namespace for this example.

    C#
    using TrackerRealm.Jazz.Client.Custom;
    
    using TrackerRealm.Jazz.Client;
    
    using TrackerRealm.Jazz.Common;
    
            /// <summary>
             /// This is a minimalist 'Jazz Class'.
             /// The '[Jazz]' attribute and inheriting from jObject are required.
             /// </summary>
             [Jazz]
             publicclass MyFirstJazzClass : jObject
             {
                 // standard C# code can go here
             }

    Creating a Role

    A jRole can be created programmatically just like a .NET object. The following factory method creates a jRole that is bound to the nexus.

    jRole managerRole = jRole.Create(nexus, "Manager");
    

    Creating a User

    A jUser can be created programmatically just like a .net object. The following code snippet:

    • Creates a jUser and default jProfile (implicitly as part of the jUser factory method)
    • Adds the "Manager" Role to the user’s Profile
    • Binds the jUser and jProfile objects to the nexus.
    jUser user =  jUser.Create(nexus, "Fred", "Manager");
    

    Creating a jObject

    The easiest way to control instantiation is to use a singleton that wraps the object creation process.

    Assigning a Role to a Method or Property

    A role can be assigned to a method or property using the grant construct.

    C#
    /// <summary>
     /// The approve method is only allowed to be executed when
     /// the logged in user(profile) has the role 'Manager'.
     /// </summary>
     [GrantRoles("Manager")]
     public void Approve()
     {
         this.IsApproved = true;
     }
    

    Access Violation Example

    For example, given the following jObject property:

    C#
    /// <summary>
     /// Make a request by setting the data.
     /// The 'Request' property may only be set when this 'jObject' instance
     /// is in the 'Start' state.
     /// </summary>
     public string Request
     {
         get { return this.data; }
         [GrantStates("Started")]
         set
         {
             this.data = value;
             // prevent the data from being changed.
             this.EnterState("Approved");
         }
     }
    

    The first set of Request demonstrates a valid access to the property. Subsequent sets of the Request property demonstrate an access violation.

    C#
    // Enter some data - this will change the current state to Approval state.
     myFlow.Request = "May I leave early on Friday?";
     try
     {
    
        // Enter some more data.  This will cause an exception.
         myFlow.Request = "I changed my mind, I want to leave Friday at noon.";
         Assert.Fail("should not reach this line!");
     }
     catch (Exception ex)
     {
         Assert.IsTrue(ex is AccessViolationException);
     }
    

    Using ‘Grant State’

    Using the ‘GrantState’ construct, the accessibility of a method or property can change dynamically as the state of the workflow changes.

    C#
    /// <summary>
     /// The approve method is only allowed to be executed when this instance is the 'Approved State'.
     /// </summary>
     [GrantStates("Approved")]
     public void Approve()
    {
         this.IsApproved = true;
         // prevent the workflow from being approved again.
         this.EnterState("Completed");
     }
    

    In this example only the property setter accessibility is controlled by the 'State' construct.

    C#
    /// <summary>
     /// Make a request by setting the data.
     /// The 'Request' property may only be set when this 'jObject' instance
     /// is in the 'Started' state.
     /// </summary>
     public string Request
     {
         get { return this.data; }
         [GrantStates("Started")]
         set
         {
             this.data = value;
             // prevent the data from being changed.
             this.EnterState("Approved");
         }
     }
    

    Adding an ACL Item

    This example shows the instantiation of a new jWorkspace and jRole. When the role is added to the workspace using ‘AclItem.Accessible’, the workspace is now secured, only allowing access to users that have the ‘Manager’ jRole.

    The last line of the code snippet is the syntax for how to add an ACL to any jObect.

    C#
    // Create a workspace
    jWorkspace mySpace = jWorkspace.Create(nexus, "MySpace");
     jRole manager = jRole.Create(nexus, "Manager");
     // set 'mySpace' to be accessible only if the user possesses the role named 'Manager'.
     mySpace.Add(jAclItem.Accessible(manager));
    

    Example with Code walk thru

    A group of users collectively creates ToDo items where any user can volunteer to be the 'AssignedTo' worker for specific ToDo items and when the items are completed they are approved by a manager.

    Figure 1: State Diagram of the To Do Item
    • Blue ellipses denote states.
    • Text on the connector lines shows the command and the Grant Role or Grant Property.

    A Windows Form project is used to demonstrate the example. The form code shows how to design simple forms without knowing very much about the actual jObject. Make changes to the ‘GroupToDo’ properties and methods and see them reflected on the form without changing the form code.

    Users, Roles and Workspaces

    There are a number of users and roles included in the demo.

    Roles include:

    • ‘ToDo’: this Role allows the user to see any public To Do Item.
    • ‘UserAdmin’: this Role allows the user to see the users and roles that are defined.

    Users include:

    • ‘Guest’ with no roles. Guest has no access to anything.
    • ‘George’ with Role: ‘ToDo’ – George can access the To Do Items.
    • ‘Sarah’ with Role: ‘ToDo’ – Sarah can access the To Do Items.
    • ‘Admin’ with Role: ‘UserAdmin’ – Admin can access the Users and Roles.
    • ‘Manager’ with Roles: ‘ToDo’ and ‘Manager’ – Manager can access the To Do Items.

    Note: Users and Roles are public jObjects that can be accessed by anyone. The demo uses UI logic to prevent users and roles from being displayed to anyone except the ‘Admin’ user.

    Workspaces include:

    • Public’: this workspace holds the classes, users, roles, and empty objects. ‘Public’ means that the objects in this workspace are accessible to all users. Properties and Methods can be decorated with Grant constructs.
    • ‘To Do Workspace’: this workspace holds the To Do Items. The ‘ToDo’ and ‘Manager’ Roles are assigned to the ‘To Do Workspace’ ACL. This means that the user MUST have the ‘ToDo’ or ‘Manager’ Role to access the To Do Items.

    Note: Users can ONLY see the ID of Inaccessible jObjects. To check this out, put a breakpoint in the login event. Log into the Guest. Check out the To Do Item. Each property in the ToDoItemExample class shows ‘The workflow, 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', is not accessible.’.

    To Do Item spec outline

    Here are the requirements for the To Do Item used in the article.

    1. To Do Items are created and viewed by all users in the To Do workspace.
      1. Users must have the "ToDo" Role to see the To Do workspace
    2. Managers have the "Manager" Role.
    3. The Guest and Admin users do not have access to the To Do workspace.
    4. Properties are displayed as edit fields if and only if the user can write to the property.
    5. Users can assign themselves as the assigned to person for the To Do Item. This allows this user and the ‘Manager’ Role to edit the To Do Item and others to read it.
    6. Users with the ‘ToDo’ Role can create a To Do Item.
    7. There is a Name and Description.
    8. There is a Assigned To Comment that only the "AssignedTo" person can edit.
    9. There is a Approver Comment that only the "Manager" role can edit.
    10. Tracking to do items in the "Started", "Defined", "Finished", "Approved" and "Deleted" states.
    11. A ‘Manager’ should be able to perform the same tasks as the ‘AssignedTo’ of the item.
    12. Only a ‘Manager’ can "Accept" or "Reject" a "Finished" To Do Item.
    13. The owner can "Delete" the To Do Item.
    14. If the To Do Item is "Completed", then nothing can be changed.
    15. If the To Do Item is "Deleted", then nothing can be changed.

    Building the To Do Item

    Inherited properties include:

    • Class: the class object that the jObject belongs to
    • Workspace: the directory that the jObject is in
    • IsValid: tells the user that the jObject is valid
    • IsDeleted: tells the user that the jObject has been deleted

    Access related properties include:

    • CanDelete – a combination of states and property checks for when the To Do Item can be deleted

    Commands include:

    • Definition_Complete: allows a granted user to mark the To Do Item as being defined what to do.
    • Claim_Task: allows the granted user to assign the To Do Item to the logged in user
    • Finish: allows a granted user to mark the To Do Item as ‘Finished’
    • Approve: allows the ‘Manager’ to approve the To Do Item.
    • Reject: allows the ‘Manager’ to return the To Do Item to the ‘Assigned’ state.
    • Delete: allows a granted user to delete the jObject

    States include:

    • Started: this is designated as the Start State
    • Defined: The To Do Item definition is complete.Finished: The assigned to user sets this to indicate that the To Do Item is done.
    • Approved: The Manager sets this state. It is a final state for the jObject.
    • Deleted: A granted user deletes the To Do Item.

    Instantiation of the ToDoItemExample yields a jObject with:

    • Name = the name of the To Do Item added
    • Description: some additional comments added by the ‘Creator’
    • Assigned To Comment: a comment added by the ‘AssignTo’ person
    • Approver Comment: a comment added by the ‘Manager’

    Note: Only granted users can see and/or set the values of these properties.

    Properties and Methods decorated with the ‘UI’ attribute make it easy to determine if the member should be presented on the User Interface.

    Jazz Code Example

    C#
    /// <summary>
    /// GroupToDo implements a simple to do item.
    /// </summary>
    [Jazz]
    public class GroupToDo : jName
    {
        # region Constructor
        /// <summary>
        /// Instantiate a to do item
        /// </summary>
        /// <param name="nexus"></param>
        /// <param name="name">name of the to do item</param>
        public GroupToDo(ClientNexus nexus, string name) : base(null, name)
        {
            this.Initialize(nexus);
            nexus.Bind(this);
        }
        /// <summary>
        /// Initialize properties
        /// </summary>
        void Initialize(ClientNexus nexus)
        {
            if (!this.IsEmpty)
            {
                this.Creator = nexus.LoginProfile;
                // Move item to To Do Workspace
                jWorkspace ws = nexus.Cache.OfType<jWorkspace>().SingleOrDefault(s => s.Name == "To Do Workspace");
                if (ws != null)
                    ws.Move(this);
            }
        }
        # endregion
        # region Properties
        /// <summary>
        /// Cast the ClientNexus
        /// </summary>
        private ClientNexus Nexus
        {
            get { return (ClientNexus)this.ClientNexus; }
        }
        /// <summary>
        /// The person who creates the to do item and defines it
        /// </summary>
        publicjProfile Creator
        {
            get;
            privateset;
        }
        /// <summary>
        /// The person assigned to the to do item
        /// </summary>
        publicjProfile AssignedTo
        {
            get;
            privateset;
        }
        /// <summary>
        /// The name of the to do item
        /// </summary>
        publicoverride string Name
        {
            get { return base.Name; }
            protectedset { base.Name = value; }
        }
        /// <summary>
        /// The 'what' of the to do item
        /// </summary>
        publicstring Description
        {
            get;
            [GrantStates("Started"), GrantProperty("Creator")]
            set;
        }
        /// <summary>
        /// Comments from the assigned to person
        /// </summary>
        publicstring AssignedTo_Comments
        {
            get;
            [GrantStates("Assigned"), GrantProperty("AssignedTo")]
            set;
        }
        publicstring Approver_Comments
        {
            get;
            [GrantStates("Finished"), GrantRoles("Manager")]
            set;
        }
        # endregion
        # region Properties used in Grant Constructs
        /// <summary>
        /// Identifies if the logged in user is the Creator
        /// </summary>
        bool IsCreator
        {
            get { return this.Creator == this.Nexus.LoginProfile; }
        }
        /// <summary>
        /// Identifies if the logged user is the person assgined to work the to do item
        /// </summary>
        bool IsMyItem
        {
            get
            {   // if no one is assigned to the item, then return true
                if (this.AssignedTo == this.Nexus.LoginProfile.Class.EmptyInstance)
                    returntrue;
                // OR logged in user must be assigned to the item
               returnthis.AssignedTo == this.Nexus.LoginProfile;
            }
        }
        /// <summary>
        /// Identifies when and whom can delete the to do item
        /// </summary>
        bool CanDelete
        {
            get
            {
                if (!this.IsValid)
                    returnfalse;
                if (this.Nexus.LoginRoles.Contains("Manager"))
                    returnthis.CurrentState.Name != "Approved";
                switch (this.CurrentState.Name)
                {
                    case"Started":
                        if (this.Nexus.LoginProfile == this.Creator)
                            return true;
                        break;
                    case"Defined":
                        if (this.Nexus.LoginProfile == this.AssignedTo)
                            return true;
                        break;
                }
                returnfalse;
            }
        }
        # endregion
        # region Public Methods
        /// <summary>
        /// The creator can mark the definition complete
        /// </summary>
        [GrantStates("Started"), GrantProperty("IsCreator")]
        publicvoid Definition_Complete()
        {
            this.EnterState("Defined");
        }
        /// <summary>
        /// Someone chooses to own the to do item
        /// </summary>
        [GrantStates("Defined"), GrantProperty("IsMyItem")]
        publicvoid Claim_Task()
        {
            this.EnterState("Assigned");
            this.AssignedTo = this.Nexus.LoginProfile;
        }
        /// <summary>
        /// The assigned person marks the to do item as finished
        /// </summary>
        [GrantStates("Assigned"), GrantProperty("IsMyItem")]
        publicvoid Finish()
        {
            this.EnterState("Finished");
        }
        /// <summary>
        /// The Manager approves the to do item
        /// </summary>
        [GrantStates("Finished"), GrantRoles("Manager")]
        publicvoid Approve()
        {
            this.EnterState("Approved");
        }
        /// <summary>
        /// The Manager rejects the finish action
        /// </summary>
        [GrantStates("Finished"), GrantRoles("Manager")]
        publicvoid Reject()
        {
            this.EnterState("Assigned");
        }
        /// <summary>
        /// Delete the to do item
        /// </summary>
        [GrantProperty("CanDelete")]
        publicvoid Delete()
        {
            this.EnterState("Deleted");
        }
        # endregion
        # region States
        /// <summary>
        /// Enters the Started state when the to do item is created
        /// </summary>
        [StartState]
        void Started()  { }
        /// <summary>
        /// Is Defined when the creator finished the description
        /// </summary>
        [State]
        void Defined()  { }
        /// <summary>
        /// Is assigned when someone chooses to own the to do item
        /// </summary>
        [State]
        void Assigned() { }
        /// <summary>
        /// Is finished when the assigned to person marks it finished
        /// </summary>
        [State]
        void Finished() { }
        /// <summary>
        /// Is approved when the manager marks the to do item approved
        /// </summary>
        [FinalState]
        void Approved() { }
        /// <summary>
        /// Is deleted when someone deletes the to to item
        /// </summary>
        [FinalState]
        void Deleted()
        {
            base._Delete();
        }
        # endregion
    }
    

    Windows Form Project

    The Windows Form Project contains the Jazz code file plus three forms:

    1. Form Jazz: the driver form used to display navigation and selected objects
    2. Form Name Prompt: prompts for the name needed for named objects
    3. Form Help: displays help for the project

    Displaying a jObject

    Figure 2 (below) shows a ‘GroupToDo’ jObject in form format. The form allows the user to change the contents of the properties and save the changes.

    The navigation pane of the form shows the default data that is created when the form is opened. There is no database, so the demo works as a standalone single user demo that updates data in RAM. Default data includes:

    Roles: roles are used in Grant constructs, assigned to users.

    Users: each user can log in to get a unique view of the data. Just click the Login button in the tool strip and select a user.

    GroupToDo: the to do item is used to explore the security aspects of Jazz.

    Click on a Role, User or ToDoItem to see the jObject displayed in Form format.

    To create a new Role or User, login as the Admin, click New in the tool strip menu and select the item that you wish to create. To create a new To Do Item, login as George, Sarah or Manager, click New in the tool strip menu and select the item that you wish to create.

    Figure 2: Displaying the jObject

    Formal Use Case

    Use Case Name: Complete the To Do Item

    Actors:

    • To Do Item Creator: Person who creates the to do item
    • To Do Item Assignee: Person assigned to the to do item
    • To Do Item Manager: Person who has a ‘Manager’ Role
    • Guest: Person unassociated with To Do Items

    Triggers:

    • ‘George’ wants to complete the ‘To Do Item for George’.

    Preconditions:

    • ‘George’ created the ‘To Do Item for George’.
    • Visual Studio is started and the JazzWindowsForm project is selected as the Startup project.
    • In Visual Studio, hit F5 to start debugging. The form will open.

    Post-conditions:

    • The ‘To Do Item for George’; is completed.

    Normal Flow [A]:

    1. Click Login at the top of the form and select ‘George’ as the Login name.
      • ‘George’ is now logged in (see top right of form).
    2. Select the ‘[Assigned] To Do Item for George’ in the navigation panel.
      • As the ‘Creator’, ‘George’ can still edit the ‘Description’. Add "Enter additional notes to complete the description" to the ‘Description’ field and click ‘Save’.
    3. Now that the definition is complete, ‘George’ clicks ‘Definition_Complete’.
      • The To Do Item is promoted into the ‘Defined’ state and the form is a read-only view of the To Do Item.
    4. Let’s assign this item to George.
      • Click ‘Claim_Task.
    5. Add a comment to the ‘AssignedTo_Comments’ field – "This is the task that I have finished." and click ‘Save’. This displays the jObject as displayed in Figure 1 above.
    6. Click the ‘Finish’ button.
      • The To Do Item is promoted into the ‘Finished’ state and the form is a read-only view of the To Do Item.
    7. Click Login As at the top of the form and select ‘Manager’ as the Login name.
    8. The Manager sees that they can include a comment and click either ‘Accept’ or ‘Reject’.
    9. Add a comment to the ‘Approver_Comments’ – "this is good" and click ‘Save’.
    10. Click the ‘Accept’ button. The To Do Item is now ‘Completed’ and the form reverts to a read-only view of the data.

    Alternate Flows [A]

    3A1: The definition for the To Do Item is not completed.

    3. Click Login As at the top of the form and select ‘Sarah’ as the Login name.

    • When ‘Sarah’ logs in, you see a read-only view of the To Do Item.

    4. Click Login As at the top of the form and select ‘Manager’ as the Login name.

    1. When ‘Manager’ logs in, you see a read-only view of the To Do Item.

    5. Click Login As at the top of the form and select ‘Guest’ as the Login name.

    1. ‘Guest’ does not have access to the To Do Item.

    5A1: The definition for the To Do Item is now completed.

    5. Click Login As at the top of the form and select ‘Sarah’ as the Login name.

    • When ‘Sarah’ logs in, ‘Sarah’ sees the ‘Claim_Task' button and can claim the To Do Item.

    6. Click Login As at the top of the form and select ‘Manager’ as the Login name.

    • When ‘Manager’ logs in, ‘Manager’ sees the ‘Claim_Task' button and can claim the To Do Item.

    8A1: ‘George’ has finished the To Do Item.

    8. Click Login As at the top of the form and select ‘Sarah’ as the Login name.
    • When ‘Sarah’ logs in, you see a read-only view of the To Do Item.

    Use Case Name: Create a To Do Item

    Actors:

    • To Do Item Creator: Person who creates the to do item

    Triggers:

    • ‘George’ wants to create ‘A new to do item’.

    Preconditions:

    • Visual Studio is started and the JazzWindowsForm project is selected as the Startup project.
    • In Visual Studio, hit F5 to start debugging. The form will open.

    Post-conditions:

    • The ‘A new to do item’; is created.

    Normal Flow [B]:

    1. Click New at the top of the form and select ‘Create_To_Do_Item’.
      • A form pops up prompting you for the name of the To Do Item. (The name appears in the navigation pane.)
    2. Enter "A new to do item" and click ‘Ok’ or press the Enter key.
    3. A new to do item is added to the Navigation pane.
    4. Select the "A new to do item’ that is just added.
      • It is displayed in the editor pane. Note that the creator matches the name of the logged in user.

    Displaying the Schema

    Figure 3 (below) shows a GroupToDo jObject schema.

    The schema view of the data shows properties and methods for the To Do Item in its current state. This is useful in getting a snap-shot of how the jObject is viewed by the logged-in user.

    Properties show the property name, the value, the ‘Can Read’ flag the ‘CanWrite’ and the Grant Constructs for States, Roles and Properties. Note that the Grant constructs for setters are prefixed by ‘[Setter]’.

    Methods show the method name, the ‘IsExecutable’ flag and the Grant Constructs for States, Roles and Properties.

    Figure 3: Reviewing jObject Schema

    Code snippets used in the Demo

    The demo is built based on a programmatic interface. Let’s explore the Jazz API.

    Get members of the jObject

    For developers writing secure applications, it is not always clear what properties and methods are available to the user. Jazz includes a jPropertyInfo and jMethodInfo that mirrors PropertyInfo and MethodInfo. These two classes provide properties that take into account the grant constructs used in your code.

    To get all of the properties used in the jObject, call GetWrappedMembers() and filter using the Linq ‘OfType<jPropertyInfo>()’ method.

    The methods are retrieved by replacing jPropertyInfo with jMethodInfo.

    IEnumerable<jPropertyInfo>   properties = obj.GetWrappedMembers().OfType<jPropertyInfo>();
    

    To get just the members of the sub-class of a jObject, use the following code. The difference is in how GetMembers filters the members of the jObject. Note that ‘ClassMethods’ is an extension method.

    IEnumerable<jMethodInfo> methods = obj.ClassMethods();
    

    Read the value of the property

    Use the jPropertyInfo to check of the property can be read. Whereas PropertyInfo.CanRead indicates whether a property has a getter, jPropertyInfo.CanRead takes Grant Constructs on the getter into account.

    C#
       jPropertyInfo pi = properties.Single(p => p.Name == "Subject");
    
    ...if (pi.CanRead) {
    
          object value = pi.Read(this.obj);

    where pi is the jPropertyInfo for a property.

    Write the value of the property

    Use the jPropertyInfo to check of the property can be written and to write it. Whereas PropertyInfo.CanWrite indicates whether a property has a setter, jPropertyInfo.CanWrite takes Grant Constructs on the setter into account.

    C#
       jPropertyInfo pi = properties.Single(p => p.Name == "Subject");
    
    ...if (pi.CanWrite)  
    
          pi.Write(this.obj, value);

    where pi is the jPropertyInfo for a property.

    Execute a command

    Use the jMethodInfo object to check if the method is executable.

    Note: The code checking for the correct signature is not shown.

    C#
    jMethodInfo mi = methods.FirstOrDefault(m => m.Name == command);
    
    if (mi.IsExecutable)
    
       mi.Execute(this.obj);
    

    where command is the name of the method to be called.

    Create a jObject

    Instantiation of the jObject is identical to creating a .NET object. By passing the nexus into the constructor, the jObject is bound to the nexus and security-ready.

    GroupToDo.Create(nexus);
    

    The demo contains a pattern for providing granted users with access to the instantiation of Users, Roles and To Do Items. The class is Creator and contains a method for each instantiation. E.g. to create a To Do Item, the user must have the ‘ToDo’ Role.

    C#
    [UI, GrantRoles("ToDo")]
    publicGroupToDo Create_To_Do_Item(string subject)
    {
        returnGroupToDo.Create(this.nexus, subject);
    }
    

    Points of Interest

    Workflows can be built simply when a powerful technique like 'Grant constructs' are used.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)