Gmail addon

Today if you want to file email from Gmail to Content Manager you need to use the Email Link module.  The limitation of this is that there is not user interface so you are unable to fill in the Record data entry form.

Gmail add-ons

Gmail supports add-ons making it theoretically possible to create a client side solution which will allow the user to complete the data entry form at the time they file the email.  These add-ons are constrained in that they have to be able to function both in the Gmail web UI and also the mobile Gmail app.

Exploring the possibilities

The sample in this video demonstrates the possibilities and the limitations of a Gmail add-on.

The future

Given the current limitations of the UI components it is difficult to see how we could build a generic add-on.  Any customer or partner who wants to build a custom add-on is welcome to start with this sample, as always feel free to contact me if you either have suggestions for an 'off the shelf' Content Manager add-on or want to discuss the building of a bespoke solution.

Authenticate the Web Client with Azure AD

I have a few posts demonstrating on-premise ADFS with the CM Web Client but so far nothing with Azure Active Directory, today I rectify that.

The Video

Steps

The steps to configure the web client to use Azure AD are:

  • configure the site to use anonymous in IIS Admin,
  • create the Azure Ad App Registration, and
  • edit the Web Client web.config.

Sample Config

configSections

<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral,PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

appSettings

<add key="ida:FederationMetadataLocation" value="https://login.windows.net/[Your Tenant ID]/FederationMetadata/2007-06/FederationMetadata.xml" />

authorization and authentication

<authorization>
  <deny users="?" />
</authorization>
<authentication mode="None" />

system.identityModel

<system.identityModel>
<identityConfiguration>
  <audienceUris>
    <add value="[APP ID URI]" />
  </audienceUris>
  <securityTokenHandlers>
    <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  </securityTokenHandlers>
  <certificateValidation certificateValidationMode="None" />
  <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
    <authority name="https://sts.windows.net/[Tenant ID]/">   
      <validIssuers>
        <add name="https://sts.windows.net/[Tenant ID]/" />
      </validIssuers>
    </authority>
  </issuerNameRegistry>
</identityConfiguration>
</system.identityModel>
<system.identityModel.services>
<federationConfiguration>
  <cookieHandler requireSsl="true" />
  <wsFederation passiveRedirectEnabled="true" issuer="https://login.windows.net/[Domain]/wsfed" realm="[APP ID URI]" requireHttps="true" />
</federationConfiguration>
</system.identityModel.services>

SAML signout

Overview

In a previous post I show how to use Component Space to add SAML support to the Web Client, here I add a sign-out button.

Steps

useADFS == true

Set useADFS to true in hprmServiceAPI.config, for example:

<setup databaseId="J1"  searchAhead="false" advancedSearch="false" workpath="C:\HP Records Manager\ServiceAPIWorkpath\Uploads" useADFS="true"/>

Setup signing certificate

If you plan to sign your logout request then you will need a certificate, either encrypt the password or store the certificate in the Windows certificate store as described in this Component Space document.

Add keep-alive

To allow for notification when the user's session has expired add the keep alive loop.  To do this edit _Initialisation.cshtml (or _Shared.cshtml in later versions) and comment out the RMStayALive function, then add this new version:

var RMStayALive2 = function () {
    var makeRequest = function () {
        $.getJSON(HP.HPTRIM.TrimClient.getServiceAPIUrl() + "/Location/me", function (data, status, xhr) {
                
        }).fail(function () { 
            if (confirm("Your session has expired, do you wish to re-authenticate?")) {
                top.location = HPRMWebConfig.virtualDirectory;
            }
            
        });
    }
    setInterval(makeRequest, (60 * 1000));

}();

Video

The process above is shown in this video.

Custom Authentication

A couple of years I wrote a post describing Google authentication in the ServiceAPI.  This is an update of that.  The details are in the samples repo and here is a video of me applying the sample from the repo to a WebDrawer and Web Client instance.

Other authentication providers

Remember the repo contains a link the ServiceStack documentation which provides assistance for using (and creating) other authentication providers.

WebDrawer (and Web Client) auto filter search

While all users may configure default filters and have ACLs restricting access to certain records it can be desirable to configure an instance of WebDrawer to only return a subset of Records.  Maybe this is the planning portal and you only want it to return planning permission related Records?

Filter WebDrawer

The hptrim.config file contains a section called routeDefaults, there are two of these elements you will need to modify.  The first has the model 'Records', add a filter attribute as seen below.   This example filters out all .doc files.

<add
    name="Record"
    model="Records"
    template="WDRecordList"
    filter="not extension:doc"
    properties="RecordRecordType,RecordExtension,RecordTitle,RecordNumber,RecordIsElectronic"
/>

If you are using custom search forms you will also need to filter the FormSearch route.

<add
      name="FormSearch"
      model="FormSearch"
      filter="not extension:doc"
      properties="RecordRecordType,RecordExtension,RecordTitle,RecordNumber,RecordIsElectronic"
/>

Filter Web Client

The Web Client may be similarly filtered, edit hptrimServiceAPI.config and edit the Records route, as seen here:

<add
   name="Record"
   model="Records"
   filter="not extension:doc"
   sortBy="recRegisteredOn- unkUri"
   includePropertyDefs="true"
/>

Saved Searches

A slightly more elegant twist on the above approach is to not use the actual search string but to use a saved search.  The example below filters using the Saved Search named 'NotDocuments'.

<add
   name="Record"
   model="Records"
   template="WDRecordList"
   filter="saved:NotDocuments"
   properties="RecordRecordType,RecordExtension,RecordTitle,RecordNumber,RecordIsElectronic"
/>

Be Careful

It is best to not copy the XML snippets directly to your config file as the other attributes may be different in your version.  Just add the filter attribute.

Use 'local' when running on the Workgroup Server

One tip that is not as well known as it should be is the use of the reserved work 'local' to refer to a local Workgroup Server.  Any time a .Net SDK application is talking to a Workgroup Server on the same machine you should use 'local' for the Workgroup Server name, rather than using the machine name.  Why is this?  Because 'local' will use a named pipes connection to the Workgroup Server rather than going through the network layer.

Web Client / ServiceAPI Users read this!

Of course the Web Client, ServiceaAPI and WebDrawer are .Net SDK applications, so of you are connecting to a WorkGroup Server on the same machine use 'local'.  Note the MSI will probably not suggest this to you so you wil have to edit the hptrim.config manually.  Make it look something like this:

  <workgroupServer port="1137"
                     workPath="C:\HP Records Manager\ServiceAPIWorkpath"
                     name="local"
                     alternateName="MY_ALTERNATE_WGS"
                     alternatePort="1137"
  />

Hello, World!

.Net SDK Developers

If you are writing an application to run on the same machine as the Workgroup Server do something like this:

using (Database database = new Database())
{
    database.Id = "J1";
    database.WorkgroupServerName = "local";
    database.Connect();

    //do something


}

WebClient ADFS setup for Office Integrations

Overview

To configure the Web Client to use ADFS follow the same steps as those required for WebDrawer.  If in addition to using ADFS via a web browser you wish to use the Office addins with the Web Client / ServiceAPI you will need to do some additional configuration.  This is to support the OAuth authentication required by client applications.  Use the powershell command Add-ADFSClient on your ADFS server to create the ADFS client, described in this post.

Warning

Be careful with case.  Uris are case sensitive and if you get this wrong everything will be broken.

Things to look for in web.config

The location for the Integration path should allow anonymous access as the authorization is handled by a bearer token sent by the Office Addin.

<location path="Integration">
  <system.web>
    <authorization>
      <allow users="?" />
    </authorization>
  </system.web>
</location>

Also, inside the location path for "hprmserviceapi" ensure that the authorization is similarly set.

Near the top of web.config is the configuration element containing configSections, this should contain the two sections below.

<section 
  name="system.identityModel" 
   type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

<section 
    name="system.identityModel.services"
    type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

