Extracting files from an SVN repository backup

Extracting files from an SVN repository backup

Say you’ve got a backup of an old SVN repository. You want to grab a couple of files, but the backup looks like this:

C:myrepo_backup    format    README.txt    conf    dab    db    hooks    locks

You figure you probably need to run some fiddly import commands to restore it into a live SVN server so you can connect and browse it right? Wrong! SVN has a feature that makes this incredibly easy: the client can browse and check out directly from a backed up repository on your own local file system.

All you need to do is use the file:// URL scheme and point to your backup, for example:

svn checkout file:///C:/myrepo_backup myrepo

And yes if you’re using Windows, TortoiseSVN does support this — so you can very easily use Repo-browser to explore any SVN backup. Very handy!

Implementing access control in a tiered application

Implementing access control in a tiered application

Recently I have been working on a reasonably-large ERP system that supports around 2,000 users. The system has approximately forty access control groups to which individual users can belong. Each of these groups grants or restricts access to parts of the system. For example, a user may be allowed to edit the price of a product if they are in the SalesManagers group. If they are in the InventoryManagers group, they can edit the number of items in stock. If they are in the Admins group they can do everything.

public class User{        // A list of groups the user belongs to.        public List<string> MembershipGroups { get; };         // ... etc}

To handle all these access groups and their associated rules, a form page’s load method contained several screenfulls of business logic to determine which elements should be editable by the user and which should not.

Here’s a simple example of what it might look like on an EditProduct page:

public class EditProductPage : Page{        // Form elements.        private TextBox ProductName;        private TextBox Price;        private TextBox NumberInStock;         public void InitializeForm()        {                if (CurrentUser.MembershipGroups.Contains("Admins"))                {                        // Admins can change anything.                        ProductName.Enabled = true;                        Price.Enabled = true;                        NumberInStock.Enabled = true;                }                else if (CurrentUser.MembershipGroups.Contains("SalesManagers"))                {                        // SalesManagers can change the product's price.                        ProductName.Enabled = false;                        Price.Enabled = true;                        NumberInStock.Enabled = false;                }                else if (CurrentUser.MembershipGroups.Contains("InventoryManagers"))                {                        // InventoryManagers can change the inventory levels.                        ProductName.Enabled = false;                        Price.Enabled = false;                        NumberInStock.Enabled = true;                }                else                {                        // Regular users can't do anything.                        ProductName.Enabled = false;                        Price.Enabled = false;                        NumberInStock.Enabled = false;                }                 // ... other complex business logic        }}

Now this is all good and well, but what happens if someone is a member of SalesManagers and InventoryManagers? Or if we add rules related to editing certain types of products? What happens if we need to make another form? This code will grow exponentially and start to spaghetti out of control.

I believe there are two fundamental problems with this design. Firstly, it is not clear what the different access groups mean in the context of the form. For example, if you’re a member of the InventoryManagers group, which fields should be editable and which should be locked? These rules are probably defined clearly in a requirements document somewhere, but are difficult to understand from reading the source code.

The second problem is that these rules are implemented at each point they are consumed, i.e. in the presentation layer. Each form is free to access and interpret what each group means. This logic clearly don’t belong here: a form page should only know how to hide and show elements! On the other hand, it seems this responsibility doesn’t lies exclusively with the business layer either, as it relates to form-specific elements. So where should it go?

In a well-designed application, the relationship between group membership and access privileges would not be exposed to the UI. Instead, we should define a list of easily-understood permission flags that have direct meaning to consumers of our model. Each of these flags will explicitly define what a user can and cannot do. They must be as simple to understand as possible.

public class AccessPermissions{        // Correct: these flags can be applied directly to a form.        public bool CanEditName { get; };        public bool CanEditPrice { get; };        public bool CanEditNumberInStock { get; };         // Incorrect: this flag is too vague.        bool IsAdmin { get; };}

We can then add these permissions to our User class:

public class User{        // A list of groups the user belongs to.        public List<string> MembershipGroups { get; };         // The user's access permissions.        public AccessPermissions Permissions { get; };         // ... etc}

Setting these flags may involve complex and conditional business logic, which is now removed from the presentation layer. These simplified flags can then be directly used by the form’s Supervising Controller:

public class ProductEditFormController : ISupervisingController         // The passive view for the current form.        private IEditFormView Form;         // Set up form state.        public void InitializeForm()        {                AccessPermissions permissions = CurrentUser.Permissions;                 if (permissions.CanEditName)                        Form.ProductName.Editable = true;                 if (permissions.CanEditPrice)                        Form.Price.Editable = true;                 if (permissions.CanEditNumberInStock)                        Form.NumberInStock.Editable = true;                 // ... etc        }}

Privileges should be separated amongst multiple classes, specific to different areas of functionality. For example, you might provide a UserPermissions class for a user management form:

// Permissions related to user management.public class UserPermissions{        bool CanEditName;        bool CanEditMembershipGroups;        bool CanDeleteUser;}

By removing access level-related logic from the point of consumption and declaring them as explicit, immediately-usable flags, we can make it much easier for developers to consistently apply access control. Removing potentially complex business logic from the presentation layer cuts down on code duplication (and bugs). The overall quality of the system improves.

Windows Integrated Authentication in ScrewTurn Wiki

Windows Integrated Authentication in ScrewTurn Wiki

ScrewTurn Wiki is a simple, open source wiki engine that reproduces much of the functionality found in Mediawiki. ScrewTurn Wiki is powered by ASP.NET 2 and Microsoft SQL Server, which makes it ideal for Windows-centric corporate environments. Unfortunately, ScrewTurn Wiki has no out-of-the box support for Windows Integrated Authentication (and, according to the developer, never will).

Windows Integrated Authentication allows users’ credentials to be automatically passed to IIS. This allows the application (if it supports it) to use Windows login details – i.e., the same user name across an entire domain. The user is automatically and seamlessly logged in to the application – a huge benefit over having to remember multiple user names and passwords.

I have created a simple Windows Integrated Authentication implementation for ScrewTurn Wiki, similar to that used by Community Server (another ASP.NET/SQL Server-based collaboration tool). Note that ScrewTurn Wiki’s Plugin Framework does not cater for extra functionality at this level (yet). When a new session is spawned on the web server, the application searches for an account matching the user’s login name. If none is found, a new account is created automatically. Events are logged via the standard ScrewTurn Wiki event log.

Replace Session_Start in ScrewTurn Wiki’s Global.asax with the following:

void Session_Start(object sender, EventArgs e){    // Code that runs when a new session is started    ScrewTurn.Wiki.Users.OnlineUsers++;    ScrewTurn.Wiki.SessionFacade.Breadcrumbs = new ScrewTurn.Wiki.BreadcrumbsManager();    // Get identity name from web server.    string identityName = System.Web.HttpContext.Current.User.Identity.Name;    if (identityName == null || identityName.Length < 1)        throw new System.ApplicationException("Could not get current Windows user name." +            "Ensure Integrated Windows Authentication is enabled.");    string username;    // Strip domain prefix (e.g. "\COMPANY.NETDoeJ").    int domainDelimOffset = identityName.IndexOf("\");    if (domainDelimOffset > 0)        username = identityName.Substring(domainDelimOffset + 1);    else        username = identityName;    if (username.Length < 1)        throw new System.ApplicationException("Username " + identityName +            " is empty after domain stripped.");    // Locate user.    ScrewTurn.Wiki.PluginFramework.UserInfo user =            ScrewTurn.Wiki.Users.Instance.Find(username);    if (user == null)    {        // User not found, add a new one.        if (!ScrewTurn.Wiki.Users.Instance.AddUser(username, string.Empty,                string.Empty, true, false, null))            throw new System.ApplicationException("Could not add user ""                + username + "".");            // Get freshly-added user.            user = ScrewTurn.Wiki.Users.Instance.Find(username);            if (user == null)                throw new System.ApplicationException("Could not find user ""                    + username + "".");    }    // Set up session.    ScrewTurn.Wiki.SessionFacade.LoginKey =        ScrewTurn.Wiki.Tools.ComputeSecuredUsernameHash(user.Username);    ScrewTurn.Wiki.SessionFacade.Username = user.Username;    ScrewTurn.Wiki.SessionFacade.Admin = user.Admin;    // Log event.    ScrewTurn.Wiki.Log.LogEntry("User " +        ScrewTurn.Wiki.SessionFacade.Username +        " logged in through Windows Integrated Authentication",        ScrewTurn.Wiki.EntryType.General, "SYSTEM");}

For more information about Windows Integrated Authentication and ScrewTurn Wiki (including comments on this implementation), see this thread at the ScrewTurn Wiki forum.