<CharlieDigital/> Programming, Politics, and uhh…pineapples

28Mar/15Off

5 Pitfalls to Software Project Failure

Posted by Charles Chen

Poorly Controlled Scope

Scope is enemy number 1; it is the amorphous blob that threatens to consume and grow until it is an uncontrollable monster, swallowing all of your carefully planned man hours.

Increases in scope are often the result of failure to manage the customer and expectations.  In any given project, there are only so many levers that can be used to control the successful delivery and it is up to the skilled project manager or client interface to toggle these levers of team size, timelines, requirements, and so on.

The worst is when growth of scope originates from within the team as it is a form of cancer that only causes teams to compromise on quality to meet timelines promised to the customer.  You see, when scope creep originates from the customer, there is a certain expectation that of course, costs will increase or timelines will need to be shifted.  After all, they are asking you to do more than was initially agreed upon.  But when new scope originates from the team itself, the customer will not readily accept this delay.

The cost of scope increases is often not well accounted for.  A change that takes a developer 2 days to make will cause ripples that force test teams to adjust their scripts, documentation teams to update their documents, and possibly trigger expensive regression testing.

Smart teams and leaders will understand that these can be controlled, in many cases, by simply creating a roadmap and understanding that desired features and capabilities that don't fit into existing timelines can be added to "v.next".

(Over) Reliance on Manual Effort

To a certain extent, software engineering requires raw manpower to execute large projects that require many hundreds of thousands of lines of code and lots of moving parts.

But within the lifecycle of a project, there are many activities that can be simplified by the use of automation.  Teams must judiciously balance the cost and effort of the automation versus the savings gained, but more often than not, even a little bit of automation is better than none.  It's crazy to think that it was once the case that all phone calls were manually routed between parties.

switchboard

Can you imagine if we never evolved past this?

Nowadays, the idea seems crazy!  Imagine if the billions of people on this Earth were to rely on the same processes to connect phone calls today!

Testing is a great example where failure to automate creates a bottleneck to progress.  It increases the cost of changes and bug fixes because it increases the cost of regression testing.  Make the regression testing virtually free and the cost of introducing changes (whether small scope creep for critical bug fixes) is decreased dramatically.

Technologies like Selenium WebDriver and Visual Studio's built in tooling make it possible to achieve significant gains in productivity when it comes to testing.  Don't let excuses hold your team back.

are-you-too-busy-to-improve2

Author's depiction of trying to convince test teams to automate

One skilled test automation engineer is worth her weight in gold!

Poor Communication and Collaboration

Strong and open channels of communication are critical for the success of projects, especially so when some or all of the resources are remote.

The flow of information and feedback from the customer to the design and engineering teams must be swift and clear so that expectations are known and any roadblocks can be communicated back.  Engineering teams will often have insights into the challenges and nuances of a customer's input and it can be dangerous to agree to timelines or make promises without clearly engaging the teams executing the implementation. Ideas that seem simple on paper or in concept can require massive engineering changes or sacrifices to achieve and not properly estimating this work is a common pitfall.

Demarco and Lister's Peopleware offers excellent insight into how to foster better communication and collaboration between teams.

