Friday, July 31, 2009

Why are we getting "(1385) System.ComponentModel.Win32Exception: Logon failure: the user has not been granted the requested logon type at this computer"

SharePoint InfoPath Service can use SSO to access external resources such as web services and SQL server databases. 
As explained at MSDN site,  an InfoPath form can access external data sources through connection files. Behind the scene, the InfoPath runtime will get the credential to impersonate through SSO API and invoke LogOnUser function with the credential if Windows authentication is used. 
You may run into the following exception while running an InfoPath form with SSO connections:
LogonUser failed for user 'domain\username' (1385) System.ComponentModel.Win32Exception: Logon failure: the user has not been granted the requested logon type at this computer      
If you Google  the exception for solution, you may find the advice to  give the delegated user “Allow Log on locally”  windows right. But, it won’t help. Even if you add the user to the local administrator group, it still doesn’t work. To solve this issue, we need to understand how InfoPath Service runtime impersonates with the credential configured in SSO. Writing a web part using SSO API and LogOnUser will help us to understand what is happening inside the InfoPath runtime. 
The following code segment explains the three steps to use SSO in a web part:
  • Retrieve the credential used to connect to the external database through SSO API;
  • Impersonate the credential using LogonUser;
  • Create a ADO connection to the database using Integrated Security; 
//*******************************************************************


//Step 1: Get the account for connecting to database from SSO API


string[] strCredentialData = null;


//Credentials.Get Credentials is a SharePoint SSO API


Credentials.GetCredentials(1, "eex", ref strCredentialData);


string strUserName = strCredentialData[0];


string[] domainUserName = strUserName.Split('\\');


string strPassword = strCredentialData[1];


 


//***********************************************************************


//Step 2: Use LogOnUser to impersonate with the account


bool result = LogonUser(domainUserName[1], domainUserName[0],


strPassword,


LogonSessionType.Batch,


LogonProvider.Default,


out token);


WindowsIdentity id = new WindowsIdentity(token);


impersonatedUser = id.Impersonate();


 


//************************************************************************


//Step 3:Use ADO.NET with Integrated Security to retrieve data from the external database.


string resultFromDb = null;


using (SqlConnection connection = new SqlConnection("Integrated Security=SSPI;Initial Catalog=external_test;Data Source=sqlserver"))


{


connection.Open();


SqlCommand command = new SqlCommand("select * from simple", connection);


 


using (SqlDataReader reader = command.ExecuteReader())


{


reader.Read();


resultFromDb = (string)reader[0];


}


connection.Close();


}



The code above explains how, behind the scene, SharePoint Form Service is using LogOnUser to impersonate an account. So the reason we got the "LogonUser failed for user 'domain\username' (1385) System.ComponentModel.Win32Exception: Logon failure: the user has not been granted the requested logon type at this computer" is that SharePoint Form Service invokes LogonUser with Batch  logon session type (the fourth parameter to LogonUser). Therefore, you need to configure the the security policy of your Web Front End to  give the delegated user “Log on as Batch” right. The following screen shot demonstrates the configuration.  
image

Thursday, July 30, 2009

Developing Orientation-Aware and Dpi-Aware Smart Device Application in .NET

When developing smart device application, we need to be aware of screen orientation and DPI(Dots Per Inch) since user’s device may vary. In this article, I am going to use a simple application to demonstrate how to create an Orientation-Aware and Dpi-Aware smart device application.

You can also read this article and download the source code from Code Project web site.

The following screen shot shows this application in Windows Mobile 6 Classic Emulator. It has some labels, textboxes and a button. But when you change the screen orientation to landscape, you will see the button disappeared. You have to scroll the screen to see it. That’s probably not a good idea for the user who prefer landscape screen.

clip_image002clip_image004

There is no easy way to solve this issue. If your form is not that crowded, you can try to squeeze everything into the upper half of the form. But in case you do have a lot of controls to display, one way to solve this issue is to create a landscape view of the form and dynamically re-position all the controls based on the screen orientation.

