1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
14: namespace Cake\Http\Client\Auth;
15:
16: use Cake\Core\Exception\Exception;
17: use Cake\Http\Client\Request;
18: use Cake\Utility\Security;
19: use RuntimeException;
20:
21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
31: class Oauth
32: {
33: 34: 35: 36: 37: 38: 39: 40:
41: public function authentication(Request $request, array $credentials)
42: {
43: if (!isset($credentials['consumerKey'])) {
44: return $request;
45: }
46: if (empty($credentials['method'])) {
47: $credentials['method'] = 'hmac-sha1';
48: }
49: $credentials['method'] = strtoupper($credentials['method']);
50:
51: $value = null;
52: switch ($credentials['method']) {
53: case 'HMAC-SHA1':
54: $hasKeys = isset(
55: $credentials['consumerSecret'],
56: $credentials['token'],
57: $credentials['tokenSecret']
58: );
59: if (!$hasKeys) {
60: return $request;
61: }
62: $value = $this->_hmacSha1($request, $credentials);
63: break;
64:
65: case 'RSA-SHA1':
66: if (!isset($credentials['privateKey'])) {
67: return $request;
68: }
69: $value = $this->_rsaSha1($request, $credentials);
70: break;
71:
72: case 'PLAINTEXT':
73: $hasKeys = isset(
74: $credentials['consumerSecret'],
75: $credentials['token'],
76: $credentials['tokenSecret']
77: );
78: if (!$hasKeys) {
79: return $request;
80: }
81: $value = $this->_plaintext($request, $credentials);
82: break;
83:
84: default:
85: throw new Exception(sprintf('Unknown Oauth signature method %s', $credentials['method']));
86: }
87:
88: return $request->withHeader('Authorization', $value);
89: }
90:
91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101:
102: protected function _plaintext($request, $credentials)
103: {
104: $values = [
105: 'oauth_version' => '1.0',
106: 'oauth_nonce' => uniqid(),
107: 'oauth_timestamp' => time(),
108: 'oauth_signature_method' => 'PLAINTEXT',
109: 'oauth_token' => $credentials['token'],
110: 'oauth_consumer_key' => $credentials['consumerKey'],
111: ];
112: if (isset($credentials['realm'])) {
113: $values['oauth_realm'] = $credentials['realm'];
114: }
115: $key = [$credentials['consumerSecret'], $credentials['tokenSecret']];
116: $key = implode('&', $key);
117: $values['oauth_signature'] = $key;
118:
119: return $this->_buildAuth($values);
120: }
121:
122: 123: 124: 125: 126: 127: 128: 129: 130:
131: protected function _hmacSha1($request, $credentials)
132: {
133: $nonce = isset($credentials['nonce']) ? $credentials['nonce'] : uniqid();
134: $timestamp = isset($credentials['timestamp']) ? $credentials['timestamp'] : time();
135: $values = [
136: 'oauth_version' => '1.0',
137: 'oauth_nonce' => $nonce,
138: 'oauth_timestamp' => $timestamp,
139: 'oauth_signature_method' => 'HMAC-SHA1',
140: 'oauth_token' => $credentials['token'],
141: 'oauth_consumer_key' => $credentials['consumerKey'],
142: ];
143: $baseString = $this->baseString($request, $values);
144:
145: if (isset($credentials['realm'])) {
146: $values['oauth_realm'] = $credentials['realm'];
147: }
148: $key = [$credentials['consumerSecret'], $credentials['tokenSecret']];
149: $key = array_map([$this, '_encode'], $key);
150: $key = implode('&', $key);
151:
152: $values['oauth_signature'] = base64_encode(
153: hash_hmac('sha1', $baseString, $key, true)
154: );
155:
156: return $this->_buildAuth($values);
157: }
158:
159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169:
170: protected function _rsaSha1($request, $credentials)
171: {
172: if (!function_exists('openssl_pkey_get_private')) {
173: throw new RuntimeException('RSA-SHA1 signature method requires the OpenSSL extension.');
174: }
175:
176: $nonce = isset($credentials['nonce']) ? $credentials['nonce'] : bin2hex(Security::randomBytes(16));
177: $timestamp = isset($credentials['timestamp']) ? $credentials['timestamp'] : time();
178: $values = [
179: 'oauth_version' => '1.0',
180: 'oauth_nonce' => $nonce,
181: 'oauth_timestamp' => $timestamp,
182: 'oauth_signature_method' => 'RSA-SHA1',
183: 'oauth_consumer_key' => $credentials['consumerKey'],
184: ];
185: if (isset($credentials['consumerSecret'])) {
186: $values['oauth_consumer_secret'] = $credentials['consumerSecret'];
187: }
188: if (isset($credentials['token'])) {
189: $values['oauth_token'] = $credentials['token'];
190: }
191: if (isset($credentials['tokenSecret'])) {
192: $values['oauth_token_secret'] = $credentials['tokenSecret'];
193: }
194: $baseString = $this->baseString($request, $values);
195:
196: if (isset($credentials['realm'])) {
197: $values['oauth_realm'] = $credentials['realm'];
198: }
199:
200: if (is_resource($credentials['privateKey'])) {
201: $resource = $credentials['privateKey'];
202: $privateKey = stream_get_contents($resource);
203: rewind($resource);
204: $credentials['privateKey'] = $privateKey;
205: }
206:
207: $credentials += [
208: 'privateKeyPassphrase' => null,
209: ];
210: if (is_resource($credentials['privateKeyPassphrase'])) {
211: $resource = $credentials['privateKeyPassphrase'];
212: $passphrase = stream_get_line($resource, 0, PHP_EOL);
213: rewind($resource);
214: $credentials['privateKeyPassphrase'] = $passphrase;
215: }
216: $privateKey = openssl_pkey_get_private($credentials['privateKey'], $credentials['privateKeyPassphrase']);
217: $signature = '';
218: openssl_sign($baseString, $signature, $privateKey);
219: openssl_free_key($privateKey);
220:
221: $values['oauth_signature'] = base64_encode($signature);
222:
223: return $this->_buildAuth($values);
224: }
225:
226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238:
239: public function baseString($request, $oauthValues)
240: {
241: $parts = [
242: $request->getMethod(),
243: $this->_normalizedUrl($request->getUri()),
244: $this->_normalizedParams($request, $oauthValues),
245: ];
246: $parts = array_map([$this, '_encode'], $parts);
247:
248: return implode('&', $parts);
249: }
250:
251: 252: 253: 254: 255: 256: 257: 258:
259: protected function _normalizedUrl($uri)
260: {
261: $out = $uri->getScheme() . '://';
262: $out .= strtolower($uri->getHost());
263: $out .= $uri->getPath();
264:
265: return $out;
266: }
267:
268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279:
280: protected function _normalizedParams($request, $oauthValues)
281: {
282: $query = parse_url($request->getUri(), PHP_URL_QUERY);
283: parse_str($query, $queryArgs);
284:
285: $post = [];
286: $body = $request->body();
287: if (is_string($body) && $request->getHeaderLine('content-type') === 'application/x-www-form-urlencoded') {
288: parse_str($body, $post);
289: }
290: if (is_array($body)) {
291: $post = $body;
292: }
293:
294: $args = array_merge($queryArgs, $oauthValues, $post);
295: $pairs = $this->_normalizeData($args);
296: $data = [];
297: foreach ($pairs as $pair) {
298: $data[] = implode('=', $pair);
299: }
300: sort($data, SORT_STRING);
301:
302: return implode('&', $data);
303: }
304:
305: 306: 307: 308: 309: 310: 311: 312:
313: protected function _normalizeData($args, $path = '')
314: {
315: $data = [];
316: foreach ($args as $key => $value) {
317: if ($path) {
318:
319:
320:
321: if (!is_numeric($key)) {
322: $key = "{$path}[{$key}]";
323: } else {
324: $key = $path;
325: }
326: }
327: if (is_array($value)) {
328: uksort($value, 'strcmp');
329: $data = array_merge($data, $this->_normalizeData($value, $key));
330: } else {
331: $data[] = [$key, $value];
332: }
333: }
334:
335: return $data;
336: }
337:
338: 339: 340: 341: 342: 343:
344: protected function _buildAuth($data)
345: {
346: $out = 'OAuth ';
347: $params = [];
348: foreach ($data as $key => $value) {
349: $params[] = $key . '="' . $this->_encode($value) . '"';
350: }
351: $out .= implode(',', $params);
352:
353: return $out;
354: }
355:
356: 357: 358: 359: 360: 361:
362: protected function _encode($value)
363: {
364: return str_replace(['%7E', '+'], ['~', ' '], rawurlencode($value));
365: }
366: }
367:
368:
369: class_alias('Cake\Http\Client\Auth\Oauth', 'Cake\Network\Http\Auth\Oauth');
370: