# Test Providers API use Test::More; use strict; use JSON; use IO::String; require 't/test-lib.pm'; our $_json = JSON->new->allow_nonref; sub check200 { my ( $test, $res ) = splice @_; #diag Dumper($res); ok( $res->[0] == 200, "$test: Result code is 200" ); count(1); checkJson( $test, $res ); } sub check404 { my ( $test, $res ) = splice @_; #diag Dumper($res); ok( $res->[0] == 404, "$test: Result code is 404" ); count(1); checkJson( $test, $res ); } sub check405 { my ( $test, $res ) = splice @_; ok( $res->[0] == 405, "$test: Result code is 405" ); count(1); checkJson( $test, $res ); } sub checkJson { my ( $test, $res ) = splice @_; my $key; #diag Dumper($res->[2]->[0]); ok( $key = from_json( $res->[2]->[0] ), "$test: Response is JSON" ); count(1); } sub add { my ( $test, $type, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; #diag Dumper($j); ok( $res = &client->_post( "/api/v1/providers/$type", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); count(1); return $res; } sub checkAdd { my ( $test, $type, $add ) = splice @_; check200( $test, add( $test, $type, $add ) ); } sub checkAddFailsIfExists { my ( $test, $type, $add ) = splice @_; check405( $test, add( $test, $type, $add ) ); } sub checkAddWithUnknownAttributes { my ( $test, $type, $add ) = splice @_; check405( $test, add( $test, $type, $add ) ); } sub get { my ( $test, $type, $confKey ) = splice @_; my $res; ok( $res = &client->_get( "/api/v1/providers/$type/$confKey", '' ), "$test: Request succeed" ); count(1); return $res; } sub checkGet { my ( $test, $type, $confKey, $attrPath, $expectedValue ) = splice @_; my $res = get( $test, $type, $confKey ); check200( $test, $res ); my @path = split '/', $attrPath; my $key = from_json( $res->[2]->[0] ); for (@path) { $key = $key->{$_}; } ok( $key eq $expectedValue, "$test: check if $attrPath value \"$key\" matches expected value \"$expectedValue\"" ); count(1); } sub checkGetNotFound { my ( $test, $type, $confKey ) = splice @_; check404( $test, get( $test, $type, $confKey ) ); } sub update { my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); #diag Dumper($j); my $res; ok( $res = &client->_patch( "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); count(1); return $res; } sub checkUpdate { my ( $test, $type, $confKey, $update ) = splice @_; check200( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateNotFound { my ( $test, $type, $confKey, $update ) = splice @_; check404( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateFailsIfExists { my ( $test, $type, $confKey, $update ) = splice @_; check405( $test, update( $test, $type, $confKey, $update ) ); } sub checkUpdateWithUnknownAttributes { my ( $test, $type, $confKey, $update ) = splice @_; check405( $test, update( $test, $type, $confKey, $update ) ); } sub replace { my ( $test, $type, $confKey, $obj ) = splice @_; my $j = $_json->encode($obj); my $res; ok( $res = &client->_put( "/api/v1/providers/$type/$confKey", '', IO::String->new($j), 'application/json', length($j) ), "$test: Request succeed" ); count(1); return $res; } sub checkReplace { my ( $test, $type, $confKey, $replace ) = splice @_; check200( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceAlreadyThere { my ( $test, $type, $confKey, $replace ) = splice @_; check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub checkReplaceNotFound { my ( $test, $type, $confKey, $update ) = splice @_; check404( $test, replace( $test, $type, $confKey, $update ) ); } sub checkReplaceWithUnknownAttribute { my ( $test, $type, $confKey, $replace ) = splice @_; check405( $test, replace( $test, $type, $confKey, $replace ) ); } sub findByConfKey { my ( $test, $type, $confKey ) = splice @_; my $res; ok( $res = &client->_get( "/api/v1/providers/$type/findByConfKey", "pattern=$confKey" ), "$test: Request succeed" ); count(1); return $res; } sub checkFindByConfKey { my ( $test, $type, $confKey, $expectedHits ) = splice @_; my $res = findByConfKey( $test, $type, $confKey ); check200( $test, $res ); my $hits = from_json( $res->[2]->[0] ); my $hit; my $counter = 0; foreach $hit ( @{$hits} ) { $counter++; ok( $hit->{confKey} =~ $confKey, "$test: check if confKey value \"$hit->{confKey}\" matches pattern \"$confKey\"" ); count(1); } ok( $counter eq $expectedHits, "$test: check if nb of hits returned ($counter) matches expectation ($expectedHits)" ); count(1); } sub findByProviderId { my ( $test, $type, $providerIdName, $providerId ) = splice @_; my $res; ok( $res = &client->_get( "/api/v1/providers/$type/findBy" . ucfirst $providerIdName, "$providerIdName=$providerId" ), "$test: Request succeed" ); count(1); return $res; } sub checkFindByProviderId { my ( $test, $type, $providerIdName, $providerId ) = splice @_; my $res = findByProviderId( $test, $type, $providerIdName, $providerId ); check200( $test, $res ); my $result = from_json( $res->[2]->[0] ); my $gotProviderId; if ( $providerIdName eq 'entityId' ) { ($gotProviderId) = $result->{metadata} =~ m/entityID=['"](.+?)['"]/i; } else { $gotProviderId = $result->{$providerIdName}; } ok( $gotProviderId eq $providerId, "$test: Check $providerIdName value returned \"$gotProviderId\" matched expected value \"$providerId\"" ); count(1); } sub checkFindByProviderIdNotFound { my ( $test, $type, $providerIdName, $providerId ) = splice @_; check404( $test, findByProviderId( $test, $type, $providerIdName, $providerId ) ); } sub deleteProvider { my ( $test, $type, $confKey ) = splice @_; my $res; ok( $res = &client->_del( "/api/v1/providers/$type/$confKey", '', '', 'application/json', 0 ), "$test: Request succeed" ); count(1); return $res; } sub checkDelete { my ( $test, $type, $confKey ) = splice @_; check200( $test, deleteProvider( $test, $type, $confKey ) ); } sub checkDeleteNotFound { my ( $test, $type, $confKey ) = splice @_; check404( $test, deleteProvider( $test, $type, $confKey ) ); } my $test; my $oidcRp = { confKey => 'myOidcRp1', clientId => 'myOidcClient1', exportedVars => { 'sub' => "uid", family_name => "sn", given_name => "givenName" }, extraClaim => { phone => 'telephoneNumber', email => 'mail' }, options => { oidcRPMetaDataOptionsClientSecret => 'secret', oidcRPMetaDataOptionsIcon => 'web.png' } }; $test = "OidcRp - Add should succeed"; checkAdd( $test, 'oidc/rp', $oidcRp ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret' ); $test = "OidcRp - Check attribute default value was set after add"; checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Add Should fail on duplicate confKey"; checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should succeed and keep existing values"; $oidcRp->{options}->{oidcRPMetaDataOptionsClientSecret} = 'secret2'; $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg} = 'RS512'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{extraClaim}; delete $oidcRp->{exportedVars}; $oidcRp->{exportedVars}->{cn} = 'cn'; checkUpdate( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsClientSecret', 'secret2' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'RS512' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'options/oidcRPMetaDataOptionsIcon', 'web.png' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/cn', 'cn' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'exportedVars/family_name', 'sn' ); checkGet( $test, 'oidc/rp', 'myOidcRp1', 'extraClaim/phone', 'telephoneNumber' ); $test = "OidcRp - Update should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; checkUpdateWithUnknownAttributes( $test, 'oidc/rp', 'myOidcRp1', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Add Should fail on duplicate clientId"; $oidcRp->{confKey} = 'myOidcRp2'; checkAddFailsIfExists( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Add Should fail on non existing options"; $oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; $oidcRp->{options}->{playingPossum} = 'ElephantInTheRoom'; checkAddWithUnknownAttributes( $test, 'oidc/rp', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - 2nd add should succeed"; checkAdd( $test, 'oidc/rp', $oidcRp ); $test = "OidcRp - Update should fail if client id exists"; $oidcRp->{clientId} = 'myOidcClient1'; checkUpdateFailsIfExists( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Update should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; checkUpdateNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); $test = "OidcRp - Replace should succeed"; $oidcRp->{confKey} = 'myOidcRp2'; $oidcRp->{clientId} = 'myOidcClient2'; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIcon}; delete $oidcRp->{options}->{oidcRPMetaDataOptionsIDTokenSignAlg}; checkReplace( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); $test = "OidcRp - Check attribute default value was set after replace"; checkGet( $test, 'oidc/rp', 'myOidcRp2', 'options/oidcRPMetaDataOptionsIDTokenSignAlg', 'HS512' ); $test = "OidcRp - Replace should fail on non existing options"; $oidcRp->{options}->{playingPossum} = 'elephant'; checkReplaceWithUnknownAttribute( $test, 'oidc/rp', 'myOidcRp2', $oidcRp ); delete $oidcRp->{options}->{playingPossum}; $test = "OidcRp - Replace should fail if confKey not found"; $oidcRp->{confKey} = 'myOidcRp3'; checkReplaceNotFound( $test, 'oidc/rp', 'myOidcRp3', $oidcRp ); $test = "OidcRp - FindByConfKey should find 2 hits"; checkFindByConfKey( $test, 'oidc/rp', '^myOidcRp.$', 2 ); $test = "OidcRp - FindByConfKey should find 1 hit"; checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp1', 1 ); $test = "OidcRp - FindByConfKey should find 0 hits"; checkFindByConfKey( $test, 'oidc/rp', 'myOidcRp3', 0 ); $test = "OidcRp - FindByClientId should find one entry"; checkFindByProviderId( $test, 'oidc/rp', 'clientId', 'myOidcClient1' ); $test = "OidcRp - FindByClientId should find nothing"; checkFindByProviderIdNotFound( $test, 'oidc/rp', 'clientId', 'myOidcClient3' ); $test = "OidcRp - Clean up"; checkDelete( $test, 'oidc/rp', 'myOidcRp1' ); checkDelete( $test, 'oidc/rp', 'myOidcRp2' ); $test = "OidcRp - Entity should not be found after clean up"; checkDeleteNotFound( $test, 'oidc/rp', 'myOidcRp1' ); my $metadata1 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; my $metadata2 = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"; my $samlSp = { confKey => 'mySamlSp1', metadata => $metadata1, exportedAttributes => { family_name => { format => "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", friendlyName => "surname", mandatory => "false", name => "sn" }, cn => { friendlyName => "commonname", mandatory => "true", name => "uid" }, uid => { mandatory => "true", name => "uid" }, phone => { mandatory => "false", format => "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", name => "telephoneNumber" }, function => { name => "title", mandatory => "false", format => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" }, given_name => { mandatory => "false", name => "givenName" } }, options => { samlSPMetaDataOptionsCheckSLOMessageSignature => 0, samlSPMetaDataOptionsEncryptionMode => "assertion", samlSPMetaDataOptionsSessionNotOnOrAfterTimeout => 36000 } }; $test = "SamlSp - Add should succeed"; checkAdd( $test, 'saml/sp', $samlSp ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsEncryptionMode', 'assertion' ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); $test = "SamlSp - Check attribute default value was set after add"; checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsNotOnOrAfterTimeout', 72000 ); $test = "SamlSp - Add Should fail on duplicate confKey"; checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should succeed and keep existing values"; $samlSp->{options}->{samlSPMetaDataOptionsCheckSLOMessageSignature} = 1; $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode} = 'nameid'; delete $samlSp->{options}->{samlSPMetaDataOptionsSessionNotOnOrAfterTimeout}; delete $samlSp->{exportedAttributes}; $samlSp->{exportedAttributes}->{cn}->{name} = "cn", $samlSp->{exportedAttributes}->{cn}->{friendlyName} = "common_name", $samlSp->{exportedAttributes}->{cn}->{mandatory} = "false", checkUpdate( $test, 'saml/sp', 'mySamlSp1', $samlSp ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsCheckSLOMessageSignature', 1 ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'options/samlSPMetaDataOptionsSessionNotOnOrAfterTimeout', 36000 ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/friendlyName', 'common_name' ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false' ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/mandatory', 'false' ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/cn/name', 'uid' ); checkGet( $test, 'saml/sp', 'mySamlSp1', 'exportedAttributes/given_name/name', 'givenName' ); $test = "SamlSp - Update should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; checkUpdateWithUnknownAttributes( $test, 'saml/sp', 'mySamlSp1', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Add Should fail on duplicate entityId"; $samlSp->{confKey} = 'mySamlSp2'; checkAddFailsIfExists( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Add Should fail on non existing options"; $samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; $samlSp->{options}->{playingPossum} = 'ElephantInTheRoom'; checkAddWithUnknownAttributes( $test, 'saml/sp', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - 2nd add should succeed"; checkAdd( $test, 'saml/sp', $samlSp ); $test = "SamlSp - Update should fail if client id exists"; $samlSp->{metadata} = $metadata1; checkUpdateFailsIfExists( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Update should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; checkUpdateNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); $test = "SamlSp - Replace should succeed"; $samlSp->{confKey} = 'mySamlSp2'; $samlSp->{metadata} = $metadata2; delete $samlSp->{options}->{samlSPMetaDataOptionsEncryptionMode}; checkReplace( $test, 'saml/sp', 'mySamlSp2', $samlSp ); $test = "SamlSp - Check attribute default value was set after replace"; checkGet( $test, 'saml/sp', 'mySamlSp2', 'options/samlSPMetaDataOptionsEncryptionMode', 'none' ); $test = "SamlSp - Replace should fail on non existing options"; $samlSp->{options}->{playingPossum} = 'elephant'; checkReplaceWithUnknownAttribute( $test, 'saml/sp', 'mySamlSp2', $samlSp ); delete $samlSp->{options}->{playingPossum}; $test = "SamlSp - Replace should fail if confKey not found"; $samlSp->{confKey} = 'mySamlSp3'; checkReplaceNotFound( $test, 'saml/sp', 'mySamlSp3', $samlSp ); $test = "SamlSp - FindByConfKey should find 2 hits"; checkFindByConfKey( $test, 'saml/sp', '^mySamlSp.$', 2 ); $test = "SamlSp - FindByConfKey should find 1 hit"; checkFindByConfKey( $test, 'saml/sp', 'mySamlSp1', 1 ); $test = "SamlSp - FindByConfKey should find 0 hits"; checkFindByConfKey( $test, 'saml/sp', 'mySamlSp3', 0 ); $test = "SamlSp - FindByEntityId should find one entry"; checkFindByProviderId( $test, 'saml/sp', 'entityId', 'https://myapp.domain.com/saml/metadata' ); $test = "SamlSp - FindByEntityId should find nothing"; checkFindByProviderIdNotFound( $test, 'saml/sp', 'entityId', 'https://myapp3.domain.com/saml/metadata' ); $test = "SamlSp - Clean up"; checkDelete( $test, 'saml/sp', 'mySamlSp1' ); checkDelete( $test, 'saml/sp', 'mySamlSp2' ); $test = "SamlSp - Entity should not be found after clean up"; checkDeleteNotFound( $test, 'saml/sp', 'mySamlSp1' ); # Clean up generated conf files, except for "lmConf-1.json" unlink grep { $_ ne "t/conf/lmConf-1.json" } glob "t/conf/lmConf-*.json"; done_testing();