Here we first create the Portrait view, copy the code regarding position and size and create a method called Portrait(). Then we rotate the design view to create the Landscape view and also create a method called Landscape().

public void Portrait()
{
this.SuspendLayout();
this.button2.Location = new System.Drawing.Point(81, 232);
this.button2.Size = new System.Drawing.Size(72, 20);
this.label0.Location = new System.Drawing.Point(45, 9);
this.label0.Size = new System.Drawing.Size(141, 20);
this.textBox1.Location = new System.Drawing.Point(111, 32);
this.textBox1.Size = new System.Drawing.Size(100, 21);
this.label1.Location = new System.Drawing.Point(16, 33);
this.label1.Size = new System.Drawing.Size(74, 20);
this.label2.Location = new System.Drawing.Point(16, 70);
this.label2.Size = new System.Drawing.Size(74, 20);
this.textBox2.Location = new System.Drawing.Point(111, 69);
this.textBox2.Size = new System.Drawing.Size(100, 21);
this.ResumeLayout(false);
}
public void Landscape()
{
this.SuspendLayout();
this.button2.Location = new System.Drawing.Point(132, 152);
this.button2.Size = new System.Drawing.Size(72, 20);
this.label0.Location = new System.Drawing.Point(101, 10);
this.label0.Size = new System.Drawing.Size(141, 20);
this.textBox1.Location = new System.Drawing.Point(52, 39);
this.textBox1.Size = new System.Drawing.Size(100, 21);
this.label1.Location = new System.Drawing.Point(3, 40);
this.label1.Size = new System.Drawing.Size(43, 20);
this.label2.Location = new System.Drawing.Point(173, 40);
this.label2.Size = new System.Drawing.Size(54, 20);
this.textBox2.Location = new System.Drawing.Point(233, 39);
this.textBox2.Size = new System.Drawing.Size(100, 21);
this.ResumeLayout(false);
}





Add the following code to Form’s Resize event, so it will change layout when the screen orientation is changed. Here we use Screen.PrimaryScreen.Bounds to determine the orientation.





void Form1_Resize(object sender, EventArgs e)
{
if (Screen.PrimaryScreen.Bounds.Height >
Screen.PrimaryScreen.Bounds.Width) Portrait();
else Landscape();
}





Now it looks nice in Landscape orientation too.



clip_image006



However, this solution works just fine until you have to run this application on a higher DPI device. Now let us change our emulator to Windows Mobile 6.1.4 Professional – VGA, which is 480x640.



clip_image008clip_image010



It doesn’t look right, does it? Yes, that is because the two methods we added. If the actual device’s DPI is different from our designer, we cannot set the absolute position in our code and expect it displays everything correctly across different devices.



You may be able to use “Dock” property to achieve some degree of flexibility. But when you have complicate layout, you may end up adding too many panels and it is not an easy thing to do.



I found this article about creating a DpiHelper for .NET CF 1.1. It provided a way to add High-Dpi support programmatically. I borrowed its idea and used it in this application to adjust controls’ location and size based on device’s DPI. Since we set the location and size in the Portrait() and Landscape() methods, we need to scale those settings after calling those methods. Below is the modified version of DpiHelper class:




/// <summary>A helper object to adjust the sizes of controls based on the DPI.</summary>
public class DpiHelper
{
/// <summary>The real dpi of the device.</summary>
private static int dpi = SafeNativeMethods.GetDeviceCaps(IntPtr.Zero, /*LOGPIXELSX*/88);

public static bool IsRegularDpi
{
get
{
if (dpi == 96) return true;
else return false;
}
}

/// <summary>Adjust the sizes of controls to account for the DPI of the device.</summary>
/// <param name="parent">The parent node of the tree of controls to adjust.</param>
public static void AdjustAllControls(Control parent)
{
if (!IsRegularDpi)
{
foreach (Control child in parent.Controls)
{
AdjustControl(child);
AdjustAllControls(child);
}
}
}

public static void AdjustControl(Control control)
{
if (control.GetType() == typeof(TabPage)) return;
switch (control.Dock)
{
case DockStyle.None:
control.Bounds = new Rectangle(
control.Left * dpi / 96,
control.Top * dpi / 96,
control.Width * dpi / 96,
control.Height * dpi / 96);
break;
case DockStyle.Left:
case DockStyle.Right:
control.Bounds = new Rectangle(
control.Left,
control.Top,
control.Width * dpi / 96,
control.Height);
break;
case DockStyle.Top:
case DockStyle.Bottom:
control.Bounds = new Rectangle(
control.Left,
control.Top,
control.Width,
control.Height * dpi / 96);
break;
case DockStyle.Fill:
//Do nothing;
break;
}
}

/// <summary>Scale a coordinate to account for the dpi.</summary>
/// <param name="x">The number of pixels at 96dpi.</param>
public static int Scale(int x)
{
return x * dpi / 96;
}

public static int UnScale(int x)
{
return x * 96 / dpi;
}

private class SafeNativeMethods
{
[DllImport("coredll.dll")]
static internal extern int GetDeviceCaps(IntPtr hdc, int nIndex);
}
}





And here is how to use it:





void Form1_Resize(object sender, EventArgs e)
{
if (Screen.PrimaryScreen.Bounds.Height > Screen.PrimaryScreen.Bounds.Width) Portrait();
else Landscape();
DpiHelper.AdjustAllControls(this);
}





As you can see in the following screen shot, everything is back to normal.



clip_image012 clip_image014



If you use user controls in your form, you may want to modify the code to make it more generic.



First, create an Interface called IRotatable.





interface IRotatable
{
void Portrait();
void Landscape();
}







Second, create the user control, which implement IRotatable interface.




public partial class UserControl1 : UserControl, IRotatable
{
public UserControl1()
{
InitializeComponent();
}

#region IRotatable Members

public void Portrait()
{
this.SuspendLayout();
this.label1.Location = new System.Drawing.Point(18, 13);
this.label1.Size = new System.Drawing.Size(100, 20);
this.checkBox1.Location = new System.Drawing.Point(43, 36);
this.checkBox1.Size = new System.Drawing.Size(100, 20);
this.button1.Location = new System.Drawing.Point(57, 96);
this.button1.Size = new System.Drawing.Size(72, 20);
this.checkBox2.Location = new System.Drawing.Point(43, 63);
this.checkBox2.Size = new System.Drawing.Size(100, 20);
this.Size = new System.Drawing.Size(210, 134);

this.ResumeLayout(false);
}

public void Landscape()
{
this.SuspendLayout();

this.label1.Location = new System.Drawing.Point(49, 15);
this.label1.Size = new System.Drawing.Size(100, 20);
this.checkBox1.Location = new System.Drawing.Point(1, 38);
this.checkBox1.Size = new System.Drawing.Size(100, 20);
this.button1.Location = new System.Drawing.Point(62, 64);
this.button1.Size = new System.Drawing.Size(72, 20);
this.checkBox2.Location = new System.Drawing.Point(107, 38);
this.checkBox2.Size = new System.Drawing.Size(100, 20);

this.Size = new System.Drawing.Size(210, 98);
this.ResumeLayout(false);
}

#endregion
}





Then modify the form to implement IRotatable interface too.





public partial class Form2 : Form, IRotatable





Add new method to recursively loop though the form and all the controls in it.





void Form2_Resize(object sender, EventArgs e)
{
SetControlLocation(this);
}
private void SetControlLocation(Control control)
{
if (control is IRotatable)
{
IRotatable rotatableControl = (IRotatable)control;
if (Screen.PrimaryScreen.Bounds.Height > Screen.PrimaryScreen.Bounds.Width) rotatableControl.Portrait();
else rotatableControl.Landscape();
}
DpiHelper.AdjustControl(control);
foreach (Control child in control.Controls)
{
SetControlLocation(child);
}
}





This is what it looks like with a user control.



clip_image016clip_image018




Thursday, July 23, 2009

Understand MOSS Search Run Time Architecture

 

Understanding SharePoint Search Run Time Architecture is very helpful in planning/configuring SharePoint farm and troubleshooting Kerberos authentication. I recently read the following paragraph from Microsoft Web Site regarding SharePoint search planning:

In Office SharePoint Server 2007, the index role is associated with a Shared Services Provider (SSP). The index role builds one index per SSP. One index server can be associated with multiple SSPs. However, indexes across SSPs cannot be combined. You can deploy multiple index servers to improve capacity. In this case, each index server is associated with different SSPs. Unlike the Windows SharePoint Services 3.0 search role, content indexes produced by the Office SharePoint Server 2007 index role are continuously propagated to all servers that host the query role in a farm. Consequently, the output of the Office SharePoint Server 2007 index server role (that is, the index) is considered redundant if the query role is deployed to more than one server computer.

Let me explain this in detail.

What are index role, query role, index server and query server?

Index role is a logic role or responsibility that crawls contents and build index files. Query role is a logic role or responsibility that returns query results. Index Server/Query Server is an Office SharePoint Server Search Windows Service instance running on a server in the SharePoint Farm. The following picture shows the Services Console of a SharePoint Server:

clip_image002

An Office SharePoint Server Search Windows Service Instance can be configured to run as an index role, a query role or both. If an instance of MOSS Windows Search Service is configured as Index role. It is called Index Server. The same applies to the Search Role.

To start and configure a Index or Search server, open SharePoint Central Administration. Click the operation tab and then click "Services on the Server", the following window displays:

clip_image004

Click the "Office SharePoint Server Search" link. The screen to configure the Server Role for the Office SharePoint Server Search displays:

clip_image006

In this page, you can determine what roles the instance of Office SharePoint Server will play by selecting the check boxes at the top of the page.

We know how to create Index and Search server for a SharePoint farm. In a SharePoint Farm, we can have multiple SSPs. Each SSP may have different contents to search. The question is which SSP the index and Search server will work for. To answer the question, we need to study another setting. While creating a SSP, you will be ask to choose an index server:

clip_image008

The "Index Server" drop down list will allow you to choose which index server to use for the SSP. However, there is no selection for a Search Server. Therefore, we know:

  1. A SSP can have one and only one index server.
  2. A Search Server will serve for all the SSPs created in the farm.

Now, let us re-visit the paragraph from Microsoft Web Site using the knowledge we just learned:

In Office SharePoint Server 2007, the index role is associated with a Shared Services Provider (SSP). The index role builds one index per SSP. One index server can be associated with multiple SSPs. However, indexes across SSPs cannot be combined. You can deploy multiple index servers to improve capacity. In this case, each index server is associated with different SSPs.

It is in line with "A SSP can have one and only one index server"

Unlike the Windows SharePoint Services 3.0 search role, content indexes produced by the Office SharePoint Server 2007 index role are continuously propagated to all servers that host the query role in a farm.

It is in line with "A Search Server will serve all the SSPs created in the farm

Consequently, the output of the Office SharePoint Server 2007 index server role (that is, the index) is considered redundant if the query role is deployed to more than one server computer.

It basically says that you can scale out for Search Server. However, you cannot scale out for Index Server inside one instance of SSP.

Saturday, July 18, 2009

Interesting observation on SharePoint Security

 

The article lists some SharePoint Security related facts/experience that may be helpful. Please add your comments if you think the list should expand

  1. Anonymous Users can add/edit/delete list items. However, they can never add/edit/delete document library items even though they can view document library items.
  2. It is not possible to make some of the items in a list/document library accessible to anonymous users while other items not accessible. All the items in a list/document library must have the same accessibility for anonymous users.
  3. You cannot add a Windows Group to Site Collection Administrators. You can only add individual users. It is by design. In my opinion, however, it is not convenient for administration.
  4. A common misconception is that a user in the Site Owner group can access all the contents in a site. In theory, you must check item level permission for all the items in all the lists to make sure a user can access all the contents in a site. To be complete, you need also to check the Security Policy for the web application. Never assume a site owner can do anything.
  5. Limited Access Permission Level is to be added by SharePoint automatically. For example, assuming a user does not have any permission in a SharePoint site, if you add an item level read permission to the user for an item of a list. The SharePoint will add Limited Access Permission Level to the user at the list and the site level automatically.
  6. SharePoint does not have the finest permission granularity. For example, it is not possible to have one user to have edit site title and image permission and the other to have delete child sub sites permission. This is because "Manage Web Site" permission includes both edit site title/image and delete child sub sites. This lack of finest permission does cause problem in practice since you have to assign the same people to perform two very different categories of administrative tasks.
  7. ...... more to come

