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