1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26:
27: 28: 29: 30: 31: 32: 33:
34: class Mage_Oauth_Model_Server
35: {
36: 37: 38:
39: const ERR_OK = 0;
40: const ERR_VERSION_REJECTED = 1;
41: const ERR_PARAMETER_ABSENT = 2;
42: const ERR_PARAMETER_REJECTED = 3;
43: const ERR_TIMESTAMP_REFUSED = 4;
44: const ERR_NONCE_USED = 5;
45: const ERR_SIGNATURE_METHOD_REJECTED = 6;
46: const ERR_SIGNATURE_INVALID = 7;
47: const ERR_CONSUMER_KEY_REJECTED = 8;
48: const ERR_TOKEN_USED = 9;
49: const ERR_TOKEN_EXPIRED = 10;
50: const ERR_TOKEN_REVOKED = 11;
51: const ERR_TOKEN_REJECTED = 12;
52: const ERR_VERIFIER_INVALID = 13;
53: const ERR_PERMISSION_UNKNOWN = 14;
54: const ERR_PERMISSION_DENIED = 15;
55:
56:
57: 58: 59:
60: const SIGNATURE_HMAC = 'HMAC-SHA1';
61: const SIGNATURE_RSA = 'RSA-SHA1';
62: const SIGNATURE_PLAIN = 'PLAINTEXT';
63:
64:
65: 66: 67:
68: const REQUEST_INITIATE = 'initiate';
69: const REQUEST_AUTHORIZE = 'authorize';
70: const REQUEST_TOKEN = 'token';
71: const REQUEST_RESOURCE = 'resource';
72:
73:
74: 75: 76:
77: const HTTP_OK = 200;
78: const HTTP_BAD_REQUEST = 400;
79: const HTTP_UNAUTHORIZED = 401;
80: const HTTP_INTERNAL_ERROR = 500;
81:
82:
83: 84: 85:
86: const TIME_DEVIATION = 600;
87:
88: 89: 90: 91: 92:
93: const CALLBACK_ESTABLISHED = 'oob';
94:
95: 96: 97: 98: 99:
100: protected $_consumer;
101:
102: 103: 104: 105: 106:
107: protected $_errors = array(
108: self::ERR_VERSION_REJECTED => 'version_rejected',
109: self::ERR_PARAMETER_ABSENT => 'parameter_absent',
110: self::ERR_PARAMETER_REJECTED => 'parameter_rejected',
111: self::ERR_TIMESTAMP_REFUSED => 'timestamp_refused',
112: self::ERR_NONCE_USED => 'nonce_used',
113: self::ERR_SIGNATURE_METHOD_REJECTED => 'signature_method_rejected',
114: self::ERR_SIGNATURE_INVALID => 'signature_invalid',
115: self::ERR_CONSUMER_KEY_REJECTED => 'consumer_key_rejected',
116: self::ERR_TOKEN_USED => 'token_used',
117: self::ERR_TOKEN_EXPIRED => 'token_expired',
118: self::ERR_TOKEN_REVOKED => 'token_revoked',
119: self::ERR_TOKEN_REJECTED => 'token_rejected',
120: self::ERR_VERIFIER_INVALID => 'verifier_invalid',
121: self::ERR_PERMISSION_UNKNOWN => 'permission_unknown',
122: self::ERR_PERMISSION_DENIED => 'permission_denied'
123: );
124:
125: 126: 127: 128: 129:
130: protected $_errorsToHttpCode = array(
131: self::ERR_VERSION_REJECTED => self::HTTP_BAD_REQUEST,
132: self::ERR_PARAMETER_ABSENT => self::HTTP_BAD_REQUEST,
133: self::ERR_PARAMETER_REJECTED => self::HTTP_BAD_REQUEST,
134: self::ERR_TIMESTAMP_REFUSED => self::HTTP_BAD_REQUEST,
135: self::ERR_NONCE_USED => self::HTTP_UNAUTHORIZED,
136: self::ERR_SIGNATURE_METHOD_REJECTED => self::HTTP_BAD_REQUEST,
137: self::ERR_SIGNATURE_INVALID => self::HTTP_UNAUTHORIZED,
138: self::ERR_CONSUMER_KEY_REJECTED => self::HTTP_UNAUTHORIZED,
139: self::ERR_TOKEN_USED => self::HTTP_UNAUTHORIZED,
140: self::ERR_TOKEN_EXPIRED => self::HTTP_UNAUTHORIZED,
141: self::ERR_TOKEN_REVOKED => self::HTTP_UNAUTHORIZED,
142: self::ERR_TOKEN_REJECTED => self::HTTP_UNAUTHORIZED,
143: self::ERR_VERIFIER_INVALID => self::HTTP_UNAUTHORIZED,
144: self::ERR_PERMISSION_UNKNOWN => self::HTTP_UNAUTHORIZED,
145: self::ERR_PERMISSION_DENIED => self::HTTP_UNAUTHORIZED
146: );
147:
148: 149: 150: 151: 152:
153: protected $_params = array();
154:
155: 156: 157: 158: 159:
160: protected $_protocolParams = array();
161:
162: 163: 164: 165: 166:
167: protected $_request;
168:
169: 170: 171: 172: 173:
174: protected $_requestType;
175:
176: 177: 178: 179: 180:
181: protected $_response = null;
182:
183: 184: 185: 186: 187:
188: protected $_token;
189:
190: 191: 192: 193: 194: 195:
196: public function __construct($request = null)
197: {
198: if (is_object($request)) {
199: if (!$request instanceof Zend_Controller_Request_Http) {
200: throw new Exception('Invalid request object passed');
201: }
202: $this->_request = $request;
203: } else {
204: $this->_request = Mage::app()->getRequest();
205: }
206: }
207:
208: 209: 210: 211: 212: 213:
214: protected function _fetchParams()
215: {
216: $authHeaderValue = $this->_request->getHeader('Authorization');
217:
218: if ($authHeaderValue && 'oauth' === strtolower(substr($authHeaderValue, 0, 5))) {
219: $authHeaderValue = substr($authHeaderValue, 6);
220:
221: foreach (explode(',', $authHeaderValue) as $paramStr) {
222: $nameAndValue = explode('=', trim($paramStr), 2);
223:
224: if (count($nameAndValue) < 2) {
225: continue;
226: }
227: if ($this->_isProtocolParameter($nameAndValue[0])) {
228: $this->_protocolParams[rawurldecode($nameAndValue[0])] = rawurldecode(trim($nameAndValue[1], '"'));
229: }
230: }
231: }
232: $contentTypeHeader = $this->_request->getHeader(Zend_Http_Client::CONTENT_TYPE);
233:
234: if ($contentTypeHeader && 0 === strpos($contentTypeHeader, Zend_Http_Client::ENC_URLENCODED)) {
235: $protocolParamsNotSet = !$this->_protocolParams;
236:
237: parse_str($this->_request->getRawBody(), $bodyParams);
238:
239: foreach ($bodyParams as $bodyParamName => $bodyParamValue) {
240: if (!$this->_isProtocolParameter($bodyParamName)) {
241: $this->_params[$bodyParamName] = $bodyParamValue;
242: } elseif ($protocolParamsNotSet) {
243: $this->_protocolParams[$bodyParamName] = $bodyParamValue;
244: }
245: }
246: }
247: $protocolParamsNotSet = !$this->_protocolParams;
248:
249: $url = $this->_request->getScheme() . '://' . $this->_request->getHttpHost() . $this->_request->getRequestUri();
250:
251: if (($queryString = Zend_Uri_Http::fromString($url)->getQuery())) {
252: foreach (explode('&', $queryString) as $paramToValue) {
253: $paramData = explode('=', $paramToValue);
254:
255: if (2 === count($paramData) && !$this->_isProtocolParameter($paramData[0])) {
256: $this->_params[rawurldecode($paramData[0])] = rawurldecode($paramData[1]);
257: }
258: }
259: }
260: if ($protocolParamsNotSet) {
261: $this->_fetchProtocolParamsFromQuery();
262: }
263: return $this;
264: }
265:
266: 267: 268: 269: 270:
271: protected function _fetchProtocolParamsFromQuery()
272: {
273: foreach ($this->_request->getQuery() as $queryParamName => $queryParamValue) {
274: if ($this->_isProtocolParameter($queryParamName)) {
275: $this->_protocolParams[$queryParamName] = $queryParamValue;
276: }
277: }
278: return $this;
279: }
280:
281: 282: 283: 284: 285:
286: protected function _getResponse()
287: {
288: if (null === $this->_response) {
289: $this->setResponse(Mage::app()->getResponse());
290: }
291: return $this->_response;
292: }
293:
294: 295: 296: 297: 298:
299: protected function _initConsumer()
300: {
301: $this->_consumer = Mage::getModel('oauth/consumer');
302:
303: $this->_consumer->load($this->_protocolParams['oauth_consumer_key'], 'key');
304:
305: if (!$this->_consumer->getId()) {
306: $this->_throwException('', self::ERR_CONSUMER_KEY_REJECTED);
307: }
308: }
309:
310: 311: 312: 313: 314: 315:
316: protected function _initToken()
317: {
318: $this->_token = Mage::getModel('oauth/token');
319:
320: if (self::REQUEST_INITIATE != $this->_requestType) {
321: $this->_validateTokenParam();
322:
323: $this->_token->load($this->_protocolParams['oauth_token'], 'token');
324:
325: if (!$this->_token->getId()) {
326: $this->_throwException('', self::ERR_TOKEN_REJECTED);
327: }
328: if (self::REQUEST_TOKEN == $this->_requestType) {
329: $this->_validateVerifierParam();
330:
331: if ($this->_token->getVerifier() != $this->_protocolParams['oauth_verifier']) {
332: $this->_throwException('', self::ERR_VERIFIER_INVALID);
333: }
334: if ($this->_token->getConsumerId() != $this->_consumer->getId()) {
335: $this->_throwException('', self::ERR_TOKEN_REJECTED);
336: }
337: if (Mage_Oauth_Model_Token::TYPE_REQUEST != $this->_token->getType()) {
338: $this->_throwException('', self::ERR_TOKEN_USED);
339: }
340: } elseif (self::REQUEST_AUTHORIZE == $this->_requestType) {
341: if ($this->_token->getAuthorized()) {
342: $this->_throwException('', self::ERR_TOKEN_USED);
343: }
344: } elseif (self::REQUEST_RESOURCE == $this->_requestType) {
345: if (Mage_Oauth_Model_Token::TYPE_ACCESS != $this->_token->getType()) {
346: $this->_throwException('', self::ERR_TOKEN_REJECTED);
347: }
348: if ($this->_token->getRevoked()) {
349: $this->_throwException('', self::ERR_TOKEN_REVOKED);
350: }
351:
352: }
353: } else {
354: $this->_validateCallbackUrlParam();
355: }
356: return $this;
357: }
358:
359: 360: 361: 362: 363: 364:
365: protected function _isProtocolParameter($attrName)
366: {
367: return (bool) preg_match('/oauth_[a-z_-]+/', $attrName);
368: }
369:
370: 371: 372: 373: 374: 375: 376:
377: protected function _processRequest($requestType)
378: {
379:
380: if (self::REQUEST_INITIATE != $requestType
381: && self::REQUEST_RESOURCE != $requestType
382: && self::REQUEST_TOKEN != $requestType
383: ) {
384: Mage::throwException('Invalid request type');
385: }
386: $this->_requestType = $requestType;
387:
388:
389: $this->_fetchParams();
390:
391:
392: $this->_validateProtocolParams();
393:
394:
395: $this->_initConsumer();
396:
397:
398: $this->_initToken();
399:
400:
401: $this->_validateSignature();
402:
403:
404: $this->_saveToken();
405:
406: return $this;
407: }
408:
409: 410: 411:
412: protected function _saveToken()
413: {
414: if (self::REQUEST_INITIATE == $this->_requestType) {
415: if (self::CALLBACK_ESTABLISHED == $this->_protocolParams['oauth_callback']
416: && $this->_consumer->getCallBackUrl()) {
417: $callbackUrl = $this->_consumer->getCallBackUrl();
418: } else {
419: $callbackUrl = $this->_protocolParams['oauth_callback'];
420: }
421: $this->_token->createRequestToken($this->_consumer->getId(), $callbackUrl);
422: } elseif (self::REQUEST_TOKEN == $this->_requestType) {
423: $this->_token->convertToAccess();
424: }
425: }
426:
427: 428: 429: 430: 431: 432: 433:
434: protected function _throwException($message = '', $code = 0)
435: {
436: throw Mage::exception('Mage_Oauth', $message, $code);
437: }
438:
439: 440: 441:
442: protected function _validateCallbackUrlParam()
443: {
444: if (!isset($this->_protocolParams['oauth_callback'])) {
445: $this->_throwException('oauth_callback', self::ERR_PARAMETER_ABSENT);
446: }
447: if (!is_string($this->_protocolParams['oauth_callback'])) {
448: $this->_throwException('oauth_callback', self::ERR_PARAMETER_REJECTED);
449: }
450: if (self::CALLBACK_ESTABLISHED != $this->_protocolParams['oauth_callback']
451: && !Zend_Uri::check($this->_protocolParams['oauth_callback'])
452: ) {
453: $this->_throwException('oauth_callback', self::ERR_PARAMETER_REJECTED);
454: }
455: }
456:
457: 458: 459: 460: 461: 462:
463: protected function _validateNonce($nonce, $timestamp)
464: {
465: $timestamp = (int) $timestamp;
466:
467: if ($timestamp <= 0 || $timestamp > (time() + self::TIME_DEVIATION)) {
468: $this->_throwException('', self::ERR_TIMESTAMP_REFUSED);
469: }
470:
471: $nonceObj = Mage::getModel('oauth/nonce');
472:
473: $nonceObj->load($nonce, 'nonce');
474:
475: if ($nonceObj->getTimestamp() == $timestamp) {
476: $this->_throwException('', self::ERR_NONCE_USED);
477: }
478: $nonceObj->setNonce($nonce)
479: ->setTimestamp($timestamp)
480: ->save();
481: }
482:
483: 484: 485: 486: 487:
488: protected function _validateProtocolParams()
489: {
490:
491: if (isset($this->_protocolParams['oauth_version']) && '1.0' != $this->_protocolParams['oauth_version']) {
492: $this->_throwException('', self::ERR_VERSION_REJECTED);
493: }
494:
495: foreach (array('oauth_consumer_key', 'oauth_signature_method', 'oauth_signature') as $reqField) {
496: if (empty($this->_protocolParams[$reqField])) {
497: $this->_throwException($reqField, self::ERR_PARAMETER_ABSENT);
498: }
499: }
500:
501: foreach ($this->_protocolParams as $paramName => $paramValue) {
502: if (!is_string($paramValue)) {
503: $this->_throwException($paramName, self::ERR_PARAMETER_REJECTED);
504: }
505: }
506:
507: if (strlen($this->_protocolParams['oauth_consumer_key']) != Mage_Oauth_Model_Consumer::KEY_LENGTH) {
508: $this->_throwException('', self::ERR_CONSUMER_KEY_REJECTED);
509: }
510:
511: if (!in_array($this->_protocolParams['oauth_signature_method'], self::getSupportedSignatureMethods())) {
512: $this->_throwException('', self::ERR_SIGNATURE_METHOD_REJECTED);
513: }
514:
515: if (self::SIGNATURE_PLAIN != $this->_protocolParams['oauth_signature_method']) {
516: if (empty($this->_protocolParams['oauth_nonce'])) {
517: $this->_throwException('oauth_nonce', self::ERR_PARAMETER_ABSENT);
518: }
519: if (empty($this->_protocolParams['oauth_timestamp'])) {
520: $this->_throwException('oauth_timestamp', self::ERR_PARAMETER_ABSENT);
521: }
522: $this->_validateNonce($this->_protocolParams['oauth_nonce'], $this->_protocolParams['oauth_timestamp']);
523: }
524: }
525:
526: 527: 528: 529: 530:
531: protected function _validateSignature()
532: {
533: $util = new Zend_Oauth_Http_Utility();
534:
535: $calculatedSign = $util->sign(
536: array_merge($this->_params, $this->_protocolParams),
537: $this->_protocolParams['oauth_signature_method'],
538: $this->_consumer->getSecret(),
539: $this->_token->getSecret(),
540: $this->_request->getMethod(),
541: $this->_request->getScheme() . '://' . $this->_request->getHttpHost() . $this->_request->getRequestUri()
542: );
543:
544: if ($calculatedSign != $this->_protocolParams['oauth_signature']) {
545: $this->_throwException($calculatedSign, self::ERR_SIGNATURE_INVALID);
546: }
547: }
548:
549: 550: 551:
552: protected function _validateTokenParam()
553: {
554: if (empty($this->_protocolParams['oauth_token'])) {
555: $this->_throwException('oauth_token', self::ERR_PARAMETER_ABSENT);
556: }
557: if (!is_string($this->_protocolParams['oauth_token'])) {
558: $this->_throwException('', self::ERR_TOKEN_REJECTED);
559: }
560: if (strlen($this->_protocolParams['oauth_token']) != Mage_Oauth_Model_Token::LENGTH_TOKEN) {
561: $this->_throwException('', self::ERR_TOKEN_REJECTED);
562: }
563: }
564:
565: 566: 567:
568: protected function _validateVerifierParam()
569: {
570: if (empty($this->_protocolParams['oauth_verifier'])) {
571: $this->_throwException('oauth_verifier', self::ERR_PARAMETER_ABSENT);
572: }
573: if (!is_string($this->_protocolParams['oauth_verifier'])) {
574: $this->_throwException('', self::ERR_VERIFIER_INVALID);
575: }
576: if (strlen($this->_protocolParams['oauth_verifier']) != Mage_Oauth_Model_Token::LENGTH_VERIFIER) {
577: $this->_throwException('', self::ERR_VERIFIER_INVALID);
578: }
579: }
580:
581: 582: 583:
584: public function accessToken()
585: {
586: try {
587: $this->_processRequest(self::REQUEST_TOKEN);
588:
589: $response = $this->_token->toString();
590: } catch (Exception $e) {
591: $response = $this->reportProblem($e);
592: }
593: $this->_getResponse()->setBody($response);
594: }
595:
596: 597: 598: 599: 600: 601: 602:
603: public function authorizeToken($userId, $userType)
604: {
605: $token = $this->checkAuthorizeRequest();
606:
607: $token->authorize($userId, $userType);
608:
609: return $token;
610: }
611:
612: 613: 614: 615: 616:
617: public function checkAccessRequest()
618: {
619: $this->_processRequest(self::REQUEST_RESOURCE);
620:
621: return $this->_token;
622: }
623:
624: 625: 626: 627: 628:
629: public function checkAuthorizeRequest()
630: {
631: if (!$this->_request->isGet()) {
632: Mage::throwException('Request is not GET');
633: }
634: $this->_requestType = self::REQUEST_AUTHORIZE;
635:
636: $this->_fetchProtocolParamsFromQuery();
637: $this->_initToken();
638:
639: return $this->_token;
640: }
641:
642: 643: 644: 645: 646:
647: public static function getSupportedSignatureMethods()
648: {
649: return array(self::SIGNATURE_RSA, self::SIGNATURE_HMAC, self::SIGNATURE_PLAIN);
650: }
651:
652: 653: 654:
655: public function initiateToken()
656: {
657: try {
658: $this->_processRequest(self::REQUEST_INITIATE);
659:
660: $response = $this->_token->toString() . '&oauth_callback_confirmed=true';
661: } catch (Exception $e) {
662: $response = $this->reportProblem($e);
663: }
664: $this->_getResponse()->setBody($response);
665: }
666:
667: 668: 669: 670: 671: 672: 673:
674: public function reportProblem(Exception $e, Zend_Controller_Response_Http $response = null)
675: {
676: $eMsg = $e->getMessage();
677:
678: if ($e instanceof Mage_Oauth_Exception) {
679: $eCode = $e->getCode();
680:
681: if (isset($this->_errors[$eCode])) {
682: $errorMsg = $this->_errors[$eCode];
683: $responseCode = $this->_errorsToHttpCode[$eCode];
684: } else {
685: $errorMsg = 'unknown_problem&code=' . $eCode;
686: $responseCode = self::HTTP_INTERNAL_ERROR;
687: }
688: if (self::ERR_PARAMETER_ABSENT == $eCode) {
689: $errorMsg .= '&oauth_parameters_absent=' . $eMsg;
690: } elseif (self::ERR_SIGNATURE_INVALID == $eCode) {
691: $errorMsg .= '&debug_sbs=' . $eMsg;
692: } elseif ($eMsg) {
693: $errorMsg .= '&message=' . $eMsg;
694: }
695: } else {
696: $errorMsg = 'internal_error&message=' . ($eMsg ? $eMsg : 'empty_message');
697: $responseCode = self::HTTP_INTERNAL_ERROR;
698: }
699: if (!$response) {
700: $response = $this->_getResponse();
701: }
702: $response->setHttpResponseCode($responseCode);
703:
704: return 'oauth_problem=' . $errorMsg;
705: }
706:
707: 708: 709: 710: 711: 712:
713: public function setResponse(Zend_Controller_Response_Http $response)
714: {
715: $this->_response = $response;
716:
717: $this->_response->setHeader(Zend_Http_Client::CONTENT_TYPE, Zend_Http_Client::ENC_URLENCODED, true);
718: $this->_response->setHttpResponseCode(self::HTTP_OK);
719:
720: return $this;
721: }
722: }
723: