Tuesday, May 12, 2009

How to get FireFox to wrap PRE tag text

For some reason FireFox does not wrap code or other text (with no good word-breaking point) that is in a PRE tag. I noticed the problem when I was trying to stop a Blogger template from truncating (cutting of) the end of lines (that were too long). I want FireFox to wrap the long lines just like Internet Explorer. As it turns out, it is quite easy to fix. Just add the following to your CSS styles.

pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}

Monday, May 11, 2009

Using SQL Server to view all alerts in SharePoint

Here is a query to see all the alerts that are in SharePoint. This query works well in Microsoft Office SharePoint Server 2007 (MOSS 2007) and Windows SharePoint Services (WSS 3.0). If you use it for Windows SharePoint Services 2.0 or SharePoint Portal Server 2003, you will need to remove the alertTitle column since it does not exist in the older database schema.

select * from 
(-- Immediate
select 'Instant' as NotifyFreq, alertTitle, listTitle, tp_email, tp_login, s.siteurl + weburl + '/_layouts/sitesubs.aspx' as url from dbo.ImmedSubscriptions s join userinfo u on (s.userid = u.tp_id)
union
-- Scheduled 
select case NotifyFreq when 1 then 'Daily' when 2 then 'Weekly' else 'Other' end , alertTitle, listTitle, tp_email, tp_login, s.siteurl + weburl + '/_layouts/sitesubs.aspx' as url from dbo.SchedSubscriptions s join userinfo u on (s.userid = u.tp_id) 
) t

The first column indicates the type of alert. The second column is what you see on the admin page for alerts. The url and tp_login columns are very useful if you want to delete a particular alert.

The url is actual the url to the alert edit page in SharePoint. You can remove ‘/_layouts/sitesubs.aspx’ from the url if you want to see the url to the list that the alert is associated with. Once you are one the alerts admin page, you can will need the tp_login to find the alert is the drop down list.

Friday, May 8, 2009

Using Wireshark to sniff Active Directory (LDAP) traffic

Wireshark is a very power network sniffing tool. It used to be known as Ethereal (long story). It supports a ton of protocols, including LDAP. Active Directory is the Microsoft implementation of LDAP. You can download Wireshark here for most any platform. WinPcap is included in the install now, which is nice and saves some effort during installation. Once you have it installed, open it up and do the following to sniff your ldap traffic.

  1. This step is optional, but I recommend it if you are interested in the actual dns names of the devices instead of just the ip addresses. Go to Edit menu | Preferences… | Name Resolution  and then make sure the Enable network name resolution is checked. This will slow the output, but it provides much easier to read data.
  2. Go to Capture menu | Interfaces… Click the Start button on the network interface(s) you want to sniff.
  3. Immediately you should start see traffic in the window. This is all traffic.
  4. Filter the output to ldap only by typing in the Filter textbox (in the toolbar), and then hitting enter or clicking the Apply button. Unless you have some ldap traffic, you will see the window go blank. Do something that you know will perform a ldap query, then you should see the traffic in the window.

    NOTE: The ldap filter is just that a filter, all network traffic is still logged, you just don’t see it. You can use the Clear button to remove the filter.
  5. Go to the Capture menu | Stop to stop capturing traffic.

Example

This example uses the Active Directory Users and Computers application as a source for generating ldap traffic. We will use the tool to query for a user and then find the query and results in the traffic.

  1. Follow the above instructions to get Wireshark sniffing ldap traffic.
  2. Go to Start menu | Administrative Tool | Active Directory Users and Computers
  3. Select your domain in the left panel.
  4. Choose Action menu | Find…
  5. Type in something that has more than one result, but not too many. We are looking for something that has a small bit of sample data to look at. Maybe 3 or so results. You can type in particular names or usernames.
  6. Go back to Wireshark and stop the traffic.
  7. Now we can do some analysis of the data. Go to Edit menu | Find Packet…
  8. Click the String radio button.
  9. Click the Packet bytes radio button (you can also try the others, they typically work well also).
  10. Type in the same text you typed into the Active Directory Users and Computers application.
  11. Click the Find button. If you don’t find any results it may be because you have some bad reassembled packet or bad checksum. If that happens it is beyond me. Also, sometimes, the Find does work for me. I can’t explain it either. Sorry, I am programmer, not a network guy, so I am not of much help explaining what might be wrong, etc.

    However, I have had pretty good luck just looking at the traffic and figuring out what is going on. See the section below.