Thursday, July 16, 2009

SharePoint Security for the contents in _layouts, _controltemplates and _vti_bin directories in the context of SharePoint NTLM authentication

A SharePoint web application can be accessed through multiple IIS web sites with different authentication such as NTLM, Kerberos and Form. It is important to differentiate a SharePoint Web Application and the IIS Web Sites accessing the SharePoint Web Application. Typically, each IIS Web Site accessing a SharePoint Web Application will correspond to a zone in SharePoint's Alternate Access Mapping. Each zone has a public address and multiple internal addresses. The multiple internal addresses typically map to addresses for load balancing.

The article discusses the authorization on accessing the files in _layouts (application pages and resources), _controltemplates (user controls and resources) , _vti_bin ( web services) in the context of an IIS Web Site or Zone configured with NTLM authentication.

If an IIS Web Site to access a SharePoint Web application is configured with NTLM authentication, the authorization to the contents in those directories is mostly determined by ASP.NET 2.0 Windows authentication mechanism.

When a SharePoint IIS web site is configured with NTLM authentication, the web.config file of the web site has "Windows" as its authentication method. In Windows authentication, Asp.NET 2.0 actually relies on IIS to perform authentication (basic, digest and integrated). The IIS will produce a security token after the authentication and then pass the security token to ASP.NET. Then, ASP.NET will perform two types of authorization using the identity encapsulated in the security token.

  1. URL authorization (You can edit the web.config files in _layouts, _controltemplates and _vti_bin to define URL authorization)
  2. ACL authorization. This authorization does not occur for Form authentication.

It is important to realize both authorizations will apply. Therefore, even if a user has URL authority to access an ASPX or image file, he/she may still not be able to access them due to the failure of ACL authorization.

Notice, the "impersonate" will not affect the above process. It will only affect when you write server side code, for example, when you try to open a local file in your web part. SharePoint always has "impersonate" as true.

In SharePoint NTLM (not Form) authentication, accessing the files and resources in _layouts, _controltemplates and _vti_bin pretty much follows the two authorization methods (URL and ACL) of ASP.NET 2.0. However, there is just one exception to the rule. For ASPX pages in the layouts directory that inherits from out-of-box class Microsoft.SharePoint.WebContorls.LayoutsPageBase, the class will check if the current SharePoint user has "View Application Pages" permission. So, it is the third authorization methods applied to those pages.

Now add anonymous users to the mix. Like authenticated users, the authorization for anonymous users is pretty much determined by ASP.NET 2.0 with one exception that is the pages inheriting from Microsoft.SharePoint.WebContorls.LayoutsPageBase. There are some interesting things worth metioning:

  1. URL authorization will use question mark "?" to denote anonymous users;
  2. ACL authorization will use IUSR_MachineName account. (This account is configurable in IIS);
  3. Anonymous users can never load pages inheriting from Microsoft.SharePoint.WebContorls.LayoutsPageBase;

The first two are just typical ASP.NET 2.0 way of handling security. The third one is really a surprise. SharePoint anonymous users by default should have "limited access" permission level that should have "View Application Pages" permission. However, anonymous users still cannot access the pages inheriting from Microsoft.SharePoint.WebContorls.LayoutsPageBase even with the "View Application Page" permission. I could not explain why and just remember that this as an exceptional scenario. To expose SharePoint Application Page to anonymous users, do not use Microsoft.SharePoint.WebContorls.LayoutsPageBase as its base page class.