Tuesday, November 10, 2015

Shibboleth-We Are Not Done Yet-Part 3

This will be quick: by applying the third solution described in the previous post the SP worked with no problem. The solution was based on ISA Server, Kentor.AuthServices and ASP.NET MVC. The ability to run sites on default HTTPS port made things simple. Linux rulez!

BUt...we are not done yet! Logout waits to be implemented.

VGhpcyB3aWxsIGJlIHF1aWNrOiBieSBhcHBseWluZyB0aGUgdGhpcmQgc29sdXRpb24gZGVzY3JpYmVkIGluIHRoZSBwcmV2aW91cyBwb3N0IHRoZSBTUCB3b3JrZWQgd2l0aCBubyBwcm9ibGVtLiBUaGUgc29sdXRpb24gd2FzIGJhc2VkIG9uIElTQSBTZXJ2ZXIsIEtlbnRvci5BdXRoU2VydmljZXMgYW5kIEFTUC5ORVQgTVZDLiBUaGUgYWJpbGl0eSB0byBydW4gc2l0ZXMgb24gZGVmYXVsdCBIVFRQUyBwb3J0IG1hZGUgdGhpbmdzIHNpbXBsZS4gTGludXggcnVsZXohDQoNCkJVdC4uLndlIGFyZSBub3QgZG9uZSB5ZXQhIExvZ291dCB3YWl0cyB0byBiZSBpbXBsZW1lbnRlZC4=

Sunday, November 8, 2015

Shibboleth-We Are Not Done Yet-Part 2

First tests were done in a LAN type environment; unfortunately life is not so simple: targeted solution should support users outside organization as well as Service Providers hosted in third party data centres. Since we do not want our IdP to be published directly on the Internet we have to use a reverse proxy which breaks our working solution. This might be particular (in terms of lack of out of the box solution-it might exist but I was unable to figure one) to ISA server but it looks (after google-ing a little bit) that a lot of people faced the same problem.

The Problem

Let's suppose our proxy has the public FQDN of proxy.mycompany.com while the internal FQDN of our Shibboleth IdP is idp.mycompany.local.

As a consequence external SP will have IdP metadata configured with URLs like https://proxy.mycompany.com/ipd/profile/SAML2/Redirect/SSO. When a request comes, the SAMLRequest will have AuthnRequest tag the attribute Destination set to this address while the proxy will forward (under default configuration) in relayed request's Host header the value of idp.mycompany.local:8443. This behaviour will lead to the failure of the request with this error message logged (sort of...) "SAML message intended destination endpoint 'https://proxy.mycompany.com/ipd/profile/SAML2/Redirect/SSO' did not match the recipient endpoint '{https://idp.mycompany.local:8443/ipd/profile/SAML2/Redirect/SSO'".

The error is generated in class org.opensaml.saml.common.binding.security.impl.ReceivedEndpointSecurityHandler, method checkEndpointURI.

We have two issues that make finding a solution non trivial:

  • We want to use HTTPS also for accessing our internal IdP (dropping this requirement will not actually lead to a working solution due to second issue)
  • Port translation from default 443 (not specified in external URL) to 8443

Usage of HTTPS for internal IdP

Initially Jetty was configured to use for SSL a certificate with common name of idp.mycompany.local with matches the URL configured in ISA publishing rule.

First solution to try was to change the default ISA behaviour in terms of Host header: check the option to forward the original header but another problem popped up: Jetty was refusing the connection since the Host header did not match any valid certificates in terms of common name.

Second solution tried was to generate a trusted (internally) certificate with common name of proxy.mycompany.com. Now ISA would reject it because it attempted a SSL connection on idp.mycompany.local and received a certificate on proxy.mycompany.com.