Understanding LDAP traffic

When you look at the Info column, you will see several different types data.

bindRequest()

This to be authentication request from us to the ldap server

bindResponse()

This is the response from the ldap server (domain controller) in response to our bindRequest().

SASL GSS-API Integrity: searchRequest()

This is a request to the ldap server. If you look in the middle pane of Wireshark, you will see a collapsed section called Lightweight-Directory-Access-Protocol | SASL Buffer | GSS-API payload | LDAPMessage searchRequest(). Here you will see specifics of what you are requesting from the ldap server. In particular, you can drill down to protocolOp: searchRequest(). Here you can see the Filter (this is the actual ldap query sent) that was used. You can also see the attributes that were requested. You can also see the baseObject here.

In terms of SQL, this is basically like the select statement.
Filter = SQL where clause
attributes = SQL list of columns to select
baseObject = roughly translates to a SQL table, but more just specifying at what level in the ldap tree the search should take place.

SASL GSS-API Integrity: searchResEntry()

This is the response to the searchRequest() request from the ldap server. If you look in the middle pane of Wireshark, you will see a collapsed section (unless you already opened it :) called Lightweight-Directory-Access-Protocol | SASL Buffer | GSS-API payload | LDAPMessage searchResEntry(). Each LDAPMessage searchResEntry() is a result from the ldap server. This is essentially a row or record. So, if the query found 7 matches, then you should have 7 LDAPMessage searchResEntry() here. You can expand each LDAPMessage searchResEntry() to see the exact objectName, and the values.

Here you will see specifics of what the response from the ldap server. In particular, you can drill down to protocolOp: searchRequest(). Here you can see the Filter (this is the actual ldap query sent) that was used. You can also see the attributes that were requested. You can also see the baseObject here.

SASL GSS-API Integrity: unbindRequest()

This is the disconnect from the ldap server. The clean up. The thing that tells the ldap server that we are done with it.

Thursday, May 7, 2009

Using ASP.NET Membership Roles (SQL table based roles) with Windows Authentication (Integrated Security)

I found this great blog posting by Scott Guthrie regarding how to use the ASP.NET Membership Roles with Windows Authentication. In this scenario users don’t have to login because Windows Authentication (I knew it as Integrated Security, but I don’t know where I got that name :). This works well with Internet Explorer and Intranet environments. The scenario also dictates that the roles are defined in SQL Server tables instead of Active Directory. Beyond that, Scott’s blog posting (noted above) explains the scenario very well.

Scott’s example goes into more functionality than I will here. However, I do cover how to create the supporting SQL tables and stored procedures and he doesn’t, but he does link to a page that does. He covers how to due Security Trimming of menus, admin interface, automatically creating the roles, etc. If you need details on those things please check ScottGu’s Blog: Implementing Role Based Security with ASP.NET using Windows Authentication and SQL Server. I have to give nearly all credit for the source of all my knowledge to Scott’s blog posting. NOTE: Scott also includes a link to download a working website project.

I want to give the basic steps only. I assume you have a website that just has a default web.config and thus default security.

Step 1: Enable Windows Authentication

Add the authentication and authorization tags to your web.config under the system.web tag. This will require all users to be authenticated. Be sure to enable Windows Integrated Authentication in IIS.

<system.web>
… <authentication mode="Windows"/> <authorization> <deny users="?"/> </authorization>

Step 2: Configure for SQL Server

