Monday, October 30, 2017

Integrating your App with Office 365 and Azure AD

Hey all, I recently built a project that integrated Azure AD users (read this as Pasport, Xbox Live, Live ID, Windows ID, Azure AD, or what ever you really want) with SharePoint 2016 on-premises.  Pretty straight forward task, build an OAuth2 application that converts to SAML 2.0 Passive Authentication, configure to authenticate with Azure AD and pass the info off to SharePoint.

Microsoft has some really great examples, that are built right into the App registration portal at https://apps.dev.microsoft.com.  My task saw me building a "Web Platform" app.  The how-to was straight forward, and I followed the steps in from the Guided Setup option when I registered my App.  I choose "Server-side Web App," with the ASP.Net Web App (OWIN) guided setup.


The walk through is really great and even points some optimizations to ensure all of your MVC app is secured, vs. only the sign-in and sign-out actions.  The example even provided a second controller (/claims) that helps you debug the results you get back from Microsoft.


The Pros


Probably the best thing about the sample is that it immediately shows authentication against the "Common" logon portal.  This is essential for the application we were building, as very few of the users logging in would be from our organization.  The other thing was that it just worked straight out of the box.

Everything was straight forward, copy code, add it to your app.  Git some OWN packages and link them in too.  Took me less than an hour to get everything up and running.

The Cons


So, Microsoft didn't follow their own best practices when putting the sample together with the default App registration.

It turns out that the whole system works great with the Office 365 out of the box security configuration.  But some of our clients opted to configure their tenant to disallow users from consenting to data sharing.  There's one setting that is either labeled differently in the Admin Portal and again in Azure AD configuration, or one directly changes the other (in the end they're a single setting).  In the Admin Portal it's Settings -> Services & Add-ins -> Integrated apps.  In Azure AD it's Azure Active Directory -> User Settings -> Enterprise Applications, Users can consent to apps accessing company data on their behalf (See below for screenshots).

When these are disabled, a Global Administrator must consent to the application to allow non-administrators the ability to log-on.  Without that special consent, the admins can logon but no-one else.








Try as we might we just couldn't shake the error:

You can't access this application
<FooBar App> needs permission to access resources in your organization that only an admin can grant. Please ask an admin to grant permission to this app before you can use it.

We followed every direction given to compose the admin authorization consent URL and consent to the application.  Finally, based on a single article on Stack Overflow, we learned that the "Dynamic Scope" must match the "Microsoft Graph Permissions" configured in you application.  When a Global Admin consents to an application, he/she is only consenting to those Permissions found in the App Registration, and not to a Scope passed in the consent URI.  Supposedly, it was a new change...

The Fix

I wasn't sure how to harmonize the Microsoft Graph Permissions with what every my client was sending.  To my knowledge, it wasn't sending anything.  Then I started digging.  In Step 2 of the guided setup (Titled Setup), you add a class derived from Object called StartupOWIN apparently looks for this class when the assembly is loaded.  I highlighted a line below.

public class Startup
{
    string clientId = ConfigurationManager.AppSettings["ClientId"];
    string redirectUri = ConfigurationManager.AppSettings["RedirectUri"];
    static string tenant = ConfigurationManager.AppSettings["Tenant"];
    string authority = String.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["Authority"], tenant);

    /// <summary>
    /// Configure OWIN to use OpenIdConnect
    /// </summary>
    /// <param name="app"></param>
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = authority,
                RedirectUri = redirectUri,
                PostLogoutRedirectUri = redirectUri,
                Scope = OpenIdConnectScopes.OpenIdProfile,
                ResponseType = OpenIdConnectResponseTypes.IdToken,
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters() 
                                                       { ValidateIssuer = false },
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthenticationFailed = OnAuthenticationFailed
                }
            }
        );
    }

    /// <summary>
    /// Handle failed authentication requests by redirecting the user to the home page with an error in the query string
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> context)
    {
        context.HandleResponse();
        context.Response.Redirect("/?errormessage=" + context.Exception.Message);
        return Task.FromResult(0);
    }
}

It turns out that the configuration was requesting openid and profile, but the default registration for my app granted access to User.Read.  As soon as I updated my registration to use only openid and profile, then used the Administrative Consent URL we were golden.


By the way, the Admin Consent URI is different for the V2.0 login endpoint, and has some limited documentation.  We used on like this:

https://login.microsoftonline.com/common/adminconsent?client_id=<APPID>&redirect_uri=<loginURL>

Just replace your App ID and Login redirect URL, and you'll be good to go!  For more reading on Admin Consent see:  https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-scopes

Wednesday, June 7, 2017

Email Enable a SharePoint Online List with Flow!

The Case

It seems like the use cases for SharePoint are a little wind blown.  Maybe it's because SharePoint Foundation came free with every Windows Server license since 2003 or maybe it's because SharePoint (ahem Enterprise) comes free with every Office 365 tenant.  Well anyway, one really good one is for a work support, or work ticket system.  You might say CRM is the way to go, except that it's really expensive, and in many cases way overblown, IMHO.

