Saturday, March 28, 2009

SharePoint, Trust Level and Code Access Security

In Windows, there are two types of security mechanisms: Role based Security and Code Access Security. Role based security is easy to understand. Code Access Security(CAS) is to prevent “Bad” module from doing any damage. A  module is “Bad” if it does not just do what it claims to do. For example, you run a module downloaded from internet to display a nice screen saver. However, although the module displays the screen saver, it also silently sends your local files to a remote location. In this case, the module does not just do what supposed to do.
How does CAS prevent “Bad” module from doing any damage? It is achieve by two mechanisms working together.
1.    Configure CAS policy so the “Bad” module won’t have the CAS permission to do damage such as sending files to a remote location.
2.    The code of sending file to a remote location must use CAS security Demand to demand the calling module to have the CAS permission. In case of sending files to a remote location. “WebRequest” CAS permission is demanded. Without the security demanding, configuring CAS policy is useless since CAS security enforcement typically starts with CAS permission demanding and then checks the security policy to see if the demanded permission is granted to the calling module. 
While developing CAS strategy, in addition to configure CAS security policy, it is important to use the second mechanism so the code you wrote won’t be used by “Bad” module to cause any damage.

In SharePoint, it is a best practice to deploy your Dlls to the bin directory instead of GAC and configure minimum CAS permissions for the Dlls through CAS policy files. The tutorial will guide your through how to do it.  The tutorial will use a couple of  simple custom web parts, deploy them into the bin directory and use them in sharepoint content pages. 

Lab 1: Deploy and use the first web part in SharePoint

Step 1: Develop DummyWebPart, build a Dll (Dummy.dll), copy dummy.dll to the SharePoint’s Bin directory

The source code of the first web part DummyWebpart is as the following

namespace Dummy



{



    [ToolboxData("<{0}:DummyWebPart runat=server></{0}:DummyWebPart>")]



    public class DummyWebPart : WebPart



    {



        protected override void RenderContents(HtmlTextWriter output)



        {



            output.Write("Hello World");



        }



    }



}




DummyWepart writes “Hello World”. That is it!


 


Step 2: Do not forget adding the following SafeControl entry to your web.config since we are using DummyWebPart in a Customized Content Page.  It is important to know “SafeControl” has nothing to do with Code Access Security. It is something used by SharePoint Page Parser to forbid unregistered WebControl in a customized content page!


 




<SafeControls>



     ......



     <SafeControl Assembly="Dummy" Namespace="Dummy" TypeName="*" Safe="True" />



     ......



</SafeControls>




Step 3: Create a SharePoint Content Page Dummy.aspx to host the DummyWebPart. The source code of Dummy.aspx is as the following


 




<%@ Page Language="C#" %>



<%@ Register assembly="Dummy" namespace="Dummy" tagprefix="Dummy" %>



<html dir="ltr">



<body>



<form id="form1" runat="server">



<Dummy:DummyWebPart ID="DummyWebPart1" runat="server"/>



</form>



</body>



</html>








Now, open Dummy.aspx in the SharePoint site in a browser. It just works without any trouble



image



 



 





Lab 2: Deploy and use the second web part,DummySPWebPart, in SharePoint, which will cause Code Access Security Exception.



Step 1: Develop DummySPWebPart, build a Dll (SPDummy.dll), copy the SPDummy.dll to Bin directory



The source code of the second  web part  DummySPWepart is as the following





namespace SPDummy



{



    [ToolboxData("<{0}:DummySPWebPart runat=server></{0}:DummySPWebPart>")]



    public class DummySPWebPart : WebPart



    {



       



        protected override void RenderContents(HtmlTextWriter output)



        {



            output.Write(string.Format("root web title is {0}",SPContext.Current.Site.RootWeb.Title));



        }



    }



}




DummySPWepart writes the Title of the SharePoint root web site. It invokes SPContent.Current.Site.RootWeb.Title in order to get the Tile.


 


Step 2: Do not forget adding the following SafeControl entry to your web.config.


 




<SafeControls>



     ......



     <SafeControl Assembly="SPDummy" Namespace="SpDummy" TypeName="*" Safe="True" />



     ......



</SafeControls>




Step 3: Create a SharePoint Content Page SPDummy.aspx to host the DummySPWebPart. The source code of SPDummy.aspx is as the following


 




<%@ Page Language="C#" %>



