Category: Uncategorized
Kerberos Success Story
- SharePoint 2007
- WCF
- SQL Server 2005
The key tool is setspn and that is an optional download which is gui-only and has no protection for the user.
The documentation is generally unclear and there is a lot of erroneous information out there. Some of the functionality required is only visible to some of the domian admins – so that if you don’t have this right then you have no way of knowing what to ask for.
Kerberos is what you need to use to solve the “two hop problem”. This happens when a service that is called by a client needs to impersonate the client to another service. NTLM will simply fail to authenticate and the call is made as the annonymous user.
The process of getting Kerberos to work is actually quite simple once you understand a few details.
The concept is that you must have a chain of trust running from the server all the way back to the client.
The client needs to be using Kerberos.
The server needs to be using Kerberos.
The identity of the process running on the server needs to be trusted for delegation if it wants to call another server.
You need to set up a SPN for the called server.
Solution to the Remote Desktop Clipboard bug
The bug is that the session will only remember the first item copied to the clipboard.
The previous solution was to log off and on again – which is not appropriate for nested virtual machines.
This solution consists of killing and restarting rdpclip.exe.
I am tempted to create a utility that does just that.
Using XML parameters in SQL Server 2000 and above
declare @text varchar(1000)
set @text = ‘<a firstname=”Joe” surname=”Blogs” >42</a>’
DECLARE @idoc int
select @text
declare @name varchar(10)
declare @name2 varchar(10)
— Create an internal representation of the XML document.
EXEC sp_xml_preparedocument @idoc OUTPUT, @text
— Execute a SELECT statement using OPENXML rowset provider.
SELECT @name=surname, @name2 = [firstname]
FROM OPENXML (@idoc, ‘/a’,1)
WITH ([firstname] varchar(10),
surname varchar(20))
select @name, @name2
SELECT age=[text]
FROM OPENXML (@idoc, ‘/a/text()’,2)
EXEC sp_xml_removedocument @idoc
Apply css to buttons
Useful website tip
log4net documentation
I recently moved an application from using the Microsoft Enterprise Application logging block to log4net.
This was caused by a problem with WCF and the EAB. There was an odd error that appeared to claim that different versions of the EAB were in use in different locations of the system (they were not!) however replacing it with the much simpler to configure log4net solved the problem. I did add a thin wrapper class so that my code is not tied to log4net.
log4net only requires that you deploy a single dll (compared to the three for EAB) and the configuartion entries are a quater the length (and don’t need a config tool to set up).
A better WF Rule Serializer
This is less than readable (can you imaging performing differences on this in version control – and you do keep your rules in version control don’t you?) and very wordy (100 lines per line of rule text).Here is a greatly improved version. It stores the rule in a format as close to that the user entered. It parses the code at serialization time. It uses the same code as the rule engine to perform the parsing, requiring reflection to get at the code…
=== TextRuleSetSerializer ===
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Workflow.Activities.Rules;
using System.Xml;
namespace PerfectStorm.Rules
{
/// <summary>
/// This is a far more compact serializer than the one supplied with the WF rules engine.
/// </summary>
public class TextRuleSetSerializer
{
/// <summary>
/// This is a proxy to the internal Parser class in the System.Workflow.Activities assembly.
/// </summary>
private class ParserProxy
{
private object _parser;
private Type _parserType;
/// <summary>
///
/// </summary>
/// <param name=”factType”></param>
public ParserProxy(Type factType)
{
//This is a bit dangerous – it may require a rewrite with each new version of the framework.
Assembly a = Assembly.Load(“System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”);
_parserType = a.GetType(“System.Workflow.Activities.Rules.Parser”);
Type[] constructorParam = { typeof(RuleValidation) };
RuleValidation validation = new RuleValidation(factType, null);
object[] callParams = { validation };
ConstructorInfo ci = _parserType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
null, constructorParam, null);
_parser = ci.Invoke(callParams);
}
/// <summary>
///
/// </summary>
/// <param name=”fragment”></param>
/// <returns></returns>
public RuleExpressionCondition ParseCondition(string fragment)
{
object[] parserParameters = { fragment };
return (RuleExpressionCondition)_parserType.InvokeMember(“ParseCondition”,
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null, _parser, parserParameters);
}
/// <summary>
///
/// </summary>
/// <param name=”statements”></param>
/// <returns></returns>
public List<RuleAction> ParseStatementList(string statements)
{
object[] parserParameters = { statements };
return (List<RuleAction>)_parserType.InvokeMember(“ParseStatementList”,
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
null, _parser, parserParameters);
}
}
/// <summary>
///
/// </summary>
/// <param name=”writer”></param>
/// <param name=”rs”></param>
public void Serialize(XmlWriter writer, RuleSet rs)
{
writer.WriteStartDocument();
writer.WriteStartElement(“RuleSet.Text”);
writer.WriteAttributeString(“Name”, rs.Name);
writer.WriteAttributeString(“Description”, rs.Description);
writer.WriteAttributeString(“ChainingBehavior”, rs.ChainingBehavior.ToString());
foreach (var rule in rs.Rules)
{
writer.WriteStartElement(“Rule”);
writer.WriteAttributeString(“Active”, rule.Active.ToString());
writer.WriteAttributeString(“Description”, rule.Description);
writer.WriteAttributeString(“Name”, rule.Name);
writer.WriteAttributeString(“Priority”, rule.Priority.ToString());
writer.WriteAttributeString(“ReevaluationBehavior”, rule.ReevaluationBehavior.ToString());
writer.WriteStartElement(“Condition”);
writer.WriteValue(rule.Condition.ToString());
writer.WriteEndElement();
writer.WriteStartElement(“ThenActions”);
foreach (var action in rule.ThenActions)
{
writer.WriteStartElement(“Action”);
writer.WriteValue(action.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteStartElement(“ElseActions”);
foreach (var action in rule.ElseActions)
{
writer.WriteStartElement(“Action”);
writer.WriteValue(action.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.Flush();
}
/// <summary>
///
/// </summary>
/// <typeparam name=”T”></typeparam>
/// <param name=”value”></param>
/// <returns></returns>
private T GetEnumValue<T>(string value)
{
if (!typeof(T).IsSubclassOf(typeof(Enum)))
throw new Exception(“Must be an Enum”);
int[] allValues = (int[])Enum.GetValues(typeof(T));
foreach (int i in allValues)
{
T newValue = (T)Enum.ToObject(typeof(T), i);
if (newValue.ToString() == value)
{
return newValue;
}
}
throw new Exception(value + ” is not of Type ” + typeof(T).Name);
}
/// <summary>
///
/// </summary>
/// <param name=”factType”></param>
/// <param name=”reader”></param>
/// <returns></returns>
public RuleSet Deserialize(Type factType, XmlReader reader)
{
RuleSet result = new RuleSet();
ParserProxy parser = new ParserProxy(factType);
//string allXml = reader.ReadOuterXml();
XmlDocument xDoc = new XmlDocument();
xDoc.Load(reader);
result.Name = xDoc.SelectSingleNode(“//RuleSet.Text/@Name”).InnerText;
result.Description = xDoc.SelectSingleNode(“//RuleSet.Text/@Description”).InnerText;
result.ChainingBehavior = GetEnumValue<RuleChainingBehavior>(xDoc.SelectSingleNode(“//RuleSet.Text/@ChainingBehavior”).InnerText);
foreach (XmlNode ruleNode in xDoc.SelectNodes(“//Rule”))
{
Rule rule = new Rule();
rule.Active = ruleNode.SelectSingleNode(“@Active”).InnerText == “true”;
rule.Description = ruleNode.SelectSingleNode(“@Description”).InnerText;
rule.Name = ruleNode.SelectSingleNode(“@Name”).InnerText;
rule.Priority = int.Parse(ruleNode.SelectSingleNode(“@Priority”).InnerText);
rule.ReevaluationBehavior = GetEnumValue<RuleReevaluationBehavior>(ruleNode.SelectSingleNode(“@ReevaluationBehavior”).InnerText);
// I have problem identifying rule condition…
rule.Condition = parser.ParseCondition(ruleNode.SelectSingleNode(“Condition”).InnerText);
foreach (XmlNode thenNode in ruleNode.SelectNodes(“ThenActions/Action”))
{
List<RuleAction> actions = parser.ParseStatementList(thenNode.InnerText);
foreach (RuleAction action in actions)
{
rule.ThenActions.Add(action);
}
}
foreach (XmlNode elseNode in ruleNode.SelectNodes(“ThenActions”))
{
List<RuleAction> actions = parser.ParseStatementList(elseNode.InnerText);
foreach (RuleAction action in actions)
{
rule.ElseActions.Add(action);
}
}
result.Rules.Add(rule);
}
return result;
}
}
}
=== Program ===
using System;
using System.Workflow.Activities.Rules;
using System.Workflow.Activities.Rules.Design;
using System.Xml;
namespace PerfectStorm.Rules
{
public class Order
{
private string _comment;
public string Comment { get { return _comment; } set { _comment = value; } }
public void setComment(string s) {this.Comment = s;}
}
class Program
{
// My key point here is that the workflow serializer creates illegible rules.
// I want to write something that stores the rules in something closer to the native form.
static void Main(string[] args)
{
// Create a RuleSet that waorks with Orders (just another .net Object)
RuleSetDialog ruleSetDialog = new RuleSetDialog(typeof(Order), null, null);
// Show the RuleSet Editor
ruleSetDialog.ShowDialog();
// Get the RuleSet after editing
RuleSet ruleSet = ruleSetDialog.RuleSet;
Order o = new Order();
RuleValidation rv = new RuleValidation(typeof(Order),null);
ruleSet.Validate(rv);
foreach (var err in rv.Errors)
{
Console.WriteLine(err.ErrorText);
}
ruleSet.Name = “Test”;
// Console.WriteLine(Pickler.Pickle<RuleSet>(ruleSet));
RuleEngine re = new RuleEngine(ruleSet, typeof(Order));
re.Execute(o);
Console.WriteLine(o.Comment);
TextRuleSetSerializer serializer = new TextRuleSetSerializer();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter rulesWriter = XmlWriter.Create(“test.rules”, settings);
serializer.Serialize(rulesWriter, ruleSet);
rulesWriter.Close();
// Deserialize from a .rules file.
XmlTextReader rulesReader = new XmlTextReader(“test.rules”);
TextRuleSetSerializer serializer2 = new TextRuleSetSerializer();
ruleSet = (RuleSet)serializer2.Deserialize(typeof(Order), rulesReader);
o.Comment = “”;
re.Execute(o);
Console.WriteLine(o.Comment);
}
}
}
[RuleSet.Text Name=’Text Ruleset’ Description=’Test Ruleset’ ChainingBehavior=’None’]
[Rule Active=’true’ Name=’R1′ Description=’Checks Name’ Priority=’1′ ReevaluationBehavior=’Never’ ]
[Condition]this.Name == null[/Condition]
[ThenActions]
[Action]this.ValidationMessages.Add(“”Name should not be empty””)[/Action]
[/ThenActions]
[/Rule]
[/RuleSet.Text]
“;
Encapsulation is an illusion in .NET
This allows any full trust code to call internal methods. I can see that this is very dangerous and useful.
This should allow me to access the Parser class from the WF foundation assembly.
Simpler WF Rules Serializer
Here is a version that creates cleaner XML:
public class TextRuleSetSerializer
{
public void Serialize(XmlWriter writer, RuleSet rs)
{
writer.WriteStartDocument();
writer.WriteStartElement(“RuleSet.Text”);
writer.WriteAttributeString(“Name”, rs.Name);
writer.WriteAttributeString(“Description”, rs.Description);
writer.WriteAttributeString(“ChainingBehaviour”, rs.ChainingBehavior.ToString());
foreach (var rule in rs.Rules)
{
writer.WriteStartElement(“Rule”);
writer.WriteAttributeString(“Active”, rule.Active.ToString());
writer.WriteAttributeString(“Description”, rule.Description);
writer.WriteAttributeString(“Name”, rule.Name);
writer.WriteAttributeString(“Priority”, rule.Priority.ToString());
writer.WriteAttributeString(“ReevaluationBehavior”, rule.ReevaluationBehavior.ToString());
writer.WriteStartElement(“Condition”);
writer.WriteAttributeString(“ConditionType”, rule.Condition.GetType().FullName);
writer.WriteAttributeString(“ConditionAssembly”, rule.Condition.GetType().AssemblyQualifiedName);
writer.WriteValue(rule.Condition.ToString());
writer.WriteEndElement();
writer.WriteStartElement(“ThenActions”);
foreach(var action in rule.ThenActions)
{
writer.WriteStartElement(“Action”);
writer.WriteAttributeString(“ActionType”, action.GetType().FullName);
writer.WriteAttributeString(“ActionAssembly”, action.GetType().AssemblyQualifiedName);
writer.WriteValue(action.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteStartElement(“ElseActions”);
foreach (var action in rule.ElseActions)
{
writer.WriteStartElement(“Action”);
writer.WriteAttributeString(“ActionType”, action.GetType().FullName);
writer.WriteAttributeString(“ActionAssembly”, action.GetType().AssemblyQualifiedName);
writer.WriteValue(action.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.Flush();
}
}
I am still working on the deserialization version. This is more difficult since the code that is used in the RuleSetDialog to compile the entered text into CodeDOM is an internal class. This leaves me with two options:
- Use reflector to create a non-internal version of the same code.
- Puppeteer the RuleSetDialog to perform the compilation for me.