php: metadata plugin based auth API

pull/4394/head
Stanley Cheung 9 years ago
parent 5a629d3929
commit 3580580367
  1. 32
      src/php/ext/grpc/call.c
  2. 4
      src/php/ext/grpc/call.h
  3. 100
      src/php/ext/grpc/call_credentials.c
  4. 14
      src/php/ext/grpc/call_credentials.h
  5. 24
      src/php/lib/Grpc/AbstractCall.php
  6. 81
      src/php/lib/Grpc/BaseStub.php
  7. 3
      src/php/tests/generated_code/AbstractGeneratedCodeTest.php
  8. 28
      src/php/tests/interop/interop_client.php
  9. 96
      src/php/tests/unit_tests/CallCredentialsTest.php

@ -42,13 +42,13 @@
#include <ext/standard/info.h>
#include <ext/spl/spl_exceptions.h>
#include "php_grpc.h"
#include "call_credentials.h"
#include <zend_exceptions.h>
#include <zend_hash.h>
#include <stdbool.h>
#include <grpc/support/log.h>
#include <grpc/support/alloc.h>
#include <grpc/grpc.h>
@ -515,11 +515,41 @@ PHP_METHOD(Call, cancel) {
grpc_call_cancel(call->wrapped, NULL);
}
/**
* Set the CallCredentials for this call.
* @param CallCredentials creds_obj The CallCredentials object
* @param int The error code
*/
PHP_METHOD(Call, setCredentials) {
zval *creds_obj;
/* "O" == 1 Object */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &creds_obj,
grpc_ce_call_credentials) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"setCredentials expects 1 CallCredentials",
1 TSRMLS_CC);
return;
}
wrapped_grpc_call_credentials *creds =
(wrapped_grpc_call_credentials *)zend_object_store_get_object(
creds_obj TSRMLS_CC);
wrapped_grpc_call *call =
(wrapped_grpc_call *)zend_object_store_get_object(getThis() TSRMLS_CC);
grpc_call_error error = GRPC_CALL_ERROR;
error = grpc_call_set_credentials(call->wrapped, creds->wrapped);
RETURN_LONG(error);
}
static zend_function_entry call_methods[] = {
PHP_ME(Call, __construct, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
PHP_ME(Call, startBatch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, getPeer, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, cancel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Call, setCredentials, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END};
void grpc_init_call(TSRMLS_D) {

@ -66,4 +66,8 @@ zval *grpc_php_wrap_call(grpc_call *wrapped, bool owned);
* call metadata */
zval *grpc_parse_metadata_array(grpc_metadata_array *metadata_array);
/* Populates a grpc_metadata_array with the data in a PHP array object.
Returns true on success and false on failure */
bool create_metadata_array(zval *array, grpc_metadata_array *metadata);
#endif /* NET_GRPC_PHP_GRPC_CHANNEL_H_ */

@ -43,6 +43,7 @@
#include <ext/standard/info.h>
#include <ext/spl/spl_exceptions.h>
#include "php_grpc.h"
#include "call.h"
#include <zend_exceptions.h>
#include <zend_hash.h>
@ -103,7 +104,7 @@ PHP_METHOD(CallCredentials, createComposite) {
zval *cred1_obj;
zval *cred2_obj;
/* "OO" == 3 Objects */
/* "OO" == 2 Objects */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "OO", &cred1_obj,
grpc_ce_call_credentials, &cred2_obj,
grpc_ce_call_credentials) == FAILURE) {
@ -125,9 +126,106 @@ PHP_METHOD(CallCredentials, createComposite) {
RETURN_DESTROY_ZVAL(creds_object);
}
/**
* Create a call credentials object from the plugin API
* @param function callback The callback function
* @return CallCredentials The new call credentials object
*/
PHP_METHOD(CallCredentials, createFromPlugin) {
zend_fcall_info *fci;
zend_fcall_info_cache *fci_cache;
fci = (zend_fcall_info *)emalloc(sizeof(zend_fcall_info));
fci_cache = (zend_fcall_info_cache *)emalloc(sizeof(zend_fcall_info_cache));
memset(fci, 0, sizeof(zend_fcall_info));
memset(fci_cache, 0, sizeof(zend_fcall_info_cache));
/* "f" == 1 function */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "f", fci,
fci_cache,
fci->params,
fci->param_count) == FAILURE) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"createFromPlugin expects 1 callback",
1 TSRMLS_CC);
return;
}
plugin_state *state;
state = (plugin_state *)emalloc(sizeof(plugin_state));
memset(state, 0, sizeof(plugin_state));
/* save the user provided PHP callback function */
state->fci = fci;
state->fci_cache = fci_cache;
grpc_metadata_credentials_plugin plugin;
plugin.get_metadata = plugin_get_metadata;
plugin.destroy = plugin_destroy_state;
plugin.state = (void *)state;
plugin.type = "";
grpc_call_credentials *creds = grpc_metadata_credentials_create_from_plugin(
plugin, NULL);
zval *creds_object = grpc_php_wrap_call_credentials(creds);
RETURN_DESTROY_ZVAL(creds_object);
}
/* Callback function for plugin creds API */
void plugin_get_metadata(void *ptr, grpc_auth_metadata_context context,
grpc_credentials_plugin_metadata_cb cb,
void *user_data) {
plugin_state *state = (plugin_state *)ptr;
/* prepare to call the user callback function with info from the
* grpc_auth_metadata_context */
zval **params[1];
zval *arg;
zval *retval;
MAKE_STD_ZVAL(arg);
ZVAL_STRING(arg, context.service_url, 1);
params[0] = &arg;
/* TODO: Need to pass user_data as well? */
state->fci->param_count = 1;
state->fci->params = params;
state->fci->retval_ptr_ptr = &retval;
/* call the user callback function */
zend_call_function(state->fci, state->fci_cache);
if (Z_TYPE_P(retval) != IS_ARRAY) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"plugin callback must return metadata array",
1 TSRMLS_CC);
}
grpc_metadata_array metadata;
if (!create_metadata_array(retval, &metadata)) {
zend_throw_exception(spl_ce_InvalidArgumentException,
"invalid metadata", 1 TSRMLS_CC);
grpc_metadata_array_destroy(&metadata);
}
/* TODO: handle error */
grpc_status_code code = GRPC_STATUS_OK;
/* Pass control back to core */
cb(user_data, metadata.metadata, metadata.count, code, NULL);
}
/* Cleanup function for plugin creds API */
void plugin_destroy_state(void *ptr) {
plugin_state *state = (plugin_state *)ptr;
efree(state->fci);
efree(state->fci_cache);
efree(state);
}
static zend_function_entry call_credentials_methods[] = {
PHP_ME(CallCredentials, createComposite, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(CallCredentials, createFromPlugin, NULL,
ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_FE_END};
void grpc_init_call_credentials(TSRMLS_D) {

@ -57,6 +57,20 @@ typedef struct wrapped_grpc_call_credentials {
grpc_call_credentials *wrapped;
} wrapped_grpc_call_credentials;
/* Struct to hold callback function for plugin creds API */
typedef struct plugin_state {
zend_fcall_info *fci;
zend_fcall_info_cache *fci_cache;
} plugin_state;
/* Callback function for plugin creds API */
void plugin_get_metadata(void *state, grpc_auth_metadata_context context,
grpc_credentials_plugin_metadata_cb cb,
void *user_data);
/* Cleanup function for plugin creds API */
void plugin_destroy_state(void *ptr);
/* Initializes the CallCredentials PHP class */
void grpc_init_call_credentials(TSRMLS_D);

@ -43,19 +43,20 @@ abstract class AbstractCall
/**
* Create a new Call wrapper object.
*
* @param Channel $channel The channel to communicate on
* @param string $method The method to call on the
* remote server
* @param callback $deserialize A callback function to deserialize
* the response
* @param (optional) long $timeout Timeout in microseconds
* @param Channel $channel The channel to communicate on
* @param string $method The method to call on the
* remote server
* @param callback $deserialize A callback function to deserialize
* the response
* @param array $options Call options (optional)
*/
public function __construct(Channel $channel,
$method,
$deserialize,
$timeout = false)
$options = [])
{
if ($timeout) {
if (isset($options['timeout']) &&
is_numeric($timeout = $options['timeout'])) {
$now = Timeval::now();
$delta = new Timeval($timeout);
$deadline = $now->add($delta);
@ -65,6 +66,13 @@ abstract class AbstractCall
$this->call = new Call($channel, $method, $deadline);
$this->deserialize = $deserialize;
$this->metadata = null;
if (isset($options['call_credentials_callback']) &&
is_callable($call_credentials_callback =
$options['call_credentials_callback'])) {
$call_credentials = CallCredentials::createFromPlugin(
$call_credentials_callback);
$this->call->setCredentials($call_credentials);
}
}
/**

@ -158,25 +158,6 @@ class BaseStub
return 'https://'.$this->hostname.$service_name;
}
/**
* extract $timeout from $metadata.
*
* @param $metadata The metadata map
*
* @return list($metadata_copy, $timeout)
*/
private function _extract_timeout_from_metadata($metadata)
{
$timeout = false;
$metadata_copy = $metadata;
if (isset($metadata['timeout'])) {
$timeout = $metadata['timeout'];
unset($metadata_copy['timeout']);
}
return [$metadata_copy, $timeout];
}
/**
* validate and normalize the metadata array.
*
@ -220,21 +201,19 @@ class BaseStub
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new UnaryCall($this->channel,
$method,
$deserialize,
$timeout);
$options);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$metadata = call_user_func($this->update_metadata,
$metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($argument, $actual_metadata, $options);
$metadata = $this->_validate_and_normalize_metadata(
$metadata);
$call->start($argument, $metadata, $options);
return $call;
}
@ -253,23 +232,22 @@ class BaseStub
*/
public function _clientStreamRequest($method,
callable $deserialize,
$metadata = [])
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new ClientStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$options);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$metadata = call_user_func($this->update_metadata,
$metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($actual_metadata);
$metadata = $this->_validate_and_normalize_metadata(
$metadata);
$call->start($metadata);
return $call;
}
@ -291,21 +269,19 @@ class BaseStub
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new ServerStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$options);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$metadata = call_user_func($this->update_metadata,
$metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($argument, $actual_metadata, $options);
$metadata = $this->_validate_and_normalize_metadata(
$metadata);
$call->start($argument, $metadata, $options);
return $call;
}
@ -321,23 +297,22 @@ class BaseStub
*/
public function _bidiRequest($method,
callable $deserialize,
$metadata = [])
$metadata = [],
$options = [])
{
list($actual_metadata, $timeout) =
$this->_extract_timeout_from_metadata($metadata);
$call = new BidiStreamingCall($this->channel,
$method,
$deserialize,
$timeout);
$options);
$jwt_aud_uri = $this->_get_jwt_aud_uri($method);
if (is_callable($this->update_metadata)) {
$actual_metadata = call_user_func($this->update_metadata,
$actual_metadata,
$metadata = call_user_func($this->update_metadata,
$metadata,
$jwt_aud_uri);
}
$actual_metadata = $this->_validate_and_normalize_metadata(
$actual_metadata);
$call->start($actual_metadata);
$metadata = $this->_validate_and_normalize_metadata(
$metadata);
$call->start($metadata);
return $call;
}

@ -41,7 +41,6 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase
* running on $GRPC_TEST_HOST.
*/
protected static $client;
protected static $timeout;
public function testWaitForNotReady()
{
@ -93,7 +92,7 @@ abstract class AbstractGeneratedCodeTest extends PHPUnit_Framework_TestCase
public function testTimeout()
{
$div_arg = new math\DivArgs();
$call = self::$client->Div($div_arg, ['timeout' => 100]);
$call = self::$client->Div($div_arg, [], ['timeout' => 100]);
list($response, $status) = $call->wait();
$this->assertSame(\Grpc\STATUS_DEADLINE_EXCEEDED, $status->code);
}

@ -84,7 +84,7 @@ function largeUnary($stub)
* @param $fillOauthScope boolean whether to fill result with oauth scope
*/
function performLargeUnary($stub, $fillUsername = false, $fillOauthScope = false,
$metadata = [])
$callback = false)
{
$request_len = 271828;
$response_len = 314159;
@ -99,7 +99,12 @@ function performLargeUnary($stub, $fillUsername = false, $fillOauthScope = false
$request->setFillUsername($fillUsername);
$request->setFillOauthScope($fillOauthScope);
list($result, $status) = $stub->UnaryCall($request, $metadata)->wait();
$options = false;
if ($callback) {
$options['call_credentials_callback'] = $callback;
}
list($result, $status) = $stub->UnaryCall($request, [], $options)->wait();
hardAssert($status->code === Grpc\STATUS_OK, 'Call did not complete successfully');
hardAssert($result !== null, 'Call returned a null response');
$payload = $result->getPayload();
@ -186,6 +191,13 @@ function oauth2AuthToken($stub, $args)
'invalid email returned');
}
function updateAuthMetadataCallback($authUri)
{
$auth_credentials = ApplicationDefaultCredentials::getCredentials();
return $auth_credentials->updateMetadata($metadata = [], $authUri);
}
/**
* Run the per_rpc_creds auth test.
*
@ -197,15 +209,9 @@ function perRpcCreds($stub, $args)
$jsonKey = json_decode(
file_get_contents(getenv(CredentialsLoader::ENV_VAR)),
true);
$auth_credentials = ApplicationDefaultCredentials::getCredentials(
$args['oauth_scope']
);
$token = $auth_credentials->fetchAuthToken();
$metadata = [CredentialsLoader::AUTH_METADATA_KEY => [sprintf('%s %s',
$token['token_type'],
$token['access_token'])]];
$result = performLargeUnary($stub, $fillUsername = true, $fillOauthScope = true,
$metadata);
'updateAuthMetadataCallback');
hardAssert($result->getUsername() == $jsonKey['client_email'],
'invalid email returned');
}
@ -363,7 +369,7 @@ function cancelAfterFirstResponse($stub)
function timeoutOnSleepingServer($stub)
{
$call = $stub->FullDuplexCall(['timeout' => 1000]);
$call = $stub->FullDuplexCall([], ['timeout' => 1000]);
$request = new grpc\testing\StreamingOutputCallRequest();
$request->setResponseType(grpc\testing\PayloadType::COMPRESSABLE);
$response_parameters = new grpc\testing\ResponseParameters();

@ -0,0 +1,96 @@
<?php
/*
*
* Copyright 2015, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
class CallCredentialsTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$credentials = Grpc\ChannelCredentials::createSsl(
file_get_contents(dirname(__FILE__).'/../data/ca.pem'));
$call_credentials = Grpc\CallCredentials::createFromPlugin(
array($this, 'callbackFunc'));
$credentials = Grpc\ChannelCredentials::createComposite(
$credentials,
$call_credentials
);
$server_credentials = Grpc\ServerCredentials::createSsl(
null,
file_get_contents(dirname(__FILE__).'/../data/server1.key'),
file_get_contents(dirname(__FILE__).'/../data/server1.pem'));
$this->server = new Grpc\Server();
$this->port = $this->server->addSecureHttp2Port('0.0.0.0:0',
$server_credentials);
$this->server->start();
$this->host_override = 'foo.test.google.fr';
$this->channel = new Grpc\Channel(
'localhost:'.$this->port,
[
'grpc.ssl_target_name_override' => $this->host_override,
'grpc.default_authority' => $this->host_override,
'credentials' => $credentials,
]
);
}
public function tearDown()
{
unset($this->channel);
unset($this->server);
}
public function callbackFunc($service_url)
{
$this->assertTrue(is_string($service_url));
return ['k1' => ['v1'], 'k2' => ['v2']];
}
public function testCreateFromPlugin()
{
$deadline = Grpc\Timeval::infFuture();
$status_text = 'xyz';
$call = new Grpc\Call($this->channel,
'/abc/dummy_method/',
$deadline,
$this->host_override);
$event = $call->startBatch([
Grpc\OP_SEND_INITIAL_METADATA => [],
Grpc\OP_SEND_CLOSE_FROM_CLIENT => true,
]);
$this->assertTrue($event->send_metadata);
$this->assertTrue($event->send_close);
}
}
Loading…
Cancel
Save