Third solution (I haven't tried because it would not solve the problem anyway due to port translation) would be:

  • The certificate installed on ISA should be wild card type
  • We have control on both external and internal DNS
  • Add an external DNS alias of idp.mycompany.com resolving to the ip of the external interface of ISA
  • Add an internal DNS alias of idp.mycompany.com resolving to the ip of the internal interface of ISA
  • Generate a trusted certificate for idp.mycompany.com and configure Jetty to use it
  • Add a publish rule for idp.mycompany.com that forwards requests to the IP of idp.mycompany.local and has filled in "This rule appliest to this published site:" idp.mycompany.com so that header Host will contain this value.

Now we should have circumvented our first problem but it will be of no help since the checkEndpointURI (through calling compareEndpointURIs, etc) will do a string comparison which will obviously fail due to the additional substring ":8443" in the actual receiverEndpoint.

8443 Port

Looking at Jetty documentation we quickly find four solutions to overcome this problem:

  • Start Jetty as root and change HTPPS port to 443; we do not want to do that (would we?)
  • Solutions based on ipchains/iptable which is not actually what we need: there will be an internal kernel routing from 443 to 8443 and at the end of the day we will get into the same problem as mentioned earlier
  • Configuring SetUID Jetty feature; this looks like solving our problem but needs some additional know how which I was not willing to acquire (native compiling, running the service as root...)

The Solution

The solution was to use Jetty rewrite handler capabilities (http://www.eclipse.org/jetty/documentation/current/rewrite-handler.html).

Unfortunately, none of the out of the box handlers provided the required functionality so I quickly wrote one.

Before going into into writing/deploying the custom handler we have to perform some prerequisites:

  • Check to see if org.eclipse.jetty.server.Request supports updating hots and port properties (since standard HttpServletRequest does not)
  • Check how to specify updated port so that it will not be included in receiverEndpoint string
  • Check how rewrite handler is activated

First create a maven project having jetty-rewrite dependency and download sources...

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-rewrite</artifactId>
            <version>9.3.5.v20151012</version>
        </dependency>

First item check passed since looks like we could use

        Request jettyRequest=(Request) request;
        jettyRequest.getMetaData().getURI().setAuthority(host, port);

Second item check was also quick: org.eclipse.jetty.util.appendSchemeHostPort shows that if specifying default ports (depending on schema) or 0 they will not be included.

Last item requires the following steps:

Add

--module=rewrite
etc/rewrite-rules.xml

in $JETTY_BASE/start.ini, where rewrite-rules.xml contains the actual rules and should be located in $JETTY_BASE/etc/rewrite-rules.xml:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">

<!-- =============================================================== -->
<!-- Configure the demos                                             -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">

  <!-- ============================================================= -->
  <!-- Add rewrite rules                                             -->
  <!-- ============================================================= -->
  <Ref refid="Rewrite">

      <!-- protect favicon handling -->
      <Call name="addRule">
        <Arg>
          <New class="ro.totalsoft.jetty.hostheaderrewriter.HostHeaderRewriter">
            <Set name="header">Host</Set>
            <Set name="host">proxy.mycompany.com</Set>
            <Set name="port">0</Set>
            <Set name="terminating">true</Set>
          </New>
        </Arg>
      </Call>
  </Ref>
</Configure>

In order to be selective (if we host also other applications in the same Jetty instance) we should also add the filtering condition

<Set name="headerValue">idp.mycompany.local:8443</Set>

The actual rewrite handler definition is located in $JETTY_HOME/etc/jetty-rewrite.xml as can be seen by inspection the $JETTY_HOME/module/rewrite.mod module definition file.

Remark: looking (as a newbie) in jetty-rewrite.xml might raise a question: what is oldhandler about? The naming is misleading, it should be something like firsthandler since it refers to the first handler in the chain of Jetty handlers. After Jetty applies the rewrite handler definition this will become the first handler in chain.

Now (supposing we have written our own header rule) we have to deploy it in the classpath known by Jetty. To do this we have to add the ext module:

--module=ext

--module=ext
The ext module will enable the lib/ext/*.jar logic.

If this module is activated, then all jar files found in the lib/ext/ paths will be automatically added to the Jetty Server Classpath.

Afterwards we can copy our jar to $JETTY_BASE/lib/ext/ and restart Jetty

**Important remark **: due to a bug (omission) in the logic that activates rules inside the rewrite handler if there is no rule defined a NullPointerException will be thrown in class org.eclipse.jetty.rewrite.handler.RuleContainer, method apply:

    protected String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        boolean original_set=_originalPathAttribute==null;
                
        for (Rule rule : _rules)
        {
            String applied=rule.matchAndApply(target,request, response);
            if (applied!=null)
            {       

If no rules are defined we will land here with _rules being null...

Epilogue

Ww are not done yet but have made significant progress having IdP side working. As we can assume, we will face same problem on SP side (we do not really want web sites exposed directly on the Internet).

So, next post about Kentor SSO. I would expect to apply the solution with external/internal DNS and using 443 default port (Windows/IIS looks more permissive in this area)

The Code

package com.mycompany.jetty.hostheaderrewriter;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.rewrite.handler.HeaderRule;
import org.eclipse.jetty.server.Request;

public class HostHeaderRewriter extends HeaderRule{
    String host;
    int port=0;

    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    
    @Override
    protected String apply(String target, String value, HttpServletRequest request, HttpServletResponse response) throws IOException {
        Request jettyRequest=(Request) request;
        jettyRequest.getMetaData().getURI().setAuthority(host, port);
        return target;
    }
}
Rmlyc3QgdGVzdHMgd2VyZSBkb25lIGluIGEgTEFOIHR5cGUgZW52aXJvbm1lbnQ7IHVuZm9ydHVuYXRlbHkgbGlmZSBpcyBub3Qgc28gc2ltcGxlOiB0YXJnZXRlZCBzb2x1dGlvbiBzaG91bGQgc3VwcG9ydCB1c2VycyBvdXRzaWRlIG9yZ2FuaXphdGlvbiBhcyB3ZWxsIGFzIFNlcnZpY2UgUHJvdmlkZXJzIGhvc3RlZCBpbiB0aGlyZCBwYXJ0eSBkYXRhIGNlbnRyZXMuIFNpbmNlIHdlIGRvIG5vdCB3YW50IG91ciBJZFAgdG8gYmUgcHVibGlzaGVkIGRpcmVjdGx5IG9uIHRoZSBJbnRlcm5ldCB3ZSBoYXZlIHRvIHVzZSBhIHJldmVyc2UgcHJveHkgd2hpY2ggYnJlYWtzIG91ciB3b3JraW5nIHNvbHV0aW9uLiBUaGlzIG1pZ2h0IGJlIHBhcnRpY3VsYXIgKGluIHRlcm1zIG9mIGxhY2sgb2Ygb3V0IG9mIHRoZSBib3ggc29sdXRpb24taXQgbWlnaHQgZXhpc3QgYnV0IEkgd2FzIHVuYWJsZSB0byBmaWd1cmUgb25lKSB0byBJU0Egc2VydmVyIGJ1dCBpdCBsb29rcyAoYWZ0ZXIgZ29vZ2xlLWluZyBhIGxpdHRsZSBiaXQpIHRoYXQgYSBsb3Qgb2YgcGVvcGxlIGZhY2VkIHRoZSBzYW1lIHByb2JsZW0uDQoNCiMgVGhlIFByb2JsZW0NCg0KTGV0J3Mgc3VwcG9zZSBvdXIgcHJveHkgaGFzIHRoZSBwdWJsaWMgRlFETiBvZiAqKnByb3h5Lm15Y29tcGFueS5jb20qKiB3aGlsZSB0aGUgaW50ZXJuYWwgRlFETiBvZiBvdXIgU2hpYmJvbGV0aCBJZFAgaXMgKippZHAubXljb21wYW55LmxvY2FsKiouIA0KDQpBcyBhIGNvbnNlcXVlbmNlIGV4dGVybmFsIFNQIHdpbGwgaGF2ZSBJZFAgbWV0YWRhdGEgY29uZmlndXJlZCB3aXRoIFVSTHMgbGlrZSBgaHR0cHM6Ly9wcm94eS5teWNvbXBhbnkuY29tL2lwZC9wcm9maWxlL1NBTUwyL1JlZGlyZWN0L1NTT2AuIFdoZW4gYSByZXF1ZXN0IGNvbWVzLCB0aGUgU0FNTFJlcXVlc3Qgd2lsbCBoYXZlIEF1dGhuUmVxdWVzdCB0YWcgdGhlIGF0dHJpYnV0ZSAqKkRlc3RpbmF0aW9uKiogc2V0IHRvIHRoaXMgYWRkcmVzcyB3aGlsZSB0aGUgcHJveHkgd2lsbCBmb3J3YXJkICh1bmRlciBkZWZhdWx0IGNvbmZpZ3VyYXRpb24pIGluIHJlbGF5ZWQgcmVxdWVzdCdzICoqSG9zdCoqIGhlYWRlciB0aGUgdmFsdWUgb2YgKippZHAubXljb21wYW55LmxvY2FsOjg0NDMqKi4gDQpUaGlzIGJlaGF2aW91ciB3aWxsIGxlYWQgdG8gdGhlIGZhaWx1cmUgb2YgdGhlIHJlcXVlc3Qgd2l0aCB0aGlzIGVycm9yIG1lc3NhZ2UgbG9nZ2VkIChzb3J0IG9mLi4uKSAqIlNBTUwgbWVzc2FnZSBpbnRlbmRlZCBkZXN0aW5hdGlvbiBlbmRwb2ludCAnaHR0cHM6Ly9wcm94eS5teWNvbXBhbnkuY29tL2lwZC9wcm9maWxlL1NBTUwyL1JlZGlyZWN0L1NTTycgZGlkIG5vdCBtYXRjaCB0aGUgcmVjaXBpZW50IGVuZHBvaW50ICd7aHR0cHM6Ly9pZHAubXljb21wYW55LmxvY2FsOjg0NDMvaXBkL3Byb2ZpbGUvU0FNTDIvUmVkaXJlY3QvU1NPJyIqLg0KDQpUaGUgZXJyb3IgaXMgZ2VuZXJhdGVkIGluIGNsYXNzIGBvcmcub3BlbnNhbWwuc2FtbC5jb21tb24uYmluZGluZy5zZWN1cml0eS5pbXBsLlJlY2VpdmVkRW5kcG9pbnRTZWN1cml0eUhhbmRsZXJgLCBtZXRob2QgYGNoZWNrRW5kcG9pbnRVUklgLg0KDQpXZSBoYXZlIHR3byBpc3N1ZXMgdGhhdCBtYWtlIGZpbmRpbmcgYSBzb2x1dGlvbiBub24gdHJpdmlhbDoNCg0KKiBXZSB3YW50IHRvIHVzZSBIVFRQUyBhbHNvIGZvciBhY2Nlc3Npbmcgb3VyIGludGVybmFsIElkUCAoZHJvcHBpbmcgdGhpcyByZXF1aXJlbWVudCB3aWxsIG5vdCBhY3R1YWxseSBsZWFkIHRvIGEgd29ya2luZyBzb2x1dGlvbiBkdWUgdG8gc2Vjb25kIGlzc3VlKQ0KKiBQb3J0IHRyYW5zbGF0aW9uIGZyb20gZGVmYXVsdCA0NDMgKG5vdCBzcGVjaWZpZWQgaW4gZXh0ZXJuYWwgVVJMKSB0byA4NDQzDQoNCiMjIFVzYWdlIG9mIEhUVFBTIGZvciBpbnRlcm5hbCBJZFANCg0KSW5pdGlhbGx5IEpldHR5IHdhcyBjb25maWd1cmVkIHRvIHVzZSBmb3IgU1NMIGEgY2VydGlmaWNhdGUgd2l0aCBjb21tb24gbmFtZSBvZiAqKmlkcC5teWNvbXBhbnkubG9jYWwqKiB3aXRoIG1hdGNoZXMgdGhlIFVSTCBjb25maWd1cmVkIGluIElTQSBwdWJsaXNoaW5nIHJ1bGUuDQoNCkZpcnN0IHNvbHV0aW9uIHRvIHRyeSB3YXMgdG8gY2hhbmdlIHRoZSBkZWZhdWx0IElTQSBiZWhhdmlvdXIgaW4gdGVybXMgb2YgKipIb3N0KiogaGVhZGVyOiBjaGVjayB0aGUgb3B0aW9uIHRvIGZvcndhcmQgdGhlIG9yaWdpbmFsIGhlYWRlciBidXQgYW5vdGhlciBwcm9ibGVtIHBvcHBlZCB1cDogSmV0dHkgd2FzIHJlZnVzaW5nIHRoZSBjb25uZWN0aW9uIHNpbmNlIHRoZSAqKkhvc3QqKiBoZWFkZXIgZGlkIG5vdCBtYXRjaCBhbnkgdmFsaWQgY2VydGlmaWNhdGVzIGluIHRlcm1zIG9mIGNvbW1vbiBuYW1lLg0KDQpTZWNvbmQgc29sdXRpb24gdHJpZWQgd2FzIHRvIGdlbmVyYXRlIGEgdHJ1c3RlZCAoaW50ZXJuYWxseSkgY2VydGlmaWNhdGUgd2l0aCBjb21tb24gbmFtZSBvZiAqKnByb3h5Lm15Y29tcGFueS5jb20qKi4gTm93IElTQSB3b3VsZCByZWplY3QgaXQgYmVjYXVzZSBpdCBhdHRlbXB0ZWQgYSBTU0wgY29ubmVjdGlvbiBvbiAqKmlkcC5teWNvbXBhbnkubG9jYWwqKiBhbmQgcmVjZWl2ZWQgYSBjZXJ0aWZpY2F0ZSBvbiAqKnByb3h5Lm15Y29tcGFueS5jb20qKi4NCg0KVGhpcmQgc29sdXRpb24gKEkgaGF2ZW4ndCB0cmllZCBiZWNhdXNlIGl0IHdvdWxkIG5vdCBzb2x2ZSB0aGUgcHJvYmxlbSBhbnl3YXkgZHVlIHRvIHBvcnQgdHJhbnNsYXRpb24pIHdvdWxkIGJlOg0KDQoqIFRoZSBjZXJ0aWZpY2F0ZSBpbnN0YWxsZWQgb24gSVNBIHNob3VsZCBiZSB3aWxkIGNhcmQgdHlwZQ0KKiBXZSBoYXZlIGNvbnRyb2wgb24gYm90aCBleHRlcm5hbCBhbmQgaW50ZXJuYWwgRE5TDQoqIEFkZCBhbiBleHRlcm5hbCBETlMgYWxpYXMgb2YgKippZHAubXljb21wYW55LmNvbSoqIHJlc29sdmluZyB0byB0aGUgaXAgb2YgdGhlIGV4dGVybmFsIGludGVyZmFjZSBvZiBJU0ENCiogQWRkIGFuIGludGVybmFsIEROUyBhbGlhcyBvZiAqKmlkcC5teWNvbXBhbnkuY29tKiogcmVzb2x2aW5nIHRvIHRoZSBpcCBvZiB0aGUgaW50ZXJuYWwgaW50ZXJmYWNlIG9mIElTQQ0KKiBHZW5lcmF0ZSBhIHRydXN0ZWQgY2VydGlmaWNhdGUgZm9yICoqaWRwLm15Y29tcGFueS5jb20qKiBhbmQgY29uZmlndXJlIEpldHR5IHRvIHVzZSBpdA0KKiBBZGQgYSBwdWJsaXNoIHJ1bGUgZm9yICoqaWRwLm15Y29tcGFueS5jb20qKiB0aGF0IGZvcndhcmRzIHJlcXVlc3RzIHRvIHRoZSBJUCBvZiAgKippZHAubXljb21wYW55LmxvY2FsKiogYW5kIGhhcyBmaWxsZWQgaW4gIlRoaXMgcnVsZSBhcHBsaWVzdCB0byB0aGlzIHB1Ymxpc2hlZCBzaXRlOiIgKippZHAubXljb21wYW55LmNvbSoqIHNvIHRoYXQgaGVhZGVyIEhvc3Qgd2lsbCBjb250YWluIHRoaXMgdmFsdWUuDQoNCk5vdyB3ZSBzaG91bGQgaGF2ZSBjaXJjdW12ZW50ZWQgb3VyIGZpcnN0IHByb2JsZW0gYnV0IGl0IHdpbGwgYmUgb2Ygbm8gaGVscCBzaW5jZSB0aGUgYGNoZWNrRW5kcG9pbnRVUklgICh0aHJvdWdoIGNhbGxpbmcgYGNvbXBhcmVFbmRwb2ludFVSSXNgLCBldGMpIHdpbGwgZG8gYSBzdHJpbmcgY29tcGFyaXNvbiB3aGljaCB3aWxsIG9idmlvdXNseSBmYWlsIGR1ZSB0byB0aGUgYWRkaXRpb25hbCBzdWJzdHJpbmcgIjo4NDQzIiBpbiB0aGUgYWN0dWFsIHJlY2VpdmVyRW5kcG9pbnQuDQoNCiMjIDg0NDMgUG9ydA0KDQpMb29raW5nIGF0IEpldHR5IGRvY3VtZW50YXRpb24gd2UgcXVpY2tseSBmaW5kIGZvdXIgc29sdXRpb25zIHRvIG92ZXJjb21lIHRoaXMgcHJvYmxlbToNCg0KKiBTdGFydCBKZXR0eSBhcyByb290IGFuZCBjaGFuZ2UgSFRQUFMgcG9ydCB0byA0NDM7IHdlIGRvIG5vdCB3YW50IHRvIGRvIHRoYXQgKHdvdWxkIHdlPykNCiogU29sdXRpb25zIGJhc2VkIG9uIGlwY2hhaW5zL2lwdGFibGUgd2hpY2ggaXMgbm90IGFjdHVhbGx5IHdoYXQgd2UgbmVlZDogdGhlcmUgd2lsbCBiZSBhbiBpbnRlcm5hbCBrZXJuZWwgcm91dGluZyBmcm9tIDQ0MyB0byA4NDQzIGFuZCBhdCB0aGUgZW5kIG9mIHRoZSBkYXkgd2Ugd2lsbCBnZXQgaW50byB0aGUgc2FtZSBwcm9ibGVtIGFzIG1lbnRpb25lZCBlYXJsaWVyDQoqIENvbmZpZ3VyaW5nIFNldFVJRCBKZXR0eSBmZWF0dXJlOyB0aGlzIGxvb2tzIGxpa2Ugc29sdmluZyBvdXIgcHJvYmxlbSBidXQgbmVlZHMgc29tZSBhZGRpdGlvbmFsIGtub3cgaG93IHdoaWNoIEkgd2FzIG5vdCB3aWxsaW5nIHRvIGFjcXVpcmUgKG5hdGl2ZSBjb21waWxpbmcsIHJ1bm5pbmcgdGhlIHNlcnZpY2UgYXMgcm9vdC4uLikNCg0KIyBUaGUgU29sdXRpb24NCg0KVGhlIHNvbHV0aW9uIHdhcyB0byB1c2UgSmV0dHkgcmV3cml0ZSBoYW5kbGVyIGNhcGFiaWxpdGllcyAoW2h0dHA6Ly93d3cuZWNsaXBzZS5vcmcvamV0dHkvZG9jdW1lbnRhdGlvbi9jdXJyZW50L3Jld3JpdGUtaGFuZGxlci5odG1sXShodHRwOi8vd3d3LmVjbGlwc2Uub3JnL2pldHR5L2RvY3VtZW50YXRpb24vY3VycmVudC9yZXdyaXRlLWhhbmRsZXIuaHRtbCkpLg0KDQpVbmZvcnR1bmF0ZWx5LCBub25lIG9mIHRoZSBvdXQgb2YgdGhlIGJveCBoYW5kbGVycyBwcm92aWRlZCB0aGUgcmVxdWlyZWQgZnVuY3Rpb25hbGl0eSBzbyBJIHF1aWNrbHkgd3JvdGUgb25lLg0KDQpCZWZvcmUgZ29pbmcgaW50byBpbnRvIHdyaXRpbmcvZGVwbG95aW5nIHRoZSBjdXN0b20gaGFuZGxlciB3ZSBoYXZlIHRvIHBlcmZvcm0gc29tZSBwcmVyZXF1aXNpdGVzOg0KDQoqIENoZWNrIHRvIHNlZSBpZiBgb3JnLmVjbGlwc2UuamV0dHkuc2VydmVyLlJlcXVlc3RgIHN1cHBvcnRzIHVwZGF0aW5nIGhvdHMgYW5kIHBvcnQgcHJvcGVydGllcyAoc2luY2Ugc3RhbmRhcmQgSHR0cFNlcnZsZXRSZXF1ZXN0IGRvZXMgbm90KQ0KKiBDaGVjayBob3cgdG8gc3BlY2lmeSB1cGRhdGVkIHBvcnQgc28gdGhhdCBpdCB3aWxsIG5vdCBiZSBpbmNsdWRlZCBpbiByZWNlaXZlckVuZHBvaW50IHN0cmluZw0KKiBDaGVjayBob3cgcmV3cml0ZSBoYW5kbGVyIGlzIGFjdGl2YXRlZA0KDQogDQpGaXJzdCBjcmVhdGUgYSBtYXZlbiBwcm9qZWN0IGhhdmluZyBqZXR0eS1yZXdyaXRlIGRlcGVuZGVuY3kgYW5kIGRvd25sb2FkIHNvdXJjZXMuLi4NCg0KYGBgeG1sDQogICAgICAgIDxkZXBlbmRlbmN5Pg0KICAgICAgICAgICAgPGdyb3VwSWQ+b3JnLmVjbGlwc2UuamV0dHk8L2dyb3VwSWQ+DQogICAgICAgICAgICA8YXJ0aWZhY3RJZD5qZXR0eS1yZXdyaXRlPC9hcnRpZmFjdElkPg0KICAgICAgICAgICAgPHZlcnNpb24+OS4zLjUudjIwMTUxMDEyPC92ZXJzaW9uPg0KICAgICAgICA8L2RlcGVuZGVuY3k+DQpgYGAgDQpGaXJzdCBpdGVtIGNoZWNrIHBhc3NlZCBzaW5jZSBsb29rcyBsaWtlIHdlIGNvdWxkIHVzZSANCmBgYGphdmENCiAgICAgICAgUmVxdWVzdCBqZXR0eVJlcXVlc3Q9KFJlcXVlc3QpIHJlcXVlc3Q7DQogICAgICAgIGpldHR5UmVxdWVzdC5nZXRNZXRhRGF0YSgpLmdldFVSSSgpLnNldEF1dGhvcml0eShob3N0LCBwb3J0KTsNCg0KYGBgDQpTZWNvbmQgaXRlbSBjaGVjayB3YXMgYWxzbyBxdWljazogYG9yZy5lY2xpcHNlLmpldHR5LnV0aWwuYXBwZW5kU2NoZW1lSG9zdFBvcnRgIHNob3dzIHRoYXQgaWYgc3BlY2lmeWluZyBkZWZhdWx0IHBvcnRzIChkZXBlbmRpbmcgb24gc2NoZW1hKSBvciAwIHRoZXkgd2lsbCBub3QgYmUgaW5jbHVkZWQuDQoNCg0KTGFzdCBpdGVtIHJlcXVpcmVzIHRoZSBmb2xsb3dpbmcgc3RlcHM6DQoNCkFkZCANCmBgYA0KLS1tb2R1bGU9cmV3cml0ZQ0KZXRjL3Jld3JpdGUtcnVsZXMueG1sDQpgYGANCmluIGAkSkVUVFlfQkFTRS9zdGFydC5pbmlgLCB3aGVyZSBgcmV3cml0ZS1ydWxlcy54bWxgIGNvbnRhaW5zIHRoZSBhY3R1YWwgcnVsZXMgYW5kIHNob3VsZCBiZSBsb2NhdGVkIGluICBgJEpFVFRZX0JBU0UvZXRjL3Jld3JpdGUtcnVsZXMueG1sYDoNCg0KYGBgeG1sDQo8P3htbCB2ZXJzaW9uPSIxLjAiPz4NCjwhRE9DVFlQRSBDb25maWd1cmUgUFVCTElDICItLy9KZXR0eS8vQ29uZmlndXJlLy9FTiIgImh0dHA6Ly93d3cuZWNsaXBzZS5vcmcvamV0dHkvY29uZmlndXJlXzlfMy5kdGQiPg0KDQo8IS0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSAtLT4NCjwhLS0gQ29uZmlndXJlIHRoZSBkZW1vcyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0tPg0KPCEtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gLS0+DQo8Q29uZmlndXJlIGlkPSJTZXJ2ZXIiIGNsYXNzPSJvcmcuZWNsaXBzZS5qZXR0eS5zZXJ2ZXIuU2VydmVyIj4NCg0KICA8IS0tID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0gLS0+DQogIDwhLS0gQWRkIHJld3JpdGUgcnVsZXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLT4NCiAgPCEtLSA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IC0tPg0KICA8UmVmIHJlZmlkPSJSZXdyaXRlIj4NCg0KICAgICAgPCEtLSBwcm90ZWN0IGZhdmljb24gaGFuZGxpbmcgLS0+DQogICAgICA8Q2FsbCBuYW1lPSJhZGRSdWxlIj4NCiAgICAgICAgPEFyZz4NCiAgICAgICAgICA8TmV3IGNsYXNzPSJyby50b3RhbHNvZnQuamV0dHkuaG9zdGhlYWRlcnJld3JpdGVyLkhvc3RIZWFkZXJSZXdyaXRlciI+DQogICAgICAgICAgICA8U2V0IG5hbWU9ImhlYWRlciI+SG9zdDwvU2V0Pg0KICAgICAgICAgICAgPFNldCBuYW1lPSJob3N0Ij5wcm94eS5teWNvbXBhbnkuY29tPC9TZXQ+DQogICAgICAgICAgICA8U2V0IG5hbWU9InBvcnQiPjA8L1NldD4NCiAgICAgICAgICAgIDxTZXQgbmFtZT0idGVybWluYXRpbmciPnRydWU8L1NldD4NCiAgICAgICAgICA8L05ldz4NCiAgICAgICAgPC9Bcmc+DQogICAgICA8L0NhbGw+DQogIDwvUmVmPg0KPC9Db25maWd1cmU+DQpgYGANCg0KSW4gb3JkZXIgdG8gYmUgc2VsZWN0aXZlIChpZiB3ZSBob3N0IGFsc28gb3RoZXIgYXBwbGljYXRpb25zIGluIHRoZSBzYW1lIEpldHR5IGluc3RhbmNlKSB3ZSBzaG91bGQgYWxzbyBhZGQgdGhlIGZpbHRlcmluZyBjb25kaXRpb24NCg0KYGBgeG1sDQo8U2V0IG5hbWU9ImhlYWRlclZhbHVlIj5pZHAubXljb21wYW55LmxvY2FsOjg0NDM8L1NldD4NCmBgYA0KDQpUaGUgYWN0dWFsIHJld3JpdGUgaGFuZGxlciBkZWZpbml0aW9uIGlzIGxvY2F0ZWQgaW4gYCRKRVRUWV9IT01FL2V0Yy9qZXR0eS1yZXdyaXRlLnhtbGAgYXMgY2FuIGJlIHNlZW4gYnkgaW5zcGVjdGlvbiB0aGUgYCRKRVRUWV9IT01FL21vZHVsZS9yZXdyaXRlLm1vZGAgbW9kdWxlIGRlZmluaXRpb24gZmlsZS4NCg0KUmVtYXJrOiBsb29raW5nIChhcyBhIG5ld2JpZSkgaW4gYGpldHR5LXJld3JpdGUueG1sYCBtaWdodCByYWlzZSBhIHF1ZXN0aW9uOiB3aGF0IGlzIGBvbGRoYW5kbGVyYCBhYm91dD8gVGhlIG5hbWluZyBpcyBtaXNsZWFkaW5nLCBpdCBzaG91bGQgYmUgc29tZXRoaW5nIGxpa2UgYGZpcnN0aGFuZGxlcmAgc2luY2UgaXQgcmVmZXJzIHRvIHRoZSBmaXJzdCBoYW5kbGVyIGluIHRoZSBjaGFpbiBvZiBKZXR0eSBoYW5kbGVycy4gQWZ0ZXIgSmV0dHkgYXBwbGllcyB0aGUgcmV3cml0ZSBoYW5kbGVyIGRlZmluaXRpb24gdGhpcyB3aWxsIGJlY29tZSB0aGUgZmlyc3QgaGFuZGxlciBpbiBjaGFpbi4NCg0KTm93IChzdXBwb3Npbmcgd2UgaGF2ZSB3cml0dGVuIG91ciBvd24gaGVhZGVyIHJ1bGUpIHdlIGhhdmUgdG8gZGVwbG95IGl0IGluIHRoZSBjbGFzc3BhdGgga25vd24gYnkgSmV0dHkuIFRvIGRvIHRoaXMgd2UgaGF2ZSB0byBhZGQgdGhlICoqZXh0KiogbW9kdWxlOg0KDQotLW1vZHVsZT1leHQNCmBgYA0KLS1tb2R1bGU9ZXh0DQpUaGUgZXh0IG1vZHVsZSB3aWxsIGVuYWJsZSB0aGUgbGliL2V4dC8qLmphciBsb2dpYy4NCg0KSWYgdGhpcyBtb2R1bGUgaXMgYWN0aXZhdGVkLCB0aGVuIGFsbCBqYXIgZmlsZXMgZm91bmQgaW4gdGhlIGxpYi9leHQvIHBhdGhzIHdpbGwgYmUgYXV0b21hdGljYWxseSBhZGRlZCB0byB0aGUgSmV0dHkgU2VydmVyIENsYXNzcGF0aC4NCmBgYA0KDQpBZnRlcndhcmRzIHdlIGNhbiBjb3B5IG91ciBqYXIgdG8gJEpFVFRZX0JBU0UvbGliL2V4dC8gYW5kIHJlc3RhcnQgSmV0dHkNCg0KKipJbXBvcnRhbnQgcmVtYXJrICoqOiBkdWUgdG8gYSBidWcgKG9taXNzaW9uKSBpbiB0aGUgbG9naWMgdGhhdCBhY3RpdmF0ZXMgcnVsZXMgaW5zaWRlIHRoZSByZXdyaXRlIGhhbmRsZXIgaWYgdGhlcmUgaXMgbm8gcnVsZSBkZWZpbmVkIGEgTnVsbFBvaW50ZXJFeGNlcHRpb24gd2lsbCBiZSB0aHJvd24gaW4gY2xhc3MgYG9yZy5lY2xpcHNlLmpldHR5LnJld3JpdGUuaGFuZGxlci5SdWxlQ29udGFpbmVyYCwgbWV0aG9kIGBhcHBseWA6DQoNCmBgYGphdmENCiAgICBwcm90ZWN0ZWQgU3RyaW5nIGFwcGx5KFN0cmluZyB0YXJnZXQsIEh0dHBTZXJ2bGV0UmVxdWVzdCByZXF1ZXN0LCBIdHRwU2VydmxldFJlc3BvbnNlIHJlc3BvbnNlKSB0aHJvd3MgSU9FeGNlcHRpb24NCiAgICB7DQogICAgICAgIGJvb2xlYW4gb3JpZ2luYWxfc2V0PV9vcmlnaW5hbFBhdGhBdHRyaWJ1dGU9PW51bGw7DQogICAgICAgICAgICAgICAgDQogICAgICAgIGZvciAoUnVsZSBydWxlIDogX3J1bGVzKQ0KICAgICAgICB7DQogICAgICAgICAgICBTdHJpbmcgYXBwbGllZD1ydWxlLm1hdGNoQW5kQXBwbHkodGFyZ2V0LHJlcXVlc3QsIHJlc3BvbnNlKTsNCiAgICAgICAgICAgIGlmIChhcHBsaWVkIT1udWxsKQ0KICAgICAgICAgICAgeyAgICAgICANCg0KYGBgDQpJZiBubyBydWxlcyBhcmUgZGVmaW5lZCB3ZSB3aWxsIGxhbmQgaGVyZSB3aXRoIGBfcnVsZXNgIGJlaW5nIG51bGwuLi4NCg0KIyBFcGlsb2d1ZQ0KDQpXdyBhcmUgbm90IGRvbmUgeWV0IGJ1dCBoYXZlIG1hZGUgc2lnbmlmaWNhbnQgcHJvZ3Jlc3MgaGF2aW5nIElkUCBzaWRlIHdvcmtpbmcuIEFzIHdlIGNhbiBhc3N1bWUsIHdlIHdpbGwgZmFjZSBzYW1lIHByb2JsZW0gb24gU1Agc2lkZSAod2UgZG8gbm90IHJlYWxseSB3YW50IHdlYiBzaXRlcyBleHBvc2VkIGRpcmVjdGx5IG9uIHRoZSBJbnRlcm5ldCkuDQoNClNvLCBuZXh0IHBvc3QgYWJvdXQgS2VudG9yIFNTTy4gSSB3b3VsZCBleHBlY3QgdG8gYXBwbHkgdGhlIHNvbHV0aW9uIHdpdGggZXh0ZXJuYWwvaW50ZXJuYWwgRE5TIGFuZCB1c2luZyA0NDMgZGVmYXVsdCBwb3J0IChXaW5kb3dzL0lJUyBsb29rcyBtb3JlIHBlcm1pc3NpdmUgaW4gdGhpcyBhcmVhKQ0KDQojIFRoZSBDb2RlDQoNCmBgYGphdmENCnBhY2thZ2UgY29tLm15Y29tcGFueS5qZXR0eS5ob3N0aGVhZGVycmV3cml0ZXI7DQoNCmltcG9ydCBqYXZhLmlvLklPRXhjZXB0aW9uOw0KaW1wb3J0IGphdmF4LnNlcnZsZXQuaHR0cC5IdHRwU2VydmxldFJlcXVlc3Q7DQppbXBvcnQgamF2YXguc2VydmxldC5odHRwLkh0dHBTZXJ2bGV0UmVzcG9uc2U7DQppbXBvcnQgb3JnLmVjbGlwc2UuamV0dHkucmV3cml0ZS5oYW5kbGVyLkhlYWRlclJ1bGU7DQppbXBvcnQgb3JnLmVjbGlwc2UuamV0dHkuc2VydmVyLlJlcXVlc3Q7DQoNCnB1YmxpYyBjbGFzcyBIb3N0SGVhZGVyUmV3cml0ZXIgZXh0ZW5kcyBIZWFkZXJSdWxlew0KICAgIFN0cmluZyBob3N0Ow0KICAgIGludCBwb3J0PTA7DQoNCiAgICBwdWJsaWMgU3RyaW5nIGdldEhvc3QoKSB7DQogICAgICAgIHJldHVybiBob3N0Ow0KICAgIH0NCiAgICBwdWJsaWMgdm9pZCBzZXRIb3N0KFN0cmluZyBob3N0KSB7DQogICAgICAgIHRoaXMuaG9zdCA9IGhvc3Q7DQogICAgfQ0KDQogICAgcHVibGljIGludCBnZXRQb3J0KCkgew0KICAgICAgICByZXR1cm4gcG9ydDsNCiAgICB9DQogICAgcHVibGljIHZvaWQgc2V0UG9ydChpbnQgcG9ydCkgew0KICAgICAgICB0aGlzLnBvcnQgPSBwb3J0Ow0KICAgIH0NCiAgICANCiAgICBAT3ZlcnJpZGUNCiAgICBwcm90ZWN0ZWQgU3RyaW5nIGFwcGx5KFN0cmluZyB0YXJnZXQsIFN0cmluZyB2YWx1ZSwgSHR0cFNlcnZsZXRSZXF1ZXN0IHJlcXVlc3QsIEh0dHBTZXJ2bGV0UmVzcG9uc2UgcmVzcG9uc2UpIHRocm93cyBJT0V4Y2VwdGlvbiB7DQogICAgICAgIFJlcXVlc3QgamV0dHlSZXF1ZXN0PShSZXF1ZXN0KSByZXF1ZXN0Ow0KICAgICAgICBqZXR0eVJlcXVlc3QuZ2V0TWV0YURhdGEoKS5nZXRVUkkoKS5zZXRBdXRob3JpdHkoaG9zdCwgcG9ydCk7DQogICAgICAgIHJldHVybiB0YXJnZXQ7DQogICAgfQ0KfQ0KYGBg

Monday, November 2, 2015

Shibboleth-We Are Not Done Yet-Part 1

First thing that popped out in my mind after basic installation/configuration worked was to check how could be the login page customized. But where to find that page?

The answer is simple: login.vm (idp-conf\src\main\resources\views\login.vm), a template based on Velocity. It was easy to find: look at the id assigned to password field (for instance), j_password and do a recursive search in sources directory.

Note: If changing main.css is necessary please notice that the UI part is packed as idp.war...

This is not enough; we want to understand how it works and where to look if we get into trouble.

To start with: a lot of Shibbolet functionality (including UI behaviour) is based on Spring Web Flow. The main configuration file (webflow-config.xml) can be found by looking into web.config.

    <servlet>
        <servlet-name>idp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>${idp.home}/system/conf/mvc-beans.xml, ${idp.home}/system/conf/webflow-config.xml</param-value>
        </init-param>
...

It defines application web-flows accessible through the registry (org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl, method getFlowDefinition, spring-webflow-2.4.1.RELEASE.jar).

The flow we are interested in is that with id="SAML2/Unsolicited/SSO". When we access HTTPS://<shibblolet-url>:<https-port>/idp/profile/SAML2/Unsolicited/SSO?providerId=<SP_entityID> DispatcherHandler will finally call org.springframework.webflow.mvc.servlet.FlowHandlerMapping, method getHandlerInternal, parameter HttpServletRequest request. Request properties are as follows:

  • contextPath: "/idp"
  • servletPath: "/profile"
  • pathInfo: "/SAML2/Unsolicited/SSO"

Based on pathInfo the web-flow with the matching id is activated. A breakpoint set in org.springframework.webflow.executor.FlowExecutorImpl, method launchExecution, after FlowDefinition flowDefinition = definitionLocator.getFlowDefinition(flowId); allows us to examine flowDefinition and see the states defined in the work-flow that is about to be executed.

Having a look in the flow definition (path="../system/flows/saml/saml2/sso-unsolicited-flow.xml") is quite confusing for the newbies: no actually flow description (according to what you will see in tutorials) but just two regular Spring beans definition: the point is that any such bean exposing an 'execute' method will be treated as a state-action and the methods will be called in the order of bean definition.

OK, but where comes in action our login.vm? The parent="saml2.sso.abstract" attribute in flow tag definition is the key: our flow inherits its definition from that flow (identified by this id in web-config.xml). Somehow (I haven't checked all the inheritance/definitions chain) the authn/Password sub-flow is activated, sub-flow that contains a view-state referring our template in the view attribute:

    <view-state id="DisplayUsernamePasswordPage" view="login">
        <on-render>
            <evaluate expression="environment" result="viewScope.environment" />
            <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext" />
...

The transition <transition on="proceed" to="ExtractUsernamePasswordFromFormRequest"> is activated when the Login button is pressed (id='_eventId_proceed'-> proceed constant will become available to be evaluated by the transition condition based on the convention id=_eventId_... ).

Action state ExtractUsernamePasswordFromFormRequest will be executed:

    <action-state id="ExtractUsernamePasswordFromFormRequest">
        <evaluate expression="ExtractUsernamePasswordFromFormRequest" />
        <evaluate expression="'proceed'" />
    
        <!-- Let the validate action handle any problems later. -->        
        <transition to="ValidateUsernamePassword" />
    </action-state>

The <evaluate expression="ExtractUsernamePasswordFromFormRequest" /> has the role to call execute method of bean with id="ExtractUsernamePasswordFromFormRequest" ( class defined in module Shibboleth IdP :: Authentication Implementation) while <evaluate expression="'proceed'"/> just makes 'proceed' the current value to be evaluated by subsequent transitions (not actually used here but pattern used in may other places).

Finally we land in ValidateUsernamePasswordAgainstLDAP.execute method.

DQpGaXJzdCB0aGluZyB0aGF0IHBvcHBlZCBvdXQgaW4gbXkgbWluZCBhZnRlciBiYXNpYyBpbnN0YWxsYXRpb24vY29uZmlndXJhdGlvbiB3b3JrZWQgd2FzIHRvIGNoZWNrIGhvdyBjb3VsZCBiZSB0aGUgbG9naW4gcGFnZSBjdXN0b21pemVkLiAgQnV0IHdoZXJlIHRvIGZpbmQgdGhhdCBwYWdlPw0KDQpUaGUgYW5zd2VyIGlzIHNpbXBsZTogbG9naW4udm0gKGBpZHAtY29uZlxzcmNcbWFpblxyZXNvdXJjZXNcdmlld3NcbG9naW4udm1gKSwgYSB0ZW1wbGF0ZSBiYXNlZCBvbiBWZWxvY2l0eS4gSXQgd2FzIGVhc3kgdG8gZmluZDogbG9vayBhdCB0aGUgaWQgYXNzaWduZWQgdG8gcGFzc3dvcmQgZmllbGQgKGZvciBpbnN0YW5jZSksIGBqX3Bhc3N3b3JkYCAgYW5kIGRvIGEgcmVjdXJzaXZlIHNlYXJjaCBpbiAgc291cmNlcyBkaXJlY3RvcnkuDQoNCioqTm90ZSoqOiBJZiBjaGFuZ2luZyAqbWFpbi5jc3MqIGlzIG5lY2Vzc2FyeSBwbGVhc2Ugbm90aWNlIHRoYXQgdGhlIFVJIHBhcnQgaXMgcGFja2VkIGFzIGlkcC53YXIuLi4NCg0KVGhpcyBpcyBub3QgZW5vdWdoOyB3ZSB3YW50IHRvIHVuZGVyc3RhbmQgaG93IGl0IHdvcmtzIGFuZCB3aGVyZSB0byBsb29rIGlmIHdlIGdldCBpbnRvIHRyb3VibGUuDQoNClRvIHN0YXJ0IHdpdGg6IGEgbG90IG9mIFNoaWJib2xldCBmdW5jdGlvbmFsaXR5IChpbmNsdWRpbmcgVUkgYmVoYXZpb3VyKSBpcyBiYXNlZCBvbiBTcHJpbmcgV2ViIEZsb3cuIFRoZSBtYWluIGNvbmZpZ3VyYXRpb24gZmlsZSAoYHdlYmZsb3ctY29uZmlnLnhtbGApIGNhbiBiZSBmb3VuZCBieSBsb29raW5nIGludG8gYHdlYi5jb25maWdgLg0KYGBgeG1sDQogICAgPHNlcnZsZXQ+DQogICAgICAgIDxzZXJ2bGV0LW5hbWU+aWRwPC9zZXJ2bGV0LW5hbWU+DQogICAgICAgIDxzZXJ2bGV0LWNsYXNzPm9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQuRGlzcGF0Y2hlclNlcnZsZXQ8L3NlcnZsZXQtY2xhc3M+DQogICAgICAgIDxpbml0LXBhcmFtPg0KICAgICAgICAgICAgPHBhcmFtLW5hbWU+Y29udGV4dENvbmZpZ0xvY2F0aW9uPC9wYXJhbS1uYW1lPg0KICAgICAgICAgICAgPHBhcmFtLXZhbHVlPiR7aWRwLmhvbWV9L3N5c3RlbS9jb25mL212Yy1iZWFucy54bWwsICR7aWRwLmhvbWV9L3N5c3RlbS9jb25mL3dlYmZsb3ctY29uZmlnLnhtbDwvcGFyYW0tdmFsdWU+DQogICAgICAgIDwvaW5pdC1wYXJhbT4NCi4uLg0KYGBgDQoNCkl0IGRlZmluZXMgYXBwbGljYXRpb24gd2ViLWZsb3dzIGFjY2Vzc2libGUgdGhyb3VnaCB0aGUgcmVnaXN0cnkgKGBvcmcuc3ByaW5nZnJhbWV3b3JrLndlYmZsb3cuZGVmaW5pdGlvbi5yZWdpc3RyeS5GbG93RGVmaW5pdGlvblJlZ2lzdHJ5SW1wbGAsIG1ldGhvZCBgZ2V0Rmxvd0RlZmluaXRpb25gLCBgc3ByaW5nLXdlYmZsb3ctMi40LjEuUkVMRUFTRS5qYXJgKS4NCg0KVGhlIGZsb3cgd2UgYXJlIGludGVyZXN0ZWQgaW4gaXMgdGhhdCB3aXRoIGBpZD0iU0FNTDIvVW5zb2xpY2l0ZWQvU1NPImAuIFdoZW4gd2UgYWNjZXNzIGBIVFRQUzovLzxzaGliYmxvbGV0LXVybD46PGh0dHBzLXBvcnQ+L2lkcC9wcm9maWxlL1NBTUwyL1Vuc29saWNpdGVkL1NTTz9wcm92aWRlcklkPTxTUF9lbnRpdHlJRD5gIGBEaXNwYXRjaGVySGFuZGxlcmAgd2lsbCBmaW5hbGx5IGNhbGwgYG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViZmxvdy5tdmMuc2VydmxldC5GbG93SGFuZGxlck1hcHBpbmdgLCBtZXRob2QgYGdldEhhbmRsZXJJbnRlcm5hbGAsIHBhcmFtZXRlciBgSHR0cFNlcnZsZXRSZXF1ZXN0IHJlcXVlc3RgLiBSZXF1ZXN0IHByb3BlcnRpZXMgYXJlIGFzIGZvbGxvd3M6DQoNCiogY29udGV4dFBhdGg6ICIvaWRwIg0KKiBzZXJ2bGV0UGF0aDogIi9wcm9maWxlIg0KKiBwYXRoSW5mbzogIi9TQU1MMi9VbnNvbGljaXRlZC9TU08iDQoNCkJhc2VkIG9uIGBwYXRoSW5mb2AgdGhlIHdlYi1mbG93IHdpdGggdGhlIG1hdGNoaW5nIGlkIGlzIGFjdGl2YXRlZC4gQSBicmVha3BvaW50IHNldCBpbiBgb3JnLnNwcmluZ2ZyYW1ld29yay53ZWJmbG93LmV4ZWN1dG9yLkZsb3dFeGVjdXRvckltcGxgLCBtZXRob2QgYGxhdW5jaEV4ZWN1dGlvbmAsIGFmdGVyIGBGbG93RGVmaW5pdGlvbiBmbG93RGVmaW5pdGlvbiA9IGRlZmluaXRpb25Mb2NhdG9yLmdldEZsb3dEZWZpbml0aW9uKGZsb3dJZCk7YCBhbGxvd3MgdXMgdG8gZXhhbWluZSBgZmxvd0RlZmluaXRpb25gIGFuZCBzZWUgdGhlIHN0YXRlcyBkZWZpbmVkIGluIHRoZSB3b3JrLWZsb3cgdGhhdCBpcyBhYm91dCB0byBiZSBleGVjdXRlZC4NCg0KSGF2aW5nIGEgbG9vayBpbiB0aGUgZmxvdyBkZWZpbml0aW9uIChgcGF0aD0iLi4vc3lzdGVtL2Zsb3dzL3NhbWwvc2FtbDIvc3NvLXVuc29saWNpdGVkLWZsb3cueG1sImApIGlzIHF1aXRlIGNvbmZ1c2luZyBmb3IgdGhlIG5ld2JpZXM6IG5vIGFjdHVhbGx5IGZsb3cgZGVzY3JpcHRpb24gKGFjY29yZGluZyB0byB3aGF0IHlvdSB3aWxsIHNlZSBpbiB0dXRvcmlhbHMpIGJ1dCBqdXN0IHR3byByZWd1bGFyIFNwcmluZyBiZWFucyBkZWZpbml0aW9uOiB0aGUgcG9pbnQgaXMgdGhhdCBhbnkgc3VjaCBiZWFuIGV4cG9zaW5nIGFuICdleGVjdXRlJyBtZXRob2Qgd2lsbCBiZSB0cmVhdGVkIGFzIGEgc3RhdGUtYWN0aW9uIGFuZCB0aGUgbWV0aG9kcyB3aWxsIGJlIGNhbGxlZCBpbiB0aGUgb3JkZXIgb2YgYmVhbiBkZWZpbml0aW9uLg0KDQpPSywgYnV0IHdoZXJlIGNvbWVzIGluIGFjdGlvbiBvdXIgYGxvZ2luLnZtYD8gVGhlIGBwYXJlbnQ9InNhbWwyLnNzby5hYnN0cmFjdCJgIGF0dHJpYnV0ZSBpbiBmbG93IHRhZyBkZWZpbml0aW9uIGlzIHRoZSBrZXk6IG91ciBmbG93IGluaGVyaXRzIGl0cyBkZWZpbml0aW9uIGZyb20gdGhhdCBmbG93IChpZGVudGlmaWVkIGJ5IHRoaXMgaWQgaW4gYHdlYi1jb25maWcueG1sYCkuIFNvbWVob3cgKEkgaGF2ZW4ndCBjaGVja2VkIGFsbCB0aGUgaW5oZXJpdGFuY2UvZGVmaW5pdGlvbnMgY2hhaW4pIHRoZSBgYXV0aG4vUGFzc3dvcmRgIHN1Yi1mbG93IGlzIGFjdGl2YXRlZCwgc3ViLWZsb3cgdGhhdCBjb250YWlucyBhIGB2aWV3LXN0YXRlYCByZWZlcnJpbmcgb3VyIHRlbXBsYXRlIGluIHRoZSB2aWV3IGF0dHJpYnV0ZToNCmBgYHhtbA0KICAgIDx2aWV3LXN0YXRlIGlkPSJEaXNwbGF5VXNlcm5hbWVQYXNzd29yZFBhZ2UiIHZpZXc9ImxvZ2luIj4NCiAgICAgICAgPG9uLXJlbmRlcj4NCiAgICAgICAgICAgIDxldmFsdWF0ZSBleHByZXNzaW9uPSJlbnZpcm9ubWVudCIgcmVzdWx0PSJ2aWV3U2NvcGUuZW52aXJvbm1lbnQiIC8+DQogICAgICAgICAgICA8ZXZhbHVhdGUgZXhwcmVzc2lvbj0ib3BlbnNhbWxQcm9maWxlUmVxdWVzdENvbnRleHQiIHJlc3VsdD0idmlld1Njb3BlLnByb2ZpbGVSZXF1ZXN0Q29udGV4dCIgLz4NCiAgICAgICAgICAgIDxldmFsdWF0ZSBleHByZXNzaW9uPSJvcGVuc2FtbFByb2ZpbGVSZXF1ZXN0Q29udGV4dC5nZXRTdWJjb250ZXh0KFQobmV0LnNoaWJib2xldGguaWRwLmF1dGhuLmNvbnRleHQuQXV0aGVudGljYXRpb25Db250ZXh0KSkiIHJlc3VsdD0idmlld1Njb3BlLmF1dGhlbnRpY2F0aW9uQ29udGV4dCIgLz4NCi4uLg0KYGBgICANClRoZSB0cmFuc2l0aW9uIGA8dHJhbnNpdGlvbiBvbj0icHJvY2VlZCIgdG89IkV4dHJhY3RVc2VybmFtZVBhc3N3b3JkRnJvbUZvcm1SZXF1ZXN0Ij5gIGlzIGFjdGl2YXRlZCB3aGVuIHRoZSBMb2dpbiBidXR0b24gaXMgcHJlc3NlZCAoYGlkPSdfZXZlbnRJZF9wcm9jZWVkJ2AtXD4gYHByb2NlZWRgIGNvbnN0YW50IHdpbGwgYmVjb21lIGF2YWlsYWJsZSB0byBiZSBldmFsdWF0ZWQgYnkgdGhlIHRyYW5zaXRpb24gY29uZGl0aW9uIGJhc2VkIG9uIHRoZSBjb252ZW50aW9uIGBpZD1fZXZlbnRJZF8uLi5gICkuDQoNCkFjdGlvbiBzdGF0ZSBgRXh0cmFjdFVzZXJuYW1lUGFzc3dvcmRGcm9tRm9ybVJlcXVlc3RgIHdpbGwgYmUgZXhlY3V0ZWQ6DQpgYGB4bWwNCiAgICA8YWN0aW9uLXN0YXRlIGlkPSJFeHRyYWN0VXNlcm5hbWVQYXNzd29yZEZyb21Gb3JtUmVxdWVzdCI+DQogICAgICAgIDxldmFsdWF0ZSBleHByZXNzaW9uPSJFeHRyYWN0VXNlcm5hbWVQYXNzd29yZEZyb21Gb3JtUmVxdWVzdCIgLz4NCiAgICAgICAgPGV2YWx1YXRlIGV4cHJlc3Npb249Iidwcm9jZWVkJyIgLz4NCiAgICANCiAgICAgICAgPCEtLSBMZXQgdGhlIHZhbGlkYXRlIGFjdGlvbiBoYW5kbGUgYW55IHByb2JsZW1zIGxhdGVyLiAtLT4gICAgICAgIA0KICAgICAgICA8dHJhbnNpdGlvbiB0bz0iVmFsaWRhdGVVc2VybmFtZVBhc3N3b3JkIiAvPg0KICAgIDwvYWN0aW9uLXN0YXRlPg0KYGBgDQpUaGUgYDxldmFsdWF0ZSBleHByZXNzaW9uPSJFeHRyYWN0VXNlcm5hbWVQYXNzd29yZEZyb21Gb3JtUmVxdWVzdCIgLz5gIGhhcyB0aGUgcm9sZSB0byBjYWxsIGBleGVjdXRlYCBtZXRob2Qgb2YgYmVhbiB3aXRoIGBpZD0iRXh0cmFjdFVzZXJuYW1lUGFzc3dvcmRGcm9tRm9ybVJlcXVlc3QiYCAoIGNsYXNzIGRlZmluZWQgaW4gbW9kdWxlIGBTaGliYm9sZXRoIElkUCA6OiBBdXRoZW50aWNhdGlvbiBJbXBsZW1lbnRhdGlvbmApIHdoaWxlIGA8ZXZhbHVhdGUgZXhwcmVzc2lvbj0iJ3Byb2NlZWQnIi8+YCBqdXN0IG1ha2VzIGAncHJvY2VlZCdgIHRoZSBjdXJyZW50IHZhbHVlIHRvIGJlIGV2YWx1YXRlZCBieSBzdWJzZXF1ZW50IHRyYW5zaXRpb25zIChub3QgYWN0dWFsbHkgdXNlZCBoZXJlIGJ1dCBwYXR0ZXJuIHVzZWQgaW4gbWF5IG90aGVyIHBsYWNlcykuDQoNCkZpbmFsbHkgd2UgbGFuZCBpbiBgVmFsaWRhdGVVc2VybmFtZVBhc3N3b3JkQWdhaW5zdExEQVAuZXhlY3V0ZWAgbWV0aG9kLg==