By default Visual Studio wants to use SQL Express instead of SQL Server. Really, the only thing that Visual Studio needs is to know where the ASP.NET Membership provider database is located. To tell Visual Studio where to connect to SQL Server, add the following (modify for your database location, etc) to your web.config.

<connectionStrings>
    <remove name="LocalSqlServer"/>
    <add name="LocalSqlServer" connectionString="Data Source=(local);Initial Catalog=MyDB;Integrated Security=True" providerName="System.Data.SqlClient"/>
    

Please note, the name LocalSqlServer is required, do not change this.

Step 3: Create supporting tables in SQL Server database

The roles membership table and stored procedures requires tables to be created in a database. This can be in your application database, or it can be in a separate database. There may be an easier way to create these tables and stored procedures, but this works well, but it is a bit of an obscure location for the .exe.

If you are using .NET 2.0 or greater (yes v3.0 and v3.5 and v3.5.1 also) the .exe you need to execute is located in a path similar to the following:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regsql.exe
Unless you want to generate support for all the other Application Services (not necessary for this scenario) I recommend using the following command line arguments. Change the server and database name to suit your environment.
aspnet_regsql.exe -E -S myServer -d MyDB -A m -A r
You can also run the aspnet_regsql.exe without parameters and a wizard will run. The wizard does NOT allow you to only install certain features, it installs all the features. Use the command line with args or use the less flexible, but prettier wizard. Your choice.

Step 4: Enable SQL based Role Management

Replace this line in your web.config

<roleManager enabled="true" />
with these lines.

<roleManager enabled="true" defaultProvider="SqlRoleManager">
<providers>
<clear/>
<add name="SqlRoleManager" type="System.Web.Security.SqlRoleProvider" connectionStringName="LocalSqlServer"/>
</providers>
</roleManager>
NOTE: In this example the membership database and my application database are the same. They don’t have to be though.
NOTE: If you are using a virtual directory for development, I would recommend adding the applicationName attribute to the SqlRoleManger tag. It doesn’t appear to be a problem with file based Web Site projects in Visual Studio 2008, but it appears that it is with IIS 5 and using virtual directories. To better understand the issue, see here.
 
Step 5: Add the roles
You can do this via code or using the configuration ui.
To use the UI, run the Project | ASP.NET Configuration tool. Click the Security tab, and click the Create or Manage roles link. Add any number of roles you want.
To create the roles (maybe on application start in the Global.asax.cs file)
void Application_Start(object sender, EventArgs e)
{
if (!Roles.RoleExists("Admin"))
{
Roles.CreateRole("Admin");

}
}
Step 6: Add users to the roles
The ASP.NET Configuration tool doesn’t work for adding users when using Windows Integrated Authentication mode. So, you have to do this via code. The following block of code checks to see if the user is in a role. If the user is not in the role, the user is then added to that role.
if (!Roles.IsUserInRole(userName, roleName))
{
Roles.AddUserToRole(userName, roleName);
}
You may want to create a custom user interface to add users to roles. Scott Gutherie provides an example of it in VB.NET in his downloadable working demo. Below is a virtual copy of the admin page that is in the project, but mine is in C#, not VB.NET.
Here is the code-behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;

namespace MyApp
{
public partial class UserAdmin : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{

}

protected void PopulateRoleList(string username)
{
RoleList.Items.Clear();
string[] roleNames = Roles.GetAllRoles();

foreach (string roleName in roleNames)
{
ListItem roleListItem = new ListItem();
roleListItem.Text = roleName;
roleListItem.Selected = Roles.IsUserInRole(username, roleName);
RoleList.Items.Add(roleListItem);
}
}

protected void UpdateRolesFromList()
{
foreach (ListItem roleListItem in RoleList.Items)
{
string roleName = roleListItem.Value;
string userName = TxtUserName.Text;
bool enableRole = roleListItem.Selected;

if (enableRole && !Roles.IsUserInRole(userName, roleName))
{
Roles.AddUserToRole(userName, roleName);
}
else if (!enableRole && Roles.IsUserInRole(userName, roleName))
{
Roles.RemoveUserFromRole(userName, roleName);
}
}
}

protected void LookupBtn_Click(object sender, EventArgs e)
{
PopulateRoleList(TxtUserName.Text);
UpdateBtn.Visible = true;
}

protected void UpdateBtn_Click(object sender, EventArgs e)
{
UpdateRolesFromList();
PopulateRoleList(TxtUserName.Text);
}

}
}