So, let's go with the work ticket system.  You want, proactive notification, you want status updates as the ticket is worked, you want work history, and probably want some sort of effort tracking.  What else do you say?.. You want to be able to send an email to a generic email box, that will kick off work to be done?

Well, all of this was built in in SharePoint on-premises versions, but with a move to Office 365, not only do you loose out on custom server side code, and the entire on-premises BI stack (Business Intelligence to keep you in the know), you also loose out on incoming email/List integration.  Not to mention you cant customize the outgoing email From or Reply-To for your site collections, web application, or farm.  You can't even specify them at a tenant level.  I'm not really sure why Microsoft choose not to do this, but there are some obscure references to mail being blocked in spam filters or something.  I'm not sure who really wanted email addresses spoofed, a good admin reply-to for support is usually what you want anyway.

Hey, we really wanted that functionality, and dang-it we want it in SharePoint Online, because ... CLOUD IS AWESOME NO MORE TOUCHY THE SERVIE NETZ!  As an aside, why does it seem that many arguments for cloud services are the same arguments to get all of the "wage slave IT riffraff" out of site and into the basement?  Well that's a whole other article

The Soln

Man, I like it when I use that weird abbreviation that comes from technical publications, makes me feel smart or whatever, go a way, it's my feeling don't be a buzz kill :)

OK, now that I got that out of my system, the solution that is both CHEEP, and INCLUDED in your O365 subscription...  Microsoft Flow!!!

Flow is pretty convenient, but what it lacks for in documentation it makes up in obscurity.  Right of the bat, it has a nice Web GUI, with all of those nice connecting lines and drag and drop type abilties, you don't even have to know anything about the PowerApps Workflow Definition Language (ok, that one's a slight stretch).  But really, the big ticket item is that it has built-in support for lots and lots of Cloud tech.

For instance you could send an email to a GMail then have the attachments saved to a Drop Box folder.  Or maybe when a new video is added to a Vimeo channel, you want to schedule an event on your calendar to remind you to watch it.  I bet you have always wanted to fetch a row from Informix whenever a issue has been assigned to you in GitHub.

Well, I never wanted those either, but I did want create an item in a SharePoint list when an email was sent to a service email box.  Well there's triggers and actions for that (check out the Services list, it reads like an SSAS who's-who)!

To the right is a screen capture of the Flow I built for a help-desk ticketing solution (sorry for the small size, click on it to zoom in, but you already knew that. Sorry, sorry, I think I'm turning Canadian).  We wanted two critical features.  First, when an email arrives, create a new SPO List Item.  Second, if a user replies to an email correctly, add any comments from the email to an appending text field on the same list item.  Here's how it works.

  1. Look for new emails in the email box.  We used rules in our Outlook 365 account to move them to a folder based on the alias used, thus we can support multiple lists with a single O365 account.
  2. When an email arrives, fetch the email received time stamp, and then do some edits on the html message body (more on that later).
  3. Decide if the email we found conforms to a "Reply-To" format or if it's something else.
  4. If it's something else:
    1. Create a new SPO List Item
    2. Build a path to store attachments based on the Item's ID and email timestamp.  Note: Flow doesn't support adding attachments to list items yet.  That's coming, but as of yesterday (June 6th, 2017) that feature is Started
    3. Save each of the attachments to a Document Library referenced in the path in the previous step
  5. Else when the email conforms to the correct Reply-To subject string
    1. Retrieve the SPO List Item ID from the email's Subject field
    2. Update the SPO List Item using the ID and the email's message body.  We specifically updated our "Appending Text" field, that you get when you use versioned list items
    3. Calculate a path to store attachments like above.
    4. Save each of the attachments to a Document Library
Check out this beauty.  A screen shot of the outcome.  Yes, that is me eating cake.  It was my birthday, OK!  I deserved it, so boo to your comments! :)


Some things to know:
  • The attachments created an interesting problem.  We can't yet add them to a List Item, and in-line pictures show up as broken empty image boxes.  JavaScript (jQuery) to the rescue... That comment above about editing the email's HTML was some prep to insert the timestamp into the <IMG> tags.  Then jQuery on the forms to fix it up (more on this in another post).
  • I created an email box specifically for this List integration.  I'm not sure what would happen if the email was marked read before Flow tried to process it.
  • I run the Flow process as the account that owns the email box.  This ensures that it would have access to Outlook 365.
  • I granted the account read access to the entire site, and contribute access to the SharePoint List.  With out read access to the site, the UI doesn't work quite right as it provides a drop down to select the list to interact with.
  • I could have created the flow as another account, but I would have had to create connections Outlook and SharePoint user id's in them.  It is much easier to keep it all under one account, IMHO.
  • Getting help for Microsoft Flow from the internet is crazy hard, unless you include PowerApps or Power Apps in your Google/Bing/Yahoo/DuckDuckGo query.  I'm new to Flow, so I suspect it was recently rebranded.  It just showed up in my Charms in February, but there's references to it on the PowerApps PowerUsers site from a couple of years ago

