From 7451d92e2928df0d6053e647dd1465be69d244b9 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Thu, 31 May 2012 10:27:39 -0500 Subject: [PATCH 01/22] Minor performance improvement to Response. Now that we have conditional handling of content(), I added a tweak to slightly improve CURL performance. --- src/HPCloud/Transport/Response.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/HPCloud/Transport/Response.php b/src/HPCloud/Transport/Response.php index 6713726..b2476e6 100644 --- a/src/HPCloud/Transport/Response.php +++ b/src/HPCloud/Transport/Response.php @@ -177,12 +177,13 @@ class Response { } } else { - while (!feof($this->handle)) { - $out .= fread($this->handle, 8192); - } + // XXX: This works fine with CURL, but will not + // work with PHP HTTP Stream Wrapper b/c the + // wrapper has a bug that will cause this to + // hang. + $out = stream_get_contents($this->handle); } - // Should we close or rewind? // Cannot rewind PHP HTTP streams. fclose($this->handle); From 3299840a14c7c4fc45d8ee90e247d6ddac9b8131 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Thu, 31 May 2012 11:34:18 -0500 Subject: [PATCH 02/22] Updated CDN and OS to use newFromIdentity(). --- doc/documentation.php | 10 +++++++--- doc/oo-tutorial.md | 3 +++ src/HPCloud/Storage/CDN.php | 22 ++++++++++++++++++++++ src/HPCloud/Storage/ObjectStorage.php | 18 ++++++++++++++++++ test/TestCase.php | 14 +------------- test/Tests/CDNTest.php | 12 ++++++++++++ test/Tests/ObjectStorageTest.php | 12 +++++++++++- 7 files changed, 74 insertions(+), 17 deletions(-) diff --git a/doc/documentation.php b/doc/documentation.php index 25a04de..2bdfeb5 100644 --- a/doc/documentation.php +++ b/doc/documentation.php @@ -171,12 +171,16 @@ * * @code * serviceCatalog('object-storage'); - * $objectStorageUrl = storageList[0]['endpoints'][0]['publicURL']; + * // $storageList = $identity->serviceCatalog('object-storage'); + * // $objectStorageUrl = storageList[0]['endpoints'][0]['publicURL']; * * // Create a new ObjectStorage instance: - * $objectStore = new \HPCloud\Storage\ObjectStorage($token, $objectStorageUrl); + * // $objectStore = new \HPCloud\Storage\ObjectStorage($token, $objectStorageUrl); + * + * // Or let ObjectStorage figure out which instance to use: + * $objectStore = \HPCloud\Storage\ObjectStorage::newFromIdentity($identity); * * // List containers: * print_r($objectStore->containers()); diff --git a/doc/oo-tutorial.md b/doc/oo-tutorial.md index 4f1f4d2..bc48453 100644 --- a/doc/oo-tutorial.md +++ b/doc/oo-tutorial.md @@ -269,6 +269,9 @@ Now we can get a new HPCloud::Storage::ObjectStorage instance: $catalog = $idService->serviceCatalog(); $store = ObjectStorage::newFromServiceCatalog($catalog, $token); + +// UPDATE: As of Beta 6, you can use newFromIdentity(): +// $store = ObjectStorage::newFromIdentity($idService); ?> ~~~ diff --git a/src/HPCloud/Storage/CDN.php b/src/HPCloud/Storage/CDN.php index cf1f17c..ad2bfa0 100644 --- a/src/HPCloud/Storage/CDN.php +++ b/src/HPCloud/Storage/CDN.php @@ -138,6 +138,28 @@ class CDN { */ protected $token; + /** + * Create a new instance from an IdentityServices object. + * + * This builds a new CDN instance form an authenticated + * IdentityServices object. + * + * In the service catalog, this selects the first service entry + * for CDN. At this time, that is sufficient. + * + * @param HPCloud::Services::IdentityServices $identity + * The identity to use. + * @retval object + * A CDN object or FALSE if no CDN services could be found + * in the catalog. + */ + public static function newFromIdentity($identity) { + $tok = $identity->token(); + $cat = $identity->serviceCatalog(); + + return self::newFromServiceCatalog($cat, $tok); + } + /** * Create a new CDN object based on a service catalog. * diff --git a/src/HPCloud/Storage/ObjectStorage.php b/src/HPCloud/Storage/ObjectStorage.php index 9336154..238982a 100644 --- a/src/HPCloud/Storage/ObjectStorage.php +++ b/src/HPCloud/Storage/ObjectStorage.php @@ -163,6 +163,24 @@ class ObjectStorage { return $store; } + /** + * Given an IdentityServices instance, create an ObjectStorage instance. + * + * This constructs a new ObjectStorage from an authenticated instance + * of an HPCloud::Services::IdentityServices object. + * + * @param HPCloud::Services::IdentityServices $identity + * An identity services object that already has a valid token and a + * service catalog. + * @retval object ObjectStorage + * A new ObjectStorage instance. + */ + public static function newFromIdentity($identity) { + $cat = $identity->serviceCatalog(); + $tok = $identity->token(); + return self::newFromServiceCatalog($cat, $tok); + } + /** * Given a service catalog and an token, create an ObjectStorage instance. * diff --git a/test/TestCase.php b/test/TestCase.php index b1e78b0..5d27f87 100644 --- a/test/TestCase.php +++ b/test/TestCase.php @@ -145,19 +145,7 @@ class TestCase extends \PHPUnit_Framework_TestCase { if ($reset || empty(self::$ostore)) { $ident = $this->identity($reset); - $services = $ident->serviceCatalog(\HPCloud\Storage\ObjectStorage::SERVICE_TYPE); - - if (empty($services)) { - throw new \Exception('No object-store service found.'); - } - - /* - //$serviceURL = $services[0]['endpoints'][0]['adminURL']; - $serviceURL = $services[0]['endpoints'][0]['publicURL']; - - $objStore = new \HPCloud\Storage\ObjectStorage($ident->token(), $serviceURL); - */ - $objStore = \HPCloud\Storage\ObjectStorage::newFromServiceCatalog($services, $ident->token()); + $objStore = \HPCloud\Storage\ObjectStorage::newFromIdentity($ident); self::$ostore = $objStore; diff --git a/test/Tests/CDNTest.php b/test/Tests/CDNTest.php index 9fc1d2d..cf2cad5 100644 --- a/test/Tests/CDNTest.php +++ b/test/Tests/CDNTest.php @@ -80,6 +80,18 @@ class CDNTest extends \HPCloud\Tests\TestCase { return $cdn; } + /** + * @depends testConstructor + */ + public function testNewFromIdentity() { + $ident = $this->identity(); + $cdn = CDN::newFromIdentity($ident); + + $this->assertInstanceOf('\HPCloud\Storage\CDN', $cdn); + + return $cdn; + } + /** * @depends testNewFromServiceCatalog */ diff --git a/test/Tests/ObjectStorageTest.php b/test/Tests/ObjectStorageTest.php index 540cffb..73657f5 100644 --- a/test/Tests/ObjectStorageTest.php +++ b/test/Tests/ObjectStorageTest.php @@ -77,7 +77,17 @@ class ObjectStorageTest extends \HPCloud\Tests\TestCase { } public function testNewFromServiceCatalog() { - $ostore = $this->objectStore(); + $ident = $this->identity(); + $tok = $ident->token(); + $cat = $ident->serviceCatalog(); + $ostore = \HPCloud\Storage\ObjectStorage::newFromServiceCatalog($cat, $tok); + $this->assertInstanceOf('\HPCloud\Storage\ObjectStorage', $ostore); + $this->assertTrue(strlen($ostore->token()) > 0); + } + + public function testNewFromIdnetity() { + $ident = $this->identity(); + $ostore = \HPCloud\Storage\ObjectStorage::newFromIdentity($ident); $this->assertInstanceOf('\HPCloud\Storage\ObjectStorage', $ostore); $this->assertTrue(strlen($ostore->token()) > 0); } From f1d69eb82fe07ae6450e45c4acb789da760e1cc8 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 4 Jun 2012 11:46:33 -0400 Subject: [PATCH 03/22] Added support to use Tenant Names as an alternative to Tenant IDs for authentication and rescoping. --- src/HPCloud/Services/IdentityServices.php | 124 ++++++++++++++++++++-- test/Tests/IdentityServicesTest.php | 76 ++++++++++++- 2 files changed, 188 insertions(+), 12 deletions(-) diff --git a/src/HPCloud/Services/IdentityServices.php b/src/HPCloud/Services/IdentityServices.php index 6316fdf..9ed9c65 100644 --- a/src/HPCloud/Services/IdentityServices.php +++ b/src/HPCloud/Services/IdentityServices.php @@ -288,17 +288,20 @@ class IdentityServices { } /** - * Authenticate to Identity Services with username, password, and tenant ID. + * Authenticate to Identity Services with username, password, and either + * tenant ID or tenant Name. * * Given an HPCloud username and password, authenticate to Identity Services. * Identity Services will then issue a token that can be used to access other * HPCloud services. * * If a tenant ID is provided, this will also associate the user with the - * given tenant ID. + * given tenant ID. If a tenant Name is provided, this will associate the user + * with the given tenant Name. Only the tenant ID or tenant Name needs to be + * given, not both. * - * If no tenant ID is given, it will likely be necessary to rescope() the - * request (See also tenants()). + * If no tenant ID or tenant Name is given, it will likely be necessary to + * rescope() the request (See also tenants()). * * Other authentication methods: * @@ -312,13 +315,16 @@ class IdentityServices { * @param string $tenantId * The tenant ID for this account. This can be obtained through the * HPCloud console. + * @param string $tenantName + * The tenant Name for this account. This can be obtained through the + * HPCloud console. * @throws HPCloud::Transport::AuthorizationException * If authentication failed. * @throws HPCloud::Exception * For abnormal network conditions. The message will give an indication as * to the underlying problem. */ - public function authenticateAsUser($username, $password, $tenantId = NULL) { + public function authenticateAsUser($username, $password, $tenantId = NULL, $tenantName = NULL) { $ops = array( 'passwordCredentials' => array( 'username' => $username, @@ -330,6 +336,12 @@ class IdentityServices { if (!empty($tenantId)) { $ops['tenantId'] = $tenantId; } + + // If a tenant name is provided, add it to the auth array. + if (!empty($tenantName)) { + $ops['tenantName'] = $tenantName; + } + return $this->authenticate($ops); } /** @@ -342,10 +354,11 @@ class IdentityServices { * The account ID and access key information can be found in the account * section of the console. * - * The third paramater allows you to specify a tenant ID. In order to access - * services, this object will need a tenant ID. If none is specified, it can - * be set later using rescope(). The tenants() method can be used to get a - * list of all available tenant IDs for this token. + * The third and fourth paramaters allow you to specify a tenant ID or + * tenantName. In order to access services, this object will need a tenant ID + * or tenant name. If none is specified, it can be set later using rescope(). + * The tenants() method can be used to get a list of all available tenant IDs + * for this token. * * Other authentication methods: * @@ -361,6 +374,9 @@ class IdentityServices { * @param string $tenantId * A valid tenant ID. This will be used to associate a tenant's services * with this token. + * @param string $tenantName + * The tenant Name for this account. This can be obtained through the + * HPCloud console. * @retval string * The auth token. * @throws HPCloud::Transport::AuthorizationException @@ -369,7 +385,7 @@ class IdentityServices { * For abnormal network conditions. The message will give an indication as * to the underlying problem. */ - public function authenticateAsAccount($account, $key, $tenantId = NULL) { + public function authenticateAsAccount($account, $key, $tenantId = NULL, $tenantName = NULL) { $ops = array( 'apiAccessKeyCredentials' => array( 'accessKey' => $account, @@ -380,6 +396,10 @@ class IdentityServices { if (!empty($tenantId)) { $ops['tenantId'] = $tenantId; } + if (!empty($tenantName)) { + $ops['tenantName'] = $tenantName; + } + return $this->authenticate($ops); } @@ -415,6 +435,24 @@ class IdentityServices { } } + /** + * Get the tenant name associated with this token. + * + * If this token has a tenant name, the name will be returned. Otherwise, this + * will return NULL. + * + * This will not be populated until after an authentication method has been + * run. + * + * @retval string + * The tenant name if available, or NULL. + */ + public function tenantName() { + if (!empty($this->tokenDetails['tenant']['name'])) { + return $this->tokenDetails['tenant']['name']; + } + } + /** * Get the token details. * @@ -623,6 +661,13 @@ class IdentityServices { } /** + * @see HPCloud::Services::IdentityServices::rescopeUsingTenantId() + */ + public function rescope($tenantId) { + return $this->rescopeUsingTenantId($tenantId); + } + + /** * Rescope the authentication token to a different tenant. * * Note that this will rebuild the service catalog and user information for @@ -652,7 +697,7 @@ class IdentityServices { * For abnormal network conditions. The message will give an indication as * to the underlying problem. */ - public function rescope($tenantId) { + public function rescopeUsingTenantId($tenantId) { $url = $this->url() . '/tokens'; $token = $this->token(); $data = array( @@ -679,6 +724,63 @@ class IdentityServices { return $this->token(); } + /** + * Rescope the authentication token to a different tenant. + * + * Note that this will rebuild the service catalog and user information for + * the current object, since this information is sensitive to tenant info. + * + * An authentication token can be in one of two states: + * + * - unscoped: It has no associated tenant ID. + * - scoped: It has a tenant ID, and can thus access that tenant's services. + * + * This method allows you to do any of the following: + * + * - Begin with an unscoped token, and assign it a tenant ID. + * - Change a token from one tenant ID to another (re-scoping). + * - Remove the tenant ID from a scoped token (unscoping). + * + * @param string $tenantName + * The tenant name that this present token should be bound to. If this is the + * empty string (`''`), the present token will be "unscoped" and its tenant + * name will be removed. + * + * @retval string + * The authentication token. + * @throws HPCloud::Transport::AuthorizationException + * If authentication failed. + * @throws HPCloud::Exception + * For abnormal network conditions. The message will give an indication as + * to the underlying problem. + */ + public function rescopeUsingTenantName($tenantName) { + $url = $this->url() . '/tokens'; + $token = $this->token(); + $data = array( + 'auth' => array( + 'tenantName' => $tenantName, + 'token' => array( + 'id' => $token, + ), + ), + ); + $body = json_encode($data); + + $headers = array( + 'Accept' => self::ACCEPT_TYPE, + 'Content-Type' => 'application/json', + 'Content-Length' => strlen($body), + //'X-Auth-Token' => $token, + ); + + $client = \HPCloud\Transport::instance(); + $response = $client->doRequest($url, 'POST', $headers, $body); + $this->handleResponse($response); + + return $this->token(); + } + /** * Given a response object, populate this object. * diff --git a/test/Tests/IdentityServicesTest.php b/test/Tests/IdentityServicesTest.php index d9e16ea..6d2fa00 100644 --- a/test/Tests/IdentityServicesTest.php +++ b/test/Tests/IdentityServicesTest.php @@ -169,6 +169,38 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $this->assertNotEmpty($service->token()); } + /** + * @depends testAuthenticateAsAccount + */ + public function testTenantName() { + $account = self::conf('hpcloud.identity.account'); + $secret = self::conf('hpcloud.identity.secret'); + $user = self::conf('hpcloud.identity.username'); + $pass = self::conf('hpcloud.identity.password'); + $tenantName = self::conf('hpcloud.identity.tenantName'); + + $service = new IdentityServices(self::conf('hpcloud.identity.url')); + $this->assertNull($service->tenantName()); + + $service->authenticateAsUser($user, $pass); + $this->assertEmpty($service->tenantName()); + + $service = new IdentityServices(self::conf('hpcloud.identity.url')); + $ret = $service->authenticateAsUser($user, $pass, NULL, $tenantName); + $this->assertNotEmpty($service->tenantName()); + + $service = new IdentityServices(self::conf('hpcloud.identity.url')); + $this->assertNull($service->tenantName()); + + $service->authenticateAsAccount($account, $secret); + $this->assertEmpty($service->tenantName()); + + $service = new IdentityServices(self::conf('hpcloud.identity.url')); + $ret = $service->authenticateAsAccount($account, $secret, NULL, $tenantName); + $this->assertNotEmpty($service->tenantName()); + $this->assertEquals($tenantName, $service->tenantName()); + } + /** * @depends testAuthenticateAsAccount */ @@ -319,7 +351,7 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $catalog = $service->serviceCatalog(); $this->assertEquals(1, count($catalog)); - $service->rescope($tenantId); + $service->rescopeUsingTenantId($tenantId); $details = $service->tokenDetails(); $this->assertEquals($tenantId, $details['tenant']['id']); @@ -327,6 +359,46 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $catalog = $service->serviceCatalog(); $this->assertGreaterThan(1, count($catalog)); + // Test unscoping + $service->rescopeUsingTenantId(''); + $details = $service->tokenDetails(); + $this->assertEmpty($details['tenant']); + $catalog = $service->serviceCatalog(); + $this->assertEquals(1, count($catalog)); + + } + + /** + * @group tenant + * @depends testTenants + */ + function testRescopeByTenantName() { + $service = new IdentityServices(self::conf('hpcloud.identity.url')); + $user = self::conf('hpcloud.identity.username'); + $pass = self::conf('hpcloud.identity.password'); + $tenantName = self::conf('hpcloud.identity.tenantName'); + + // Authenticate without a tenant ID. + $token = $service->authenticateAsUser($user, $pass); + + $this->assertNotEmpty($token); + + $details = $service->tokenDetails(); + $this->assertEmpty($details['tenant']); + + // With no tenant ID, there should be only + // one entry in the catalog. + $catalog = $service->serviceCatalog(); + $this->assertEquals(1, count($catalog)); + + $service->rescopeUsingTenantName($tenantName); + + $details = $service->tokenDetails(); + $this->assertEquals($tenantName, $details['tenant']['name']); + + $catalog = $service->serviceCatalog(); + $this->assertGreaterThan(1, count($catalog)); + // Test unscoping $service->rescope(''); $details = $service->tokenDetails(); @@ -335,4 +407,6 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $this->assertEquals(1, count($catalog)); } + + } From 0d8c38ef7aefe4b3be9fb0c6a64451458d39f9aa Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 11 Jun 2012 17:58:32 -0400 Subject: [PATCH 04/22] Fixed the swift v1.0 auth tests. --- test/TestCase.php | 4 ++-- test/example.settings.ini | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/TestCase.php b/test/TestCase.php index 5d27f87..51e1f65 100644 --- a/test/TestCase.php +++ b/test/TestCase.php @@ -102,8 +102,8 @@ class TestCase extends \PHPUnit_Framework_TestCase { $user = self::$settings['hpcloud.swift.account']; $key = self::$settings['hpcloud.swift.key']; - // $url = self::$settings['hpcloud.swift.url']; - $url = self::$settings['hpcloud.identity.url']; + $url = self::$settings['hpcloud.swift.url']; + //$url = self::$settings['hpcloud.identity.url']; return \HPCloud\Storage\ObjectStorage::newFromSwiftAuth($user, $key, $url); diff --git a/test/example.settings.ini b/test/example.settings.ini index 344b992..5072b11 100644 --- a/test/example.settings.ini +++ b/test/example.settings.ini @@ -4,9 +4,13 @@ ;;;;;;;;;;;;;;;;;; ; Settings to work with swift: +; Account is the tenandId:console username. hpcloud.swift.account = 12345678:87654321 +; Key is the console account password. hpcloud.swift.key = abcdef123456 -hpcloud.swift.url = https://region-a.geo-1.objects.hpcloudsvc.com/auth/v1.0/ +; URL is the same as used for identity services calls (including port) except +; with /auth/v1.0/ appended to the end. +hpcloud.swift.url = https://region-a.geo-1.identity.hpcloudsvc.com:35357/auth/v1.0/ ; Container used for testing. hpcloud.swift.container = "I♡HPCloud" From 51a2872512bc87800b2f1f7dd9cdea01c34ea2d5 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 12 Jun 2012 09:52:58 -0400 Subject: [PATCH 05/22] Added an IdentityServices factory to Bootstrap from Bootstrap config. Calling Bootstrap::identity() will return an IdentityServices object. If the token has expired it will get a new one. --- src/HPCloud/Bootstrap.php | 66 +++++++++++++++++++++++++++++ test/Tests/IdentityServicesTest.php | 41 ++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/src/HPCloud/Bootstrap.php b/src/HPCloud/Bootstrap.php index f493668..02a34bf 100644 --- a/src/HPCloud/Bootstrap.php +++ b/src/HPCloud/Bootstrap.php @@ -29,6 +29,9 @@ SOFTWARE. namespace HPCloud; +use HPCloud\Services\IdentityServices; +use HPCloud\Exception; + /** * Bootstrapping services. * @@ -128,6 +131,12 @@ class Bootstrap { 'transport' => '\HPCloud\Transport\CURLTransport', ); + /** + * An identity services object created from the global settings. + * @var [type] + */ + public static $identity = NULL; + /** * Add the autoloader to PHP's autoloader list. * @@ -313,4 +322,61 @@ class Bootstrap { public static function hasConfig($name) { return isset(self::$config[$name]); } + + /** + * Get a HPCloud::Services::IdentityService object from the bootstrap config. + * + * A factory helper function that uses the bootstrap configuration to create + * a ready to use HPCloud::Services::IdentityService object. + * + * @param bool $force + * Whether to force the generation of a new object even if one is already + * cached. + * @retval HPCloud::Services::IdentityService + * An authenticated ready to use HPCloud::Services::IdentityService object. + * @throws HPCloud::Exception + * When the needed configuration to authenticate is not available. + */ + public static function identity($force = FALSE) { + + // If we already have an identity make sure the token is not expired. + $expired = FALSE; + if (!is_null(self::$identity)) { + // Make sure the token we have is not expired. + $tokenDetails = self::$identity->tokenDetails(); + $tokenExpires = new \DateTime($tokenDetails['expires']); + $currentDateTime = new \DateTime('now'); + if ($currentDateTime > $tokenExpires) { + $expired = TRUE; + } + } + + if (is_null(self::$identity) || $expired || $force) { + + // Make sure we have an endpoint to use + if (!self::hasConfig('endpoint')) { + throw new Exception('Unable to authenticate. No endpoint supplied.'); + } + + // Check if we have a username/password + if (self::hasConfig('username') && self::hasConfig('password')) { + $is = new IdentityServices(self::config('endpoint')); + $is->authenticateAsUser(self::config('username'), self::config('password'), self::config('tenantid', NULL)); + self::$identity = $is; + } + + // Otherwise we go with access/secret keys + elseif (self::hasConfig('account') && self::hasConfig('key')) { + $is = new IdentityServices(self::config('endpoint')); + $is->authenticateAsAccount(self::config('account'), self::config('key'), self::config('tenantid', NULL)); + self::$identity = $is; + } + + else { + throw new Exception('Unable to authenticate. No account credentials supplied.'); + } + } + + return self::$identity; + } } diff --git a/test/Tests/IdentityServicesTest.php b/test/Tests/IdentityServicesTest.php index d9e16ea..fe399f4 100644 --- a/test/Tests/IdentityServicesTest.php +++ b/test/Tests/IdentityServicesTest.php @@ -30,6 +30,7 @@ require_once 'src/HPCloud/Bootstrap.php'; require_once 'test/TestCase.php'; use \HPCloud\Services\IdentityServices; +use \HPCloud\Bootstrap; class IdentityServicesTest extends \HPCloud\Tests\TestCase { @@ -335,4 +336,44 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $this->assertEquals(1, count($catalog)); } + + /** + * Test the bootstrap identity factory. + * @depends testAuthenticateAsAccount + * @depends testAuthenticateAsUser + */ + function testBootstrap() { + + // Test authenticating as a user. + $settings = array( + 'username' => self::conf('hpcloud.identity.username'), + 'password' => self::conf('hpcloud.identity.password'), + 'endpoint' => self::conf('hpcloud.identity.url'), + 'tenantid' => self::conf('hpcloud.identity.tenantId'), + ); + Bootstrap::setConfiguration($settings); + + $is = Bootstrap::identity(TRUE); + $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $is); + + // Test authenticating as an account. + $settings = array( + 'account' => self::conf('hpcloud.identity.account'), + 'key' => self::conf('hpcloud.identity.secret'), + 'endpoint' => self::conf('hpcloud.identity.url'), + 'tenantid' => self::conf('hpcloud.identity.tenantId'), + ); + Bootstrap::setConfiguration($settings); + + $is = Bootstrap::identity(TRUE); + $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $is); + + // Test getting a second instance from the cache. + $is2 = Bootstrap::identity(); + $this->assertEquals($is, $is2); + + // Test that forcing a refresh does so. + $is2 = Bootstrap::identity(TRUE); + $this->assertNotEquals($is, $is2); + } } From 26c325b704bbe4fdf0cb6da157e60c9a6b299315 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Tue, 12 Jun 2012 09:46:20 -0500 Subject: [PATCH 06/22] Updated docs. --- .../Storage/ObjectStorage/StreamWrapper.php | 48 ++++++++++++------- test/Tests/StreamWrapperTest.php | 2 +- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/HPCloud/Storage/ObjectStorage/StreamWrapper.php b/src/HPCloud/Storage/ObjectStorage/StreamWrapper.php index 608dbad..cd22218 100644 --- a/src/HPCloud/Storage/ObjectStorage/StreamWrapper.php +++ b/src/HPCloud/Storage/ObjectStorage/StreamWrapper.php @@ -108,7 +108,8 @@ use \HPCloud\Storage\ObjectStorage; * array('swift' => array( * 'account' => ACCOUNT_NUMBER, * 'key' => SECRET_KEY, - * 'tenantId' => TENANT_ID + * 'tenantid' => TENANT_ID, + * 'tenantname' => TENANT_NAME, // Optional instead of tenantid. * 'endpoint' => AUTH_ENDPOINT_URL, * ) * ) @@ -218,9 +219,12 @@ use \HPCloud\Storage\ObjectStorage; * -# User login: username, password, tenantid, endpoint * -# Existing (valid) token: token, swift_endpoint * + * @attention + * As of 1.0.0-beta6, you may use `tenantname` instead of `tenantid`. + * * The third method (token) can be used when the application has already - * authenticated. In this case, a token has been generated and assigneet - * to an account and tenant ID. + * authenticated. In this case, a token has been generated and assigned + * to an account and tenant. * * The following parameters may be set either in the stream context * or through HPCloud::Bootstrap::setConfiguration(): @@ -230,10 +234,10 @@ use \HPCloud\Storage\ObjectStorage; * option. * - swift_endpoint: The URL to the swift instance. This is only necessary if * 'token' is set. Otherwise it is ignored. - * - username: A username. MUST be accompanied by 'password' and 'tenantid'. - * - password: A password. MUST be accompanied by 'username' and 'tenantid'. - * - account: An account ID. MUST be accompanied by a 'key' and 'tenantid'. - * - key: A secret key. MUST be accompanied by an 'account' and 'tenantid'. + * - username: A username. MUST be accompanied by 'password' and 'tenantid' (or 'tenantname'). + * - password: A password. MUST be accompanied by 'username' and 'tenantid' (or 'tenantname'). + * - account: An account ID. MUST be accompanied by a 'key' and 'tenantid' (or 'tenantname'). + * - key: A secret key. MUST be accompanied by an 'account' and 'tenantid' (or 'tenantname'). * - endpoint: The URL to the authentication endpoint. Necessary if you are not * using a 'token' and 'swift_endpoint'. * - use_swift_auth: If this is set to TRUE, it will force the app to use @@ -251,6 +255,10 @@ use \HPCloud\Storage\ObjectStorage; * - cdn_require_ssl: If this is set to FALSE, then CDN-based requests * may use plain HTTP instead of HTTPS. This will spead up CDN * fetches at the cost of security. + * - tenantid: The tenant ID for the services you will use. (An account may + * have multiple tenancies associated.) + * - tenantname: The tenant name for the services you will use. You may use + * this in lieu of tenant ID. * * @attention * ADVANCED: You can also pass an HPCloud::Storage::CDN object in use_cdn instead of @@ -533,7 +541,8 @@ class StreamWrapper { * @code * '1234', + * 'tenantname' => 'foo@example.com', + * // 'tenantid' => '1234', // You can use this instead of tenantname * 'account' => '1234', * 'secret' => '4321', * 'endpoint' => 'https://auth.example.com', @@ -947,7 +956,7 @@ class StreamWrapper { * @code * '12345', + * 'tenantname' => 'me@example.com', * 'username' => 'me@example.com', * 'password' => 'secret', * 'endpoint' => 'https://auth.example.com', @@ -1475,10 +1484,10 @@ class StreamWrapper { * option. * - swift_endpoint: The URL to the swift instance. This is only necessary if * 'token' is set. Otherwise it is ignored. - * - username: A username. MUST be accompanied by 'password' and 'tenantid'. - * - password: A password. MUST be accompanied by 'username' and 'tenantid'. - * - account: An account ID. MUST be accompanied by a 'key' and 'tenantid'. - * - key: A secret key. MUST be accompanied by an 'account' and 'tenantid'. + * - username: A username. MUST be accompanied by 'password' and 'tenantname'. + * - password: A password. MUST be accompanied by 'username' and 'tenantname'. + * - account: An account ID. MUST be accompanied by a 'key' and 'tenantname'. + * - key: A secret key. MUST be accompanied by an 'account' and 'tenantname'. * - endpoint: The URL to the authentication endpoint. Necessary if you are not * using a 'token' and 'swift_endpoint'. * - use_swift_auth: If this is set to TRUE, it will force the app to use @@ -1498,6 +1507,7 @@ class StreamWrapper { $key = $this->cxt('key'); $tenantId = $this->cxt('tenantid'); + $tenantName = $this->cxt('tenantname'); $authUrl = $this->cxt('endpoint'); $endpoint = $this->cxt('swift_endpoint'); @@ -1522,8 +1532,11 @@ class StreamWrapper { } // If we get here and tenant ID is not set, we can't get a container. - elseif (empty($tenantId) || empty($authUrl)) { - throw new \HPCloud\Exception('Tenant ID (tenantid) and endpoint are required.'); + elseif (empty($tenantId) && empty($tenantName)) { + throw new \HPCloud\Exception('Either Tenant ID (tenantid) or Tenant Name (tenantname) is required.'); + } + elseif (empty($authUrl)) { + throw new \HPCloud\Exception('An Identity Service Endpoint (endpoint) is required.'); } // Try to authenticate and get a new token. else { @@ -1612,6 +1625,7 @@ class StreamWrapper { $key = $this->cxt('key'); $tenantId = $this->cxt('tenantid'); + $tenantName = $this->cxt('tenantname'); $authUrl = $this->cxt('endpoint'); $ident = new \HPCloud\Services\IdentityServices($authUrl); @@ -1619,10 +1633,10 @@ class StreamWrapper { // Frustrated? Go burninate. http://www.homestarrunner.com/trogdor.html if (!empty($username) && !empty($password)) { - $token = $ident->authenticateAsUser($username, $password, $tenantId); + $token = $ident->authenticateAsUser($username, $password, $tenantId, $tenantName); } elseif (!empty($account) && !empty($key)) { - $token = $ident->authenticateAsAccount($account, $key, $tenantId); + $token = $ident->authenticateAsAccount($account, $key, $tenantId, $tenantName); } else { throw new \HPCloud\Exception('Either username/password or account/key must be provided.'); diff --git a/test/Tests/StreamWrapperTest.php b/test/Tests/StreamWrapperTest.php index 70e768d..b1cd5e0 100644 --- a/test/Tests/StreamWrapperTest.php +++ b/test/Tests/StreamWrapperTest.php @@ -126,7 +126,7 @@ class StreamWrapperTest extends \HPCloud\Tests\TestCase { 'account' => self::$settings['hpcloud.identity.account'], 'key' => self::$settings['hpcloud.identity.secret'], 'endpoint' => self::$settings['hpcloud.identity.url'], - 'tenantit' => self::$settings['hpcloud.identity.tenantId'], + 'tenantid' => self::$settings['hpcloud.identity.tenantId'], 'token' => $this->objectStore()->token(), 'swift_endpoint' => $this->objectStore()->url(), ); From 0ff6212c16710c8bc69bfac623d5a44eefc7fb1e Mon Sep 17 00:00:00 2001 From: Technosophos Date: Tue, 12 Jun 2012 12:12:05 -0500 Subject: [PATCH 07/22] Added isExpired() to IdentityServices object. --- src/HPCloud/Services/IdentityServices.php | 25 +++++++++++++++++++++++ test/Tests/IdentityServicesTest.php | 10 +++++++++ 2 files changed, 35 insertions(+) diff --git a/src/HPCloud/Services/IdentityServices.php b/src/HPCloud/Services/IdentityServices.php index 9ed9c65..492e03f 100644 --- a/src/HPCloud/Services/IdentityServices.php +++ b/src/HPCloud/Services/IdentityServices.php @@ -483,6 +483,31 @@ class IdentityServices { return $this->tokenDetails; } + /** + * Check whether the current identity has an expired token. + * + * This does not perform a round-trip to the server. Instead, it compares the + * machine's local timestamp with the server's expiration time stamp. A + * mis-configured machine timestamp could give spurious results. + * + * @retval boolean + * This will return FALSE if there is a current token and it has + * not yet expired (according to the date info). In all other cases + * it returns TRUE. + */ + public function isExpired() { + $details = $this->tokenDetails(); + + if (empty($details['expires'])) { + return TRUE; + } + + $currentDateTime = new \DateTime('now'); + $expireDateTime = new \DateTime($details['expires']); + + return $currentDateTime > $expireDateTime; + } + /** * Get the service catalog, optionaly filtering by type. * diff --git a/test/Tests/IdentityServicesTest.php b/test/Tests/IdentityServicesTest.php index 88559de..d624e04 100644 --- a/test/Tests/IdentityServicesTest.php +++ b/test/Tests/IdentityServicesTest.php @@ -170,6 +170,16 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $this->assertNotEmpty($service->token()); } + /** + * @depends testAuthenticateAsAccount + */ + public function testIsExpired($service) { + $this->assertFalse($service->isExpired()); + + $service2 = new IdentityServices(self::conf('hpcloud.identity.url')); + $this->assertTrue($service2->isExpired()); + } + /** * @depends testAuthenticateAsAccount */ From 9731456945437a44089e5b22a2aa0711f8561a88 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Tue, 12 Jun 2012 12:18:11 -0500 Subject: [PATCH 08/22] Refactored Bootstrap::identity() to use IdentityServices::isExpired(). --- src/HPCloud/Bootstrap.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/HPCloud/Bootstrap.php b/src/HPCloud/Bootstrap.php index 02a34bf..0ba78e5 100644 --- a/src/HPCloud/Bootstrap.php +++ b/src/HPCloud/Bootstrap.php @@ -133,7 +133,7 @@ class Bootstrap { /** * An identity services object created from the global settings. - * @var [type] + * @var object HPCloud::Services::IdentityServices */ public static $identity = NULL; @@ -340,18 +340,7 @@ class Bootstrap { public static function identity($force = FALSE) { // If we already have an identity make sure the token is not expired. - $expired = FALSE; - if (!is_null(self::$identity)) { - // Make sure the token we have is not expired. - $tokenDetails = self::$identity->tokenDetails(); - $tokenExpires = new \DateTime($tokenDetails['expires']); - $currentDateTime = new \DateTime('now'); - if ($currentDateTime > $tokenExpires) { - $expired = TRUE; - } - } - - if (is_null(self::$identity) || $expired || $force) { + if ($force || is_null(self::$identity) || self::$identity->isExpired()) { // Make sure we have an endpoint to use if (!self::hasConfig('endpoint')) { @@ -364,7 +353,7 @@ class Bootstrap { $is->authenticateAsUser(self::config('username'), self::config('password'), self::config('tenantid', NULL)); self::$identity = $is; } - + // Otherwise we go with access/secret keys elseif (self::hasConfig('account') && self::hasConfig('key')) { $is = new IdentityServices(self::config('endpoint')); From 026a6e58bf7cd17163e03bbf1f0550593e08ee46 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Tue, 12 Jun 2012 15:37:07 -0500 Subject: [PATCH 09/22] Fixed bug on Bootstrap::identity(). First, replaced 'key' with 'secret'; second, made sure that 'username' and 'account' weren't empty. --- src/HPCloud/Bootstrap.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/HPCloud/Bootstrap.php b/src/HPCloud/Bootstrap.php index 0ba78e5..64ae56c 100644 --- a/src/HPCloud/Bootstrap.php +++ b/src/HPCloud/Bootstrap.php @@ -347,17 +347,23 @@ class Bootstrap { throw new Exception('Unable to authenticate. No endpoint supplied.'); } + // Neither user nor account can be an empty string, so we need + // to do more checking than self::hasConfig(), which returns TRUE + // if an item exists and is an empty string. + $user = self::config('username', NULL); + $account = self::config('account', NULL); + // Check if we have a username/password - if (self::hasConfig('username') && self::hasConfig('password')) { + if (!empty($user) && self::hasConfig('password')) { $is = new IdentityServices(self::config('endpoint')); - $is->authenticateAsUser(self::config('username'), self::config('password'), self::config('tenantid', NULL)); + $is->authenticateAsUser($user, self::config('password'), self::config('tenantid', NULL)); self::$identity = $is; } // Otherwise we go with access/secret keys - elseif (self::hasConfig('account') && self::hasConfig('key')) { + elseif (!empty($account) && self::hasConfig('secret')) { $is = new IdentityServices(self::config('endpoint')); - $is->authenticateAsAccount(self::config('account'), self::config('key'), self::config('tenantid', NULL)); + $is->authenticateAsAccount($account, self::config('secret'), self::config('tenantid', NULL)); self::$identity = $is; } From 5914dd19849a7de2de7bc5c571046fef5aa1c863 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Tue, 12 Jun 2012 16:47:12 -0500 Subject: [PATCH 10/22] Added explicit testing and support for IdenityService serialization. --- src/HPCloud/Services/IdentityServices.php | 30 ++++++++++++++++++++++- test/Tests/IdentityServicesTest.php | 27 ++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/HPCloud/Services/IdentityServices.php b/src/HPCloud/Services/IdentityServices.php index 492e03f..ac103d4 100644 --- a/src/HPCloud/Services/IdentityServices.php +++ b/src/HPCloud/Services/IdentityServices.php @@ -122,9 +122,14 @@ namespace HPCloud\Services; * - tenants() * - rescope() * + * Serializing + * + * IdentityServices has been intentionally built to serialize well. + * This allows implementors to cache IdentityServices objects rather + * than make repeated requests for identity information. * */ -class IdentityServices { +class IdentityServices /*implements Serializable*/ { /** * The version of the API currently supported. */ @@ -171,6 +176,8 @@ class IdentityServices { */ protected $catalog = array(); + protected $userDetails; + /** * Build a new IdentityServices object. * @@ -687,6 +694,7 @@ class IdentityServices { /** * @see HPCloud::Services::IdentityServices::rescopeUsingTenantId() + * @deprecated */ public function rescope($tenantId) { return $this->rescopeUsingTenantId($tenantId); @@ -824,4 +832,24 @@ class IdentityServices { $this->serviceCatalog = $json['access']['serviceCatalog']; } + /* Not necessary. + public function serialize() { + $data = array( + 'tokenDetails' => $this->tokenDetails, + 'userDetails' => $this->userDetails, + 'serviceCatalog' => $this->serviceCatalog, + 'endpoint' => $this->endpoint, + ); + return serialize($data); + } + + public function unserialize($data) { + $vals = unserialize($data); + $this->tokenDetails = $vals['tokenDetails']; + $this->userDetails = $vals['userDetails']; + $this->serviceCatalog = $vals['serviceCatalog']; + $this->endpoint = $vals['endpoint']; + } + */ + } diff --git a/test/Tests/IdentityServicesTest.php b/test/Tests/IdentityServicesTest.php index d624e04..29e0761 100644 --- a/test/Tests/IdentityServicesTest.php +++ b/test/Tests/IdentityServicesTest.php @@ -314,6 +314,33 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $this->assertNotEmpty($user['roles']); } + /** + * @depends testAuthenticateAsAccount + * @group serialize + */ + public function testSerialization($service) { + + $ser = serialize($service); + + $this->assertNotEmpty($ser); + + $again = unserialize($ser); + + $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $again); + + $this->assertEquals($service->tenantId(), $again->tenantId()); + $this->assertEquals($service->serviceCatalog(), $again->serviceCatalog()); + $this->assertEquals($service->tokenDetails(), $again->tokenDetails()); + $this->assertEquals($service->user(), $again->user()); + $this->assertFalse($again->isExpired()); + + $tenantId = $again->tenantId(); + + $newTok = $again->rescopeUsingTenantId($tenantId); + + $this->assertNotEmpty($newTok); + } + /** * @group tenant */ From 7448eb528017e5121ad54105a9daa320a824801b Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Wed, 13 Jun 2012 15:37:13 -0400 Subject: [PATCH 11/22] Added tenantName support to Bootstrap::identity. --- src/HPCloud/Bootstrap.php | 4 ++-- test/Tests/IdentityServicesTest.php | 35 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/HPCloud/Bootstrap.php b/src/HPCloud/Bootstrap.php index 64ae56c..a47d771 100644 --- a/src/HPCloud/Bootstrap.php +++ b/src/HPCloud/Bootstrap.php @@ -356,14 +356,14 @@ class Bootstrap { // Check if we have a username/password if (!empty($user) && self::hasConfig('password')) { $is = new IdentityServices(self::config('endpoint')); - $is->authenticateAsUser($user, self::config('password'), self::config('tenantid', NULL)); + $is->authenticateAsUser($user, self::config('password'), self::config('tenantid', NULL), self::config('tenantname', NULL)); self::$identity = $is; } // Otherwise we go with access/secret keys elseif (!empty($account) && self::hasConfig('secret')) { $is = new IdentityServices(self::config('endpoint')); - $is->authenticateAsAccount($account, self::config('secret'), self::config('tenantid', NULL)); + $is->authenticateAsAccount($account, self::config('secret'), self::config('tenantid', NULL), self::config('tenantname', NULL)); self::$identity = $is; } diff --git a/test/Tests/IdentityServicesTest.php b/test/Tests/IdentityServicesTest.php index 29e0761..ec0c783 100644 --- a/test/Tests/IdentityServicesTest.php +++ b/test/Tests/IdentityServicesTest.php @@ -453,6 +453,12 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { */ function testBootstrap() { + // We need to save the config settings and reset the bootstrap to this. + // It does not remove the old settings. The means the identity fall through + // for different settings may not happen because of ordering. So, we cache + // and reset back to the default for each test. + $reset = Bootstrap::$config; + // Test authenticating as a user. $settings = array( 'username' => self::conf('hpcloud.identity.username'), @@ -465,10 +471,12 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { $is = Bootstrap::identity(TRUE); $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $is); + Bootstrap::$config = $reset; + // Test authenticating as an account. $settings = array( 'account' => self::conf('hpcloud.identity.account'), - 'key' => self::conf('hpcloud.identity.secret'), + 'secret' => self::conf('hpcloud.identity.secret'), 'endpoint' => self::conf('hpcloud.identity.url'), 'tenantid' => self::conf('hpcloud.identity.tenantId'), ); @@ -484,5 +492,30 @@ class IdentityServicesTest extends \HPCloud\Tests\TestCase { // Test that forcing a refresh does so. $is2 = Bootstrap::identity(TRUE); $this->assertNotEquals($is, $is2); + + Bootstrap::$config = $reset; + + // Test with tenant name + $settings = array( + 'account' => self::conf('hpcloud.identity.account'), + 'secret' => self::conf('hpcloud.identity.secret'), + 'endpoint' => self::conf('hpcloud.identity.url'), + 'tenantname' => self::conf('hpcloud.identity.tenantName'), + ); + Bootstrap::setConfiguration($settings); + + $is = Bootstrap::identity(TRUE); + $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $is); + + $settings = array( + 'username' => self::conf('hpcloud.identity.username'), + 'password' => self::conf('hpcloud.identity.password'), + 'endpoint' => self::conf('hpcloud.identity.url'), + 'tenantname' => self::conf('hpcloud.identity.tenantName'), + ); + Bootstrap::setConfiguration($settings); + + $is = Bootstrap::identity(TRUE); + $this->assertInstanceOf('\HPCloud\Services\IdentityServices', $is); } } From e1844b3060b32e0634eeaded89422f8789056e66 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Thu, 14 Jun 2012 13:46:04 -0500 Subject: [PATCH 12/22] Fixed minor bug in authtest. --- test/AuthTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/AuthTest.php b/test/AuthTest.php index 0f4d8ca..d258d91 100644 --- a/test/AuthTest.php +++ b/test/AuthTest.php @@ -26,7 +26,8 @@ SOFTWARE. * You can run the test with `php test/AuthTest.php username key`. */ -require_once 'src/HPCloud/Bootstrap.php'; +$base = dirname(__DIR__); +require_once $base . '/src/HPCloud/Bootstrap.php'; use \HPCloud\Storage\ObjectStorage; use \HPCloud\Services\IdentityServices; From fbc04a426ae9a49568f1c0d972350b568167ee96 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Jun 2012 11:46:59 -0500 Subject: [PATCH 13/22] Fixed bug with additional headers. --- src/HPCloud/Storage/ObjectStorage/RemoteObject.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php index bda1543..f458d32 100644 --- a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php +++ b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php @@ -274,6 +274,14 @@ class RemoteObject extends Object { return $this->allHeaders; } + public function additionalHeaders() { + // Any additional headers will be set. Note that $this->headers will contain + // some headers that are NOT additional. But we do not know which headers are + // additional and which are from Swift because Swift does not commit to using + // a specific set of headers. + return parent::additionalHeaders() + $this->headers; + } + /** * Get the content of this object. * From 6013bf3b8adbc6377a4c8db794c9171044dc36a1 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Jun 2012 12:13:37 -0500 Subject: [PATCH 14/22] Added more header support. --- .../Storage/ObjectStorage/RemoteObject.php | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php index f458d32..a7291f1 100644 --- a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php +++ b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php @@ -125,7 +125,8 @@ class RemoteObject extends Object { public static function newFromHeaders($name, $headers, $token, $url, $cdnUrl = NULL, $cdnSslUrl = NULL) { $object = new RemoteObject($name); - $object->allHeaders = $headers; + //$object->allHeaders = $headers; + $object->setHeaders($headers); //throw new \Exception(print_r($headers, TRUE)); @@ -259,6 +260,16 @@ class RemoteObject extends Object { return $this->metadata; } + public function setHeaders($headers) { + $this->allHeaders = array(); + + foreach ($headers as $name => $value) { + if (strpos($name, Container::METADATA_HEADER_PREFIX) !== 0) { + $this->allHeaders[$name] = $value; + } + } + } + /** * Get the HTTP headers sent by the server. * @@ -279,7 +290,20 @@ class RemoteObject extends Object { // some headers that are NOT additional. But we do not know which headers are // additional and which are from Swift because Swift does not commit to using // a specific set of headers. - return parent::additionalHeaders() + $this->headers; + $additionalHeaders = parent::additionalHeaders() + $this->headers; + + return $additionalHeaders; + } + + /** + * Given an array of keys, remove the headers. + * + */ + public function removeHeaders($keys) { + foreach ($keys as $key) { + unset($this->allHeaders[$key]); + unset($this->additionalHeaders[$key]); + } } /** From fde9d44030a19e44d35ffedebec0e7166a99f091 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Jun 2012 17:11:34 -0500 Subject: [PATCH 15/22] Header management code added. --- src/HPCloud/Storage/ObjectStorage/Object.php | 22 +++++++++++++++++++ .../Storage/ObjectStorage/RemoteObject.php | 16 +++++++++++++- test/Tests/ObjectTest.php | 20 +++++++++++++++++ test/Tests/RemoteObjectTest.php | 13 +++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/HPCloud/Storage/ObjectStorage/Object.php b/src/HPCloud/Storage/ObjectStorage/Object.php index c03237f..90b87a9 100644 --- a/src/HPCloud/Storage/ObjectStorage/Object.php +++ b/src/HPCloud/Storage/ObjectStorage/Object.php @@ -451,6 +451,28 @@ class Object { return $this->additionalHeaders; } + /** + * Remove headers. + * + * This takes an array of header names, and removes + * any matching headers. Typically, only headers set + * by setAdditionalHeaders() are removed from an Object. + * (RemoteObject works differently). + * + * @attention + * Many headers are generated automatically, such as + * Content-Type and Content-Length. Removing these + * will simply result in their being regenerated. + * + * @param array $keys + * The header names to be removed. + */ + public function removeHeaders($keys) { + foreach ($keys as $k) { + unset($this->additionalHeaders[$k]); + } + } + /** * This object should be transmitted in chunks. * diff --git a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php index a7291f1..08b0891 100644 --- a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php +++ b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php @@ -296,8 +296,22 @@ class RemoteObject extends Object { } /** - * Given an array of keys, remove the headers. + * Given an array of header names. * + * This will remove the given headers from the existing headers. + * Both additional headers and the original headers from the + * server are affected here. + * + * Note that you cannot remove metadata through this mechanism, + * as it is managed using the metadata() methods. + * + * @attention + * Many headers are generated automatically, such as + * Content-Type and Content-Length. Removing these + * will simply result in their being regenerated. + * + * @param array $keys + * The header names to be removed. */ public function removeHeaders($keys) { foreach ($keys as $key) { diff --git a/test/Tests/ObjectTest.php b/test/Tests/ObjectTest.php index c7a1d3c..2e18716 100644 --- a/test/Tests/ObjectTest.php +++ b/test/Tests/ObjectTest.php @@ -131,4 +131,24 @@ class ObjectTest extends \HPCloud\Tests\TestCase { $this->assertEquals('Leibniz', $got['Gottfried']); } + + public function testAdditionalHeaders() { + $o = $this->basicObjectFixture(); + + $extra = array( + 'a' => 'b', + 'aaa' => 'bbb', + 'ccc' => 'bbb', + ); + $o->setAdditionalHeaders($extra); + + $got = $o->additionalHeaders(); + $this->assertEquals(3, count($got)); + + $o->removeHeaders(array('ccc')); + + + $got = $o->additionalHeaders(); + $this->assertEquals(2, count($got)); + } } diff --git a/test/Tests/RemoteObjectTest.php b/test/Tests/RemoteObjectTest.php index 3d6768b..916db9a 100644 --- a/test/Tests/RemoteObjectTest.php +++ b/test/Tests/RemoteObjectTest.php @@ -53,6 +53,10 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase { $object->setMetadata(array(self::FMETA_NAME => self::FMETA_VALUE)); $object->setDisposition(self::FDISPOSITION); $object->setEncoding(self::FENCODING); + $object->setAdditionalHeaders(array( + 'Access-Control-Allow-Origin' => 'http://example.com', + 'Access-control-allow-origin' => 'http://example.com', + )); // Need some headers that Swift actually stores and returns. This // one does not seem to be returned ever. @@ -146,6 +150,15 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase { $headers = $obj->headers(); $this->assertTrue(count($headers) > 1); + fwrite(STDOUT, print_r($headers, TRUE)); + + $this->assertNotEmpty($headers['Date']); + + $obj->removeHeaders(array('Date')); + + $headers = $obj->headers(); + $this->assertFalse(isset($headers['Date'])); + // Swift doesn't return CORS headers even though it is supposed to. //$this->assertEquals(self::FCORS_VALUE, $headers[self::FCORS_NAME]); } From d27aaba85ed826ae1b3caede8613bde270cddf94 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Thu, 5 Jul 2012 15:01:41 -0500 Subject: [PATCH 16/22] Removed debugging in unit test. --- test/Tests/RemoteObjectTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Tests/RemoteObjectTest.php b/test/Tests/RemoteObjectTest.php index 916db9a..2a57b58 100644 --- a/test/Tests/RemoteObjectTest.php +++ b/test/Tests/RemoteObjectTest.php @@ -150,7 +150,7 @@ class RemoteObjectTest extends \HPCloud\Tests\TestCase { $headers = $obj->headers(); $this->assertTrue(count($headers) > 1); - fwrite(STDOUT, print_r($headers, TRUE)); + //fwrite(STDOUT, print_r($headers, TRUE)); $this->assertNotEmpty($headers['Date']); From 51c1f4b481c1facd71890db447beff0a7202c018 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Fri, 13 Jul 2012 21:57:24 -0500 Subject: [PATCH 17/22] Fixed casing of Etag to be consistent with swift. --- src/HPCloud/Storage/ObjectStorage/Container.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HPCloud/Storage/ObjectStorage/Container.php b/src/HPCloud/Storage/ObjectStorage/Container.php index ead371b..795760e 100644 --- a/src/HPCloud/Storage/ObjectStorage/Container.php +++ b/src/HPCloud/Storage/ObjectStorage/Container.php @@ -513,7 +513,7 @@ class Container implements \Countable, \IteratorAggregate { if (empty($file)) { // Now build up the rest of the headers: - $headers['ETag'] = $obj->eTag(); + $headers['Etag'] = $obj->eTag(); // If chunked, we set transfer encoding; else // we set the content length. @@ -541,7 +541,7 @@ class Container implements \Countable, \IteratorAggregate { $hash = hash_init('md5'); hash_update_stream($hash, $file); $etag = hash_final($hash); - $headers['ETag'] = $etag; + $headers['Etag'] = $etag; // Not sure if this is necessary: rewind($file); From e7f8e0e7793c01d2f474392ad4c4aab078c80715 Mon Sep 17 00:00:00 2001 From: Technosophos Date: Fri, 13 Jul 2012 21:57:48 -0500 Subject: [PATCH 18/22] Updated RemoteObject to handle extra headers differently. --- .../Storage/ObjectStorage/RemoteObject.php | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php index 08b0891..b4494e5 100644 --- a/src/HPCloud/Storage/ObjectStorage/RemoteObject.php +++ b/src/HPCloud/Storage/ObjectStorage/RemoteObject.php @@ -65,7 +65,7 @@ class RemoteObject extends Object { * serve as a good indicator that the object does not have all * attributes set. */ - protected $allHeaders; + protected $allHeaders = array(); protected $cdnUrl; protected $cdnSslUrl; @@ -285,16 +285,41 @@ class RemoteObject extends Object { return $this->allHeaders; } - public function additionalHeaders() { + public function additionalHeaders($mergeAll = FALSE) { // Any additional headers will be set. Note that $this->headers will contain // some headers that are NOT additional. But we do not know which headers are // additional and which are from Swift because Swift does not commit to using // a specific set of headers. - $additionalHeaders = parent::additionalHeaders() + $this->headers; + if ($mergeAll) { + $additionalHeaders = parent::additionalHeaders() + $this->allHeaders; + $this->filterHeaders($additionalHeaders); + } + else { + $additionalHeaders = parent::additionalHeaders(); + } return $additionalHeaders; } + protected $reservedHeaders = array( + 'etag' => TRUE, 'content-length' => TRUE, + 'x-auth-token' => TRUE, + 'transfer-encoding' => TRUE, + 'x-trans-id' => TRUE, + ); + public function filterHeaders(&$headers) { + $unset = array(); + foreach ($headers as $name => $value) { + $lower = strtolower($name); + if (isset($this->reservedHeaders[$lower])) { + $unset[] = $name; + } + } + foreach ($unset as $u) { + unset($headers[$u]); + } + } + /** * Given an array of header names. * From 2fc49c0320d01c7bd01cc48b13e847b9486ae10a Mon Sep 17 00:00:00 2001 From: Technosophos Date: Fri, 13 Jul 2012 21:59:28 -0500 Subject: [PATCH 19/22] Issue #1 fixed. --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 5b3c8f2..8a3fe97 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "Access HPCloud and OpenStack services in PHP.", "type": "library", "keywords": ["openstack","hpcloud","cloud","swift","nova"], - "license": "MIT-like", + "license": "MIT", "homepage": "http://hpcloud.com", "authors": [ { From a2b40d01c172edca63f03cad46d669319997706e Mon Sep 17 00:00:00 2001 From: Technosophos Date: Thu, 26 Jul 2012 19:15:00 -0500 Subject: [PATCH 20/22] Fixed missed encoding. --- src/HPCloud/Storage/ObjectStorage/Container.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HPCloud/Storage/ObjectStorage/Container.php b/src/HPCloud/Storage/ObjectStorage/Container.php index 795760e..aabbf5e 100644 --- a/src/HPCloud/Storage/ObjectStorage/Container.php +++ b/src/HPCloud/Storage/ObjectStorage/Container.php @@ -148,7 +148,7 @@ class Container implements \Countable, \IteratorAggregate { */ public static function objectUrl($base, $oname) { if (strpos($oname, '/') === FALSE) { - return $base . '/' . $oname; + return $base . '/' . rawurlencode($oname); } $oParts = explode('/', $oname); From 602bb781cc6d3d40e69d1af39ac3acba73c649d4 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 6 Aug 2012 13:05:19 -0400 Subject: [PATCH 21/22] The token urls had cases of a //. Cleaned this up. --- src/HPCloud/Services/IdentityServices.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/HPCloud/Services/IdentityServices.php b/src/HPCloud/Services/IdentityServices.php index ac103d4..bce841f 100644 --- a/src/HPCloud/Services/IdentityServices.php +++ b/src/HPCloud/Services/IdentityServices.php @@ -268,12 +268,11 @@ class IdentityServices /*implements Serializable*/ { * to the underlying problem. */ public function authenticate(array $ops) { - $url = $this->url() . '/tokens'; + $url = $this->url() . 'tokens'; $envelope = array( 'auth' => $ops, ); - $body = json_encode($envelope); $headers = array( @@ -731,7 +730,7 @@ class IdentityServices /*implements Serializable*/ { * to the underlying problem. */ public function rescopeUsingTenantId($tenantId) { - $url = $this->url() . '/tokens'; + $url = $this->url() . 'tokens'; $token = $this->token(); $data = array( 'auth' => array( @@ -788,7 +787,7 @@ class IdentityServices /*implements Serializable*/ { * to the underlying problem. */ public function rescopeUsingTenantName($tenantName) { - $url = $this->url() . '/tokens'; + $url = $this->url() . 'tokens'; $token = $this->token(); $data = array( 'auth' => array( From 6ea9015e369a8fd033aad89dc575cc531ec3170a Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 6 Aug 2012 13:12:09 -0400 Subject: [PATCH 22/22] Cleaning up object storage when done with the stream wrapper tests. --- test/Tests/StreamWrapperFSTest.php | 42 +++++++++++++++++++++++++++++- test/Tests/StreamWrapperTest.php | 40 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/test/Tests/StreamWrapperFSTest.php b/test/Tests/StreamWrapperFSTest.php index 3639e14..dc31352 100644 --- a/test/Tests/StreamWrapperFSTest.php +++ b/test/Tests/StreamWrapperFSTest.php @@ -45,6 +45,46 @@ class StreamWrapperFSTest extends \HPCloud\Tests\TestCase { /*public static function setUpBeforeClass() { }*/ + /** + * Cleaning up the test container so we can reuse it for other tests. + */ + public static function tearDownAfterClass() { + + // First we get an identity + $user = self::conf('hpcloud.identity.username'); + $pass = self::conf('hpcloud.identity.password'); + $tenantId = self::conf('hpcloud.identity.tenantId'); + $url = self::conf('hpcloud.identity.url'); + + $ident = new \HPCloud\Services\IdentityServices($url); + + $token = $ident->authenticateAsUser($user, $pass, $tenantId); + + // Then we need to get an instance of storage + $store = \HPCloud\Storage\ObjectStorage::newFromIdentity($ident); + + + // Delete the container and all the contents. + $cname = self::$settings['hpcloud.swift.container']; + + try { + $container = $store->container($cname); + } + // The container was never created. + catch (\HPCloud\Transport\FileNotFoundException $e) { + return; + } + + foreach ($container as $object) { + try { + $container->delete($object->name()); + } + catch (\Exception $e) {} + } + + $store->deleteContainer($cname); + } + protected function newUrl($objectName) { $scheme = StreamWrapperFS::DEFAULT_SCHEME; $cname = self::$settings['hpcloud.swift.container']; @@ -129,7 +169,7 @@ class StreamWrapperFSTest extends \HPCloud\Tests\TestCase { 'account' => self::$settings['hpcloud.identity.account'], 'key' => self::$settings['hpcloud.identity.secret'], 'endpoint' => self::$settings['hpcloud.identity.url'], - 'tenantit' => self::$settings['hpcloud.identity.tenantId'], + 'tenantid' => self::$settings['hpcloud.identity.tenantId'], 'token' => $this->objectStore()->token(), 'swift_endpoint' => $this->objectStore()->url(), ); diff --git a/test/Tests/StreamWrapperTest.php b/test/Tests/StreamWrapperTest.php index b1cd5e0..ff4ef59 100644 --- a/test/Tests/StreamWrapperTest.php +++ b/test/Tests/StreamWrapperTest.php @@ -42,6 +42,46 @@ class StreamWrapperTest extends \HPCloud\Tests\TestCase { const FNAME = 'streamTest.txt'; const FTYPE = 'application/x-tuna-fish; charset=iso-8859-13'; + /** + * Cleaning up the test container so we can reuse it for other tests. + */ + public static function tearDownAfterClass() { + + // First we get an identity + $user = self::conf('hpcloud.identity.username'); + $pass = self::conf('hpcloud.identity.password'); + $tenantId = self::conf('hpcloud.identity.tenantId'); + $url = self::conf('hpcloud.identity.url'); + + $ident = new \HPCloud\Services\IdentityServices($url); + + $token = $ident->authenticateAsUser($user, $pass, $tenantId); + + // Then we need to get an instance of storage + $store = \HPCloud\Storage\ObjectStorage::newFromIdentity($ident); + + + // Delete the container and all the contents. + $cname = self::$settings['hpcloud.swift.container']; + + try { + $container = $store->container($cname); + } + // The container was never created. + catch (\HPCloud\Transport\FileNotFoundException $e) { + return; + } + + foreach ($container as $object) { + try { + $container->delete($object->name()); + } + catch (\Exception $e) {} + } + + $store->deleteContainer($cname); + } + protected function newUrl($objectName) { $scheme = StreamWrapper::DEFAULT_SCHEME; $cname = self::$settings['hpcloud.swift.container'];