| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  | <?php namespace Tests; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  | use BookStack\Auth\Role; | 
					
						
							|  |  |  | use BookStack\Auth\User; | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-22 21:17:14 +08:00
										 |  |  | class Saml2Test extends TestCase | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function setUp(): void | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         parent::setUp(); | 
					
						
							|  |  |  |         // Set default config for SAML2
 | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  |             'saml2.name' => 'SingleSignOn-Testing', | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  |             'saml2.enabled' => true, | 
					
						
							|  |  |  |             'saml2.auto_register' => true, | 
					
						
							|  |  |  |             'saml2.email_attribute' => 'email', | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  |             'saml2.display_name_attributes' => ['first_name', 'last_name'], | 
					
						
							|  |  |  |             'saml2.external_id_attribute' => 'uid', | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  |             'saml2.user_to_groups' => false, | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  |             'saml2.group_attribute' => 'user_groups', | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  |             'saml2.remove_from_groups' => false, | 
					
						
							|  |  |  |             'saml2.onelogin_overrides' => null, | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  |             'saml2.onelogin.idp.entityId' => 'http://saml.local/saml2/idp/metadata.php', | 
					
						
							|  |  |  |             'saml2.onelogin.idp.singleSignOnService.url' => 'http://saml.local/saml2/idp/SSOService.php', | 
					
						
							|  |  |  |             'saml2.onelogin.idp.singleLogoutService.url' => 'http://saml.local/saml2/idp/SingleLogoutService.php', | 
					
						
							| 
									
										
										
										
											2019-11-17 22:44:26 +08:00
										 |  |  |             'saml2.autoload_from_metadata' => false, | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  |             'saml2.onelogin.idp.x509cert' => $this->testCert, | 
					
						
							|  |  |  |             'saml2.onelogin.debug' => false, | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  |         ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_metadata_endpoint_displays_xml_as_expected() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $req = $this->get('/saml2/metadata'); | 
					
						
							|  |  |  |         $req->assertHeader('Content-Type', 'text/xml; charset=UTF-8'); | 
					
						
							|  |  |  |         $req->assertSee('md:EntityDescriptor'); | 
					
						
							|  |  |  |         $req->assertSee(url('/saml2/acs')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_onelogin_overrides_functions_as_expected() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $json = '{"sp": {"assertionConsumerService": {"url": "https://example.com/super-cats"}}, "contactPerson": {"technical": {"givenName": "Barry Scott", "emailAddress": "barry@example.com"}}}'; | 
					
						
							|  |  |  |         config()->set(['saml2.onelogin_overrides' => $json]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $req = $this->get('/saml2/metadata'); | 
					
						
							|  |  |  |         $req->assertSee('https://example.com/super-cats'); | 
					
						
							|  |  |  |         $req->assertSee('md:ContactPerson'); | 
					
						
							|  |  |  |         $req->assertSee('<md:GivenName>Barry Scott</md:GivenName>'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-18 03:15:37 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public function test_login_option_shows_on_login_page() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $req = $this->get('/login'); | 
					
						
							|  |  |  |         $req->assertSeeText('SingleSignOn-Testing'); | 
					
						
							|  |  |  |         $req->assertElementExists('a[href$="/saml2/login"]'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_login_option_shows_on_register_page_only_when_auto_register_enabled() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->setSettings(['app-public' => 'true', 'registration-enabled' => 'true']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $req = $this->get('/register'); | 
					
						
							|  |  |  |         $req->assertSeeText('SingleSignOn-Testing'); | 
					
						
							|  |  |  |         $req->assertElementExists('a[href$="/saml2/login"]'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config()->set(['saml2.auto_register' => false]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $req = $this->get('/register'); | 
					
						
							|  |  |  |         $req->assertDontSeeText('SingleSignOn-Testing'); | 
					
						
							|  |  |  |         $req->assertElementNotExists('a[href$="/saml2/login"]'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_login() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $req = $this->get('/saml2/login'); | 
					
						
							|  |  |  |         $redirect = $req->headers->get('location'); | 
					
						
							|  |  |  |         $this->assertStringStartsWith('http://saml.local/saml2/idp/SSOService.php', $redirect, 'Login redirects to SSO location'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config()->set(['saml2.onelogin.strict' => false]); | 
					
						
							|  |  |  |         $this->assertFalse($this->isAuthenticated()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $acsPost->assertRedirect('/'); | 
					
						
							|  |  |  |             $this->assertTrue($this->isAuthenticated()); | 
					
						
							|  |  |  |             $this->assertDatabaseHas('users', [ | 
					
						
							|  |  |  |                 'email' => 'user@example.com', | 
					
						
							|  |  |  |                 'external_auth_id' => 'user', | 
					
						
							|  |  |  |                 'email_confirmed' => true, | 
					
						
							|  |  |  |                 'name' => 'Barry Scott' | 
					
						
							|  |  |  |             ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_group_role_sync_on_login() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |             'saml2.user_to_groups' => true, | 
					
						
							|  |  |  |             'saml2.remove_from_groups' => false, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $memberRole = factory(Role::class)->create(['external_auth_id' => 'member']); | 
					
						
							|  |  |  |         $adminRole = Role::getSystemRole('admin'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () use ($memberRole, $adminRole) { | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $user = User::query()->where('external_auth_id', '=', 'user')->first(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $userRoleIds = $user->roles()->pluck('id'); | 
					
						
							|  |  |  |             $this->assertContains($memberRole->id, $userRoleIds, 'User was assigned to member role'); | 
					
						
							|  |  |  |             $this->assertContains($adminRole->id, $userRoleIds, 'User was assigned to admin role'); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_group_role_sync_removal_option_works_as_expected() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |             'saml2.user_to_groups' => true, | 
					
						
							|  |  |  |             'saml2.remove_from_groups' => true, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () { | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $user = User::query()->where('external_auth_id', '=', 'user')->first(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $randomRole = factory(Role::class)->create(['external_auth_id' => 'random']); | 
					
						
							|  |  |  |             $user->attachRole($randomRole); | 
					
						
							|  |  |  |             $this->assertContains($randomRole->id, $user->roles()->pluck('id')); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             auth()->logout(); | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $this->assertNotContains($randomRole->id, $user->roles()->pluck('id')); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_logout_redirects_to_saml_logout_when_active_saml_session() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () { | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $lastLoginType = session()->get('last_login_type'); | 
					
						
							|  |  |  |             $this->assertEquals('saml2', $lastLoginType); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $req = $this->get('/logout'); | 
					
						
							|  |  |  |             $req->assertRedirect('/saml2/logout'); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_logout_sls_flow() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $handleLogoutResponse = function () { | 
					
						
							|  |  |  |             $this->assertTrue($this->isAuthenticated()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $req = $this->get('/saml2/sls'); | 
					
						
							|  |  |  |             $req->assertRedirect('/'); | 
					
						
							|  |  |  |             $this->assertFalse($this->isAuthenticated()); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $loginAndStartLogout = function () use ($handleLogoutResponse) { | 
					
						
							|  |  |  |             $this->post('/saml2/acs'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $req = $this->get('/saml2/logout'); | 
					
						
							|  |  |  |             $redirect = $req->headers->get('location'); | 
					
						
							|  |  |  |             $this->assertStringStartsWith('http://saml.local/saml2/idp/SingleLogoutService.php', $redirect); | 
					
						
							|  |  |  |             $this->withGet(['SAMLResponse' => $this->sloResponseData], $handleLogoutResponse); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], $loginAndStartLogout); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_logout_sls_flow_when_sls_not_configured() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |             'saml2.onelogin.idp.singleLogoutService.url' => null, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $loginAndStartLogout = function () { | 
					
						
							|  |  |  |             $this->post('/saml2/acs'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $req = $this->get('/saml2/logout'); | 
					
						
							|  |  |  |             $req->assertRedirect('/'); | 
					
						
							|  |  |  |             $this->assertFalse($this->isAuthenticated()); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], $loginAndStartLogout); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_dump_user_details_option_works() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |             'saml2.dump_user_details' => true, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () { | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $acsPost->assertJsonStructure([ | 
					
						
							|  |  |  |                 'id_from_idp', | 
					
						
							|  |  |  |                 'attrs_from_idp' => [], | 
					
						
							|  |  |  |                 'attrs_after_parsing' => [], | 
					
						
							|  |  |  |             ]); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_user_registration_with_existing_email() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set([ | 
					
						
							|  |  |  |             'saml2.onelogin.strict' => false, | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $viewer = $this->getViewer(); | 
					
						
							|  |  |  |         $viewer->email = 'user@example.com'; | 
					
						
							|  |  |  |         $viewer->save(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $this->withPost(['SAMLResponse' => $this->acsPostData], function () { | 
					
						
							|  |  |  |             $acsPost = $this->post('/saml2/acs'); | 
					
						
							|  |  |  |             $acsPost->assertRedirect('/'); | 
					
						
							|  |  |  |             $errorMessage = session()->get('error'); | 
					
						
							|  |  |  |             $this->assertEquals('Registration unsuccessful since a user already exists with email address "user@example.com"', $errorMessage); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function test_saml_routes_are_only_active_if_saml_enabled() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         config()->set(['saml2.enabled' => false]); | 
					
						
							|  |  |  |         $getRoutes = ['/login', '/logout', '/metadata', '/sls']; | 
					
						
							|  |  |  |         foreach ($getRoutes as $route) { | 
					
						
							|  |  |  |             $req = $this->get('/saml2' . $route); | 
					
						
							|  |  |  |             $req->assertRedirect('/'); | 
					
						
							|  |  |  |             $error = session()->get('error'); | 
					
						
							|  |  |  |             $this->assertStringStartsWith('You do not have permission to access', $error); | 
					
						
							|  |  |  |             session()->flush(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $postRoutes = ['/acs']; | 
					
						
							|  |  |  |         foreach ($postRoutes as $route) { | 
					
						
							|  |  |  |             $req = $this->post('/saml2' . $route); | 
					
						
							|  |  |  |             $req->assertRedirect('/'); | 
					
						
							|  |  |  |             $error = session()->get('error'); | 
					
						
							|  |  |  |             $this->assertStringStartsWith('You do not have permission to access', $error); | 
					
						
							|  |  |  |             session()->flush(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected function withGet(array $options, callable $callback) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->withGlobal($_GET, $options, $callback); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected function withPost(array $options, callable $callback) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->withGlobal($_POST, $options, $callback); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected function withGlobal(array &$global, array $options, callable $callback) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $original = []; | 
					
						
							|  |  |  |         foreach ($options as $key => $val) { | 
					
						
							|  |  |  |             $original[$key] = $global[$key] ?? null; | 
					
						
							|  |  |  |             $global[$key] = $val; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $callback(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foreach ($options as $key => $val) { | 
					
						
							|  |  |  |             $val = $original[$key]; | 
					
						
							|  |  |  |             if ($val) { | 
					
						
							|  |  |  |                 $global[$key] = $val; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 unset($global[$key]); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The post data for a callback for single-sign-in. | 
					
						
							|  |  |  |      * Provides the following attributes: | 
					
						
							|  |  |  |      * array:5 [ | 
					
						
							|  |  |  |         "uid" => array:1 [ | 
					
						
							|  |  |  |             0 => "user" | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         "first_name" => array:1 [ | 
					
						
							|  |  |  |             0 => "Barry" | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         "last_name" => array:1 [ | 
					
						
							|  |  |  |             0 => "Scott" | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         "email" => array:1 [ | 
					
						
							|  |  |  |             0 => "user@example.com" | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         "user_groups" => array:2 [ | 
					
						
							|  |  |  |             0 => "member" | 
					
						
							|  |  |  |             1 => "admin" | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     protected $acsPostData = 'PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfNGRkNDU2NGRjNzk0MDYxZWYxYmFhMDQ2N2Q3OTAyOGNlZDNjZTU0YmVlIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxOS0xMS0xN1QxNzo1MzozOVoiIERlc3RpbmF0aW9uPSJodHRwOi8vYm9va3N0YWNrLmxvY2FsL3NhbWwyL2FjcyIgSW5SZXNwb25zZVRvPSJPTkVMT0dJTl82YTBmNGYzOTkzMDQwZjE5ODdmZDM3MDY4YjUyOTYyMjlhZDUzNjFjIj48c2FtbDpJc3N1ZXI+aHR0cDovL3NhbWwubG9jYWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+CiAgPGRzOlNpZ25lZEluZm8+PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz4KICAgIDxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CiAgPGRzOlJlZmVyZW5jZSBVUkk9IiNfNGRkNDU2NGRjNzk0MDYxZWYxYmFhMDQ2N2Q3OTAyOGNlZDNjZTU0YmVlIj48ZHM6VHJhbnNmb3Jtcz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz48ZHM6RGlnZXN0VmFsdWU+dm1oL1M3NU5mK2crZWNESkN6QWJaV0tKVmx1ZzdCZnNDKzlhV05lSXJlUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU+dnJhZ0tKWHNjVm5UNjJFaEk3bGk4MERUWHNOTGJOc3lwNWZ2QnU4WjFYSEtFUVA3QWpPNkcxcVBwaGpWQ2dRMzd6TldVVTZvUytQeFA3UDlHeG5xL3hKejRUT3lHcHJ5N1RoK2pIcHc0YWVzQTdrTmp6VU51UmU2c1ltWTlrRXh2VjMvTmJRZjROMlM2Y2RhRHIzWFRodllVVDcxYzQwNVVHOFJpQjJaY3liWHIxZU1yWCtXUDBnU2Qrc0F2RExqTjBJc3pVWlVUNThadFpEVE1ya1ZGL0pIbFBFQ04vVW1sYVBBeitTcUJ4c25xTndZK1oxYUt3MnlqeFRlNnUxM09Kb29OOVN1REowNE0rK2F3RlY3NkI4cXEyTzMxa3FBbDJibm1wTGxtTWdRNFEraUlnL3dCc09abTV1clphOWJObDNLVEhtTVBXbFpkbWhsLzgvMy9IT1RxN2thWGs3cnlWRHRLcFlsZ3FUajNhRUpuL0dwM2o4SFp5MUVialRiOTRRT1ZQMG5IQzB1V2hCaE13TjdzVjFrUSsxU2NjUlpUZXJKSGlSVUQvR0srTVg3M0YrbzJVTFRIL1Z6Tm9SM2o4N2hOLzZ1UC9JeG5aM1RudGR1MFZPZS9ucEdVWjBSMG9SWFhwa2JTL2poNWk1ZjU0RXN4eXZ1VEM5NHdKaEM8L2RzOlNpZ25hdHVyZVZhbHVlPgo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlFYXpDQ0F0T2dBd0lCQWdJVWU3YTA4OENucjRpem1ybkJFbng1cTNIVE12WXdEUVlKS29aSWh2Y05BUUVMQlFBd1JURUxNQWtHQTFVRUJoTUNSMEl4RXpBUkJnTlZCQWdNQ2xOdmJXVXRVM1JoZEdVeElUQWZCZ05WQkFvTUdFbHVkR1Z5Ym1WMElGZHBaR2RwZEhNZ1VIUjVJRXgwWkRBZUZ3MHhPVEV4TVRZeE1qRTNNVFZhRncweU9URXhNVFV4TWpFM01UVmFNRVV4Q3pBSkJnTlZCQVlUQWtkQ01STXdFUVlEVlFRSURBcFRiMjFsTFZOMFlYUmxNU0V3SHdZRFZRUUtEQmhKYm5SbGNtNWxkQ0JYYVdSbmFYUnpJRkIwZVNCTWRHUXdnZ0dpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCandBd2dnR0tBb0lCZ1FEekxlOUZmZHlwbFR4SHA0U3VROWdRdFpUM3QrU0RmdkVMNzJwcENmRlp3NytCNXM1Qi9UNzNhWHBvUTNTNTNwR0kxUklXQ2dlMmlDVVEydHptMjdhU05IMGl1OWFKWWNVUVovUklUcWQwYXl5RGtzMU5BMlBUM1RXNnQzbTdLVjVyZTRQME5iK1lEZXV5SGRreitqY010cG44Q21Cb1QwSCtza2hhMGhpcUlOa2prUlBpSHZMSFZHcCt0SFVFQS9JNm1ONGFCL1VFeFNUTHM3OU5zTFVmdGVxcXhlOSt0dmRVYVRveURQcmhQRmpPTnMrOU5LQ2t6SUM2dmN2N0o2QXR1S0c2bkVUK3pCOXlPV2d0R1lRaWZYcVFBMnk1ZEw4MUJCMHE1dU1hQkxTMnBxM2FQUGp6VTJGMytFeXNqeVNXVG5Da2ZrN0M1U3NDWFJ1OFErVTk1dHVucE5md2Y1b2xFNldhczQ4Tk1NK1B3VjdpQ05NUGtOemxscTZQQ2lNK1A4RHJNU2N6elVaWlFVU3Y2ZFN3UENvK1lTVmltRU0wT2czWEpUaU5oUTVBTmxhSW42Nkt3NWdmb0JmdWlYbXlJS2lTRHlBaURZbUZhZjQzOTV3V3dMa1RSK2N3OFdmamFIc3dLWlRvbW4xTVIzT0pzWTJVSjBlUkJZTStZU3NDQXdFQUFhTlRNRkV3SFFZRFZSME9CQllFRkltcDJDWUNHZmNiN3c5MUgvY1NoVENrWHdSL01COEdBMVVkSXdRWU1CYUFGSW1wMkNZQ0dmY2I3dzkxSC9jU2hUQ2tYd1IvTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFTEJRQURnZ0dCQUErZy9DN3VMOWxuK1crcUJrbkxXODFrb2pZZmxnUEsxSTFNSEl3bk12bC9aVEhYNGRSWEtEcms3S2NVcTFLanFhak5WNjZmMWNha3AwM0lpakJpTzBYaTFnWFVaWUxvQ2lOR1V5eXA5WGxvaUl5OVh3MlBpV25ydzAreVp5dlZzc2JlaFhYWUpsNFJpaEJqQld1bDlSNHdNWUxPVVNKRGUyV3hjVUJoSm54eU5ScytQMHhMU1FYNkIybjZueG9Ea280cDA3czhaS1hRa2VpWjJpd0ZkVHh6UmtHanRoTVV2NzA0bnpzVkdCVDBEQ1B0ZlNhTzVLSlpXMXJDczN5aU10aG5CeHE0cUVET1FKRklsKy9MRDcxS2JCOXZaY1c1SnVhdnpCRm1rS0dOcm8vNkcxSTdlbDQ2SVI0d2lqVHlORkNZVXVEOWR0aWduTm1wV3ROOE9XK3B0aUwvanRUeVNXdWtqeXMwcyt2TG44M0NWdmpCMGRKdFZBSVlPZ1hGZ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected $sloResponseData = 'fZHRa8IwEMb/lZJ3bdJa04a2MOYYglOY4sNe5JKms9gmpZfC/vxF3ZjC8OXgLvl938ddjtC1vVjZTzu6d429NaiDr641KC5PBRkHIyxgg8JAp1E4JbZPbysRTanoB+ussi25QR4TgKgH11hDguWiIIeawTxOaK1iPYt5XcczHUlJeVRlMklBJjOuM1qDVCTY6wE9WRAv5HHEUS8NOjDOjyjLJoxNGN+xVESpSNgHCRYaXWPAXaijc70IQ2ntyUPqNG2tgjY8Z45CbNFLmt8V7GxBNuuX1eZ1uT7EcZJKAE4TJhXPaMxlVlFffPKKJnXE5ryusoiU+VlMXJIN5Y/feXRn1VR92GkHFTiY9sc+D2+p/HqRrQM34n33bCsd7KEd9eMd4+W32I5KaUQSlleHP9Hwv6uX3w=='; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected $testCert = 'MIIEazCCAtOgAwIBAgIUe7a088Cnr4izmrnBEnx5q3HTMvYwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTExMTYxMjE3MTVaFw0yOTExMTUxMjE3MTVaMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDzLe9FfdyplTxHp4SuQ9gQtZT3t+SDfvEL72ppCfFZw7+B5s5B/T73aXpoQ3S53pGI1RIWCge2iCUQ2tzm27aSNH0iu9aJYcUQZ/RITqd0ayyDks1NA2PT3TW6t3m7KV5re4P0Nb+YDeuyHdkz+jcMtpn8CmBoT0H+skha0hiqINkjkRPiHvLHVGp+tHUEA/I6mN4aB/UExSTLs79NsLUfteqqxe9+tvdUaToyDPrhPFjONs+9NKCkzIC6vcv7J6AtuKG6nET+zB9yOWgtGYQifXqQA2y5dL81BB0q5uMaBLS2pq3aPPjzU2F3+EysjySWTnCkfk7C5SsCXRu8Q+U95tunpNfwf5olE6Was48NMM+PwV7iCNMPkNzllq6PCiM+P8DrMSczzUZZQUSv6dSwPCo+YSVimEM0Og3XJTiNhQ5ANlaIn66Kw5gfoBfuiXmyIKiSDyAiDYmFaf4395wWwLkTR+cw8WfjaHswKZTomn1MR3OJsY2UJ0eRBYM+YSsCAwEAAaNTMFEwHQYDVR0OBBYEFImp2CYCGfcb7w91H/cShTCkXwR/MB8GA1UdIwQYMBaAFImp2CYCGfcb7w91H/cShTCkXwR/MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggGBAA+g/C7uL9ln+W+qBknLW81kojYflgPK1I1MHIwnMvl/ZTHX4dRXKDrk7KcUq1KjqajNV66f1cakp03IijBiO0Xi1gXUZYLoCiNGUyyp9XloiIy9Xw2PiWnrw0+yZyvVssbehXXYJl4RihBjBWul9R4wMYLOUSJDe2WxcUBhJnxyNRs+P0xLSQX6B2n6nxoDko4p07s8ZKXQkeiZ2iwFdTxzRkGjthMUv704nzsVGBT0DCPtfSaO5KJZW1rCs3yiMthnBxq4qEDOQJFIl+/LD71KbB9vZcW5JuavzBFmkKGNro/6G1I7el46IR4wijTyNFCYUuD9dtignNmpWtN8OW+ptiL/jtTySWukjys0s+vLn83CVvjB0dJtVAIYOgXFdIuii66gczwwM/LGiOExJn0dTNzsJ/IYhpxL4FBEuP0pskY0o0aUlJ2LS2j+wSQTRKsBgMjyrUrekle2ODStStn3eabjIx0/FHlpFr0jNIm/oMP7kwjtUX4zaNe47QI4Gg=='; | 
					
						
							| 
									
										
										
										
											2019-11-17 21:26:43 +08:00
										 |  |  | } |