<%@ Register assembly="SPDummy" namespace="SPDummy" tagprefix="SPDummy" %>



<html dir="ltr">



<body>



<form id="form1" runat="server">



<SPDummy:DummySPWebPart runat="server"/>



</form>



</body>



</html>




Now, open SPDummy.aspx in the SharePoint site in a browser. You will get the following:


image











The exception thrown is caused by invoking SPContext.Current.Site.RootWeb.Title, which requests for the CAS permission of type 'Microsoft.SharePoint.Security.SharePointPermission”. However, the current security policy does not grant the SPDummy.dll the CAS permission. In Lab 3, we will grant the CAS “SharePointPermission” to SPDummy.dll by editing CAS security policy.



Lab 3: Step by step guide on editing CAS



Step 1: Create a custom policy file wss_custom.config by copying the out-of-box policy file

a.    Open directory “C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG”. The directory contains the out-of-box policy files defining Wss_Medium and Wss_Minimal trust levels of SharePoint.


b.    Make a copy wss_minimaltrust.config in the same directory and rename it to wss_custom.config.



Step 2: Refer to the “wss_custom.config” policy file from the web.config

a.    Add a new trustLevel entry to the web.config. The trustLevel points to the new policy file  wss_custom.config your created in step 1.





<securityPolicy>



      ......



      <trustLevel name="WSS_Custom" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\config\wss_custom.config" />



</securityPolicy>




b.    Change <trust level="WSS_Minimal" originUrl="" /> to <trust level="WSS_Custom" originUrl="" />





<trust level="WSS_Custom" originUrl="" />






After the first two steps, your sharepoint web application starts to use the custom poplicy file, wss_custom.config. Rememeber the “wss_custom.config” is just a copy of the out-of-box “wss_minimaltrust.config”. Now, we need to modify the file.



Step 3: Modify the “wss_custom.config” policy file so the Dlls in bin directory of the sharepoint web application have CAS SharepointPermission. You just need to add one IPermission element to an existing permissionset element as the follows:





<PermissionSet  class="NamedPermissionSet"  version="1" Name="SPRestricted">



<!-- add the following element -->



<IPermission



 class="Microsoft.SharePoint.Security.SharePointPermission, Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"



 version="1"



 ObjectModel="True"



/>



<!-- end of the element -->



</PermissionSet>




Save your change and open SPDummy.aspx. Now, it works without any exception!



image



How did I figure out to add the SharePointPermission to the SPRestricted permissionset? First, the exception message already tells you that we need to add SharePointPermission. So, you need to add IPermission element with SharePointPermission as its class.You can just use Version=”1”. The real catch is how to figure out adding ObjectModel=”true”. Go to http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.security.sharepointpermission_properties.aspx. You notice the SharePointPermission is a .NET class with three public properties. ObjectModel is one of them. What I did was to try each property and assign them to true. After a few tries, ObjectModel=”True” is the only one that really matters. There is no silver bullet here.


You should also ask why I choose to add it to SPRestricted PermissionSet. This requires a basic understanding of the structure of the CAS Policy file. Do a search on “SPRestricted” in the WSS_custom.config file. You will find the following element:



 





<CodeGroup class="UnionCodeGroup"  version="1" PermissionSetName="SPRestricted">



 <IMembershipCondition



    class="UrlMembershipCondition"



    version="1"



    Url="$AppDirUrl$/*"



 />



</CodeGroup>




This is the code group that dictates the CAS permission for all the Dlls under "$AppDirUrl$ directory, which includes the bin directory. This code group uses “SPRestricted” permission set. That is why to add the IPermission element to the “SPRestricted” permissionset.



NOTE, the tutorial explains the CAS security in SharePoint. In real world, you should use Windows SharePoint Packaging (WSP) to deploy the changes to CAS policy. This tutorial tells you what happens behind the scene. I will write another blog regarding the deployment in near future.

Wednesday, March 18, 2009

Updating User Interface during long running process in .NET

When an application has long running process, you may need to notify the user interface by updating a text box or a progress bar, etc. I created the following application to demonstrate. This is a simple demo. When you click on "Test" button. The text box will be updated with progress information.
image
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000); // do something
this.textBox1.Text += @"
line "
+ Convert.ToString(i);
}
}



The code above will update the text box, but only after the whole loop, i.e 5 seconds. That was because this is a single thread application. The update to the text box will be executed after the click event.





If you want real time update, you will need to create a thread.









private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(DoSomeWork);
thread.Start();
}

private void DoSomeWork()
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000); // do something
this.textBox1.Text += @"
line "
+ Convert.ToString(i);
}
}



But when you run the code above, you will get "Cross-thread operation not valid: Control 'textBox1' accessed from a thread other than the thread it was created on." exception





image





That was because the text box was created by the main thread. You can not access it directly from a new thread. There are a lot of articles talking about this issue. You may check this one. Basically what they are saying is you need a way to call back from the new thread. Here is quote talking about the Control.InvokeRequired property from MSDN:







Controls in Windows Forms are bound to a specific thread and are not thread safe. Therefore, if you are calling a control's method from a different thread, you must use one of the control's invoke methods to marshal the call to the proper thread. This property can be used to determine if you must call an invoke method, which can be useful if you do not know what thread owns a control.



Below is the sample code. When you run it, you will see that the text box will be updated every second.









public partial class Form1 : Form
{
private delegate void UICallerDelegate(int i);

public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(DoSomeWork);
thread.Start();
}

private void DoSomeWork()
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000); // do something
UpdateTextbox(i);
}
}

private void UpdateTextbox(int i)
{
if (this.InvokeRequired)
{
UICallerDelegate dlg = new UICallerDelegate(UpdateTextbox);
BeginInvoke(dlg, i);
}
else
{
this.textBox1.Text += @"
"
+ Convert.ToString(i);
}
}
}


Tuesday, March 17, 2009

Display HTML code in HTML web page

To display HTML code in a HTML page, you need first to replace "<" and ">" with "&lt;" and "&gt;", then add <pre></pre> around them. Here is an example:

To display the following in a HTML page

<h2>Header</h2>

<p>This is my HTML code</p>

You actually have the following text in your HTML page

<pre>&lt;h2&gt;Header&lt;/h2&gt;

&lt;p&gt;This is my HTML code&lt;/p&gt;</pre>

Monday, March 16, 2009

Using OLE web browser control in PowerBuilder?

Using OLE web browser control in PowerBuilder is easy. You choose "Create OLE control" from control list. Click on "Insert Control" tab and choose "Microsoft Web Browser". You may use it for web browsing or display PDF files. When I worked on a project in 2001, I had an issue with OLE Adobe Reader control in PowerBuilder. It had memory leak. After opening a handful PDF files, it would crash. Finally I found the solution to work around this issue. That was using "Microsoft Web Browser" control.

Here is how to use it and some of the features I used:

1. How to navigate?

ole_browser.object.navigate("http://www.google.com")

2. How to go back and forward?

ole_browser.object.GoBack()

ole_browser.object.GoForward()

If you have the "Back" and "Forward" button, you will want to enable them only when there are pages you can go back and forward. Then you need to have the following code in the "commandstatechange" event of the OLE control.

CHOOSE CASE command
    CASE 1 //CSC_NAVIGATEFORWARD
        cb_forrward.enabled = enable
    CASE 2 //CSC_NAVIGATEBACK
        cb_back.enabled = enable
END CHOOSE

3. How to get the url and name

In "navigatecomplete2" event you can use the following properties

ole_browser.Object.LocationUrl

ole_browser.Object.LocationName

4. How to do some common actions?

You may use ExecWBcmdID As OLECMDID, cmdexecopt As OLECMDEXECOPT,  [pvaIn As Variant,]  [pvaOut As Variant]) to perform some common actions in Web Browser Control. For example, ole_browser.object.ExecWB(12, 2) is to copy the highlighted text. Below is the list of command arguments:

typedef enum  
{
OLECMDID_OPEN = 1,
OLECMDID_NEW = 2,
OLECMDID_SAVE = 3,
OLECMDID_SAVEAS = 4,
OLECMDID_SAVECOPYAS = 5,
OLECMDID_PRINT = 6,
OLECMDID_PRINTPREVIEW = 7,
OLECMDID_PAGESETUP = 8,
OLECMDID_SPELL = 9,
OLECMDID_PROPERTIES = 10,
OLECMDID_CUT = 11,
OLECMDID_COPY = 12,
OLECMDID_PASTE = 13,
OLECMDID_PASTESPECIAL = 14,
OLECMDID_UNDO = 15,
OLECMDID_REDO = 16,
OLECMDID_SELECTALL = 17,
OLECMDID_CLEARSELECTION = 18,
OLECMDID_ZOOM = 19,
OLECMDID_GETZOOMRANGE = 20,
OLECMDID_UPDATECOMMANDS = 21,
OLECMDID_REFRESH = 22,
OLECMDID_STOP = 23,
OLECMDID_HIDETOOLBARS = 24,
OLECMDID_SETPROGRESSMAX = 25,
OLECMDID_SETPROGRESSPOS = 26,
OLECMDID_SETPROGRESSTEXT = 27,
OLECMDID_SETTITLE = 28,
OLECMDID_SETDOWNLOADSTATE = 29,
OLECMDID_STOPDOWNLOAD = 30,
OLECMDID_ONTOOLBARACTIVATED = 31,
OLECMDID_FIND = 32,
OLECMDID_DELETE = 33,
OLECMDID_HTTPEQUIV = 34,
OLECMDID_HTTPEQUIV_DONE = 35,
OLECMDID_ENABLE_INTERACTION = 36,
OLECMDID_ONUNLOAD = 37,
OLECMDID_PROPERTYBAG2 = 38,
OLECMDID_PREREFRESH = 39,
OLECMDID_SHOWSCRIPTERROR = 40,
OLECMDID_SHOWMESSAGE = 41,
OLECMDID_SHOWFIND = 42,
OLECMDID_SHOWPAGESETUP = 43,
OLECMDID_SHOWPRINT = 44,
OLECMDID_CLOSE = 45,
OLECMDID_ALLOWUILESSSAVEAS = 46,
OLECMDID_DONTDOWNLOADCSS = 47,
OLECMDID_UPDATEPAGESTATUS = 48,
OLECMDID_PRINT2 = 49,
OLECMDID_PRINTPREVIEW2 = 50,
OLECMDID_SETPRINTTEMPLATE = 51,
OLECMDID_GETPRINTTEMPLATE = 52,
OLECMDID_PAGEACTIONBLOCKED = 55,
OLECMDID_PAGEACTIONUIQUERY = 56,
OLECMDID_FOCUSVIEWCONTROLS = 57,
OLECMDID_FOCUSVIEWCONTROLSQUERY = 58,
OLECMDID_SHOWPAGEACTIONMENU = 59,
OLECMDID_ADDTRAVELENTRY = 60,
OLECMDID_UPDATETRAVELENTRY = 61,
OLECMDID_UPDATEBACKFORWARDSTATE = 62,
OLECMDID_OPTICAL_ZOOM = 63,
OLECMDID_OPTICAL_GETZOOMRANGE = 64,
OLECMDID_WINDOWSTATECHANGED = 65,
OLECMDID_ACTIVEXINSTALLSCOPE = 66
} OLECMDID;


typedef enum  
{
OLECMDEXECOPT_DODEFAULT = 0,
OLECMDEXECOPT_PROMPTUSER = 1,
OLECMDEXECOPT_DONTPROMPTUSER = 2,
OLECMDEXECOPT_SHOWHELP = 3
} OLECMDEXECOPT;


Although it says OLECMDEXECOPT_DONTPROMPTUSER = 2 doesn't show the dialog, but when you run execwb(6,2) to print a web page, it will always show the dialog. That is be design. Check Microsoft KB http://support.microsoft.com/kb/247671 for more info.

Validation of viewstate MAC failed

Recently I ran into a issue on my web site. If I opened a page and didn't do anything for a while, say 10 minutes, I would get the following error if I click a link on the page.

*Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.*

*Description: *An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

I sent an email to the support of my web hosting because I am not sure if my site is hosted by a Web Farm. This is their reply:

"This can happen when your application looses the session. It can loses the session when the application pool is recycled."

This didn't help me much. So I googled. Some people suggested to set EnableViewStateMac="false" in web.config. That didn't work for me.

I am thinking if my web site is running on a web farm and their machine keys do not match, there is nothing I can do about it. But I may be able to override the machine key in my web.config file. Finally I found this "Generate MachineKey Online Tool" by Scott Forsyth. I think that is exactly what I need. It says:

"Run the following tool to create a valid random machine key to place in your web.config or machine.config file.  This is beneficial in a webfarm where all of the server nodes need to have the same machine key, and it is also beneficial on a single box to keep the machine key consistent between IIS recycles and server reboots. "

I run it and copied the generated Machine Key to my web.config file. The problem is gone.