Often, one of the simplest solutions is to simply talk to each other instead of using emails, chat messages, and worst of all: assumption ("Oh, I thought you already knew that"; we've all heard that one before!).  Get in front of a whiteboard and draw out ideas, deadlines, goals, and so on.  Go out to eat lunch together.  Plan team activities that engage everyone.  Make sure that everyone is on the same page on a professional level as well as a personal level.

Not Keeping Your Eyes on the Prize

It's easy for a team to get distracted and lose their focus on the goals of the project and the conditions of victory.

It is therefore critical that teams focus on a goal-oriented approach to the delivery of software projects.  This is a mind-set that scales up from daily scrums to weekly reviews and so on.  Even a short coffee break can be used to re-orient a wandering team member towards the goal posts.  Small, daily victories can help teams build momentum and continuously align towards the long term milestones.

It's important that individuals and teams know, at any given time, what is expected of them and what the priorities of the project are.  This allows individuals to make decisions autonomously and with little managerial overhead as they understand how to align themselves with the goals of the project and team.  Clear communication of goals allows any misunderstandings to surface early by pinning expectations to milestones -- be they simply daily ones, weekly ones, or project level milestones.

Teams and leaders that are poor at communication and collaboration will often lose their focus on the prize because there is a lack of understanding about shifting goals and priorities; there is a dependence on assumption instead of clearly aligning all parties to a set of well-defined conditions of victory.  These anti-leaders will focus on the tasks instead of the goals; it should be the other way around - focus on the goals and derive your tasks from them.

Unwillingness to Compromise

Teams must always be ready to compromise because this is the real world where timelines and successful delivery of usable software matters, but people also have families and life outside of work.  Unplanned circumstances arise that challenge the best laid blueprints.

If it is discovered that a feature will negatively impact performance of the system in the current architecture, compromise must be made on either the feature or the timelines to ensure that the desired capability can be delivered as usable software.

If unforeseen circumstances eat into the project timelines, compromise must be made to clearly redefine the scope and conditions of victory.

This is the real-world; man-hours are not unlimited and an unwillingness to compromise when necessary leads to poor quality as a team pushes to make up time.

In many cases, it is a bitter pill to swallow as it may mean telling a customer that a feature must be delayed or built into the next release, but I find that more often than not, openness and clearly communicating these issues as early as reasonable is productive and allows for rational decision making.

14Mar/15Off

Adding Support for Azure AD Login (O365) to MVC Apps

Posted by Charles Chen

I spent the day toying around with ASP.NET MVC 5 web applications and authentication.  I won't cover the step-by-step as there are plenty of blogs that have it covered.

It seems that online, most examples and tutorials show you either how to use your organizational Azure AD account or social identity providers but not both.

I wanted to be able to log in using Facebook, Google, and/or the organizational account I use to connect to Office 365.

This requires that you select Individual User Accounts when prompted to change the authentication mode (whereas most tutorials have you select "Organization Accounts"):

mvc-use-individual-account

This will give you the baseline needed to add the social login providers (more on that later).

To enable Windows Azure AD, you will need to first login into Azure and add an application to your default AD domain.  In the management portal:

  1. Click on ACTIVE DIRECTORY in the left nav
  2. Click the directory
  3. Click the APPLICATIONS link at the top
  4. Now at the bottom, click ADD to add a new application
  5. Select Add an application my organization is developing
  6. Enter an arbitrary name and click next
  7. Now in the App properties screen, you will need to enter your login URL (e.g. https://localhost:4465/Account/Login) and for the APP ID URI, you cannot use "localhost".  You should use your Azure account info like: https://myazure.onmicrosoft.com/MyApp.  The "MyApp" part is arbitrary, but the bolded text must match your directory identifier.

Most importantly, once you've created it, you need to click on the CONFIGURE link at the top and turn on the setting APPLICATION IS MULTI-TENANT:

mvc-multi-tenant

If you fail to turn this on, the logins are limited to the users that are in your Azure AD instance only; you will not be able to log on with accounts you use to connect to Office 365.  You'll get an error like this:

Error: AADSTS50020: User account ‘jdoe@myo365domain.com’ from external identity provider ‘https://sts.windows.net/1234567e-b123-4123-9112-912345678e51/’ is not supported for application ‘2123456f-b123-4123-9123-4123456789e5'. The account needs to be added as an external user in the tenant. Please sign out and sign in again with an Azure Active Directory user account.

An important note is that if you used "localhost" in step 7, the UI will not allow you to save the settings with an error "The App ID URI is not available. The App ID URI must be from a verified domain within your organization's directory."

Once you've enabled this, we're ready to make the code changes required.

First, you will need to install the OpenId package from nuget using the following command:

install-package microsoft.owin.security.openidconnect

Next, in the default Startup.Auth.cs file generated by the project template, you will need to add some additional code.

First, add this line:

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

Then, add this:

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
    ClientId = "138C1130-4B29-4101-9C84-D8E0D34D222A",
    Authority = "https://login.windows.net/common",
    PostLogoutRedirectUri = "https://localhost:44301/",                
    Description = new AuthenticationDescription
    {
        AuthenticationType = "OpenIdConnect",
        Caption = "Azure OpenId  Connect"
    },
    TokenValidationParameters = new TokenValidationParameters
    {
        // If you don't add this, you get IDX10205
        ValidateIssuer = false   
    }
});

