Extened service behavior to check authorization

2009 October 2
by Frank Mao

I wish WCF can provide something like this:

instead of

[PrincipalPermission(SecurityAction.Demand, Role ="Administrators")]

We can use

[PrincipalPermission(SecurityAction.Demand, RoleProvider = typeof(MyAuthorizationChecker)]

So developers won’t care if security requirement changed.

I posted before about explicitly calling

permissionSet.Demand()

right in service method, it works, but it would be nice to use service behavior attribute to make code looks cleaner.

Extending WCF service is not that hard, thanks to this post.

    public class CheckAuthorizationBehaviorAttribute : Attribute, IOperationBehavior
    {
        public string MethodName { get; set; }

        public void Validate(OperationDescription operationDescription)
        {
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new AuthorizationChecker(dispatchOperation.Invoker
                , MethodName);
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {

        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {

        }
    }

    public class AuthorizationChecker : IOperationInvoker
    {
        private readonly IOperationInvoker _invoker;
        private readonly string _methodName;

        public AuthorizationChecker(IOperationInvoker invoker, string methodName)
        {
            _invoker = invoker;
            _methodName = methodName;
        }

        public object[] AllocateInputs()
        {
            return _invoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
             Console.WriteLine("Checking permission on methodname {0}.{1}", instance.ToString(),_methodName);

            if (!.....(my authorization logic here ))
                throw new FaultException<SecurityAccessDeniedException>(new SecurityAccessDeniedException("No access"))
                ;

            string value;
            value = (string)_invoker.Invoke(instance, inputs, out outputs);
            return value;

        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return _invoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return _invoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get { return _invoker.IsSynchronous; }
        }
    }

Then my service contract is just like this:

        [ServiceContract]
        public interface ITestService
        {
            [CheckAuthorizationBehavior(MethodName = "PingWithUserName")]
            [OperationContract]
            void Ping(string username);

I was having difficult to get the method name inside invoker, the methodname is hide in the private syncInvoker class, so I had to add the public attribute to my behavior. Later I found out this might be a better idea, because what if the method is an overload one?

Still, it would be nice to set the default method name to the one we just attached. Hope I can figure this out soon.

maximum string content length quota (8192) has been exceeded

2009 October 1
by Frank Mao

This should be a simple configuration trick, just do it, increasing client side app.config.

            var binding = new WSHttpBinding();
            binding.MaxReceivedMessageSize = 2147483647;
            XmlDictionaryReaderQuotas readerQuotas = XmlDictionaryReaderQuotas.Max;

            binding.ReaderQuotas = readerQuotas;
            channelFactory = new ChannelFactory<IRequestManagementService>(
                binding,
                new EndpointAddress(_url));

             var _proxy = channelFactory.CreateChannel();

But this isn’t enough, you need to change the same setting on serverside as well. Unfortunately, the default web.config auto-generated by VS doesn’t have binding section at all, so you have to add this by your own. Here is an example:

  <system.serviceModel>
    <services>
      <service behaviorConfiguration="..."
        name="..."
               >
        <endpoint address="" binding="wsHttpBinding"
                  bindingConfiguration="maxStringBinding"
                  contract="I...Service">

          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="soap" binding="basicHttpBinding" contract="I...Service" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>...        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <wsHttpBinding>
        <binding
         name="maxStringBinding"
         maxReceivedMessageSize="2147483647">
          <readerQuotas
            maxDepth="2147483647"
            maxStringContentLength="2147483647"
            maxArrayLength="2147483647"
            maxBytesPerRead="2147483647"
            maxNameTableCharCount="2147483647" />
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>

Use Dos command to pull/deploy version from cc.net

2009 September 24
by Frank Mao

When using Cruisecontrol.net + EAserver, we want to use pull instead of push style to deploy packages upto EAServer. Because the Cruisecontrol stores all the build result into the artifacts folder, what we need is a dos command can pull the specific version then deploy to EAServer. Here is the command I created:


@echo off
set artifacts_dir=\\ccnet\Work_Dir\Artifacts\my_project
cls
echo *****************************************
echo Please type in the version to deploy:
echo *****************************************
set /p version_number=
echo *****************************************
echo Starting to deploy %version_number%
echo *****************************************
if  "%version_number%"=="" goto help

pause

call jagtool -local delete Package:MyPackage
call jagtool -local deploy -type jagjar -jagjartype Package  %artifacts_dir%\%version_number%\jar\MyPackage.jar

pause

exit /b

:help
echo You need to specify the version to deploy
pause

A possible way to upgrade PB to DotNet

2009 September 23
by Frank Mao

EAserver acts as a client to consume WCF SOAP WebService, wrap it in WebDatawindow accessed by JSP web user.

Powerbuilder richclient can also directly consume WCF SOAP webservice, or access the EAF component which calls WebService.

Hybrid_app

The PB we are using 11.2 still have some limitation on webservice datawindow:

  1. Doesn’t support long type parameter, we had to change it to string.
  2. wsdl is wrapped in datawindow is not configurable, might be better if can change it through ini or dw property.

When deploying webservice datawindwo upto EAServer, should copy those auto-gened dlls to EAServer/bin folder, not /dll.

How to switch wsdl in dw?

  1. dw.Describe/Modify or dw.Create(syntax) won’t work for EAF/component, it keeps killing EAServer.
  2. Re-compile the auto-gened temp proxy cs file, like this post did. The interesting part is, the proxy’s ctor is already trying to read default configuration from some ini/config file:
    public RequestManagementService() {
                string urlSetting = System.Configuration.ConfigurationManager.AppSettings["EndpointURL"];
                if ((urlSetting != null)) {
                    this.Url = urlSetting;
                }
                else {
                    this.Url = "http://appdev01/RequestManagement/RequestService.svc/soap";
                }
            }
    

    My question is, can’t we create a config file to intercept this ctor reading?

    
    <configuration>
         <appSettings>
              <add key="EndpointURL" value="http://appdev02/RequestManagement/RequestService.svc/soap" />
         </appSettings>
    </configuration>
    

    Yes, that’s the place you can control to switch wsdl, not the one shown in dw syntax. In fact, if you change the wsdl in dw to an invalid one, it still works!

    How to control it? Create a config file named as your_app.exe.config, paste the configuration content in it. That’s it.
    Thanks to this post helping me out by showing how to get the current configuration file path: AppDomain.CurrentDomain.SetupInformation.ConfigurationFile

I’m using PBUnit, so the config file for testing is called pbunit.exe.config, when I deployed it upto EAserver, the config file should rename to jagsrv.exe.config.

If the whole EAServer is only using one webservice, this solution works; if you have multiple webservice wsdl to consume, you have to add more config settings by re-compiling this temp proxy file in /TmpWebService folder.

How to catch WCF error in PB?

We spent quite a lot of time on this, PB developer should catch RunTimeError instead of Exception type in their proxy calls.

In case you got “StringBuilder” error in java

2009 September 18
by Frank Mao

This broken upgrade drove us crazy, make sure you set this in your eclipse.

eclipse_14

WPF ListView Control

2009 September 11
tags: ,
by Frank Mao

We build framework, you build application.

This should be the motto for Microsoft, and any framework provider. But thing is different in WPF world, try google how many people are working so hard to make ListView header click sortable.

I don’t like code behind, the classic event hook up isn’t a clean solution at all. There are a few smart guys using attached property to implement this sort feature, xaml can be much simpler, and we can slowly build our own WPF framewrok extension. (?)

The idea can be found from this post, but the attached source code are missing, this post uses same technology with full version of source code. In fact, I think Thomas’ solution is better, he is using a technique called Adorner, which is cleaner than the other one using switching template. The only part I don’t like is the extra GridViewSort.PropertyName in his xmal to set the sorting property name. Why not just use the native binding name? like this:

propertyName = ((System.Windows.Data.Binding)headerClicked.Column.DisplayMemberBinding).Path.Path;

That’s for click-header-sorting.

How about double-click to trigger event/command in view model? Check this post.

Is there an open source project somewhere called WPF contrib? Yes, check out the WPF tool kit on codeplex, at least the click-header-sorting is supported, including some other fancy feature, such as the alternate row color.

No configuration Wcf host and client

2009 August 21
by Frank Mao

Using Exception in Wcf should be very caucious, as I blogged before, wcf exception should be marked as Serializable, and implement all 4 ctor from base exception class. An unit-test for each exception might be overkill, but it’s worth to try.

In-proc wcf host is neat for this purpose, by using channel Factory, client side doesn’t need configuration either.

    [TestFixture]
    public class MyDataNotFoundExceptionSpecs
    {
        private readonly ServiceHost _host;
        private const string _url = "net.tcp://localhost:9000/TestException";

        public MyDataNotFoundExceptionSpecs()
        {
            _host = new ServiceHost(typeof(TestService));

            _host.AddServiceEndpoint(typeof(ITestService),
                new NetTcpBinding(), _url);
            _host.Open();
            Console.WriteLine("wcf service started.");
        }

        ~MyDataNotFoundExceptionSpecs()
        {
            _host.Close();
        }
            private ChannelFactory<ITestService> channelFactory;
            [SetUp]
            public void SetupChannelFacotry()
            {
                channelFactory = new ChannelFactory<ITestService>(
                   new NetTcpBinding(),
                   new EndpointAddress(_url));
            }
            [TearDown]
            public void CleanUpChannelFacotry()
            {
                    channelFactory.Abort();
            }
        [Test]
        [ExpectedException(typeof(FaultException<MyDataNotFoundException>))]
        public void should_passed_to_client()
        {
            ITestService proxy = channelFactory.CreateChannel();

            proxy.Ping("Frank");

          }

    }

    public class TestService : ITestService
    {
        public void Ping(string user)
        {
            throw new FaultException<MyDataNotFoundException>(new MyDataNotFoundException());
        }
    }

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        [FaultContract(typeof(MyDataNotFoundException))]
        void Ping(string user);
    }

Ruby project management

2009 August 19
tags:
by Frank Mao

I was coding ruby for the past few days, RubyMine 1.1 released means my free evaluation period finished. Because I just code ruby for fun,  $400+ doesn’t make sense to me. Had to switch back to the free NetBeans 5.7.1.

(I still use SciTe on my slow laptop as Ruby IDE, and it’s a very handy lightweight editor, the only thing drove me crazy is switch between editing file can also change the current path of shell, I’m tired of adding and removing ‘test/’  before my rb file. I could use absolute path, but is there any better solution?)

Fortunately, NetBeans also supports subversion. At first I wasn’t happy with the way it forced me to create a default project structure, e.g., all source code supposed be in /lib/ folder, which obviously is an industry standard, but I missed that part.

Another good thing is that NetBeans created a standard Rakefile at the root folder. Simplely throw all my test rb files into /test/ folder, running “rake test”, I got my local build script!

Why didn’t RubyMine create this?

Mock in Ruby, I end up with using Mocha as my mock framework because I still on test:unitest mode. But gem install won’t bring the mocha rb into my ruby search path, had to manually copy mocha into my site_ruby folder, woala, it works.

Recently I found the book The Ruby Way 2nd edition is very good book, but I badly need a book can cover some ruby project management p&p.

Currently I’m the only developer to my ruby project, CI might be overkill, but someday I will need it. I’m supprised that CruiseControl.rb isn’t that popular in ruby community, so many choices, Hudson, cerberus, etc, including CruiseControl.net. I know this is an open/free teamcity ci server somewhere, can it support ruby project?

Recursively get ActiveDirectiory user groups

2009 August 6
tags:
by Frank Mao

The native System.DirectoryService can’t recursively retrieve groups, so I created this enchanced one.


    public class ActiveDirectoryService
    {
        private readonly string _gusetUserid;
        private readonly string _guestPasswd;
        private readonly DirectoryEntry _ldapEntry;

        public enum FilterType
        {
            CN,             // subGroup
            SAMAccountName  // user
        }

        public ActiveDirectoryService(string host, string baseDN, string guestUserId, string guestPasswd)
        {
            _gusetUserid = guestUserId;
            _guestPasswd = guestPasswd;

            _ldapEntry = new DirectoryEntry("LDAP://" + host + "/" + baseDN, _gusetUserid, _guestPasswd);
        }

        public IEnumerable<string> GetADUserGroups(string username)
        {
            return GetADUserGroups(username, true);
        }

        public IEnumerable<string> GetADUserGroups(string username, bool recursive)
        {
            return GetParentGroups(username, new List<string>(), FilterType.SAMAccountName, recursive);
        }

        private IList<string> GetParentGroups(string childName,
            IList<string> originalGroups,
            FilterType filterType,
            bool recursive)
        {
            var search = new DirectorySearcher(_ldapEntry)
                             {
                                 Filter = string.Format("({0}={1})", filterType, childName)
                             };
            search.PropertiesToLoad.Add("memberOf");
            SearchResult result = search.FindOne();

            if (result != null)
            {
                int propertyCount = result.Properties["memberOf"].Count;

                string dn;

                for (int i = 0; i < propertyCount; i++)
                {
                    dn = result.Properties["memberOf"][i].ToString();
                    int equalsIndex = dn.IndexOf("=", 1);
                    int commaIndex = dn.IndexOf(",", 1);
                    if (equalsIndex != -1)
                    {
                        string groupName = dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1);
                        if (!originalGroups.Contains(groupName))
                        {
                            originalGroups.Add(groupName);

                            if (recursive) originalGroups = GetParentGroups(groupName, originalGroups, FilterType.CN, true);
                        }
                    }
                }
            }

            return originalGroups;
        }
    }
namespace ActiveDirectoryServiceSpecs
{
    [TestFixture]
    public class ActiveDirectoryServiceSpecs
    {
        const string LDAP_HOST = "Dcxx";
        const string LDAP_BASE_DN = "DC=xxxx,DC=xxx,DC=xx";
        const string LDAP_GUEST_USERID = "guest";
        const string LDAP_GUEST_PASSWD = "hello";

        private ActiveDirectoryService _SUT;
        [SetUp]
        public void Setup()
        {
            _SUT = new ActiveDirectoryService(LDAP_HOST, LDAP_BASE_DN, LDAP_GUEST_USERID, LDAP_GUEST_PASSWD);
        }

        [Test]
        public void should_get_immediate_user_ad_groups()
        {
            IEnumerable<string> adUserGroups = _SUT.GetADUserGroups("fmao", false);
            Assert.That(adUserGroups.Count() > 0);

            Assert.IsFalse(adUserGroups.Contains("Information Systems"));

            foreach (var s in adUserGroups)
            {
                Console.WriteLine(s);
            }
        }   

        [Test]
        public void should_get_recursive_user_ad_groups()
        {
            IEnumerable<string> adUserGroups = _SUT.GetADUserGroups("fmao", true);
            Assert.That(adUserGroups.Count() > 20);

            Assert.That(adUserGroups.Contains("Information Systems"));
            foreach (var s in adUserGroups)
            {
                Console.WriteLine(s);
            }
        }
    }
}

Test cascade delete using sql query

2009 August 5
by Frank Mao

When first starting NH mapping, we just want to make sure the data is really being deleted, for cascaded child, some tricky code are needed to work around the strong relationship we created in domain entities. SqlQuery is one of those.


[Test]
 public void Should_delete_email()
 {
 // Retrieve
 Organization org = _repository.FetchById(8172);

 // Ensure org was retrieved.
 Assert.That(org, Is.Not.Null);

 var emailToAdd = new Email("Home Alternate", "x@y");
 org.AddEmail(emailToAdd);

 _repository.Update(org, org.LastModifiedDate);
Assert.That(emailToAdd.Id == 0);
_session.Flush();
Assert.That(emailToAdd.Id > 0);

 // Delete
 org.DeleteEmail(emailToAdd);
 _session.Flush();

 // Ensure email data is physically deleted
 ISQLQuery query = _session.CreateSQLQuery("select * from Stakeholder_Email_CV where stakeholder_email_id = " +
 emailToAdd.Id);
 Assert.That(query.List().Count == 0);

 }

Quite interesting testing code for NHibernate mapping.