Friday, October 30, 2015

Shibboleth-Finally

After about two weeks of trial and error I finally succeeded to have a SSO/SAML POC based on open source components (Shibbolet/ApacheDS/KentorIT) working.

The last step was struggling with convincing Shibbolet to respond to SAML requests.

The main problem (for newbies) is the lack of any overview type information; almost all documentation (https://wiki.shibboleth.net/confluence/display/IDP30/Home) focuses on detailed specification aspects so you get lost in tens of .properties/.xml configuration files.

Debugging

Of course my first concern was about being able to set breakpoints and go step by step through sources.

Go to jetty_base and edit start.ini by adding the following lines:

-Xdebug 
-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n

Restart jetty.

Download latest source, open main pom.xml and rebuild project.

First steps

Supposing you have the metadata file for a target SP (we do not need the SP implementation for now) deploy it in shibbolet_sp/metadata (you will also find there idp_metadata.xml, the metadata related with IsP).

Edit conf/metadata-providers.xml by adding information about you SP; something like this:

  • <MetadataProvider id="LocalMetadata" xsi:type="FilesystemMetadataProvider" metadataFile="%{idp.home}/metadata/mySPmetadata.xml"/>

Edit conf/ldap.proprierties (watch out for useStartTLS):

  • idp.authn.LDAP.ldapURL = <ldap url>
  • idp.authn.LDAP.useStartTLS = false
  • idp.authn.LDAP.returnAttributes = <comma separated LDAP attributes>
  • idp.authn.LDAP.baseDN = <base DN for search)
  • idp.authn.LDAP.userFilter = (<LDAP attribute>={user})

Remark: This is enaough for default anonSearchAuthenticator; if bindSearchAuthenticator is required be sure to:

  • uncomment and change accordingly idp.authn.LDAP.authenticator = bindSearchAuthenticator
  • change the two configartion entries:
    • idp.authn.LDAP.bindDN = <DN of bind user>
    • idp.authn.LDAP.bindDNCredential = <password>

At this moment we can test authentication by using the IdP initiated login URL : HTTPS://<shibblolet-url>:<https-port>/idp/profile/SAML2/Unsolicited/SSO?providerId=<SP_entityID>

<SP_entityID> is the the SP EntityId as specified in metadata file.

Logout can be achieved through https://<shibblolet-url>:<https-port>/idp/profile/Logout

In order to debug the authentication proceeds have a look in net.shibboleth.idp.authn.impl.ValidateUsernamePasswordAgainstLDAP (method doExecute), module Shibboleth IdP :: Authentication Implementation; also look at org.ldaptive.auth.Authenticator from ladptive-1.0.0.jar (select the jar with right click->Download source, etc).

Configure Response

Now, even if you will get a browser error after going through the login UI process (since we dot have the actual SP implementation) you can use browser developer tools to check the SAMLResponse (use SSOCircle POST decoder). We notice that the Subject part is encrypted.

Response configuration can be done through conf/relying-party.xml; inside <bean id="shibboleth.DefaultRelyingParty" parent="RelyingParty"> change the SAML2.SSO attributes according to your needs:

  • (sample): <bean parent="SAML2.SSO" p:postAuthenticationFlows="attribute-release" p:encryptAssertions="false" p:encryptAttributes="false" p:encryptNameIDs="false"/>

Now we can see in clear text (after decoding) the Subject info but surprise(s):

  • NameID has a strange value (some Base64 encoded value)
  • No other assertions, even if we configured LDAP to return more attributes

NameID

I was used with ADFS where you can easily return sAMAcountName as NameID. It turned out to be a very difficult task so at the end of the day I gave up since I (somehow) understood that the role of NameID is different of what I wanted to achieve.

Some insights: You can have a look at (as a starting debugging point) getSAML2NameIDGenerator method from net.shibboleth.idp.saml.nameid.impl.NameIdentifierGenerationServiceImpl, module Shibboleth IdP :: SAML Profile Implementation. The main issue here is that the returned value (saml2Generator) is an implementation of org.opensaml.saml.saml2.profile.SAML2NameIDGenerator. Since in the module there is a compile dependency only to open-saml-api-3.1.1.jar you will have to add manually in pom.xml the dependency to the actual implementation.

        <dependency>
            <groupId>${opensaml.groupId}</groupId>
            <artifactId>opensaml-saml-impl</artifactId>
            <version>${opensaml.version}</version>
        </dependency>
 