The appSetting will probably contain the ida:FederationMetadataLocation property if you are authenticating with the WebCleitn using ADFS.  It is not necessary for the Outlook integration wo work.

<add key="ida:FederationMetadataLocation" value="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml" />

Make sure the system.identityModel and system.identityModel.services have been added and configured, for example:

<system.identityModel>
  <identityConfiguration>
    <audienceUris>
      <add value="https://giri12012r2.trim.lab/HPEContentManager/" />
      <add value="https://giri12012r2/HPEContentManager/" />
    </audienceUris>
    <securityTokenHandlers>
      <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
      <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    </securityTokenHandlers>
    <certificateValidation certificateValidationMode="None" />
    <!--Element below commented by: ValidatingIssuerNameRegistry.WriteToConfg on: '24/06/2016 5:12:27 AM (UTC)'. Differences were found in the Metatdata from: 'https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml'.-->
    <!--<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry"><authority name="http://ADFS1.testteam.local/adfs/services/trust"><keys><add thumbprint="538B9CF4D293995C9406D7EA74C3F7353C5DD62A" /></keys><validIssuers><add name="http://ADFS1.testteam.local/adfs/services/trust" /></validIssuers></authority></issuerNameRegistry>-->
    <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
      <authority name="http://ADFS1.testteam.local/adfs/services/trust">
        <keys>
          <add thumbprint="1247E2A79A46AD1EB71BFEA4582C0FB9465EE9BE" />
        </keys>
        <validIssuers>
          <add name="http://ADFS1.testteam.local/adfs/services/trust" />
        </validIssuers>
      </authority>
    </issuerNameRegistry>
  </identityConfiguration>
</system.identityModel>
<system.identityModel.services>
  <federationConfiguration>
    <cookieHandler requireSsl="true" />
    <wsFederation passiveRedirectEnabled="true" issuer="https://adfs1.testteam.local/adfs/ls" realm="https://giri12012r2.trim.lab/HPEContentManager/"  requireHttps="true" />
  </federationConfiguration>
</system.identityModel.services>

I discuss this in more detail when describing ADFS setup for WebDrawer.  The key points to consider are:

  • replace the audienceUri with one that is valid id for your relying party trust
  • ensure that the authority name points to your ADFS server
  • replace the issuer and realm with your ADFS server name and your realm

Things to look for in hprmServiceAPI.config

The <setup /> element has an attibute called useADFS, which would better be called showADFSLogout.  Set this to true to show logout link in the user menum as seen here.

The authentication section gives the WebClient the information it requires to use OAuth authentication for the UI components.  The audience should match the identifier in your relying party trust and the federation endpoint is the same as that in ida:FederationMetadataLocation. 

<authentication allowAnonymous="false">
  <activeDirectory>
    <add 
         name="adfs" 
         audience="https://giri12012r2.trim.lab/HPEContentManager/" 
         metadataEndpoint="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml"/>
  </activeDirectory>
</authentication>

ADFS\config.xml file

Within the WebClient folder you will find an ADFS folder which contains a file called config.xml.  This file stores the information your Office clients need to connect to ADFS.  It must be available via anonymous access so that it can be fetched before authenticatin has happened.  Before you will have the values required for this file you will need to have configured and ADFS client as descirbed in this post. The four values in this file are:

ElementSource
clientAuthority The address of your adfs instance.
Example
https://adfs1.testteam.local/adfs
clientResourceUri The identifier of your relying party trust.
Example
https://MyServer/MyWebClient/
clientID The guid used in the client created by Add-ADFSClient.
clientReturnUri The RedirectUri specified when running Add-ADFSClient.

ADFS and the trailing slash

The problem

Those who have experimented with ADFS authentication for the web client (or Web Drawer) may have noticed that if the trailing slash is not included in the URL then the authentication fails.

The solution

The solution is to include a trailing slash.

The details

What is actually going on here is not, technically a bug, but a feature.  The slashes in the URL separate out the realms. So when the trailing slash is ommitted ADFS considers that rather than authenticating with the HPRMWebClient realm you are actually opening the page HPRMWebClient in the www.myserver.com realm.

A 'better' solution?

I have experimented with this a fair bit, one solution would be for us to re-design the Web Client so that, for example, the base URL was /HPRMWebClient/home.  This may be a solution for the future but what about the present.  IIS URL re-write had some promise but from what I can see the ADFS authentication module kicks in before the rewrite.

So...

This post on stackoverflow provided a handy shortcut to a solution, override the authentication module to append the trailing slash if it is missing.  I have added the code, or you can download the DLL. To use this:

  1. copy the DLL to the web client bin folder,
  2. edit your web.config modules section to look like the below, and
  3. ensure that the identifier in your ADFS relying party trust is the same as your web client base URL (e.g. https://myserver.com/HPRMWebClient/)
<modules>
  <add name="FixedWSFederationAuthenticationModule" type="FixedWSFederationAuthenticationModule.FixedWSFederationAuthenticationModule, FixedWSFederationAuthenticationModule" preCondition="managedHandler" />
  <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
</modules>

An update on the Web Client and Web Client classic

If you deal with the trailing slash using the technique above in both the web client and web client classic then you will have one more thing to do.  The web client enables linking to the web client classic, if the base address does not include the trailing slash this will fail.  To fix this:

  1. edit hprmServiceAPI.config in the web client folder,
  2. find the setup element, then
  3. change the webClientClassicUrl to include a trailing slash.

For example:

<setup 
    databaseId="T1" 
    webClientClassicUrl="/HPRMWebClientClassic/" 
    searchAhead="false" 
    advancedSearch="false" 
    workpath="C:\HP Records Manager\ServiceAPIWorkpath\Uploads"/>

New in 83 - Web Client add-on menu items

Hey, wouldn't it be nice to be able to add my own menu items in the Web Client?  Yes it would and now you can, if you are willing to dirty your hands with a little JavaScript.

The code

This is the add-on that I included in the CustomScripts folder

//It's a good idea to wrap your code in a function to avoid conflict
//overriding existing variables and other addon
var RMVersionListAddon = function () {
    var buttonCaption = "Show all versions";
    var versionListButton = new HP.HPTRIM.Addon.RecordAddonButton({
        caption: buttonCaption,
        clickHandler: function () {
            var cntx = this.context;
            $.getJSON(HPRMWebConfig.virtualDirectory + "HPRMServiceAPI/Record?pageSize=2&q=allVersions:" + cntx.Uri, function (data) {
                if (data.Results && data.Results.length > 1) {
                    // window.location.href = '?t=Record&q=allVersions:' + cntx.Uri;
                    $('#global-search-input').val('allVersions:' + cntx.Uri);

                    var trimApp = window['root'];
                    trimApp.trimGlobalSearchPanel.search('allVersions:' + cntx.Uri);
                } else {
                    alert("There are no versions for this Record.")
                }
            });           
        }
    });

    //Prerender should have the context object
    versionListButton.preRender = function () {
        var record = this.context;
        if (record.RecordRecordType.RecordTypeAllowVersions.Value !== true) {
            this.setVisible(false);
        }
    };

    HP.HPTRIM.Addon.CustomScriptManager.register(versionListButton);

}();

Debugging

Don't forget you can swith debugging on to get a better view of your code, like this:

<compilation debug="true" targetFramework="4.5" />


ADFS claim rule mapping

Map User-Principal-Name to Name

This is a for those of you who notice a gap in the current RM Web Client ADFS configuration documentation.  The documentation for the RM native client specifies the Relying Party claim rule should have the LDAP attribute User-Principle-Name mapped to the outgoing claim UPN.  The Web Client documentation contains a simple custom rule but does not specify exactly which claims are are required.  Unlike the native client the User-Principal-Name must be mapped to the outgoing claim Name, like this:

Some good news

In a an upcoming version we plan to support UPN for the web client, which will check UPN first and then Name, using whichever is sent by ADFS.

ADFS - Web Client Classic

In another post I have examined how to configure ADFS for WebDrawer, the process is very similar for Web Client Classic as can be seen in this video.

 

Additional steps

Below I list all the configuration that requires modification, in addition to this you also need to:

  • add a relying party trust in ADFS,
  • enable anonymous authentication in IIS, and
  • copy the file System.IdentityModel.Tokens.ValidatingIssuerNameRegistry.dll from your Web Client\bin folder to Web Client Classic\bin.

I look at both of these in the video above.

Config to copy, paste and modify

Overview

All of the modifications are made in the Web Client Classic web.config file.

configSections

Copy this into the configSections element.

<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<section name="system.identityModel.services" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

appSettings

Create an appSettings element and replace the URL to our ADFS server with your ADFS server.

<appSettings file="user.config">
    <add key="ida:FederationMetadataLocation" value="https://adfs1.testteam.local/FederationMetadata/2007-06/FederationMetadata.xml" />
</appSettings>

authorization and authentication

These go inside the system.Web element, make sure you remove the existing authentication element.

<authorization>
    <deny users="?" />
</authorization>
<authentication mode="None" />

Modules

Inside the system.webServer/modules element add these two modules.

<add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />

WIFHandler

Add this location inside the root (configuration) element.

<location path="WIFHandler">
  <system.web>
    <httpRuntime requestValidationMode="2.0" />
  </system.web>
</location>

webHttpBinding

Find your webHttpBinding element and modify it to look like this.

<webHttpBinding>
<binding name="hptrimBinding" sendTimeout="00.10:00">
    <security mode="Transport">
    <transport clientCredentialType="None"/>
  </security>
</binding>
</webHttpBinding>

system.identityModel

The XML below is taken directly from my web.config file so you will need to find all of the URLs pointing to my Web Client Classic and ADFS server and change them.  You will also need to use the powershell command  Get-AdfsCertificate to get your thumbprint.

<system.identityModel>
    <identityConfiguration>
        <audienceUris>
            <add value="https://rmcloudweb.testteam.local/HPRMWebClientClassic/" />
        </audienceUris>
        <securityTokenHandlers>
            <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
            <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
        </securityTokenHandlers>
        <certificateValidation certificateValidationMode="None" /> 
        <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
            <authority name="http://ADFS1.testteam.local/adfs/services/trust">
                <keys>
                    <add thumbprint="538B9CF4D293995C9406D7EA74C3F7353C5DD62A" />
                </keys>
                <validIssuers>
                    <add name="http://ADFS1.testteam.local/adfs/services/trust" />
                </validIssuers>
            </authority>
        </issuerNameRegistry>
    </identityConfiguration>
</system.identityModel>
<system.identityModel.services>
    <federationConfiguration>
        <cookieHandler requireSsl="true" />
        <wsFederation passiveRedirectEnabled="true" issuer="https://adfs1.testteam.local/adfs/ls" realm="https://rmcloudweb.testteam.local/HPRMWebClientClassic/" reply="https://rmcloudweb.testteam.local/HPRMWebClientClassic/WIFHandler" requireHttps="true" />
    </federationConfiguration>
</system.identityModel.services>

Paging issues in the web client

Background

Somewhere lost in the mists of time (HPRM 8.1.x or so) there was an issue in the HPRM web client which caused the second page of your search results to sometimes contain results that you had already seen in the first page.  We fixed this in a patch of course.

But wait!

Upgrading to the latest patch is not always achievable, maybe, just maybe there is a work-around? Turns out there is, try putting this inside the routeDefaults element of your hprmServiceAPI.config file:

<add
   name="Record"
   model="Records"
   sortBy="recRegisteredOn- unkUri"
   includePropertyDefs="true"
/>

What  is going on here? The sort by Uri puts the search results in a predictable and static sequence, the sort by date registered puts them in a useful sequence.

Not only a work-around

Given that the Web Client does not yet support custom sorting you may wish to use this technique to sort search results by something more meaningful than the default.