oauth_common.module 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <?php
  2. define('OAUTH_COMMON_CODE_BRANCH', '6.x-3.x');
  3. define('OAUTH_COMMON_TOKEN_TYPE_REQUEST', 0);
  4. define('OAUTH_COMMON_TOKEN_TYPE_ACCESS', 1);
  5. define('OAUTH_COMMON_VERSION_1', 1); // The original 1.0 spec
  6. define('OAUTH_COMMON_VERSION_1_RFC', 2); // The RFC 5849 1.0 spec
  7. //TODO: Don't act as a provider by default.
  8. //TODO: Check for other functions with breaking changes
  9. //TODO: Move admin pages to more regular places
  10. //TODO: Add watchdog messages about deprecated methods?
  11. //TODO: Move provider ui related pages to provider ui
  12. /**
  13. * Implements hook_permission().
  14. */
  15. function oauth_common_permission() {
  16. $permissions = array(
  17. 'oauth authorize any consumers' => array(
  18. 'title' => t('Authorize any OAuth consumers'),
  19. 'restrict access' => TRUE,
  20. ),
  21. 'oauth register any consumers' => array(
  22. 'title' => t('Register any OAuth consumers'),
  23. 'restrict access' => TRUE,
  24. ),
  25. 'administer oauth' => array(
  26. 'title' => t('Administer OAuth'),
  27. 'restrict access' => TRUE,
  28. ),
  29. 'administer consumers' => array(
  30. 'title' => t('Administer OAuth consumers'),
  31. 'restrict access' => TRUE,
  32. ),
  33. );
  34. // Add seperate permissions for creating and
  35. // authorizing consumers in each context.
  36. foreach (oauth_common_context_list() as $name => $title) {
  37. $permissions[sprintf('oauth register consumers in %s', $name)] = array(
  38. 'title' => t('Register OAuth consumers in %context', array('%context' => $title)),
  39. );
  40. $permissions[sprintf('oauth authorize consumers in %s', $name)] = array(
  41. 'title' => t('Authorize OAuth consumers in %context', array('%context' => $title)),
  42. );
  43. }
  44. return $permissions;
  45. }
  46. /**
  47. * Implements hook_menu().
  48. */
  49. function oauth_common_menu() {
  50. $menu = array();
  51. $admin_base = array(
  52. 'access arguments' => array('administer oauth'),
  53. 'file' => 'oauth_common.admin.inc',
  54. );
  55. $menu['admin/config/services/oauth'] = array(
  56. 'title' => 'OAuth',
  57. 'description' => 'Settings for OAuth',
  58. 'page callback' => 'drupal_get_form',
  59. 'page arguments' => array('_oauth_common_admin'),
  60. 'type' => MENU_NORMAL_ITEM,
  61. ) + $admin_base;
  62. $menu['admin/config/services/oauth/settings'] = array(
  63. 'title' => 'Settings',
  64. 'description' => 'Settings for OAuth',
  65. 'page callback' => 'drupal_get_form',
  66. 'page arguments' => array('_oauth_common_admin'),
  67. 'type' => MENU_DEFAULT_LOCAL_TASK,
  68. 'weight' => 0,
  69. ) + $admin_base;
  70. // OAuth doesn't need different endpoints for the different context as all
  71. // actions are done with a specific consumer, which in itself is associated
  72. // with a context.
  73. $provider_base = array(
  74. 'access callback' => 'oauth_commmon_is_provider',
  75. 'file' => 'oauth_common.pages.inc',
  76. 'type' => MENU_CALLBACK,
  77. );
  78. // The endpoint that consumers use to get a request token.
  79. $menu['oauth/request_token'] = array(
  80. 'page callback' => 'oauth_common_callback_request_token',
  81. ) + $provider_base;
  82. // The page a user gets sent to to authorize a request token.
  83. $menu['oauth/authorize'] = array(
  84. 'page callback' => 'drupal_get_form',
  85. 'page arguments' => array('oauth_common_form_authorize'),
  86. ) + $provider_base;
  87. // The endpoint that consumers use to get a access token.
  88. $menu['oauth/access_token'] = array(
  89. 'page callback' => 'oauth_common_callback_access_token',
  90. ) + $provider_base;
  91. // This page is used both in consumer and provider mode. For consumers it is
  92. // the callback url and triggers hook_oauth_common_authorized(). For
  93. // providers it is the page where users end up if no callback url exists.
  94. $menu['oauth/authorized'] = array(
  95. 'title' => 'Authorization finished',
  96. 'page callback' => 'oauth_common_page_authorized',
  97. 'access arguments' => array('access content'),
  98. 'file' => 'oauth_common.pages.inc',
  99. 'type' => MENU_CALLBACK,
  100. );
  101. // TODO: Different structures makes sense depending on whether oauth_common is
  102. // acting as a provider or as a consumer.
  103. $menu['oauth/test/valid-consumer'] = array(
  104. 'file' => 'oauth_common.pages.inc',
  105. 'page callback' => '_oauth_common_validate_request_callback',
  106. 'page arguments' => array('consumer'),
  107. 'access callback' => 'oauth_commmon_is_provider',
  108. 'type' => MENU_CALLBACK,
  109. );
  110. $menu['oauth/test/valid-access-token'] = array(
  111. 'file' => 'oauth_common.pages.inc',
  112. 'page callback' => '_oauth_common_validate_request_callback',
  113. 'page arguments' => array('access token'),
  114. 'access callback' => 'oauth_commmon_is_provider',
  115. 'type' => MENU_CALLBACK,
  116. );
  117. return $menu;
  118. }
  119. /**
  120. * Menu system wildcard loader for provider consumers.
  121. *
  122. * @param string $key
  123. */
  124. function oauth_common_consumer_load($csid) {
  125. $consumer = DrupalOAuthConsumer::loadById($csid, TRUE);
  126. if (!$consumer) {
  127. $consumer = FALSE;
  128. }
  129. return $consumer;
  130. }
  131. /**
  132. * Menu system wildcard loader for provider tokens.
  133. *
  134. * @param string $key
  135. */
  136. function oauth_common_provider_token_load($tid) {
  137. $token = DrupalOAuthToken::loadByID($tid);
  138. if (!$token) {
  139. $token = FALSE;
  140. }
  141. return $token;
  142. }
  143. /**
  144. * Implements hook_cron().
  145. */
  146. function oauth_common_cron() {
  147. $token_condition = db_and()->condition('expires', 0, '<>')->condition('expires', REQUEST_TIME, '<=');
  148. db_delete('oauth_common_provider_token')
  149. ->condition('tid', db_select('oauth_common_token', 't')->condition($token_condition)->fields('t', array('tid')), 'IN')
  150. ->execute();
  151. db_delete('oauth_common_token')
  152. ->condition($token_condition)
  153. ->execute();
  154. // Should 300 be overriden in DrupalOAuthServer and made configurable?
  155. db_delete('oauth_common_nonce')
  156. ->condition('timestamp', REQUEST_TIME - 300, '<')
  157. ->execute();
  158. }
  159. /**
  160. * Implements hook_oauth_default_contexts().
  161. */
  162. function oauth_common_default_oauth_common_context() {
  163. $contexts = array();
  164. $context = new stdClass;
  165. $context->disabled = FALSE; /* Edit this to true to make a default context disabled initially */
  166. $context->name = 'default';
  167. $context->title = 'Default context';
  168. $context->authorization_options = array();
  169. $context->authorization_levels = array(
  170. '*' => array(
  171. 'title' => 'Full access',
  172. 'description' => 'This will give @appname the same permissions that you normally have and will allow it to access the full range of services that @sitename provides.',
  173. ),
  174. 'read' => array(
  175. 'title' => 'Read access',
  176. 'description' => 'This will allow @appname to fetch content that you have access to on @sitename.',
  177. ),
  178. 'update' => array(
  179. 'title' => 'Update access',
  180. 'description' => 'This will allow @appname to update content that you have permissions to edit.',
  181. ),
  182. 'create' => array(
  183. 'title' => 'Create access',
  184. 'description' => 'This will allow @appname to create new content on @sitename.',
  185. ),
  186. 'delete' => array(
  187. 'title' => 'Delete access',
  188. 'description' => 'This will allow @appname to delete content from @sitename.',
  189. ),
  190. );
  191. $contexts[$context->name] = $context;
  192. return $contexts;
  193. }
  194. /**
  195. * Implements hook_user_delete().
  196. */
  197. function oauth_common_user_delete($account) {
  198. // Delete all tokens and consumers related to a user
  199. module_load_include('inc', 'oauth_common');
  200. $consumer_condition = db_select('oauth_common_provider_consumer', 'c')->condition('uid', $account->uid)->fields('c', array('csid'));
  201. $token_condition = db_or()->condition('uid', $account->uid)->condition('csid', $consumer_condition, 'IN');
  202. db_delete('oauth_common_provider_token')
  203. ->condition('tid', db_select('oauth_common_token', 't')->condition($token_condition)->fields('t', array('tid')), 'IN')
  204. ->execute();
  205. db_delete('oauth_common_token')
  206. ->condition($token_condition)
  207. ->execute();
  208. db_delete('oauth_common_consumer')
  209. ->condition('csid', $consumer_condition, 'IN')
  210. ->execute();
  211. db_delete('oauth_common_provider_consumer')
  212. ->condition('uid', $account->uid)
  213. ->execute();
  214. }
  215. /**
  216. * Implements hook_xrds().
  217. */
  218. function services_oauth_xrds() {
  219. $xrds = array();
  220. $xrds['oauth'] = array(
  221. 'services' => array(
  222. array(
  223. 'data' => array(
  224. 'Type' => array('http://oauth.net/discovery/1.0'),
  225. 'URI' => array('#main'),
  226. ),
  227. ),
  228. array(
  229. 'data' => array(
  230. 'Type' => array(
  231. 'http://oauth.net/core/1.0/endpoint/request',
  232. 'http://oauth.net/core/1.0/parameters/auth-header',
  233. 'http://oauth.net/core/1.0/parameters/uri-query',
  234. 'http://oauth.net/core/1.0/signature/HMAC-SHA1',
  235. ),
  236. 'URI' => array(url('oauth/request_token', array('absolute' => TRUE))),
  237. ),
  238. ),
  239. array(
  240. 'data' => array(
  241. 'Type' => array(
  242. 'http://oauth.net/core/1.0/endpoint/authorize',
  243. 'http://oauth.net/core/1.0/parameters/uri-query',
  244. ),
  245. 'URI' => array(url('oauth/authorize', array('absolute' => TRUE))),
  246. ),
  247. ),
  248. array(
  249. 'data' => array(
  250. 'Type' => array(
  251. 'http://oauth.net/core/1.0/endpoint/access',
  252. 'http://oauth.net/core/1.0/parameters/auth-header',
  253. 'http://oauth.net/core/1.0/parameters/uri-query',
  254. 'http://oauth.net/core/1.0/signature/HMAC-SHA1',
  255. ),
  256. 'URI' => array(url('oauth/access_token', array('absolute' => TRUE))),
  257. ),
  258. ),
  259. ),
  260. );
  261. return $xrds;
  262. }
  263. /**
  264. * Access callback function used by several menu items.
  265. *
  266. * @param stdClass $user
  267. * A user object.
  268. * @param string $permission
  269. * The permission that is needed in addition to edit access on the $user.
  270. */
  271. function _oauth_common_user_access($user, $permission = NULL) {
  272. return user_edit_access($user) && (empty($permission) || user_access($permission));
  273. }
  274. /**
  275. * Checks if the user has permission to edit the consumer. Edit access is
  276. * granted if the user has the 'administer consumers' permission or may
  277. * edit the account connected to the consumer.
  278. *
  279. * @param DrupalOAuthConsumer $consumer
  280. * @return bool
  281. */
  282. function oauth_common_can_edit_consumer($consumer) {
  283. $may_edit = user_access('administer consumers');
  284. // If the user doesn't have consumer admin privileges, check for account
  285. // edit access.
  286. if (!$may_edit && $consumer->uid) {
  287. $user = user_load($consumer->uid);
  288. $may_edit = user_edit_access($user);
  289. }
  290. return $may_edit;
  291. }
  292. /**
  293. * Deterines if a user has the necessary permissions to create consumers.
  294. *
  295. * @param object $account
  296. * The user account to check permissions for. Defaults to the currently
  297. * logged in user.
  298. * @return bool
  299. */
  300. function oauth_common_can_create_consumers($account = NULL) {
  301. global $user;
  302. if (!$account) {
  303. $account = $user;
  304. }
  305. $can_register_consumers = user_access('oauth register any consumers', $account);
  306. if (!$can_register_consumers) {
  307. foreach (oauth_common_context_list() as $context => $title) {
  308. $can_register_consumers = $can_register_consumers || user_access(sprintf('oauth register consumers in %s', $context), $account);
  309. }
  310. }
  311. return $can_register_consumers;
  312. }
  313. /**
  314. * This function is used as a access callback
  315. * when the authentication of the request shouldn't be
  316. * done by the menu system.
  317. *
  318. * @return bool
  319. * Always returns TRUE
  320. */
  321. function _oauth_common_always_true() {
  322. return TRUE;
  323. }
  324. /**
  325. * Access callback that checks if a user may create authorizations in the
  326. * consumers context.
  327. *
  328. * @param DrupalOAuthConsumer $consumer
  329. * @return bool
  330. */
  331. function oauth_common_can_authorize_consumer($consumer) {
  332. return user_access(sprintf('oauth authorize consumers in %s', $consumer->context));
  333. }
  334. /**
  335. * Check if oauth_common is acting as a provider.
  336. */
  337. function oauth_commmon_is_provider() {
  338. return variable_get('oauth_common_enable_provider', TRUE);
  339. }
  340. /**
  341. * Gets a request token from a oauth provider and returns the authorization
  342. * url. The request token is saved in the database.
  343. *
  344. * @param OAuthToken $consumer_token
  345. * The consumer token to use
  346. * @param string $request_endpoint
  347. * Optional. Pass a custom endpoint if needed. Defaults to '/oauth/request_token'.
  348. * @param string $authorize_endpoint
  349. * Optional. Pass a custom endpoint if needed. Defaults to '/oauth/authorize'.
  350. * @return string
  351. * The url that the client should be redirected to to authorize
  352. * the request token.
  353. */
  354. function oauth_common_get_request_token($consumer_token, $request_endpoint = '/oauth/request_token', $authorize_endpoint = '/oauth/authorize') {
  355. $client = new DrupalOAuthClient($consumer_token);
  356. $request_token = $client->getRequestToken($request_endpoint);
  357. $request_token->write();
  358. return $client->getAuthorizationUrl($authorize_endpoint);
  359. }
  360. /**
  361. * Gets the tokens for a user.
  362. *
  363. * @param string $uid
  364. * @param string $type
  365. * @return array
  366. */
  367. function oauth_common_get_user_provider_tokens($uid) {
  368. $res = db_query("SELECT t.*, pt.created, pt.changed, pt.services, pt.authorized FROM {oauth_common_token} t
  369. INNER JOIN {oauth_common_provider_token} pt WHERE t.uid = :uid AND t.type = :type", array(
  370. ':uid' => $uid,
  371. ':type' => OAUTH_COMMON_TOKEN_TYPE_ACCESS,
  372. ));
  373. $tokens = array();
  374. while ($token = DrupalOAuthToken::fromResult($res)) {
  375. $tokens[] = $token;
  376. }
  377. return $tokens;
  378. }
  379. /**
  380. * Create a new context with defaults appropriately set from schema.
  381. *
  382. * @return stdClass
  383. * An context initialized with the default values.
  384. */
  385. function oauth_common_context_new() {
  386. if (!module_exists('ctools')) {
  387. return FALSE;
  388. }
  389. ctools_include('export');
  390. return ctools_export_new_object('oauth_common_context');
  391. }
  392. /**
  393. * Load a single context.
  394. *
  395. * @param string $name
  396. * The name of the context.
  397. * @return stdClass
  398. * The context configuration.
  399. */
  400. function oauth_common_context_load($name) {
  401. if (!module_exists('ctools')) {
  402. return FALSE;
  403. }
  404. ctools_include('export');
  405. $result = ctools_export_load_object('oauth_common_context', 'names', array($name));
  406. if (isset($result[$name])) {
  407. return $result[$name];
  408. }
  409. else {
  410. return FALSE;
  411. }
  412. }
  413. /**
  414. * Loads the context for a request.
  415. *
  416. * @param OAuthRequest $request
  417. * @return object
  418. * The context configuration.
  419. */
  420. function oauth_common_context_from_request($request) {
  421. $context = NULL;
  422. $consumer_key = $request->get_parameter('oauth_consumer_key');
  423. $token_key = $request->get_parameter('oauth_token');
  424. if (empty($consumer_key) && !empty($token_key)) {
  425. $token = DrupalOAuthToken::loadByKey($token_key, FALSE, OAUTH_COMMON_TOKEN_TYPE_REQUEST);
  426. if ($token) {
  427. $consumer = $token->consumer;
  428. }
  429. }
  430. if (!empty($consumer_key)) {
  431. $consumer = DrupalOAuthConsumer::loadProviderByKey($consumer_key);
  432. }
  433. if (!empty($consumer)) {
  434. $context = oauth_common_context_load($consumer->context);
  435. }
  436. return $context;
  437. }
  438. /**
  439. * Load all contexts.
  440. *
  441. * @return array
  442. * Array of context objects keyed by context names.
  443. */
  444. function oauth_common_context_load_all() {
  445. if (!module_exists('ctools')) {
  446. return FALSE;
  447. }
  448. ctools_include('export');
  449. return ctools_export_load_object('oauth_common_context');
  450. }
  451. /**
  452. * Saves an context in the database.
  453. *
  454. * @return void
  455. */
  456. function oauth_common_context_save($context) {
  457. $update = (isset($context->cid)) ? array('cid') : array();
  458. drupal_write_record('oauth_common_context', $context, $update);
  459. }
  460. /**
  461. * Remove an context.
  462. *
  463. * @return void
  464. */
  465. function oauth_common_context_delete($context) {
  466. db_delete('oauth_common_context')
  467. ->condition('name', $context->name)
  468. ->condition('cid', $context->cid)
  469. ->execute();
  470. }
  471. /**
  472. * Export an context.
  473. *
  474. * @return string
  475. */
  476. function oauth_common_context_export($context, $indent = '') {
  477. if (!module_exists('ctools')) {
  478. return FALSE;
  479. }
  480. ctools_include('export');
  481. $output = ctools_export_object('oauth_common_context', $context, $indent);
  482. return $output;
  483. }
  484. /**
  485. * Lists all available contexts.
  486. *
  487. * @return array
  488. */
  489. function oauth_common_context_list() {
  490. $return = array();
  491. $contexts = oauth_common_context_load_all();
  492. if ($contexts) {
  493. foreach ($contexts as $context) {
  494. $return[$context->name] = $context->title;
  495. }
  496. }
  497. return $return;
  498. }
  499. /**
  500. * Finds the current version of the OAuth module, used in eg. user agents
  501. *
  502. * @return string
  503. */
  504. function _oauth_common_version() {
  505. static $version;
  506. if (!isset($version)) {
  507. $info = db_query("SELECT info FROM {system} WHERE name = 'oauth_common'")->fetchField();
  508. $info = $info ? unserialize($info) : FALSE;
  509. if (!$info || empty($info['version'])) {
  510. $version = OAUTH_COMMON_CODE_BRANCH;
  511. }
  512. else {
  513. $version = $info['version'];
  514. }
  515. }
  516. return $version;
  517. }