Skip to content

Commit 794fd10

Browse files
committed
Added support for OAuth2
Support OAuth2 through HTTP client listener. Related: #34
1 parent 48ada3b commit 794fd10

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the bitbucket-api package.
5+
*
6+
* (c) Alexandru G. <alex@gentle.ro>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Bitbucket\API\Exceptions;
13+
14+
use Buzz\Message\MessageInterface;
15+
use Buzz\Message\RequestInterface;
16+
17+
/**
18+
* @author Alexandru G. <alex@gentle.ro>
19+
*/
20+
class HttpResponseException extends \Exception
21+
{
22+
/** @var MessageInterface */
23+
private $response;
24+
25+
/** @var RequestInterface */
26+
private $request;
27+
28+
/**
29+
* @access public
30+
* @return RequestInterface
31+
*/
32+
public function getRequest()
33+
{
34+
return $this->request;
35+
}
36+
37+
/**
38+
* @access public
39+
* @param RequestInterface $request
40+
* @return $this
41+
*/
42+
public function setRequest($request)
43+
{
44+
$this->request = $request;
45+
46+
return $this;
47+
}
48+
49+
/**
50+
* @access public
51+
* @return MessageInterface
52+
*/
53+
public function getResponse()
54+
{
55+
return $this->response;
56+
}
57+
58+
/**
59+
* @access public
60+
* @param MessageInterface $response
61+
* @return $this
62+
*/
63+
public function setResponse($response)
64+
{
65+
$this->response = $response;
66+
67+
return $this;
68+
}
69+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the bitbucket-api package.
5+
*
6+
* (c) Alexandru G. <alex@gentle.ro>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Bitbucket\API\Http\Listener;
13+
14+
use Bitbucket\API\Exceptions\ForbiddenAccessException;
15+
use Bitbucket\API\Exceptions\HttpResponseException;
16+
use Bitbucket\API\Http\Client;
17+
use Bitbucket\API\Http\ClientInterface;
18+
use Buzz\Message\MessageInterface;
19+
use Buzz\Message\RequestInterface;
20+
21+
/**
22+
* @author Alexandru G. <alex@gentle.ro>
23+
*/
24+
class OAuth2Listener implements ListenerInterface
25+
{
26+
const ENDPOINT_ACCESS_TOKEN = 'access_token';
27+
const ENDPOINT_AUTHORIZE = 'authorize';
28+
29+
/** @var array */
30+
protected static $config = array(
31+
'oauth_client_id' => 'anon',
32+
'oauth_client_secret' => 'anon',
33+
'token_type' => 'bearer',
34+
'scopes' => array()
35+
);
36+
37+
/** @var ClientInterface */
38+
private $httpClient;
39+
40+
public function __construct(array $config, ClientInterface $client = null)
41+
{
42+
self::$config = array_merge(self::$config, $config);
43+
$this->httpClient = (null !== $client) ? $client : new Client(
44+
array(
45+
'base_url' => 'https://bitbucket.org/site',
46+
'api_version' => 'oauth2',
47+
'api_versions' => array('oauth2')
48+
)
49+
);
50+
}
51+
52+
/**
53+
* {@inheritDoc}
54+
*/
55+
public function getName()
56+
{
57+
return 'oauth2';
58+
}
59+
60+
/**
61+
* {@inheritDoc}
62+
*
63+
* @throws ForbiddenAccessException
64+
* @throws \InvalidArgumentException
65+
*/
66+
public function preSend(RequestInterface $request)
67+
{
68+
if (($oauth2Header = $request->getHeader('Authorization')) &&
69+
(strpos($oauth2Header, 'Bearer') !== false)
70+
) {
71+
return;
72+
}
73+
74+
if (false === array_key_exists('access_token', self::$config)) {
75+
try {
76+
$data = $this->getAccessToken();
77+
self::$config['token_type'] = $data['token_type'];
78+
self::$config['access_token'] = $data['access_token'];
79+
} catch (HttpResponseException $e) {
80+
throw new ForbiddenAccessException("Can't fetch access_token.", 0, $e);
81+
}
82+
}
83+
84+
$request->addHeader(
85+
sprintf(
86+
'Authorization: %s %s',
87+
ucfirst(strtolower(self::$config['token_type'])),
88+
self::$config['access_token']
89+
)
90+
);
91+
}
92+
93+
/**
94+
* {@inheritDoc}
95+
*/
96+
public function postSend(RequestInterface $request, MessageInterface $response)
97+
{
98+
}
99+
100+
/**
101+
* Fetch access token with a grant_type of client_credentials
102+
*
103+
* @access public
104+
* @return array
105+
*
106+
* throws \InvalidArgumentException
107+
* @throws HttpResponseException
108+
*/
109+
protected function getAccessToken()
110+
{
111+
$response = $this->httpClient
112+
->post(
113+
self::ENDPOINT_ACCESS_TOKEN,
114+
array(
115+
'grant_type' => 'client_credentials',
116+
'client_id' => self::$config['client_id'],
117+
'client_secret' => self::$config['client_secret'],
118+
'scope' => implode(',', self::$config['scopes'])
119+
)
120+
)
121+
;
122+
123+
$data = json_decode($response->getContent(), true);
124+
125+
if (json_last_error() !== JSON_ERROR_NONE) {
126+
$ex = new HttpResponseException('[access_token] Invalid JSON: '. json_last_error_msg());
127+
$ex
128+
->setResponse($this->httpClient->getLastResponse())
129+
->setRequest($this->httpClient->getLastRequest())
130+
;
131+
132+
throw $ex;
133+
}
134+
135+
if (false === array_key_exists('access_token', $data)) {
136+
throw new HttpResponseException('access_token is missing from response. '. $response->getContent());
137+
}
138+
139+
return $data;
140+
}
141+
}

0 commit comments

Comments
 (0)