Here is the .aspx content.
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="UserAdmin.aspx.cs" Inherits="MyApp.UserAdmin" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">


<h3>Role Manager</h3>

<div>
Enter UserName:
<asp:TextBox ID="TxtUserName" runat="server"></asp:TextBox>

<asp:Button ID="LookupBtn" runat="server" Text="Search"
onclick="LookupBtn_Click" />

</div>

<div class="roleList">
<asp:CheckBoxList ID="RoleList" runat="server">
</asp:CheckBoxList>
</div>

<div>
<asp:button ID="UpdateBtn" text="Update" Visible="false" runat="server"
onclick="UpdateBtn_Click" />
</div>

</asp:Content>
Step 7: Authorizing Access based on Roles
The most basic way to implement security is at the page level. This can be done via code using the membership api and Server.Transfer() calls, but it can also be done via the web.config and is the recommended way.
Let’s make sure users in our Admin role have access to everything by default. To do this modify the web.config authorization tag so that it it allows the Admin role, and blocks everyone else. This example assume your admin group is called Admin.
<authorization>
<deny users="?"/>
<allow roles="Admin"/>
</authorization>

To control which pages (or directories) different roles have access to you will need to explicitly grant rights. You need to add a location tag for each page you want to grant rights to. The location tag needs to be added in  the configuration tag. You can specify the path to be a file or a directory. You can specify multiple roles using a commas, but do NOT use semi-colons. Semi-colons sort of work such that the first item in the list is read. You can also create different web.config in directories to control the rights for that directory only. This can help reduce web.config clutter. Here is an example entry for your web.config.
<configuration>
<location path="Review.aspx">
<system.web>
<authorization>
<allow roles="Reviewer,Process Manager"/>
<deny users="*"/>
</authorization>
</system.web>
</location>
Tips: In the web.config, when you are specifying users or roles, * and ? have special meaning.
* means all users
? means anonymous
It is also important to understand that the allow and deny tags are applied in the order they show in the parent tag. Order matters.

Wednesday, May 6, 2009

Binding a control property to a code-behind property or method.

In ASP.NET often it is easier to bind a code-behind property with a control property instead of using FindControl() method to find the control on the page and set it through code. It is also less bound to where the control you are trying to work with is on the page, which means less bugs and less maintenance issues.

Let’s assume you have a button on the page that you want to show or hide based on some logic you have on your code-behind class or maybe in a static class or singleton class you can access in one line of code. You can bind to it from the .aspx or .ascx page.

Here are some examples of how we could bind the code-behind property Test to the Visible property of the button. The Visible property will only be updated when the container of this button is databound. This is usually done with the DataBind() method.

<asp:LinkButton ID="LinkButton1"runat="server"Text="Submit"Visible="<%# Test %>"/>

<asp:LinkButton ID="LinkButton1" runat="server" Text="Submit" Visible=’<%# Test %>’/>

Do NOT do the following (use equal sign instead of number sign), because it will NOT work.

<asp:LinkButton ID="LinkButton1" runat="server" Text="Submit" Visible=’<%= Test %>’/>

<asp:LinkButton ID="LinkButton1" runat="server" Text="Submit" Visible="<%= Test %>"/>

Let’s assume in the code-behind you have a property called Test. It would look something like one of these, but with more logic.

protected bool Test { get { return true; } }
protected Boolean Test { get { return true; } }