There are two very important notes.  The first is that the Authority must have the /common path and not your Azure AD *.onmicrosoft.com path.

The second note is that you must add the TokenValidationParameters and set ValidateIssuer to false.

If you don't set this to false, you'll get the following 500 error after you successfully authenticate against Azure AD with your organizational O365 account:

IDX10205: Issuer validation failed. Issuer: ‘https://sts.windows.net/F92E09B4-DDD1-40A1-AE24-D51528361FEC/’. Did not match: validationParameters.ValidIssuer: ‘null’ or validationParameters.ValidIssuers: ‘https://sts.windows.net/{tenantid}/’

I think that this is a hack and to be honest, I'm not quite certain of the consequences of not validating the issuer, but it seems that there aren't many answers on the web for this scenario yet.  Looking at the source code where the exception originates, you'll see the method that generates it:

public static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
    if (validationParameters == null)
    {
        throw new ArgumentNullException("validationParameters");
    }
    
    if (!validationParameters.ValidateIssuer)
    {
        return issuer;
    }
    
    if (string.IsNullOrWhiteSpace(issuer))
    {
        throw new SecurityTokenInvalidIssuerException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10211));
    }
    
    // Throw if all possible places to validate against are null or empty
    if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) && (validationParameters.ValidIssuers == null))
    {
        throw new SecurityTokenInvalidIssuerException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10204));
    }
    
    if (string.Equals(validationParameters.ValidIssuer, issuer, StringComparison.Ordinal))
    {
        return issuer;
    }
    
    if (null != validationParameters.ValidIssuers)
    {
        foreach (string str in validationParameters.ValidIssuers)
        {
            if (string.Equals(str, issuer, StringComparison.Ordinal))
            {
                return issuer;
            }
        }
    }
    
    throw new SecurityTokenInvalidIssuerException(
        string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10205, issuer, validationParameters.ValidIssuer ?? "null", Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)));
}

We're simply short circuiting the process.  It's clear that there is no matching issuer, but it's not quite clear to me yet where/how to configure that.

So what about the other social IdP's?  It's important to note that for Google, not only do you have to create a new client ID in the Google Developer Console, but you also need to enable the Google+ API:

mvc-google-api

You'll just get a bunch of useless error messages if you don't enable the API.

If you manage to get it all working, you should see the following options in the login screen:

mvc-azure

And when you click it, you should be able to log in using the same organizational credentials that you use to connect to Office 365:

mvc-login-azure

Filed under: .Net, MVC 1 Comment
   
  • vocal
  • trap
  • trance
  • techno
  • symphonic-rock
  • spain
  • soundtrack
  • soul
  • singer
  • score
  • rock
  • rnb
  • reggaeton
  • reggae
  • rap
  • punk
  • progressive
  • post-grunge
  • pop
  • other
  • new-audio
  • metalcore
  • lounge
  • latino
  • jazz
  • instrumental
  • indie
  • house
  • hip-hop
  • heavy-metal
  • hard-rock
  • funk
  • folk
  • electronic
  • dubstep
  • drum-and-bass
  • downtempo
  • deep-house
  • dance
  • country
  • club-house
  • classical
  • classic-rock
  • chillout
  • breakbeat
  • blues
  • ambient
  • alternative-rock