Sunday, July 31, 2016

Zen of Ducati Fuel System Maintenance

So if you you were on my Facebook feed last last summer you would have seen this picture.  Yes, that is my 1999 Ducati 900 Supersport, not running.  I was about half way through a 30 mile round trip when the motor started to cough and sputter.  My main ride had just spent about 14 days in the shop with work related to a check-engine light coming on that turned out to be a bunch of work related to the variable timing.  With that thought in the back of my head I feared the worst.  Needless to say, it was the 4th of July, and I had to call my wife so she could borrow her girlfriend's pickup and we hauled the Italian artwork home.

So, did I say that I was completely freaked out, thinking it was the fuel injection computer, clogged injectors, or maybe some set of sensors out that I wouldn't be able to diagnose, because 1999 Ducati 900 Supersports don't have and ODB-II port as far as I can tell.  Obviously, this is more on the hardware side of things... And not really computer related.

Well, I let it set, and then took another look.  I had just replaced the nut that holds the fuel level sensor (a fuel sender in Ducati speak) in.  It had been leaking slowly, and although my wife complained of a gasoline smell, I never really could locate the leak, well until I did, and then well, three nuts later I finally found one that screwed on, sorta.

First step is to drain the tank.  I got it mostly drained with the handy little pump I picked up for about $7.00 at a local Harbor Freight tool store (don't try the tube and mouth method, don't ask me why I know not to do that, just don't).  Next I removed the cap.  There's four (4) screws that hold it on.   The ring actually has six (6) plus one one more near the hinge, but it turns out the twelve, four, and seven o'clock screws are the only ones that actually attach.  the other three are just for beauty.

Next, the fuel inlet is secured in place by a bunch of tiny hex (allen) screws.  I don't know if you have to take them all the way out but I did, because I pulled and pulled but that stupid thing wouldn't come out.  So, since I'm a tool using kind of sentient being, I used the handle of my mallet to give it some extra leverage.  It popped right out.



There are two hoses attached to the fuel inlet.  They are attached to pipes that terminate outside the tank, and in a diagram they were labeled over pressure lines.  Handily, the left one attaches to the left port and the right one on the right.  So no need to label.  I fully detached them so I could get my hands inside and dig around.  Turns out that was a pretty good thing...  (The picture to the right is actually as I was reassembling.)

 Once I got the inlet off, I found that the rubber extender had fallen into the tank.  So I grabbed that and reinstalled it on the inlet.  The o-ring looked like it was breaking down, but after removing it and giving it a closer look it just had some crud that rubbed off.
Inside the tank was a whole other story.  The reason I wanted to get in there was because the fuel lines had rotted down to the reinforcing braid.  The guy at the parts store said that these should be replaced every two years, and well this bike is 17 years old.  Silly me.  In the bottom of the tank was all of the rubber bits.  The bad part was that when I scooped them up, they turned into a black paste.

The job was then to get all of the gunk out.  I ended up taking the fuel pump out too.  Turns out that was a good decision.  There's no way I'd be able to clamp on that two inch piece of hose between the pump and filter.    On the right is a picture of the reassembled pump and filter.  Notice how different the new hoses look?

I re-used the hose clamps that were there.  Ones I've sen before either have a worm screw to tighten them down, or pinch together and use spring action to hold everything together.  Not these.  Instead of pinching the clamps to release them, they pinch and lock together.  Overall they give a tighter lock, but like I said before, I'd never have been able to install them inside the tank.

They all went back in pretty easy, and it was just a matter of reconnecting the fuel dump lines and replacing the inlet.  The picture to the right shows the fuel pump remounted and the fuel filter connected to the line that heads out to the fuel rails.

I'll give a big a shout-out to Blue over at Ducati Moster Forum for good instructions on how to replace the black beauty ring that goes round the outside of the fuel inlet.  That method of partially installing the inlet and then fitting the ring really did the trick.

The picture below shows my 1988 Honda Hurricane (CBR-600F), in the background.  It's got it's own fuel problems.  One day.  For now the Ducati is running again.  It's back to starting with one revolution of the motor, which it hadn't done since about 2004 (I bought it in 2001).  My guess is that the fuel inlet will be off the tank again sometime in the future.


 ===

So it pays to pay attention at the gas pump.  I only put enough gas in the tank to cover the fuel pump, then I was off to the gas station.  With the rubber guide back in the tank it was hard to see the bottom of the thank when I was filling it, especially from the left side.  As I was leaning in to take a look, my left leg got a little close to the exhaust.  Well at least it will be a nice scar.