Be sure to remove the test time dependency (a compile error will prevent to successfully compile the module anyway).

The default configured strategy relies on TransientSAML2NameIDGenerator and CryptoTransientIdGenerationStrategy (generate method).

Configuration of the strategy is done in conf/saml-nameid.xml. Initially I tried switching from <ref bean="shibboleth.SAML2TransientGenerator" /> to

    <!-- urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress -->
    <bean parent="shibboleth.SAML2AttributeSourcedGenerator"
            p:format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
            p:attributeSourceIds="#{ {'cn'} }" />

Watch out of the p:format attribute value, changed from email to transient as the net.shibboleth.idp.saml.nameid.impl.AttributeSourcedSAML2NameIDGenerator (one of the subsequent calls to other classes methods) will check for attributes with this format.

Anyway, it didn't work because here we speak about SAML attributes (that will go into Assertions) not about LDAP attributes and since I didn't get in the response no Assertions...no NameID was also generated.

Finally Getting the Assertions

After further hours spent in analysing online wiki it was obvious that AttributeResolverConfiguration was next step to success.

Having a look in conf/attribute-resolver.xml and putting breakpoints in classes like from net.shibboleth.idp.attribute.resolver.ad.impl namespace like (PrincipalNameAttributeDefinition, ScopedAttributeDefinition, AbstractAttributeReleaseAction or AttributeResolverImpl) it looked that everything went Ok since I've got all 4 attributes with the expected values (by watching variables during breakpoint sessions) but no Assertions were generated.

So I had a look in conf/atribute-filter.xml and surprise, attribute generation was filtered by a fictious EntityId of https://sp.example.org. By changing the attribute value to the right value in <afp:PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="<mySPEntityID>" /> everything started to work as expected.

QWZ0ZXIgYWJvdXQgdHdvIHdlZWtzIG9mIHRyaWFsIGFuZCBlcnJvciBJIGZpbmFsbHkgc3VjY2VlZGVkIHRvIGhhdmUgYSBTU08vU0FNTCBQT0MgYmFzZWQgb24gb3BlbiBzb3VyY2UgY29tcG9uZW50cyAoU2hpYmJvbGV0L0FwYWNoZURTL0tlbnRvcklUKSB3b3JraW5nLiANCg0KVGhlIGxhc3Qgc3RlcCB3YXMgc3RydWdnbGluZyB3aXRoIGNvbnZpbmNpbmcgU2hpYmJvbGV0IHRvIHJlc3BvbmQgdG8gU0FNTCByZXF1ZXN0cy4NCg0KVGhlIG1haW4gcHJvYmxlbSAoZm9yIG5ld2JpZXMpIGlzIHRoZSBsYWNrIG9mIGFueSBvdmVydmlldyB0eXBlIGluZm9ybWF0aW9uOyBhbG1vc3QgYWxsIGRvY3VtZW50YXRpb24gKFtodHRwczovL3dpa2kuc2hpYmJvbGV0aC5uZXQvY29uZmx1ZW5jZS9kaXNwbGF5L0lEUDMwL0hvbWVdKGh0dHBzOi8vd2lraS5zaGliYm9sZXRoLm5ldC9jb25mbHVlbmNlL2Rpc3BsYXkvSURQMzAvSG9tZSkpIGZvY3VzZXMgb24gZGV0YWlsZWQgc3BlY2lmaWNhdGlvbiBhc3BlY3RzIHNvIHlvdSBnZXQgbG9zdCBpbiB0ZW5zIG9mIC5wcm9wZXJ0aWVzLy54bWwgY29uZmlndXJhdGlvbiBmaWxlcy4NCg0KIyBEZWJ1Z2dpbmcNCg0KT2YgY291cnNlIG15IGZpcnN0IGNvbmNlcm4gd2FzIGFib3V0IGJlaW5nIGFibGUgdG8gc2V0IGJyZWFrcG9pbnRzIGFuZCBnbyBzdGVwIGJ5IHN0ZXAgdGhyb3VnaCBzb3VyY2VzLg0KDQpHbyB0byBqZXR0eV9iYXNlIGFuZCBlZGl0IHN0YXJ0LmluaSBieSBhZGRpbmcgdGhlIGZvbGxvd2luZyBsaW5lczoNCg0KYGBgDQotWGRlYnVnIA0KLVhydW5qZHdwOnRyYW5zcG9ydD1kdF9zb2NrZXQsYWRkcmVzcz01MDA1LHNlcnZlcj15LHN1c3BlbmQ9bg0KYGBgDQoNClJlc3RhcnQgamV0dHkuDQoNCkRvd25sb2FkIGxhdGVzdCBzb3VyY2UsIG9wZW4gbWFpbiBwb20ueG1sIGFuZCByZWJ1aWxkIHByb2plY3QuDQoNCiMgRmlyc3Qgc3RlcHMNCg0KU3VwcG9zaW5nIHlvdSBoYXZlIHRoZSBtZXRhZGF0YSBmaWxlIGZvciBhIHRhcmdldCBTUCAod2UgZG8gbm90IG5lZWQgdGhlIFNQIGltcGxlbWVudGF0aW9uIGZvciBub3cpIGRlcGxveSBpdCBpbiBgc2hpYmJvbGV0X3NwL21ldGFkYXRhYCAoeW91IHdpbGwgYWxzbyBmaW5kIHRoZXJlIGlkcF9tZXRhZGF0YS54bWwsIHRoZSBtZXRhZGF0YSByZWxhdGVkIHdpdGggSXNQKS4NCg0KRWRpdCBgY29uZi9tZXRhZGF0YS1wcm92aWRlcnMueG1sYCBieSBhZGRpbmcgaW5mb3JtYXRpb24gYWJvdXQgeW91IFNQOyBzb21ldGhpbmcgbGlrZSB0aGlzOg0KKiAgICBgPE1ldGFkYXRhUHJvdmlkZXIgaWQ9IkxvY2FsTWV0YWRhdGEiICB4c2k6dHlwZT0iRmlsZXN5c3RlbU1ldGFkYXRhUHJvdmlkZXIiIG1ldGFkYXRhRmlsZT0iJXtpZHAuaG9tZX0vbWV0YWRhdGEvbXlTUG1ldGFkYXRhLnhtbCIvPmANCg0KDQpFZGl0IGNvbmYvbGRhcC5wcm9wcmllcnRpZXMgKHdhdGNoIG91dCBmb3IgdXNlU3RhcnRUTFMpOg0KDQoqIGlkcC5hdXRobi5MREFQLmxkYXBVUkwJCQkJPSA8bGRhcCB1cmxcPg0KKiBpZHAuYXV0aG4uTERBUC51c2VTdGFydFRMUyAgICAJCT0gZmFsc2UNCiogaWRwLmF1dGhuLkxEQVAucmV0dXJuQXR0cmlidXRlcyAgIAk9IFw8Y29tbWEgc2VwYXJhdGVkIExEQVAgYXR0cmlidXRlc1w+DQoqIGlkcC5hdXRobi5MREFQLmJhc2VETgkJCQkJPSA8YmFzZSBETiBmb3Igc2VhcmNoKQ0KKiBpZHAuYXV0aG4uTERBUC51c2VyRmlsdGVyICAgICAgICAgICAgID0gKFw8TERBUCBhdHRyaWJ1dGVcPj17dXNlcn0pDQoNCioqUmVtYXJrKio6IFRoaXMgaXMgZW5hb3VnaCBmb3IgZGVmYXVsdCAqYW5vblNlYXJjaEF1dGhlbnRpY2F0b3IqOyBpZiAqYmluZFNlYXJjaEF1dGhlbnRpY2F0b3IqIGlzIHJlcXVpcmVkIGJlIHN1cmUgdG86DQoNCiogdW5jb21tZW50IGFuZCBjaGFuZ2UgYWNjb3JkaW5nbHkgYGlkcC5hdXRobi5MREFQLmF1dGhlbnRpY2F0b3IgPSBiaW5kU2VhcmNoQXV0aGVudGljYXRvcmANCiogY2hhbmdlIHRoZSB0d28gY29uZmlnYXJ0aW9uIGVudHJpZXM6DQoJKiBpZHAuYXV0aG4uTERBUC5iaW5kRE4gICAgICAgICAgICAgICAgICAgICAgICAgICA9IFw8RE4gb2YgYmluZCB1c2VyXD4NCgkqIGlkcC5hdXRobi5MREFQLmJpbmRETkNyZWRlbnRpYWwgICAgICAgICAgICAgICAgID0gXDxwYXNzd29yZFw+DQoNCg0KDQpBdCB0aGlzIG1vbWVudCB3ZSBjYW4gdGVzdCBhdXRoZW50aWNhdGlvbiBieSB1c2luZyB0aGUgSWRQIGluaXRpYXRlZCBsb2dpbiBVUkwgOiBgSFRUUFM6Ly88c2hpYmJsb2xldC11cmw+OjxodHRwcy1wb3J0Pi9pZHAvcHJvZmlsZS9TQU1MMi9VbnNvbGljaXRlZC9TU08/cHJvdmlkZXJJZD08U1BfZW50aXR5SUQ+YA0KDQo8U1BfZW50aXR5SUQ+IGlzIHRoZSB0aGUgU1AgRW50aXR5SWQgYXMgc3BlY2lmaWVkIGluIG1ldGFkYXRhIGZpbGUuDQoNCkxvZ291dCBjYW4gYmUgYWNoaWV2ZWQgdGhyb3VnaCBgaHR0cHM6Ly88c2hpYmJsb2xldC11cmw+OjxodHRwcy1wb3J0Pi9pZHAvcHJvZmlsZS9Mb2dvdXRgDQoNCkluIG9yZGVyIHRvIGRlYnVnIHRoZSBhdXRoZW50aWNhdGlvbiBwcm9jZWVkcyBoYXZlIGEgbG9vayBpbiBgbmV0LnNoaWJib2xldGguaWRwLmF1dGhuLmltcGwuVmFsaWRhdGVVc2VybmFtZVBhc3N3b3JkQWdhaW5zdExEQVBgIChtZXRob2QgZG9FeGVjdXRlKSwgbW9kdWxlIGBTaGliYm9sZXRoIElkUCA6OiBBdXRoZW50aWNhdGlvbiBJbXBsZW1lbnRhdGlvbmA7IGFsc28gbG9vayBhdCBgb3JnLmxkYXB0aXZlLmF1dGguQXV0aGVudGljYXRvcmAgZnJvbSBsYWRwdGl2ZS0xLjAuMC5qYXIgKHNlbGVjdCB0aGUgamFyIHdpdGggcmlnaHQgY2xpY2stXD5Eb3dubG9hZCBzb3VyY2UsIGV0YykuDQoNCiMgQ29uZmlndXJlIFJlc3BvbnNlDQoNCk5vdywgZXZlbiBpZiB5b3Ugd2lsbCBnZXQgYSBicm93c2VyIGVycm9yIGFmdGVyIGdvaW5nIHRocm91Z2ggdGhlIGxvZ2luIFVJIHByb2Nlc3MgKHNpbmNlIHdlIGRvdCBoYXZlIHRoZSBhY3R1YWwgU1AgaW1wbGVtZW50YXRpb24pIHlvdSBjYW4gdXNlIGJyb3dzZXIgZGV2ZWxvcGVyIHRvb2xzIHRvIGNoZWNrIHRoZSBTQU1MUmVzcG9uc2UgKHVzZSBTU09DaXJjbGUgUE9TVCBkZWNvZGVyKS4gV2Ugbm90aWNlIHRoYXQgdGhlIFN1YmplY3QgcGFydCBpcyBlbmNyeXB0ZWQuDQoNClJlc3BvbnNlIGNvbmZpZ3VyYXRpb24gY2FuIGJlIGRvbmUgdGhyb3VnaCAgY29uZi9yZWx5aW5nLXBhcnR5LnhtbDsgaW5zaWRlIGAgPGJlYW4gaWQ9InNoaWJib2xldGguRGVmYXVsdFJlbHlpbmdQYXJ0eSIgcGFyZW50PSJSZWx5aW5nUGFydHkiPmAgY2hhbmdlIHRoZSBTQU1MMi5TU08gYXR0cmlidXRlcyBhY2NvcmRpbmcgdG8geW91ciBuZWVkczoNCg0KKiAoc2FtcGxlKTogYDxiZWFuIHBhcmVudD0iU0FNTDIuU1NPIiBwOnBvc3RBdXRoZW50aWNhdGlvbkZsb3dzPSJhdHRyaWJ1dGUtcmVsZWFzZSIgcDplbmNyeXB0QXNzZXJ0aW9ucz0iZmFsc2UiIHA6ZW5jcnlwdEF0dHJpYnV0ZXM9ImZhbHNlIiBwOmVuY3J5cHROYW1lSURzPSJmYWxzZSIvPmANCiANCk5vdyB3ZSBjYW4gc2VlIGluIGNsZWFyIHRleHQgKGFmdGVyIGRlY29kaW5nKSB0aGUgU3ViamVjdCBpbmZvIGJ1dCBzdXJwcmlzZShzKToNCg0KKiBOYW1lSUQgaGFzIGEgc3RyYW5nZSB2YWx1ZSAoc29tZSBCYXNlNjQgZW5jb2RlZCB2YWx1ZSkNCiogTm8gb3RoZXIgYXNzZXJ0aW9ucywgZXZlbiBpZiB3ZSBjb25maWd1cmVkIExEQVAgdG8gcmV0dXJuIG1vcmUgYXR0cmlidXRlcw0KDQojIE5hbWVJRA0KDQpJIHdhcyB1c2VkIHdpdGggQURGUyB3aGVyZSB5b3UgY2FuIGVhc2lseSByZXR1cm4gc0FNQWNvdW50TmFtZSBhcyBOYW1lSUQuIEl0IHR1cm5lZCBvdXQgdG8gYmUgYSB2ZXJ5IGRpZmZpY3VsdCB0YXNrIHNvIGF0IHRoZSBlbmQgb2YgdGhlIGRheSBJIGdhdmUgdXAgc2luY2UgSSAoc29tZWhvdykgdW5kZXJzdG9vZCB0aGF0IHRoZSByb2xlIG9mIE5hbWVJRCBpcyBkaWZmZXJlbnQgb2Ygd2hhdCBJIHdhbnRlZCB0byBhY2hpZXZlLg0KDQpTb21lIGluc2lnaHRzOg0KWW91IGNhbiBoYXZlIGEgbG9vayBhdCAoYXMgYSBzdGFydGluZyBkZWJ1Z2dpbmcgcG9pbnQpIGBnZXRTQU1MMk5hbWVJREdlbmVyYXRvcmAgbWV0aG9kIGZyb20gYG5ldC5zaGliYm9sZXRoLmlkcC5zYW1sLm5hbWVpZC5pbXBsLk5hbWVJZGVudGlmaWVyR2VuZXJhdGlvblNlcnZpY2VJbXBsYCwgbW9kdWxlIGBTaGliYm9sZXRoIElkUCA6OiBTQU1MIFByb2ZpbGUgSW1wbGVtZW50YXRpb25gLiBUaGUgbWFpbiBpc3N1ZSBoZXJlIGlzIHRoYXQgdGhlIHJldHVybmVkIHZhbHVlIChzYW1sMkdlbmVyYXRvcikgaXMgYW4gaW1wbGVtZW50YXRpb24gb2YgYG9yZy5vcGVuc2FtbC5zYW1sLnNhbWwyLnByb2ZpbGUuU0FNTDJOYW1lSURHZW5lcmF0b3JgLiBTaW5jZSBpbiB0aGUgbW9kdWxlIHRoZXJlIGlzIGEgY29tcGlsZSBkZXBlbmRlbmN5IG9ubHkgdG8gYG9wZW4tc2FtbC1hcGktMy4xLjEuamFyYCB5b3Ugd2lsbCBoYXZlIHRvIGFkZCBtYW51YWxseSBpbiBwb20ueG1sIHRoZSBkZXBlbmRlbmN5IHRvIHRoZSBhY3R1YWwgaW1wbGVtZW50YXRpb24uDQoNCmBgYHhtbA0KICAgICAgICA8ZGVwZW5kZW5jeT4NCiAgICAgICAgICAgIDxncm91cElkPiR7b3BlbnNhbWwuZ3JvdXBJZH08L2dyb3VwSWQ+DQogICAgICAgICAgICA8YXJ0aWZhY3RJZD5vcGVuc2FtbC1zYW1sLWltcGw8L2FydGlmYWN0SWQ+DQogICAgICAgICAgICA8dmVyc2lvbj4ke29wZW5zYW1sLnZlcnNpb259PC92ZXJzaW9uPg0KICAgICAgICA8L2RlcGVuZGVuY3k+DQogDQpgYGANCg0KQmUgc3VyZSB0byByZW1vdmUgdGhlIHRlc3QgdGltZSBkZXBlbmRlbmN5IChhIGNvbXBpbGUgZXJyb3Igd2lsbCBwcmV2ZW50IHRvIHN1Y2Nlc3NmdWxseSBjb21waWxlIHRoZSBtb2R1bGUgYW55d2F5KS4NCg0KVGhlIGRlZmF1bHQgY29uZmlndXJlZCBzdHJhdGVneSByZWxpZXMgb24gYFRyYW5zaWVudFNBTUwyTmFtZUlER2VuZXJhdG9yYCBhbmQgYENyeXB0b1RyYW5zaWVudElkR2VuZXJhdGlvblN0cmF0ZWd5YCAoYGdlbmVyYXRlYCBtZXRob2QpLg0KDQpDb25maWd1cmF0aW9uIG9mIHRoZSBzdHJhdGVneSBpcyBkb25lIGluIGBjb25mL3NhbWwtbmFtZWlkLnhtbGAuIEluaXRpYWxseSBJIHRyaWVkIHN3aXRjaGluZyBmcm9tIGA8cmVmIGJlYW49InNoaWJib2xldGguU0FNTDJUcmFuc2llbnRHZW5lcmF0b3IiIC8+YCB0byANCmBgYA0KCTwhLS0gdXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6MS4xOm5hbWVpZC1mb3JtYXQ6ZW1haWxBZGRyZXNzIC0tPg0KCTxiZWFuIHBhcmVudD0ic2hpYmJvbGV0aC5TQU1MMkF0dHJpYnV0ZVNvdXJjZWRHZW5lcmF0b3IiDQogICAgICAgICAgICBwOmZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6dHJhbnNpZW50Ig0KICAgICAgICAgICAgcDphdHRyaWJ1dGVTb3VyY2VJZHM9IiN7IHsnY24nfSB9IiAvPg0KDQpgYGANCldhdGNoIG91dCBvZiB0aGUgYHA6Zm9ybWF0YCBhdHRyaWJ1dGUgdmFsdWUsIGNoYW5nZWQgZnJvbSAqZW1haWwqIHRvICp0cmFuc2llbnQqIGFzIHRoZSBgbmV0LnNoaWJib2xldGguaWRwLnNhbWwubmFtZWlkLmltcGwuQXR0cmlidXRlU291cmNlZFNBTUwyTmFtZUlER2VuZXJhdG9yYCAob25lIG9mIHRoZSBzdWJzZXF1ZW50IGNhbGxzIHRvIG90aGVyIGNsYXNzZXMgbWV0aG9kcykgd2lsbCBjaGVjayAgZm9yIGF0dHJpYnV0ZXMgd2l0aCB0aGlzIGZvcm1hdC4NCg0KQW55d2F5LCBpdCBkaWRuJ3Qgd29yayBiZWNhdXNlIGhlcmUgd2Ugc3BlYWsgYWJvdXQgU0FNTCBhdHRyaWJ1dGVzICh0aGF0IHdpbGwgZ28gaW50byBBc3NlcnRpb25zKSAgbm90IGFib3V0IExEQVAgYXR0cmlidXRlcyBhbmQgc2luY2UgSSBkaWRuJ3QgZ2V0IGluIHRoZSByZXNwb25zZSBubyBBc3NlcnRpb25zLi4ubm8gTmFtZUlEIHdhcyBhbHNvIGdlbmVyYXRlZC4NCg0KIyBGaW5hbGx5IEdldHRpbmcgdGhlIEFzc2VydGlvbnMNCg0KQWZ0ZXIgZnVydGhlciBob3VycyBzcGVudCBpbiBhbmFseXNpbmcgb25saW5lIHdpa2kgaXQgd2FzIG9idmlvdXMgdGhhdCAgW0F0dHJpYnV0ZVJlc29sdmVyQ29uZmlndXJhdGlvbl0oaHR0cHM6Ly93aWtpLnNoaWJib2xldGgubmV0L2NvbmZsdWVuY2UvZGlzcGxheS9JRFAzMC9BdHRyaWJ1dGVSZXNvbHZlckNvbmZpZ3VyYXRpb24pIHdhcyBuZXh0IHN0ZXAgdG8gc3VjY2Vzcy4NCg0KSGF2aW5nIGEgbG9vayBpbiBgY29uZi9hdHRyaWJ1dGUtcmVzb2x2ZXIueG1sYCBhbmQgcHV0dGluZyBicmVha3BvaW50cyBpbiBjbGFzc2VzIGxpa2UgZnJvbSBgbmV0LnNoaWJib2xldGguaWRwLmF0dHJpYnV0ZS5yZXNvbHZlci5hZC5pbXBsYCBuYW1lc3BhY2UgbGlrZSAoYFByaW5jaXBhbE5hbWVBdHRyaWJ1dGVEZWZpbml0aW9uYCwgYFNjb3BlZEF0dHJpYnV0ZURlZmluaXRpb25gLCBgQWJzdHJhY3RBdHRyaWJ1dGVSZWxlYXNlQWN0aW9uYCBvciBgQXR0cmlidXRlUmVzb2x2ZXJJbXBsYCkgaXQgbG9va2VkIHRoYXQgZXZlcnl0aGluZyB3ZW50IE9rIHNpbmNlIEkndmUgZ290IGFsbCA0IGF0dHJpYnV0ZXMgd2l0aCB0aGUgZXhwZWN0ZWQgdmFsdWVzIChieSB3YXRjaGluZyB2YXJpYWJsZXMgZHVyaW5nIGJyZWFrcG9pbnQgc2Vzc2lvbnMpIGJ1dCBubyBBc3NlcnRpb25zIHdlcmUgZ2VuZXJhdGVkLg0KDQpTbyBJIGhhZCBhIGxvb2sgaW4gYGNvbmYvYXRyaWJ1dGUtZmlsdGVyLnhtbGAgYW5kIHN1cnByaXNlLCBhdHRyaWJ1dGUgZ2VuZXJhdGlvbiB3YXMgZmlsdGVyZWQgYnkgYSBmaWN0aW91cyBFbnRpdHlJZCBvZiAqaHR0cHM6Ly9zcC5leGFtcGxlLm9yZyouIEJ5IGNoYW5naW5nIHRoZSBhdHRyaWJ1dGUgKnZhbHVlKiB0byB0aGUgcmlnaHQgdmFsdWUgaW4gYDxhZnA6UG9saWN5UmVxdWlyZW1lbnRSdWxlIHhzaTp0eXBlPSJiYXNpYzpBdHRyaWJ1dGVSZXF1ZXN0ZXJTdHJpbmciIHZhbHVlPSI8bXlTUEVudGl0eUlEPiIgLz5gIGV2ZXJ5dGhpbmcgc3RhcnRlZCB0byB3b3JrIGFzIGV4cGVjdGVkLg0K

No comments :

Post a Comment