Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremykendall committed Oct 4, 2014
2 parents 834b326 + 0e50b81 commit f98bc83
Show file tree
Hide file tree
Showing 49 changed files with 2,094 additions and 1,708 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
build
vendor
composer.lock
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -3,6 +3,7 @@ language: php
php:
- 5.4
- 5.5
- 5.6

script: phpunit

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013 Jeremy Kendall
Copyright (c) 2014 Jeremy Kendall

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
142 changes: 81 additions & 61 deletions README.md
Expand Up @@ -6,82 +6,104 @@ Signature generation and validation for REST API query authentication

## API Query Authentication

Most APIs require some sort of query authentication: a method of signing API
requests with an API key and signature. The signature is usually generated
Most APIs require some sort of query authentication, frequently a method of signing API
requests with an API key and signature. The signature is usually generated
using a shared secret. When you're consuming an API, there are (hopefully) easy
to follow steps to create signatures. When you're writing your own API, you
have to whip up both server-side signature validation and a client-side
have to whip up both a server-side signature validation strategy and a client-side
signature creation strategy. This library endeavors to handle both of those
tasks; signature creation and signature validation.
tasks for you.

## Philosophy
## Sample Implementation

Query Auth is intended to be -- and is written as -- a bare bones library. Many of
niceties and abstractions you'd find in a fully featured API library or SDK are
absent here. The point of the library is to provide you with the ability to
focus on writing your API in any way you see fit, without adding any additional
dependencies to the mix, while allowing you to hand off the query authentication
to this library.
A [sample implementation of the Query Auth library](https://github.com/jeremykendall/query-auth-impl)
is available in order to better demonstrate how one might employ the library.

## Usage

## Sample Implementation
There are three components to this library:

I've provided a [sample implementation of the Query Auth library](https://github.com/jeremykendall/query-auth-impl)
in order to better demonstrate how one might employ the library, from both the
API consumer and API creator perspectives.
* Request signing
* Request validation
* API key and secret generation

## Usage
Request signing and validation are made possible by the use of request adapters.

### Request Adapters

Query Auth request adapters wrap outgoing and incoming requests and adapt them to the
request interface that Query Auth expects.

#### Outgoing

Outgoing request adapters are used to facilitate request signing. There are
currently two available in the `QueryAuth\Request\Adapter\Outgoing` namespace:

* `GuzzleRequestAdapter` for use with Guzzle v3
* `GuzzleHttpRequestAdapter` for use with Guzzle v4

#### Incoming

Incoming request adapters are used to facilitate request validation. There is
currently one available in the `QueryAuth\Request\Adapter\Incoming` namespace:

There are three components to this library: Request signing for API consumers
and creators, request signature validation for API creators, and API key and
API secret generation.
* `SlimRequestAdapter` for use with Slim PHP v2

#### Custom

If you would prefer to use an HTTP library other than Guzzle, or if you prefer
to use an application framework other than Slim, you will need to write your own
request adapter(s). Please refer to the existing request adapters for examples.

### Request Signing

``` php
$factory = new QueryAuth\Factory();
$client = $factory->newClient();
use GuzzleHttp\Client as GuzzleHttpClient;
use QueryAuth\Credentials\Credentials;
use QueryAuth\Factory;
use QueryAuth\Request\Adapter\Outgoing\GuzzleHttpRequestAdapter;

$key = 'API_KEY';
$secret = 'API_SECRET';
$method = 'GET';
$host = 'api.example.com';
$path = '/resources';
$params = array('type' => 'vehicles');
$factory = new Factory();
$requestSigner = $factory->newRequestSigner();
$credentials = new Credentials('key', 'secret');

$signedParameters = $client->getSignedRequestParams($key, $secret, $method, $host, $path, $params);
```
// Create a GET request and set an endpoint
$guzzle = new GuzzleHttpClient(['base_url' => 'http://api.example.com']);
$request = $guzzle->createRequest('GET', '/endpoint');

// Sign the request
$requestSigner->signRequest(new GuzzleHttpRequestAdapter($request), $credentials);

`Client::getSignedRequestParams()` returns an array of parameters to send via
the querystring (for `GET` requests) or the request body. The parameters are
those provided to the method (if any), plus `timestamp`, `key`, and `signature`.
// Send signed request
$response = $guzzle->send($request);
```

### Signature Validation
### Request Validation

``` php
$factory = new QueryAuth\Factory();
$server = $factory->newServer();

$secret = 'API_SECRET_FROM_PERSISTENCE_LAYER';
$method = 'GET';
$host = 'api.example.com';
$path = '/resources';
// querystring params or request body as an array,
// which includes timestamp, key, and signature params from the client's
// getSignedRequestParams method
$params = 'PARAMS_FROM_REQUEST';

$isValid = $server->validateSignature($secret, $method, $host, $path, $params);
use QueryAuth\Credentials\Credentials;
use QueryAuth\Factory;
use QueryAuth\Request\Adapter\Incoming\SlimRequestAdapter;

$factory = new Factory();
$requestValidator = $factory->newRequestValidator();
$credentials = new Credentials('key', 'secret');

// Get the Slim request (in the context of a Slim route, hook, or middleware)
$request = $app->request;

// $isValid is a boolean
$isValid = $requestValidator->isValid(new SlimRequestAdapter($request), $credentials);
```

`Server::validateSignature()` will return either true or false. It might also
`RequestValidator::isValid()` will return either true or false. It might also
throw one of three exceptions:
* `MaximumDriftExceededException`: If timestamp is too far in the future
* `MinimumDriftExceededException`: It timestamp is too far in the past
* `DriftExceededException`: It timestamp is beyond +- `RequestValidator::$drift`
* `SignatureMissingException`: If signature is missing from request params
* `TimestampMissingException`: If timestamp is missing from request params

Drift defaults to 15 seconds, meaning there is a 30 second window during which the
request is valid. The default value can be modified using `Server::setDrift()`.
request is valid. The default value can be modified using `RequestValidator::setDrift()`.

### Replay Attack Prevention

Expand Down Expand Up @@ -147,18 +169,18 @@ random string generator.
Package installation is handled by Composer.

* If you haven't already, please [install Composer](http://getcomposer.org/doc/00-intro.md#installation-nix)
* Create `composer.json` in the root of your project:
* Create `composer.json` in the root of your project and add query-auth as a dependency:

``` json
{
"require": {
"jeremykendall/query-auth": "dev-develop"
"jeremykendall/query-auth": "*"
}
}
```

* Run `composer install`
* Require Composer's `vendor/autoload` script in your bootstrap/init script
* Require Composer's `vendor/autoload.php` script in your bootstrap/init script

## Feedback and Contributions

Expand All @@ -169,14 +191,12 @@ Package installation is handled by Composer.

## Credits

* The Client, Signer, and ParameterCollection code are my own implementation of
the [Signature Version 2
implementation](https://github.com/aws/aws-sdk-php/blob/master/src/Aws/Common/Signature/SignatureV2.php)
from the [AWS SDK for PHP
2](https://github.com/aws/aws-sdk-php/blob/master/src/Aws/Common/Signature/SignatureV2.php).
As such, a version of the Apache License Version 2.0 is included with this
distribution, and the applicable portion of the AWS SDK for PHP 2 NOTICE file
is included.
* Query Auth is my own implementation of the [Signature Version 2
implementation](https://github.com/aws/aws-sdk-php/blob/master/src/Aws/Common/Signature/SignatureV2.php)
from the [AWS SDK for PHP 2](https://github.com/aws/aws-sdk-php/blob/master/src/Aws/Common/Signature/SignatureV2.php).
As such, a version of the Apache License Version 2.0 is included with this
distribution, and the applicable portion of the AWS SDK for PHP 2 NOTICE file
is included.

* API key and API secret generation is handled by Anthony Ferrara's
[RandomLib](https://github.com/ircmaxell/RandomLib) random string generator.
2 changes: 2 additions & 0 deletions build.properties
@@ -0,0 +1,2 @@
project.basedir = .
passthru = true
133 changes: 133 additions & 0 deletions build.xml
@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>

<project name="Query Auth" default="build">

<property file="build.properties" />

<fileset id="src-files" dir="${project.basedir}/src">
<include name="**/*.php" />
</fileset>

<fileset id="test-files" dir="${project.basedir}/tests">
<include name="**/*.php" />
</fileset>

<target name="clean" description="Clean build path">
<delete dir="${project.basedir}/build" />
<mkdir dir="${project.basedir}/build" />
<mkdir dir="${project.basedir}/build/api" />
<mkdir dir="${project.basedir}/build/cache" />
<mkdir dir="${project.basedir}/build/code-browser" />
<mkdir dir="${project.basedir}/build/coverage" />
<mkdir dir="${project.basedir}/build/logs" />
<mkdir dir="${project.basedir}/build/pdepend" />
</target>

<target name="phplint" description="Running php lint check">
<phplint haltonfailure="true">
<fileset refid="src-files" />
<fileset refid="test-files" />
</phplint>
</target>

<target name="phpunit" description="Running unit tests">
<exec
passthru="${passthru}"
dir="${project.basedir}"
command="phpunit
--log-junit=${project.basedir}/build/logs/junit.xml
--coverage-clover=${project.basedir}/build/logs/clover.xml
--coverage-html=${project.basedir}/build/coverage" />
</target>

<target name="phpunit-gen-report">
<phpunitreport
infile="${project.basedir}/build/logs/junit.xml"
format="frames"
todir="${project.basedir}/build/logs"
/>
</target>

<target name="phpdoc" description="Generate API documentation">
<exec
passthru="${passthru}"
command="phpdoc
-d ${project.basedir}/src
-t ${project.basedir}/build/api
--defaultpackagename=QueryAuth
--title='Query Auth'
--force" />
</target>

<target name="phpcs" description="Coding Standards Analysis">
<exec
passthru="${passthru}"
command="phpcs
--report=checkstyle
--report-file=${project.basedir}/build/logs/checkstyle.xml
--standard=PSR2
--extensions=php
${project.basedir}/src" />
</target>

<target name="phpcpd" description="Copy/Paste detection">
<phpcpd>
<fileset refid="src-files" />
<formatter
type="pmd"
outfile="${project.basedir}/build/logs/pmd-cpd.xml" />
</phpcpd>
</target>

<target name="phploc" description="Generate phploc.csv">
<phploc reportType="csv" reportName="phploc" reportDirectory="${project.basedir}/build/logs">
<fileset refid="src-files" />
</phploc>
</target>

<target name="phpcb" description="Source code browser">
<exec
passthru="${passthru}"
command="phpcb
--log ${project.basedir}/build/logs
--source ${project.basedir}/src
--output ${project.basedir}/build/code-browser" />
</target>

<target name="pdepend" description="Calculate dependencies">
<exec
passthru="${passthru}"
dir="${project.basedir}/src"
command="pdepend
--configuration=${project.basedir}/pdepend.xml
--jdepend-chart=${project.basedir}/build/pdepend/dependencies.svg
--jdepend-xml=${project.basedir}/build/logs/jdepend.xml
--overview-pyramid=${project.basedir}/build/pdepend/overview-pyramid.svg
--suffix=php
--ignore=tests,vendor
${project.basedir}" />
</target>

<target name="phpmd" description="Mess detection">
<exec
passthru="${passthru}"
dir="${project.basedir}/src"
command="phpmd ${project.basedir}/src xml cleancode,codesize,controversial,design,naming,unusedcode
--suffixes .php
--reportfile ${project.basedir}/build/logs/pmd.xml" />
</target>

<target name="build" description="Build app">
<phingCall target="clean" />
<phingCall target="phplint" />
<phingCall target="phpdoc" />
<phingCall target="phpcs" />
<phingCall target="phpunit" />
<phingCall target="phpunit-gen-report" />
<phingCall target="phpcpd" />
<phingCall target="phpmd" />
<phingCall target="pdepend" />
<phingCall target="phploc" />
<phingCall target="phpcb" />
</target>
</project>
18 changes: 16 additions & 2 deletions composer.json
Expand Up @@ -9,17 +9,31 @@
"role": "Developer"
}
],
"keywords": ["REST", "authentication", "query authentication", "signature", "API"],
"keywords": [
"REST",
"authentication",
"query authentication",
"API"
],
"require": {
"php": ">=5.4",
"ircmaxell/random-lib": "v1.0.0"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
"guzzle/guzzle": ">=3.7.0,<=3.9.9",
"guzzlehttp/guzzle": "~4",
"mockery/mockery": "0.9.2",
"phpunit/phpunit": "4.*",
"slim/slim": "2.*"
},
"autoload": {
"psr-0": {
"QueryAuth\\": "src/"
}
},
"suggest": {
"guzzle/guzzle": "To use the Guzzle v3 outgoing request adapter",
"guzzlehttp/guzzle": "To use the Guzzle v4 outgoing request adapter",
"slim/slim": "To use the Slim v2 incoming request adapter"
}
}

0 comments on commit f98bc83

Please sign in to comment.