The property can be protected or public, but cannot be private because private properties in the code-behind are not visible to the .aspx or .ascx page. It is important that the return type match what the control property is expecting

You can also do the same thing (same variations apply), but use a method instead of a property in the code-behind.

<asp:LinkButton ID="LinkButton1"runat="server"Text="Submit" Visible="<%# Test2() %>"/>

<asp:LinkButton ID="LinkButton1" runat="server" Text="Submit" Visible=’<%# Test2() %>’/>
public bool Test2() { return true; }

If you don’t want to create a bunch of properties, you can also use standard code also.

<asp:LinkButton ID="LinkButton1" runat="server" Text="Submit" Visible=’<%# MyMode != “ReadOnly” && OtherConditional %>’/>

 

Monday, May 4, 2009

ManualWorkflowScheduler causes DelayActivity to not execute

If you are using ASP.NET and Windows Workflow Foundation (WF) you should be using the ManualWorkflowScheduler instead of the default scheduler. Otherwise you will use 2 threads for ever web request instead of 1. This of course assumes you call WF on each request.

If you have a DelayActity in your workflow you may have noticed that it does not automatically execute like it does when you use the default scheduler. I see the behavior when I use a State Machine Workflow. I can't comment on the Sequential Workflow, other than it appears from what I have read that it is affected also. Any comments from anyone?

The reason I believe the behavior is different is because like the name of the ManualWorkflowScheduler implies, it is manual. This translates to the fact that the execution thread that ASP.NET is using is temporarily used for the workflow execute, and then is given back when it is done with it.

In order to restore the behavior to be like the default scheduler where Timers fire as expected, all you need to do is tell the ManualWorkflowScheduler service use active timers. The easiest way is to add the attribute to the line in the web.config where you include the ManualWorkflowScheduler to begin with.

The line in my web.config looks like the following after the change.

<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" UseActiveTimers="True"/>

The thing to take note of is the UseActiveTimers=”True”;

One important thing to note about setting UseActiveTimers=”True” is that yes time will run, but it is AFTER ASP.NET response is finished. The stuff after the timer is basically asynchronously executed instead of synchronously like the rest of the workflow. Remember, we are using the ManualWorkflowScheduler to change this behavior.  We have now, change the behavior back to what the default scheduler would have provided, but only for DelayActivities.

This means that anything that happens after the timer will require another server postback to get any changes that happened asynchronously. This may not be what you had in mind.

References

Using FindControl to find your control that is inside a FormView control.

The FindControl method is overloaded. So, be sure you are using the one you want to. The page object has one, but that will only find controls that are at the page level. This means that nothing in a container, or data control such as a FormView will be found by this method, unless you give it the path to the control.

Here is the simple example of a TextBox at the Page level.

<asp:TextBox ID="TextBox1"runat="server" />

To find the control from the C# code behind, you would do something like this.

TextBox tb = Page.FindControl("TextBox1") as TextBox;

Now for an example where the same TextBox is in the FormView control.

<asp:FormView ID="FormView1" runat="server">
    <ItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" />
    </ItemTemplate>
</asp:FormView>

Here is how you would find that same control if it was in a FormView control.

TextBox tb = Page.FindControl("FormView1$TextBox1") as TextBox;

The above line of code only works if the FormView1 control is directly on the page and NOT in some other container. Also, TextBox1 can’t be in a container that is inside of FormView1. If you have additional containers, delimit them with ‘$’.

I recommend saving yourself a bit of headache and use the overloaded FindControl that FormView provides. This way, the path is relative to FormView1 at least. Less dependency on where the controls are placed on the page is good in my book.

TextBox tb = FormView1.FindControl("TextBox1") as TextBox;

The FindControl is nice, but it is NOT recursive. However, this gives you the code to search recursively. There is a bit of a performance penalty (theoretically), so I can’t recommend it. Though, there are likely some cases where you don’t know where the control is on the page or if it is nested in a container, etc, so this kind of strategy might be necessary. Use with care. :)

Reference