diff options
63 files changed, 3144 insertions, 63 deletions
diff --git a/phpBB/adm/style/auth_provider_oauth.html b/phpBB/adm/style/auth_provider_oauth.html new file mode 100644 index 0000000000..25e40ff596 --- /dev/null +++ b/phpBB/adm/style/auth_provider_oauth.html @@ -0,0 +1,17 @@ +<h2>{L_AUTH_PROVIDER_OAUTH_TITLE}</h2> + +<p>{L_AUTH_PROVIDER_OAUTH_EXPLAIN}</p> + +<!-- BEGIN oauth_services --> +<fieldset> + <legend>{oauth_services.ACTUAL_NAME}</legend> + <dl> + <dt><label for="oauth_service_{oauth_services.NAME}_key">{L_AUTH_PROVIDER_OAUTH_KEY}{L_COLON}</label></dt> + <dd><input type="text" id="oauth_service_{oauth_services.NAME}_key" size="40" name="config[auth_oauth_{oauth_services.NAME}_key]" value="{oauth_services.KEY}" /></dd> + </dl> + <dl> + <dt><label for="oauth_service_{oauth_services.NAME}_secret">{L_AUTH_PROVIDER_OAUTH_SECRET}{L_COLON}</label></dt> + <dd><input type="text" id="oauth_service_{oauth_services.NAME}_secret" size="40" name="config[auth_oauth_{oauth_services.NAME}_secret]" value="{oauth_services.SECRET}" /></dd> + </dl> +</fieldset> +<!-- END oauth_services --> diff --git a/phpBB/composer.json b/phpBB/composer.json index a4bf8bf977..d3a24d7964 100644 --- a/phpBB/composer.json +++ b/phpBB/composer.json @@ -1,6 +1,7 @@ { "minimum-stability": "beta", "require": { + "lusitanian/oauth": "0.2.*", "symfony/config": "2.3.*", "symfony/dependency-injection": "2.3.*", "symfony/event-dispatcher": "2.3.*", diff --git a/phpBB/composer.lock b/phpBB/composer.lock index e04e169f4b..26e904bdbb 100644 --- a/phpBB/composer.lock +++ b/phpBB/composer.lock @@ -3,9 +3,72 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "fcad562b3b6768f0e355d93edc0db405", + "hash": "a14960de85feb64fdb19eabd165cc09b", "packages": [ { + "name": "lusitanian/oauth", + "version": "v0.2.1", + "source": { + "type": "git", + "url": "https://github.com/Lusitanian/PHPoAuthLib.git", + "reference": "00c667d93058e983fc1b7d3d1cebdb1bc03fb043" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/00c667d93058e983fc1b7d3d1cebdb1bc03fb043", + "reference": "00c667d93058e983fc1b7d3d1cebdb1bc03fb043", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "predis/predis": "0.8.*@dev", + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "predis/predis": "Allows using the Redis storage backend.", + "symfony/http-foundation": "Allows using the Symfony Session storage backend." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-0": { + "OAuth": "src", + "OAuth\\Unit": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Desberg", + "email": "david@daviddesberg.com" + }, + { + "name": "Pieter Hordijk", + "email": "info@pieterhordijk.com", + "homepage": "https://pieterhordijk.com", + "role": "developer" + } + ], + "description": "PHP 5.3+ oAuth 1/2 Library", + "keywords": [ + "Authentication", + "authorization", + "oauth", + "security" + ], + "time": "2013-08-29 21:40:04" + }, + { "name": "psr/log", "version": "1.0.0", "source": { @@ -586,13 +649,13 @@ "version": "v0.1.0", "source": { "type": "git", - "url": "https://github.com/fabpot/Goutte.git", - "reference": "dcfba09b0f3781b2629693ab627bfa61ad4753bb" + "url": "https://github.com/fabpot/Goutte", + "reference": "v0.1.0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fabpot/Goutte/zipball/dcfba09b0f3781b2629693ab627bfa61ad4753bb", - "reference": "dcfba09b0f3781b2629693ab627bfa61ad4753bb", + "url": "https://github.com/fabpot/Goutte/archive/v0.1.0.zip", + "reference": "v0.1.0", "shasum": "" }, "require": { @@ -638,13 +701,13 @@ "version": "v3.0.7", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "f31f35d1669382936861533bd0217fcf830dc9a9" + "url": "https://github.com/guzzle/guzzle", + "reference": "v3.0.7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f31f35d1669382936861533bd0217fcf830dc9a9", - "reference": "f31f35d1669382936861533bd0217fcf830dc9a9", + "url": "https://github.com/guzzle/guzzle/archive/v3.0.7.zip", + "reference": "v3.0.7", "shasum": "" }, "require": { @@ -730,13 +793,13 @@ "version": "2.4.14", "source": { "type": "git", - "url": "https://github.com/phingofficial/phing.git", - "reference": "41075d93ca254f1c90c79ec7ce81be2b2629e138" + "url": "https://github.com/phingofficial/phing", + "reference": "2.4.14" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phingofficial/phing/zipball/41075d93ca254f1c90c79ec7ce81be2b2629e138", - "reference": "41075d93ca254f1c90c79ec7ce81be2b2629e138", + "url": "https://github.com/phingofficial/phing/archive/2.4.14.zip", + "reference": "2.4.14", "shasum": "" }, "require": { @@ -783,12 +846,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/dbunit.git", - "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d" + "reference": "1.2.3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/8386782a2d55153e44a06eb1a9d13d6ed35d9c2d", - "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d", + "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/1.2.3", + "reference": "1.2.3", "shasum": "" }, "require": { @@ -841,12 +904,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0e9958c459d675fb497d8dc5001c91d335734e48" + "reference": "1.2.12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e9958c459d675fb497d8dc5001c91d335734e48", - "reference": "0e9958c459d675fb497d8dc5001c91d335734e48", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", + "reference": "1.2.12", "shasum": "" }, "require": { @@ -901,13 +964,13 @@ "version": "1.3.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "16a78140ed2fc01b945cfa539665fadc6a038029" + "url": "git://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "1.3.3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/16a78140ed2fc01b945cfa539665fadc6a038029", - "reference": "16a78140ed2fc01b945cfa539665fadc6a038029", + "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3", + "reference": "1.3.3", "shasum": "" }, "require": { @@ -939,20 +1002,20 @@ "filesystem", "iterator" ], - "time": "2012-10-11 11:44:38" + "time": "2012-10-11 04:44:38" }, { "name": "phpunit/php-text-template", "version": "1.1.4", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5180896f51c5b3648ac946b05f9ec02be78a0b23" + "url": "git://github.com/sebastianbergmann/php-text-template.git", + "reference": "1.1.4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5180896f51c5b3648ac946b05f9ec02be78a0b23", - "reference": "5180896f51c5b3648ac946b05f9ec02be78a0b23", + "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4", + "reference": "1.1.4", "shasum": "" }, "require": { @@ -983,7 +1046,7 @@ "keywords": [ "template" ], - "time": "2012-10-31 18:15:28" + "time": "2012-10-31 11:15:28" }, { "name": "phpunit/php-timer", @@ -1158,13 +1221,13 @@ "version": "1.2.3", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" + "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1.2.3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", - "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip", + "reference": "1.2.3", "shasum": "" }, "require": { diff --git a/phpBB/config/auth_providers.yml b/phpBB/config/auth_providers.yml index bcc448e4d7..dfa229fa42 100644 --- a/phpBB/config/auth_providers.yml +++ b/phpBB/config/auth_providers.yml @@ -35,3 +35,45 @@ services: - @user tags: - { name: auth.provider } + auth.provider.oauth: + class: phpbb_auth_provider_oauth + arguments: + - @dbal.conn + - @config + - @request + - @user + - %tables.auth_provider_oauth_token_storage% + - %tables.auth_provider_oauth_account_assoc% + - @auth.provider.oauth.service_collection + - %tables.users% + - %core.root_path% + - %core.php_ext% + tags: + - { name: auth.provider } + auth.provider.oauth.service_collection: + class: phpbb_di_service_collection + arguments: + - @service_container + tags: + - { name: service_collection, tag: auth.provider.oauth.service } + auth.provider.oauth.service.bitly: + class: phpbb_auth_provider_oauth_service_bitly + arguments: + - @config + - @request + tags: + - { name: auth.provider.oauth.service } + auth.provider.oauth.service.facebook: + class: phpbb_auth_provider_oauth_service_facebook + arguments: + - @config + - @request + tags: + - { name: auth.provider.oauth.service } + auth.provider.oauth.service.google: + class: phpbb_auth_provider_oauth_service_google + arguments: + - @config + - @request + tags: + - { name: auth.provider.oauth.service } diff --git a/phpBB/config/tables.yml b/phpBB/config/tables.yml index fdb448f4e0..0d364eb6b0 100644 --- a/phpBB/config/tables.yml +++ b/phpBB/config/tables.yml @@ -1,4 +1,6 @@ parameters: + tables.auth_provider_oauth_token_storage: %core.table_prefix%oauth_tokens + tables.auth_provider_oauth_account_assoc: %core.table_prefix%oauth_accounts tables.config: %core.table_prefix%config tables.config_text: %core.table_prefix%config_text tables.ext: %core.table_prefix%ext diff --git a/phpBB/docs/CHANGELOG.html b/phpBB/docs/CHANGELOG.html index 85e0e6b92b..c2eb48137a 100644 --- a/phpBB/docs/CHANGELOG.html +++ b/phpBB/docs/CHANGELOG.html @@ -145,7 +145,6 @@ <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11265">PHPBB3-11265</a>] - Functional tests do not assert that board installation succeeded</li> <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11269">PHPBB3-11269</a>] - Travis functional test case errors</li> <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11278">PHPBB3-11278</a>] - Firebird tables are not removed correctly on 3.0.9-rc1 update</li> -<li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11288">PHPBB3-11288</a>] - Search fooled by hyphens</li> <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11291">PHPBB3-11291</a>] - "Could not open input file: ../composer.phar" error during phing's create-package</li> <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11292">PHPBB3-11292</a>] - Newlines removed in display of PM reports, no clickable links in PM reports</li> <li>[<a href="http://tracker.phpbb.com/browse/PHPBB3-11301">PHPBB3-11301</a>] - "String offset cast occured" error on PHP 5.4</li> diff --git a/phpBB/includes/acp/acp_board.php b/phpBB/includes/acp/acp_board.php index 12e2a1bf72..51a7628b68 100644 --- a/phpBB/includes/acp/acp_board.php +++ b/phpBB/includes/acp/acp_board.php @@ -658,6 +658,13 @@ class acp_board $auth_tpl = $provider->get_acp_template($this->new_config); if ($auth_tpl) { + if (array_key_exists('BLOCK_VAR_NAME', $auth_tpl)) + { + foreach ($auth_tpl['BLOCK_VARS'] as $block_vars) + { + $template->assign_block_vars($auth_tpl['BLOCK_VAR_NAME'], $block_vars); + } + } $template->assign_vars($auth_tpl['TEMPLATE_VARS']); $template->assign_block_vars('auth_tpl', array( 'TEMPLATE_FILE' => $auth_tpl['TEMPLATE_FILE'], diff --git a/phpBB/includes/constants.php b/phpBB/includes/constants.php index c1f4c6ac0e..ae55a71e50 100644 --- a/phpBB/includes/constants.php +++ b/phpBB/includes/constants.php @@ -61,6 +61,7 @@ define('LOGIN_CONTINUE', 1); define('LOGIN_BREAK', 2); define('LOGIN_SUCCESS', 3); define('LOGIN_SUCCESS_CREATE_PROFILE', 20); +define('LOGIN_SUCCESS_LINK_PROFILE', 21); define('LOGIN_ERROR_USERNAME', 10); define('LOGIN_ERROR_PASSWORD', 11); define('LOGIN_ERROR_ACTIVE', 12); diff --git a/phpBB/includes/db/schema_data.php b/phpBB/includes/db/schema_data.php index 9940a9380f..69d39e0f8c 100644 --- a/phpBB/includes/db/schema_data.php +++ b/phpBB/includes/db/schema_data.php @@ -573,6 +573,31 @@ $schema_data['phpbb_notifications'] = array( ), ); +$schema_data['phpbb_oauth_accounts'] = array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'provider' => array('VCHAR', ''), + 'oauth_provider_id' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => array( + 'user_id', + 'provider', + ), +); + +$schema_data['phpbb_oauth_tokens'] = array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), // phpbb_users.user_id + 'session_id' => array('CHAR:32', ''), // phpbb_sessions.session_id used only when user_id not set + 'provider' => array('VCHAR', ''), // Name of the OAuth provider + 'oauth_token' => array('MTEXT', ''), // Serialized token + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'provider' => array('INDEX', 'provider'), + ), +); + $schema_data['phpbb_poll_options'] = array( 'COLUMNS' => array( 'poll_option_id' => array('TINT:4', 0), diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php index 4d2d704a43..bf973fe141 100644 --- a/phpBB/includes/functions.php +++ b/phpBB/includes/functions.php @@ -3206,7 +3206,7 @@ function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_bo function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true) { global $db, $user, $template, $auth, $phpEx, $phpbb_root_path, $config; - global $request; + global $request, $phpbb_container; if (!class_exists('phpbb_captcha_factory', false)) { @@ -3233,7 +3233,7 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa trigger_error('NO_AUTH_ADMIN'); } - if (isset($_POST['login'])) + if ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')) { // Get credential if ($admin) @@ -3374,6 +3374,29 @@ function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = fa $s_hidden_fields['credential'] = $credential; } + $auth_provider = $phpbb_container->get('auth.provider.' . $config['auth_method']); + + $auth_provider_data = $auth_provider->get_login_data(); + if ($auth_provider_data) + { + if (isset($auth_provider_data['VARS'])) + { + $template->assign_vars($auth_provider_data['VARS']); + } + + if (isset($auth_provider_data['BLOCK_VAR_NAME'])) + { + foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars) + { + $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars); + } + } + + $template->assign_vars(array( + 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'], + )); + } + $s_hidden_fields = build_hidden_fields($s_hidden_fields); $template->assign_vars(array( @@ -5733,6 +5756,8 @@ function phpbb_create_symfony_request(phpbb_request $request) */ function phpbb_get_web_root_path(Request $symfony_request, $phpbb_root_path = '') { + global $phpbb_container; + static $path; if (null !== $path) { @@ -5746,7 +5771,11 @@ function phpbb_get_web_root_path(Request $symfony_request, $phpbb_root_path = '' return $path; } - $corrections = substr_count($path_info, '/'); + $filesystem = $phpbb_container->get('filesystem'); + $path_info = $filesystem->clean_path($path_info); + + // Do not count / at start of path + $corrections = substr_count(substr($path_info, 1), '/'); // When URL Rewriting is enabled, app.php is optional. We have to // correct for it not being there diff --git a/phpBB/includes/mcp/mcp_pm_reports.php b/phpBB/includes/mcp/mcp_pm_reports.php index cb61b25174..f0452b37a5 100644 --- a/phpBB/includes/mcp/mcp_pm_reports.php +++ b/phpBB/includes/mcp/mcp_pm_reports.php @@ -161,6 +161,7 @@ class mcp_pm_reports 'S_CLOSE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $report_id), 'S_CAN_VIEWIP' => $auth->acl_getf_global('m_info'), 'S_POST_REPORTED' => $pm_info['message_reported'], + 'S_REPORT_CLOSED' => $report['report_closed'], 'S_USER_NOTES' => true, 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $report_id), diff --git a/phpBB/includes/mcp/mcp_reports.php b/phpBB/includes/mcp/mcp_reports.php index 3f48c58073..8db5bb9727 100644 --- a/phpBB/includes/mcp/mcp_reports.php +++ b/phpBB/includes/mcp/mcp_reports.php @@ -189,6 +189,7 @@ class mcp_reports 'S_POST_REPORTED' => $post_info['post_reported'], 'S_POST_UNAPPROVED' => ($post_info['post_visibility'] == ITEM_UNAPPROVED), 'S_POST_LOCKED' => $post_info['post_edit_locked'], + 'S_REPORT_CLOSED' => $report['report_closed'], 'S_USER_NOTES' => true, 'U_EDIT' => ($auth->acl_get('m_edit', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&f={$post_info['forum_id']}&p={$post_info['post_id']}") : '', diff --git a/phpBB/includes/ucp/info/ucp_auth_link.php b/phpBB/includes/ucp/info/ucp_auth_link.php new file mode 100644 index 0000000000..ee88b15ea8 --- /dev/null +++ b/phpBB/includes/ucp/info/ucp_auth_link.php @@ -0,0 +1,34 @@ +<?php +/** +* +* @package ucp +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @package module_install +*/ +class ucp_auth_link_info +{ + function module() + { + return array( + 'filename' => 'ucp_auth_link', + 'title' => 'UCP_AUTH_LINK', + 'version' => '1.0.0', + 'modes' => array( + 'auth_link' => array('title' => 'UCP_AUTH_LINK_MANAGE', 'auth' => '', 'cat' => array('UCP_PROFILE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/phpBB/includes/ucp/ucp_auth_link.php b/phpBB/includes/ucp/ucp_auth_link.php new file mode 100644 index 0000000000..5a5653e0b2 --- /dev/null +++ b/phpBB/includes/ucp/ucp_auth_link.php @@ -0,0 +1,142 @@ +<?php +/** +* +* @package ucp +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class ucp_auth_link +{ + /** + * @var string + */ + public $u_action; + + /** + * Generates the ucp_auth_link page and handles the auth link process + * + * @param int $id + * @param string $mode + */ + public function main($id, $mode) + { + global $config, $request, $template, $phpbb_container, $user; + + $error = array(); + + $auth_provider = $phpbb_container->get('auth.provider.' . $config['auth_method']); + + // confirm that the auth provider supports this page + $provider_data = $auth_provider->get_auth_link_data(); + if ($provider_data === null) + { + $error[] = 'UCP_AUTH_LINK_NOT_SUPPORTED'; + } + + $s_hidden_fields = array(); + add_form_key('ucp_auth_link'); + + $submit = $request->variable('submit', false, false, phpbb_request_interface::POST); + + // This path is only for primary actions + if (!sizeof($error) && $submit) + { + if (!check_form_key('ucp_auth_link')) + { + $error[] = 'FORM_INVALID'; + } + + if (!sizeof($error)) + { + // Any post data could be necessary for auth (un)linking + $link_data = $request->get_super_global(phpbb_request_interface::POST); + + // The current user_id is also necessary + $link_data['user_id'] = $user->data['user_id']; + + // Tell the provider that the method is auth_link not login_link + $link_data['link_method'] = 'auth_link'; + + if ($request->variable('link', 0, false, phpbb_request_interface::POST)) + { + $error[] = $auth_provider->link_account($link_data); + } + else + { + $error[] = $auth_provider->unlink_account($link_data); + } + + // Template data may have changed, get new data + $provider_data = $auth_provider->get_auth_link_data(); + } + } + + // In some cases, a request to an external server may be required. In + // these cases, the GET parameter 'link' should exist and should be true + if ($request->variable('link', false)) + { + // In this case the link data should only be populated with the + // link_method as the provider dictates how data is returned to it. + $link_data = array('link_method' => 'auth_link'); + + $error[] = $auth_provider->link_account($link_data); + + // Template data may have changed, get new data + $provider_data = $auth_provider->get_auth_link_data(); + } + + if (isset($provider_data['VARS'])) + { + // Handle hidden fields separately + if (isset($provider_data['VARS']['HIDDEN_FIELDS'])) + { + $s_hidden_fields = array_merge($s_hidden_fields, $provider_data['VARS']['HIDDEN_FIELDS']); + unset($provider_data['VARS']['HIDDEN_FIELDS']); + } + + $template->assign_vars($provider_data['VARS']); + } + + if (isset($provider_data['BLOCK_VAR_NAME'])) + { + foreach ($provider_data['BLOCK_VARS'] as $block_vars) + { + // See if there are additional hidden fields. This should be an associative array + if (isset($block_vars['HIDDEN_FIELDS'])) + { + $block_vars['HIDDEN_FIELDS'] = build_hidden_fields($block_vars['HIDDEN_FIELDS']); + } + + $template->assign_block_vars($provider_data['BLOCK_VAR_NAME'], $block_vars); + } + } + + $s_hidden_fields = build_hidden_fields($s_hidden_fields); + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + $error = implode('<br />', $error); + + $template->assign_vars(array( + 'ERROR' => $error, + + 'PROVIDER_TEMPLATE_FILE' => $provider_data['TEMPLATE_FILE'], + + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_UCP_ACTION' => $this->u_action, + )); + + $this->tpl_name = 'ucp_auth_link'; + $this->page_title = 'UCP_AUTH_LINK'; + } +} diff --git a/phpBB/includes/ucp/ucp_login_link.php b/phpBB/includes/ucp/ucp_login_link.php new file mode 100644 index 0000000000..4620eb9b9e --- /dev/null +++ b/phpBB/includes/ucp/ucp_login_link.php @@ -0,0 +1,243 @@ +<?php +/** +* +* @package ucp +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_login_link +* Allows users of external accounts link those accounts to their phpBB accounts +* during an attempted login. +* @package ucp +*/ +class ucp_login_link +{ + /** + * @var string + */ + public $u_action; + + /** + * Generates the ucp_login_link page and handles login link process + * + * @param int $id + * @param string $mode + */ + function main($id, $mode) + { + global $config, $phpbb_container, $request, $template, $user; + global $phpbb_root_path, $phpEx; + + // Initialize necessary variables + $login_error = null; + $login_link_error = null; + $login_username = null; + + // Build the data array + $data = $this->get_login_link_data_array(); + + // Ensure the person was sent here with login_link data + if (empty($data)) + { + $login_link_error = $user->lang['LOGIN_LINK_NO_DATA_PROVIDED']; + } + + // Use the auth_provider requested even if different from configured + $auth_provider = 'auth.provider.' . $request->variable('auth_provider', $config['auth_method']); + $auth_provider = $phpbb_container->get($auth_provider); + + // Set the link_method to login_link + $data['link_method'] = 'login_link'; + + // Have the authentication provider check that all necessary data is available + $result = $auth_provider->login_link_has_necessary_data($data); + if ($result !== null) + { + $login_link_error = $user->lang[$result]; + } + + // Perform link action if there is no error + if (!$login_link_error) + { + if ($request->is_set_post('login')) + { + $login_username = $request->variable('login_username', '', false, phpbb_request_interface::POST); + $login_password = $request->untrimmed_variable('login_password', '', true, phpbb_request_interface::POST); + + $login_result = $auth_provider->login($login_username, $login_password); + + // We only care if there is or is not an error + $login_error = $this->process_login_result($login_result); + + if (!$login_error) + { + // Give the user_id to the data + $data['user_id'] = $login_result['user_row']['user_id']; + + // The user is now logged in, attempt to link the user to the external account + $result = $auth_provider->link_account($data); + + if ($result) + { + $login_link_error = $user->lang[$result]; + } + else + { + // Finish login + $result = $user->session_create($login_result['user_row']['user_id'], false, false, true); + + // Perform a redirect as the account has been linked + $this->perform_redirect(); + } + } + } + } + + $template->assign_vars(array( + // Common template elements + 'LOGIN_LINK_ERROR' => $login_link_error, + 'PASSWORD_CREDENTIAL' => 'login_password', + 'USERNAME_CREDENTIAL' => 'login_username', + 'S_HIDDEN_FIELDS' => $this->get_hidden_fields($data), + + // Registration elements + 'REGISTER_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), + + // Login elements + 'LOGIN_ERROR' => $login_error, + 'LOGIN_USERNAME' => $login_username, + )); + + $this->tpl_name = 'ucp_login_link'; + $this->page_title = 'UCP_LOGIN_LINK'; + } + + /** + * Builds the hidden fields string from the data array. + * + * @param array $data This function only includes data in the array + * that has a key that begins with 'login_link_' + * @return string A string of hidden fields that can be included in the + * template + */ + protected function get_hidden_fields($data) + { + $fields = array(); + + foreach ($data as $key => $value) + { + $fields['login_link_' . $key] = $value; + } + + return build_hidden_fields($fields); + } + + /** + * Builds the login_link data array + * + * @return array All login_link data. This is all GET data whose names + * begin with 'login_link_' + */ + protected function get_login_link_data_array() + { + global $request; + + $var_names = $request->variable_names(phpbb_request_interface::GET); + $login_link_data = array(); + $string_start_length = strlen('login_link_'); + + foreach ($var_names as $var_name) + { + if (strpos($var_name, 'login_link_') === 0) + { + $key_name = substr($var_name, $string_start_length); + $login_link_data[$key_name] = $request->variable($var_name, '', false, phpbb_request_interface::GET); + } + } + + return $login_link_data; + } + + /** + * Processes the result array from the login process + * @param array $result The login result array + * @return string|null If there was an error in the process, a string is + * returned. If the login was successful, then null is + * returned. + */ + protected function process_login_result($result) + { + global $config, $request, $template, $user; + + $login_error = null; + + if ($result['status'] != LOGIN_SUCCESS) + { + // Handle all errors first + if ($result['status'] == LOGIN_BREAK) + { + trigger_error($result['error_msg']); + } + + switch ($result['status']) + { + case LOGIN_ERROR_ATTEMPTS: + + $captcha = phpbb_captcha_factory::get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + + $template->assign_vars(array( + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); + + $login_error = $user->lang[$result['error_msg']]; + break; + + case LOGIN_ERROR_PASSWORD_CONVERT: + $login_error = sprintf( + $user->lang[$result['error_msg']], + ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '', + ($config['email_enable']) ? '</a>' : '', + ($config['board_contact']) ? '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">' : '', + ($config['board_contact']) ? '</a>' : '' + ); + break; + + // Username, password, etc... + default: + $login_error = $user->lang[$result['error_msg']]; + + // Assign admin contact to some error messages + if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD') + { + $login_error = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '<a href="mailto:' . htmlspecialchars($config['board_contact']) . '">', '</a>'); + } + + break; + } + } + + return $login_error; + } + + /** + * Performs a post login redirect + */ + protected function perform_redirect() + { + global $phpbb_root_path, $phpEx; + $url = append_sid($phpbb_root_path . 'index.' . $phpEx); + redirect($url); + } +} diff --git a/phpBB/includes/ucp/ucp_register.php b/phpBB/includes/ucp/ucp_register.php index 7bc7ac8191..44621e6dea 100644 --- a/phpBB/includes/ucp/ucp_register.php +++ b/phpBB/includes/ucp/ucp_register.php @@ -27,7 +27,7 @@ class ucp_register function main($id, $mode) { global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx; - global $request; + global $request, $phpbb_container; // if ($config['require_activation'] == USER_ACTIVATION_DISABLE) @@ -78,19 +78,37 @@ class ucp_register } } - $cp = new custom_profile(); $error = $cp_data = $cp_error = array(); + $s_hidden_fields = array(); + + // Handle login_link data added to $_hidden_fields + $login_link_data = $this->get_login_link_data_array(); + + if (!empty($login_link_data)) + { + // Confirm that we have all necessary data + $auth_provider = 'auth.provider.' . $request->variable('auth_provider', $config['auth_method']); + $auth_provider = $phpbb_container->get($auth_provider); + + $result = $auth_provider->login_link_has_necessary_data($login_link_data); + if ($result !== null) + { + $error[] = $user->lang[$result]; + } + + $s_hidden_fields = array_merge($s_hidden_fields, $this->get_login_link_data_for_hidden_fields($login_link_data)); + } if (!$agreed || ($coppa === false && $config['coppa_enable']) || ($coppa && !$config['coppa_enable'])) { $add_lang = ($change_lang) ? '&change_lang=' . urlencode($change_lang) : ''; $add_coppa = ($coppa !== false) ? '&coppa=' . $coppa : ''; - $s_hidden_fields = array( + $s_hidden_fields = array_merge($s_hidden_fields, array( 'change_lang' => '', - ); + )); // If we change the language, we want to pass on some more possible parameter. if ($change_lang) @@ -398,15 +416,28 @@ class ucp_register } } + // Perform account linking if necessary + if (!empty($login_link_data)) + { + $login_link_data['user_id'] = $user_id; + + $result = $auth_provider->link_account($login_link_data); + + if ($result) + { + $message = $message . '<br /><br />' . $user->lang[$result]; + } + } + $message = $message . '<br /><br />' . sprintf($user->lang['RETURN_INDEX'], '<a href="' . append_sid("{$phpbb_root_path}index.$phpEx") . '">', '</a>'); trigger_error($message); } } - $s_hidden_fields = array( + $s_hidden_fields = array_merge($s_hidden_fields, array( 'agreed' => 'true', 'change_lang' => 0, - ); + )); if ($config['coppa_enable']) { @@ -474,4 +505,49 @@ class ucp_register $this->tpl_name = 'ucp_register'; $this->page_title = 'UCP_REGISTRATION'; } + + /** + * Creates the login_link data array + * + * @return array Returns an array of all POST paramaters whose names + * begin with 'login_link_' + */ + protected function get_login_link_data_array() + { + global $request; + + $var_names = $request->variable_names(phpbb_request_interface::POST); + $login_link_data = array(); + $string_start_length = strlen('login_link_'); + + foreach ($var_names as $var_name) + { + if (strpos($var_name, 'login_link_') === 0) + { + $key_name = substr($var_name, $string_start_length); + $login_link_data[$key_name] = $request->variable($var_name, '', false, phpbb_request_interface::POST); + } + } + + return $login_link_data; + } + + /** + * Prepends they key names of an associative array with 'login_link_' for + * inclusion on the page as hidden fields. + * + * @param array $data The array to be modified + * @return array The modified array + */ + protected function get_login_link_data_for_hidden_fields($data) + { + $new_data = array(); + + foreach ($data as $key => $value) + { + $new_data['login_link_' . $key] = $value; + } + + return $new_data; + } } diff --git a/phpBB/install/schemas/firebird_schema.sql b/phpBB/install/schemas/firebird_schema.sql index ca68ea387d..1e47008d73 100644 --- a/phpBB/install/schemas/firebird_schema.sql +++ b/phpBB/install/schemas/firebird_schema.sql @@ -128,6 +128,27 @@ CREATE INDEX phpbb_acl_users_user_id ON phpbb_acl_users(user_id);; CREATE INDEX phpbb_acl_users_auth_option_id ON phpbb_acl_users(auth_option_id);; CREATE INDEX phpbb_acl_users_auth_role_id ON phpbb_acl_users(auth_role_id);; +# Table: 'phpbb_oauth_tokens' +CREATE TABLE phpbb_oauth_tokens ( + user_id INTEGER DEFAULT 0 NOT NULL, + session_id CHAR(32) CHARACTER SET NONE DEFAULT '' NOT NULL, + provider VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + oauth_token BLOB SUB_TYPE TEXT CHARACTER SET NONE DEFAULT '' NOT NULL +);; + +CREATE INDEX phpbb_oauth_tokens_user_id ON phpbb_oauth_tokens(user_id);; +CREATE INDEX phpbb_oauth_tokens_provider ON phpbb_oauth_tokens(provider);; + +# Table: 'phpbb_oauth_accounts' +CREATE TABLE phpbb_oauth_accounts ( + user_id INTEGER DEFAULT 0 NOT NULL, + provider VARCHAR(255) CHARACTER SET NONE DEFAULT '' NOT NULL, + oauth_provider_id BLOB SUB_TYPE TEXT CHARACTER SET UTF8 DEFAULT '' NOT NULL +);; + +ALTER TABLE phpbb_oauth_accounts ADD PRIMARY KEY (user_id, provider);; + + # Table: 'phpbb_banlist' CREATE TABLE phpbb_banlist ( ban_id INTEGER NOT NULL, diff --git a/phpBB/install/schemas/mssql_schema.sql b/phpBB/install/schemas/mssql_schema.sql index a2a6d2192c..922313236e 100644 --- a/phpBB/install/schemas/mssql_schema.sql +++ b/phpBB/install/schemas/mssql_schema.sql @@ -167,6 +167,43 @@ GO /* + Table: 'phpbb_oauth_tokens' +*/ +CREATE TABLE [phpbb_oauth_tokens] ( + [user_id] [int] DEFAULT (0) NOT NULL , + [session_id] [char] (32) DEFAULT ('') NOT NULL , + [provider] [varchar] (255) DEFAULT ('') NOT NULL , + [oauth_token] [text] DEFAULT ('') NOT NULL +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO + +CREATE INDEX [user_id] ON [phpbb_oauth_tokens]([user_id]) ON [PRIMARY] +GO + +CREATE INDEX [provider] ON [phpbb_oauth_tokens]([provider]) ON [PRIMARY] +GO + + +/* + Table: 'phpbb_oauth_accounts' +*/ +CREATE TABLE [phpbb_oauth_accounts] ( + [user_id] [int] DEFAULT (0) NOT NULL , + [provider] [varchar] (255) DEFAULT ('') NOT NULL , + [oauth_provider_id] [varchar] (4000) DEFAULT ('') NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [phpbb_oauth_accounts] WITH NOCHECK ADD + CONSTRAINT [PK_phpbb_oauth_accounts] PRIMARY KEY CLUSTERED + ( + [user_id], + [provider] + ) ON [PRIMARY] +GO + + +/* Table: 'phpbb_banlist' */ CREATE TABLE [phpbb_banlist] ( diff --git a/phpBB/install/schemas/mysql_40_schema.sql b/phpBB/install/schemas/mysql_40_schema.sql index 2c5931bae4..e07a768387 100644 --- a/phpBB/install/schemas/mysql_40_schema.sql +++ b/phpBB/install/schemas/mysql_40_schema.sql @@ -90,6 +90,26 @@ CREATE TABLE phpbb_acl_users ( ); +# Table: 'phpbb_oauth_tokens' +CREATE TABLE phpbb_oauth_tokens ( + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + session_id binary(32) DEFAULT '' NOT NULL, + provider varbinary(255) DEFAULT '' NOT NULL, + oauth_token mediumblob NOT NULL, + KEY user_id (user_id), + KEY provider (provider) +); + + +# Table: 'phpbb_oauth_accounts' +CREATE TABLE phpbb_oauth_accounts ( + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + provider varbinary(255) DEFAULT '' NOT NULL, + oauth_provider_id blob NOT NULL, + PRIMARY KEY (user_id, provider) +); + + # Table: 'phpbb_banlist' CREATE TABLE phpbb_banlist ( ban_id mediumint(8) UNSIGNED NOT NULL auto_increment, diff --git a/phpBB/install/schemas/mysql_41_schema.sql b/phpBB/install/schemas/mysql_41_schema.sql index 7b7be3c462..d3ed1ee15e 100644 --- a/phpBB/install/schemas/mysql_41_schema.sql +++ b/phpBB/install/schemas/mysql_41_schema.sql @@ -90,6 +90,26 @@ CREATE TABLE phpbb_acl_users ( ) CHARACTER SET `utf8` COLLATE `utf8_bin`; +# Table: 'phpbb_oauth_tokens' +CREATE TABLE phpbb_oauth_tokens ( + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + session_id char(32) DEFAULT '' NOT NULL, + provider varchar(255) DEFAULT '' NOT NULL, + oauth_token mediumtext NOT NULL, + KEY user_id (user_id), + KEY provider (provider) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + +# Table: 'phpbb_oauth_accounts' +CREATE TABLE phpbb_oauth_accounts ( + user_id mediumint(8) UNSIGNED DEFAULT '0' NOT NULL, + provider varchar(255) DEFAULT '' NOT NULL, + oauth_provider_id text NOT NULL, + PRIMARY KEY (user_id, provider) +) CHARACTER SET `utf8` COLLATE `utf8_bin`; + + # Table: 'phpbb_banlist' CREATE TABLE phpbb_banlist ( ban_id mediumint(8) UNSIGNED NOT NULL auto_increment, diff --git a/phpBB/install/schemas/oracle_schema.sql b/phpBB/install/schemas/oracle_schema.sql index 75c01446d8..f32980e378 100644 --- a/phpBB/install/schemas/oracle_schema.sql +++ b/phpBB/install/schemas/oracle_schema.sql @@ -211,6 +211,34 @@ CREATE INDEX phpbb_acl_users_auth_role_id ON phpbb_acl_users (auth_role_id) / /* + Table: 'phpbb_oauth_tokens' +*/ +CREATE TABLE phpbb_oauth_tokens ( + user_id number(8) DEFAULT '0' NOT NULL, + session_id char(32) DEFAULT '' , + provider varchar2(255) DEFAULT '' , + oauth_token clob DEFAULT '' +) +/ + +CREATE INDEX phpbb_oauth_tokens_user_id ON phpbb_oauth_tokens (user_id) +/ +CREATE INDEX phpbb_oauth_tokens_provider ON phpbb_oauth_tokens (provider) +/ + +/* + Table: 'phpbb_oauth_accounts' +*/ +CREATE TABLE phpbb_oauth_accounts ( + user_id number(8) DEFAULT '0' NOT NULL, + provider varchar2(255) DEFAULT '' , + oauth_provider_id clob DEFAULT '' , + CONSTRAINT pk_phpbb_oauth_accounts PRIMARY KEY (user_id, provider) +) +/ + + +/* Table: 'phpbb_banlist' */ CREATE TABLE phpbb_banlist ( diff --git a/phpBB/install/schemas/postgres_schema.sql b/phpBB/install/schemas/postgres_schema.sql index c7fbe9a507..14435898eb 100644 --- a/phpBB/install/schemas/postgres_schema.sql +++ b/phpBB/install/schemas/postgres_schema.sql @@ -189,6 +189,30 @@ CREATE INDEX phpbb_acl_users_auth_option_id ON phpbb_acl_users (auth_option_id); CREATE INDEX phpbb_acl_users_auth_role_id ON phpbb_acl_users (auth_role_id); /* + Table: 'phpbb_oauth_tokens' +*/ +CREATE TABLE phpbb_oauth_tokens ( + user_id INT4 DEFAULT '0' NOT NULL CHECK (user_id >= 0), + session_id char(32) DEFAULT '' NOT NULL, + provider varchar(255) DEFAULT '' NOT NULL, + oauth_token TEXT DEFAULT '' NOT NULL +); + +CREATE INDEX phpbb_oauth_tokens_user_id ON phpbb_oauth_tokens (user_id); +CREATE INDEX phpbb_oauth_tokens_provider ON phpbb_oauth_tokens (provider); + +/* + Table: 'phpbb_oauth_accounts' +*/ +CREATE TABLE phpbb_oauth_accounts ( + user_id INT4 DEFAULT '0' NOT NULL CHECK (user_id >= 0), + provider varchar(255) DEFAULT '' NOT NULL, + oauth_provider_id varchar(4000) DEFAULT '' NOT NULL, + PRIMARY KEY (user_id, provider) +); + + +/* Table: 'phpbb_banlist' */ CREATE SEQUENCE phpbb_banlist_seq; diff --git a/phpBB/install/schemas/sqlite_schema.sql b/phpBB/install/schemas/sqlite_schema.sql index 72b2b276da..de88900f06 100644 --- a/phpBB/install/schemas/sqlite_schema.sql +++ b/phpBB/install/schemas/sqlite_schema.sql @@ -89,6 +89,26 @@ CREATE INDEX phpbb_acl_users_user_id ON phpbb_acl_users (user_id); CREATE INDEX phpbb_acl_users_auth_option_id ON phpbb_acl_users (auth_option_id); CREATE INDEX phpbb_acl_users_auth_role_id ON phpbb_acl_users (auth_role_id); +# Table: 'phpbb_oauth_tokens' +CREATE TABLE phpbb_oauth_tokens ( + user_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + session_id char(32) NOT NULL DEFAULT '', + provider varchar(255) NOT NULL DEFAULT '', + oauth_token mediumtext(16777215) NOT NULL DEFAULT '' +); + +CREATE INDEX phpbb_oauth_tokens_user_id ON phpbb_oauth_tokens (user_id); +CREATE INDEX phpbb_oauth_tokens_provider ON phpbb_oauth_tokens (provider); + +# Table: 'phpbb_oauth_accounts' +CREATE TABLE phpbb_oauth_accounts ( + user_id INTEGER UNSIGNED NOT NULL DEFAULT '0', + provider varchar(255) NOT NULL DEFAULT '', + oauth_provider_id text(65535) NOT NULL DEFAULT '', + PRIMARY KEY (user_id, provider) +); + + # Table: 'phpbb_banlist' CREATE TABLE phpbb_banlist ( ban_id INTEGER PRIMARY KEY NOT NULL , diff --git a/phpBB/language/en/acp/board.php b/phpBB/language/en/acp/board.php index ce15dfefb4..abc7738ee3 100644 --- a/phpBB/language/en/acp/board.php +++ b/phpBB/language/en/acp/board.php @@ -394,6 +394,12 @@ $lang = array_merge($lang, array( 'AUTH_METHOD' => 'Select an authentication method', + 'AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING' => 'Both the key and secret of each enabled OAuth service provider must be provided. Only one was provided for an OAuth service provider.', + 'AUTH_PROVIDER_OAUTH_EXPLAIN' => 'Each OAuth provider requires a unique secret and key in order to authenticate with the external server.<br />These should be supplied by the OAuth service when you register your website with them and should be entered exactly as provided to you.<br />Any service that does not have both a key and a secret entered here will not be available for use by the forum users.', + 'AUTH_PROVIDER_OAUTH_KEY' => 'Key', + 'AUTH_PROVIDER_OAUTH_TITLE' => 'OAuth', + 'AUTH_PROVIDER_OAUTH_SECRET' => 'Secret', + 'APACHE_SETUP_BEFORE_USE' => 'You have to setup apache authentication before you switch phpBB to this authentication method. Keep in mind that the username you use for apache authentication has to be the same as your phpBB username. Apache authentication can only be used with mod_php (not with a CGI version) and safe_mode disabled.', 'LDAP_DN' => 'LDAP base <var>dn</var>', diff --git a/phpBB/language/en/common.php b/phpBB/language/en/common.php index b188d90f3a..5c7df2c5fd 100644 --- a/phpBB/language/en/common.php +++ b/phpBB/language/en/common.php @@ -88,6 +88,14 @@ $lang = array_merge($lang, array( 'ATTACHED_IMAGE_NOT_IMAGE' => 'The image file you tried to attach is invalid.', 'AUTHOR' => 'Author', 'AUTH_NO_PROFILE_CREATED' => 'The creation of a user profile was unsuccessful.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY' => 'Invalid database entry.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE' => 'Invalid service type provided to OAuth service handler.', + 'AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED' => 'OAuth service not created', + 'AUTH_PROVIDER_OAUTH_SERVICE_BITLY' => 'Bitly', + 'AUTH_PROVIDER_OAUTH_SERVICE_FACEBOOK' => 'Facebook', + 'AUTH_PROVIDER_OAUTH_SERVICE_GOOGLE' => 'Google', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED' => 'OAuth token not stored.', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED' => 'OAuth token incorrectly stored.', 'AVATAR_DISALLOWED_CONTENT' => 'The upload was rejected because the uploaded file was identified as a possible attack vector.', 'AVATAR_DISALLOWED_EXTENSION' => 'This file cannot be displayed because the extension <strong>%s</strong> is not allowed.', 'AVATAR_EMPTY_REMOTE_DATA' => 'The specified avatar could not be uploaded because the remote data appears to be invalid or corrupted.', @@ -351,6 +359,7 @@ $lang = array_merge($lang, array( 'LOGIN_CONFIRM_EXPLAIN' => 'To prevent brute forcing accounts the board requires you to enter a confirmation code after a maximum amount of failed logins. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', // unused 'LOGIN_ERROR_ATTEMPTS' => 'You exceeded the maximum allowed number of login attempts. In addition to your username and password you now also have to solve the CAPTCHA below.', 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE' => 'You have not been authenticated by Apache.', + 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST' => 'A non-existant OAuth service has been requested.', 'LOGIN_ERROR_PASSWORD' => 'You have specified an incorrect password. Please check your password and try again. If you continue to have problems please contact the %sBoard Administrator%s.', 'LOGIN_ERROR_PASSWORD_CONVERT' => 'It was not possible to convert your password when updating this bulletin board’s software. Please %srequest a new password%s. If you continue to have problems please contact the %sBoard Administrator%s.', 'LOGIN_ERROR_USERNAME' => 'You have specified an incorrect username. Please check your username and try again. If you continue to have problems please contact the %sBoard Administrator%s.', diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php index 51823ddb12..03a14336b5 100644 --- a/phpBB/language/en/ucp.php +++ b/phpBB/language/en/ucp.php @@ -270,6 +270,10 @@ $lang = array_merge($lang, array( 'LINK_REMOTE_SIZE' => 'Avatar dimensions', 'LINK_REMOTE_SIZE_EXPLAIN' => 'Specify the width and height of the avatar, leave blank to attempt automatic verification.', 'LOGIN_EXPLAIN_UCP' => 'Please login in order to access the User Control Panel.', + 'LOGIN_LINK' => 'Link or register your account on an external service with your board account', + 'LOGIN_LINK_EXPLAIN' => 'You have attempted to login with an external service that is not yet connected to an account on this board. You must now either link this account to an existing account or create a new account.', + 'LOGIN_LINK_MISSING_DATA' => 'Data that is necessary to link your account with an external service is not available. Please restart the login process.', + 'LOGIN_LINK_NO_DATA_PROVIDED' => 'No data has been provided to this page to link an external account to a forum account. Please contact the board administrator if you continue to experience problems.', 'LOGIN_KEY' => 'Login Key', 'LOGIN_TIME' => 'Login Time', 'LOGIN_REDIRECT' => 'You have been successfully logged in.', @@ -475,11 +479,19 @@ $lang = array_merge($lang, array( 'UCP_ADMIN_ACTIVATE' => 'Please note that you will need to enter a valid email address before your account is activated. The administrator will review your account and if approved you will receive an email at the address you specified.', 'UCP_AIM' => 'AOL Instant Messenger', 'UCP_ATTACHMENTS' => 'Attachments', + 'UCP_AUTH_LINK' => 'External accounts', + 'UCP_AUTH_LINK_ASK' => 'You currently have no account associated with this external service. Click the button below to link your board account to an account with this external service.', + 'UCP_AUTH_LINK_ID' => 'Unique identifier', + 'UCP_AUTH_LINK_LINK' => 'Link', + 'UCP_AUTH_LINK_MANAGE' => 'Manage external account associations', + 'UCP_AUTH_LINK_TITLE' => 'Manage your external account associations', + 'UCP_AUTH_LINK_UNLINK' => 'Unlink', 'UCP_COPPA_BEFORE' => 'Before %s', 'UCP_COPPA_ON_AFTER' => 'On or after %s', 'UCP_EMAIL_ACTIVATE' => 'Please note that you will need to enter a valid email address before your account is activated. You will receive an email at the address you provide that contains an account activation link.', 'UCP_ICQ' => 'ICQ number', 'UCP_JABBER' => 'Jabber address', + 'UCP_LOGIN_LINK' => 'Set up an external account association', 'UCP_MAIN' => 'Overview', 'UCP_MAIN_ATTACHMENTS' => 'Manage attachments', diff --git a/phpBB/phpbb/auth/auth.php b/phpBB/phpbb/auth/auth.php index 279959974d..81f8c76fc8 100644 --- a/phpBB/phpbb/auth/auth.php +++ b/phpBB/phpbb/auth/auth.php @@ -970,6 +970,18 @@ class phpbb_auth ); } + // If the auth provider wants us to link an empty account do so and redirect + if ($login['status'] == LOGIN_SUCCESS_LINK_PROFILE) + { + // If this status exists a fourth field is in the $login array called 'redirect_data' + // This data is passed along as GET data to the next page allow the account to be linked + + $params = array('mode' => 'login_link'); + $url = append_sid($phpbb_root_path . 'ucp.' . $phpEx, array_merge($params, $login['redirect_data'])); + + redirect($url); + } + // If login succeeded, we will log the user in... else we pass the login array through... if ($login['status'] == LOGIN_SUCCESS) { diff --git a/phpBB/phpbb/auth/provider/base.php b/phpBB/phpbb/auth/provider/base.php index 7eaf8bb2d3..09e918cee4 100644 --- a/phpBB/phpbb/auth/provider/base.php +++ b/phpBB/phpbb/auth/provider/base.php @@ -57,6 +57,22 @@ abstract class phpbb_auth_provider_base implements phpbb_auth_provider_interface /** * {@inheritdoc} */ + public function get_login_data() + { + return; + } + + /** + * {@inheritdoc} + */ + public function get_auth_link_data() + { + return; + } + + /** + * {@inheritdoc} + */ public function logout($data, $new_session) { return; @@ -69,4 +85,28 @@ abstract class phpbb_auth_provider_base implements phpbb_auth_provider_interface { return; } + + /** + * {@inheritdoc} + */ + public function login_link_has_necessary_data($login_link_data) + { + return; + } + + /** + * {@inheritdoc} + */ + public function link_account(array $link_data) + { + return; + } + + /** + * {@inheritdoc} + */ + public function unlink_account(array $link_data) + { + return; + } } diff --git a/phpBB/phpbb/auth/provider/interface.php b/phpBB/phpbb/auth/provider/interface.php index 47043bc107..eadd5f01d1 100644 --- a/phpBB/phpbb/auth/provider/interface.php +++ b/phpBB/phpbb/auth/provider/interface.php @@ -45,6 +45,11 @@ interface phpbb_auth_provider_interface * 'error_msg' => string * 'user_row' => array * ) + * A fourth key of the array may be present: + * 'redirect_data' This key is only used when 'status' is + * equal to LOGIN_SUCCESS_LINK_PROFILE and its value is an + * associative array that is turned into GET variables on + * the redirect url. */ public function login($username, $password); @@ -80,10 +85,49 @@ interface phpbb_auth_provider_interface * 'TEMPLATE_FILE' => string, * 'TEMPLATE_VARS' => array(...), * ) + * An optional third element may be added to this + * array: 'BLOCK_VAR_NAME'. If this is present, + * then its value should be a string that is used + * to designate the name of the loop used in the + * ACP template file. When this is present, an + * additional key named 'BLOCK_VARS' is required. + * This must be an array containing at least one + * array of variables that will be assigned during + * the loop in the template. An example of this is + * presented below: + * array( + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array( + * 'KEY IS UNIMPORTANT' => array(...), + * ), + * 'TEMPLATE_FILE' => string, + * 'TEMPLATE_VARS' => array(...), + * ) */ public function get_acp_template($new_config); /** + * Returns an array of data necessary to build custom elements on the login + * form. + * + * @return array|null If this function is not implemented on an auth + * provider then it returns null. If it is implemented + * it will return an array of up to four elements of + * which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is + * present then 'BLOCK_VARS' must also be present in + * the array. The fourth element 'VARS' is also + * optional. The array, with all four elements present + * looks like the following: + * array( + * 'TEMPLATE_FILE' => string, + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array(...), + * 'VARS' => array(...), + * ) + */ + public function get_login_data(); + + /** * Performs additional actions during logout. * * @param array $data An array corresponding to @@ -102,4 +146,52 @@ interface phpbb_auth_provider_interface * session should be closed, or null if not implemented. */ public function validate_session($user); + + /** + * Checks to see if $login_link_data contains all information except for the + * user_id of an account needed to successfully link an external account to + * a forum account. + * + * @param array $link_data Any data needed to link a phpBB account to + * an external account. + * @return string|null Returns a string with a language constant if there + * is data missing or null if there is no error. + */ + public function login_link_has_necessary_data($login_link_data); + + /** + * Links an external account to a phpBB account. + * + * @param array $link_data Any data needed to link a phpBB account to + * an external account. + */ + public function link_account(array $link_data); + + /** + * Returns an array of data necessary to build the ucp_auth_link page + * + * @return array|null If this function is not implemented on an auth + * provider then it returns null. If it is implemented + * it will return an array of up to four elements of + * which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is + * present then 'BLOCK_VARS' must also be present in + * the array. The fourth element 'VARS' is also + * optional. The array, with all four elements present + * looks like the following: + * array( + * 'TEMPLATE_FILE' => string, + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array(...), + * 'VARS' => array(...), + * ) + */ + public function get_auth_link_data(); + + /** + * Unlinks an external account from a phpBB account. + * + * @param array $link_data Any data needed to unlink a phpBB account + * from a phpbb account. + */ + public function unlink_account(array $link_data); } diff --git a/phpBB/phpbb/auth/provider/oauth/oauth.php b/phpBB/phpbb/auth/provider/oauth/oauth.php new file mode 100644 index 0000000000..be0b8bb7d6 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/oauth.php @@ -0,0 +1,618 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +use OAuth\Common\Consumer\Credentials; +use OAuth\Common\Http\Uri\Uri; + +/** +* OAuth authentication provider for phpBB3 +* +* @package auth +*/ +class phpbb_auth_provider_oauth extends phpbb_auth_provider_base +{ + /** + * Database driver + * + * @var phpbb_db_driver + */ + protected $db; + + /** + * phpBB config + * + * @var phpbb_config + */ + protected $config; + + /** + * phpBB request object + * + * @var phpbb_request + */ + protected $request; + + /** + * phpBB user + * + * @var phpbb_user + */ + protected $user; + + /** + * OAuth token table + * + * @var string + */ + protected $auth_provider_oauth_token_storage_table; + + /** + * OAuth account association table + * + * @var string + */ + protected $auth_provider_oauth_token_account_assoc; + + /** + * All OAuth service providers + * + * @var phpbb_di_service_collection Contains phpbb_auth_provider_oauth_service_interface + */ + protected $service_providers; + + /** + * Users table + * + * @var string + */ + protected $users_table; + + /** + * Cached current uri object + * + * @var \OAuth\Common\Http\Uri\UriInterface|null + */ + protected $current_uri; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extenstion + * + * @var string + */ + protected $php_ext; + + /** + * OAuth Authentication Constructor + * + * @param phpbb_db_driver $db + * @param phpbb_config $config + * @param phpbb_request $request + * @param phpbb_user $user + * @param string $auth_provider_oauth_token_storage_table + * @param string $auth_provider_oauth_token_account_assoc + * @param phpbb_di_service_collection $service_providers Contains phpbb_auth_provider_oauth_service_interface + * @param string $users_table + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(phpbb_db_driver $db, phpbb_config $config, phpbb_request $request, phpbb_user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_token_account_assoc, phpbb_di_service_collection $service_providers, $users_table, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->request = $request; + $this->user = $user; + $this->auth_provider_oauth_token_storage_table = $auth_provider_oauth_token_storage_table; + $this->auth_provider_oauth_token_account_assoc = $auth_provider_oauth_token_account_assoc; + $this->service_providers = $service_providers; + $this->users_table = $users_table; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function init() + { + // This does not test whether or not the key and secret provided are valid. + foreach ($this->service_providers as $service_provider) + { + $credentials = $service_provider->get_service_credentials(); + + if (($credentials['key'] && !$credentials['secret']) || (!$credentials['key'] && $credentials['secret'])) + { + return $this->user->lang['AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING']; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // Temporary workaround for only having one authentication provider available + if (!$this->request->is_set('oauth_service')) + { + $provider = new phpbb_auth_provider_db($this->db, $this->config, $this->request, $this->user, $this->phpbb_root_path, $this->php_ext); + return $provider->login($username, $password); + } + + // Requst the name of the OAuth service + $service_name_original = $this->request->variable('oauth_service', '', false); + $service_name = 'auth.provider.oauth.service.' . strtolower($service_name_original); + if ($service_name_original === '' || !array_key_exists($service_name, $this->service_providers)) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + // Get the service credentials for the given service + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + + $storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); + $query = 'mode=login&login=external&oauth_service=' . $service_name_original; + $service = $this->get_service($service_name_original, $storage, $service_credentials, $this->service_providers[$service_name]->get_auth_scope(), $query); + + if ($this->request->is_set('code', phpbb_request_interface::GET)) + { + $this->service_providers[$service_name]->set_external_service_provider($service); + $unique_id = $this->service_providers[$service_name]->perform_auth_login(); + + // Check to see if this provider is already assosciated with an account + $data = array( + 'provider' => $service_name_original, + 'oauth_provider_id' => $unique_id + ); + $sql = 'SELECT user_id FROM ' . $this->auth_provider_oauth_token_account_assoc . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + // The user does not yet exist, ask to link or create profile + return array( + 'status' => LOGIN_SUCCESS_LINK_PROFILE, + 'error_msg' => 'LOGIN_OAUTH_ACCOUNT_NOT_LINKED', + 'user_row' => array(), + 'redirect_data' => array( + 'auth_provider' => 'oauth', + 'login_link_oauth_service' => $service_name_original, + ), + ); + } + + // Retrieve the user's account + $sql = 'SELECT user_id, username, user_password, user_passchg, user_pass_convert, user_email, user_type, user_login_attempts + FROM ' . $this->users_table . ' + WHERE user_id = ' . (int) $row['user_id']; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + throw new Exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY'); + } + + // Update token storage to store the user_id + $storage->set_user_id($row['user_id']); + + // The user is now authenticated and can be logged in + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + else + { + $url = $service->getAuthorizationUri(); + header('Location: ' . $url); + } + } + + /** + * Returns the cached current_uri object or creates and caches it if it is + * not already created. In each case the query string is updated based on + * the $query parameter. + * + * @param string $service_name The name of the service + * @param string $query The query string of the current_uri + * used in redirects + * @return \OAuth\Common\Http\Uri\UriInterface + */ + protected function get_current_uri($service_name, $query) + { + if ($this->current_uri) + { + $this->current_uri->setQuery($query); + return $this->current_uri; + } + + $uri_factory = new \OAuth\Common\Http\Uri\UriFactory(); + $current_uri = $uri_factory->createFromSuperGlobalArray($this->request->get_super_global(phpbb_request_interface::SERVER)); + $current_uri->setQuery($query); + + $this->current_uri = $current_uri; + return $current_uri; + } + + /** + * Returns a new service object + * + * @param string $service_name The name of the service + * @param phpbb_auth_oauth_token_storage $storage + * @param array $service_credentials {@see phpbb_auth_provider_oauth::get_service_credentials} + * @param array $scope The scope of the request against + * the api. + * @param string $query The query string of the + * current_uri used in redirection + * @return \OAuth\Common\Service\ServiceInterface + */ + protected function get_service($service_name, phpbb_auth_provider_oauth_token_storage $storage, array $service_credentials, array $scopes = array(), $query) + { + $current_uri = $this->get_current_uri($service_name, $query); + + // Setup the credentials for the requests + $credentials = new Credentials( + $service_credentials['key'], + $service_credentials['secret'], + $current_uri->getAbsoluteUri() + ); + + $service_factory = new \OAuth\ServiceFactory(); + $service = $service_factory->createService($service_name, $credentials, $storage, $scopes); + + if (!$service) + { + throw new Exception('AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED'); + } + + return $service; + } + + /** + * {@inheritdoc} + */ + public function get_login_data() + { + $login_data = array( + 'TEMPLATE_FILE' => 'login_body_oauth.html', + 'BLOCK_VAR_NAME' => 'oauth', + 'BLOCK_VARS' => array(), + ); + + foreach ($this->service_providers as $service_name => $service_provider) + { + // Only include data if the credentials are set + $credentials = $service_provider->get_service_credentials(); + if ($credentials['key'] && $credentials['secret']) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $redirect_url = build_url(false) . '&login=external&oauth_service=' . $actual_name; + $login_data['BLOCK_VARS'][$service_name] = array( + 'REDIRECT_URL' => redirect($redirect_url, true), + 'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + ); + } + } + + return $login_data; + } + + /** + * {@inheritdoc} + */ + public function acp() + { + $ret = array(); + + foreach ($this->service_providers as $service_name => $service_provider) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $ret[] = 'auth_oauth_' . $actual_name . '_key'; + $ret[] = 'auth_oauth_' . $actual_name . '_secret'; + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function get_acp_template($new_config) + { + $ret = array( + 'BLOCK_VAR_NAME' => 'oauth_services', + 'BLOCK_VARS' => array(), + 'TEMPLATE_FILE' => 'auth_provider_oauth.html', + 'TEMPLATE_VARS' => array(), + ); + + foreach ($this->service_providers as $service_name => $service_provider) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $ret['BLOCK_VARS'][$actual_name] = array( + 'ACTUAL_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + 'KEY' => $new_config['auth_oauth_' . $actual_name . '_key'], + 'NAME' => $actual_name, + 'SECRET' => $new_config['auth_oauth_' . $actual_name . '_secret'], + ); + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function login_link_has_necessary_data($login_link_data) + { + if (empty($login_link_data)) + { + return 'LOGIN_LINK_NO_DATA_PROVIDED'; + } + + if (!array_key_exists('oauth_service', $login_link_data) || !$login_link_data['oauth_service'] || + !array_key_exists('link_method', $login_link_data) || !$login_link_data['link_method']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function link_account(array $link_data) + { + // Check for a valid link method (auth_link or login_link) + if (!array_key_exists('link_method', $link_data) || + !in_array($link_data['link_method'], array( + 'auth_link', + 'login_link', + ))) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + // We must have an oauth_service listed, check for it two ways + if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) + { + $link_data['oauth_service'] = $this->request->variable('oauth_service', ''); + + if (!$link_data['oauth_service']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + } + + $service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']); + if (!array_key_exists($service_name, $this->service_providers)) + { + return 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST'; + } + + switch ($link_data['link_method']) + { + case 'auth_link': + return $this->link_account_auth_link($link_data, $service_name); + case 'login_link': + return $this->link_account_login_link($link_data, $service_name); + } + } + + /** + * Performs the account linking for login_link + * + * @param array $link_data The same variable given to {@see phpbb_auth_provider_interface::link_account} + * @param string $service_name The name of the service being used in + * linking. + * @return string|null Returns a language constant (string) if an error is + * encountered, or null on success. + */ + protected function link_account_login_link(array $link_data, $service_name) + { + $storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); + + // Check for an access token, they should have one + if (!$storage->has_access_token_by_session($service_name)) + { + return 'LOGIN_LINK_ERROR_OAUTH_NO_ACCESS_TOKEN'; + } + + // Prepare the query string + $query = 'mode=login_link&login_link_oauth_service=' . strtolower($link_data['oauth_service']); + + // Prepare for an authentication request + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + $scopes = $this->service_providers[$service_name]->get_auth_scope(); + $service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $scopes, $query); + $this->service_providers[$service_name]->set_external_service_provider($service); + + // The user has already authenticated successfully, request to authenticate again + $unique_id = $this->service_providers[$service_name]->perform_token_auth(); + + // Insert into table, they will be able to log in after this + $data = array( + 'user_id' => $link_data['user_id'], + 'provider' => strtolower($link_data['oauth_service']), + 'oauth_provider_id' => $unique_id, + ); + + $this->link_account_perform_link($data); + // Update token storage to store the user_id + $storage->set_user_id($link_data['user_id']); + } + + /** + * Performs the account linking for auth_link + * + * @param array $link_data The same variable given to {@see phpbb_auth_provider_interface::link_account} + * @param string $service_name The name of the service being used in + * linking. + * @return string|null Returns a language constant (string) if an error is + * encountered, or null on success. + */ + protected function link_account_auth_link(array $link_data, $service_name) + { + $storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); + $query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . strtolower($link_data['oauth_service']); + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + $scopes = $this->service_providers[$service_name]->get_auth_scope(); + $service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $scopes, $query); + + if ($this->request->is_set('code', phpbb_request_interface::GET)) + { + $this->service_providers[$service_name]->set_external_service_provider($service); + $unique_id = $this->service_providers[$service_name]->perform_auth_login(); + + // Insert into table, they will be able to log in after this + $data = array( + 'user_id' => $this->user->data['user_id'], + 'provider' => strtolower($link_data['oauth_service']), + 'oauth_provider_id' => $unique_id, + ); + + $this->link_account_perform_link($data); + } + else + { + $url = $service->getAuthorizationUri(); + header('Location: ' . $url); + } + } + + /** + * Performs the query that inserts an account link + * + * @param array $data This array is passed to db->sql_build_array + */ + protected function link_account_perform_link(array $data) + { + $sql = 'INSERT INTO ' . $this->auth_provider_oauth_token_account_assoc . ' + ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function logout($data, $new_session) + { + // Clear all tokens belonging to the user + $storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); + $storage->clearAllTokens(); + + return; + } + + /** + * {@inheritdoc} + */ + public function get_auth_link_data() + { + $block_vars = array(); + + // Get all external accounts tied to the current user + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + ); + $sql = 'SELECT oauth_provider_id, provider FROM ' . $this->auth_provider_oauth_token_account_assoc . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $oauth_user_ids = array(); + + if ($rows !== false && sizeof($rows)) + { + foreach ($rows as $row) + { + $oauth_user_ids[$row['provider']] = $row['oauth_provider_id']; + } + } + unset($rows); + + foreach ($this->service_providers as $service_name => $service_provider) + { + // Only include data if the credentials are set + $credentials = $service_provider->get_service_credentials(); + if ($credentials['key'] && $credentials['secret']) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + + $block_vars[$service_name] = array( + 'HIDDEN_FIELDS' => array( + 'link' => (!isset($oauth_user_ids[$actual_name])), + 'oauth_service' => $actual_name, + ), + + 'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + 'UNIQUE_ID' => (isset($oauth_user_ids[$actual_name])) ? $oauth_user_ids[$actual_name] : null, + ); + } + } + + return array( + 'BLOCK_VAR_NAME' => 'oauth', + 'BLOCK_VARS' => $block_vars, + + 'TEMPLATE_FILE' => 'ucp_auth_link_oauth.html', + ); + } + + /** + * {@inheritdoc} + */ + public function unlink_account(array $link_data) + { + if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + // Remove the link + $sql = 'DELETE FROM ' . $this->auth_provider_oauth_token_account_assoc . " + WHERE provider = '" . $this->db->sql_escape($link_data['oauth_service']) . "' + AND user_id = " . (int) $this->user->data['user_id']; + $this->db->sql_query($sql); + + // Clear all tokens belonging to the user on this servce + $service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']); + $storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table); + $storage->clearToken($service_name); + + return; + } +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/base.php b/phpBB/phpbb/auth/provider/oauth/service/base.php new file mode 100644 index 0000000000..1eb49b4265 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/base.php @@ -0,0 +1,55 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Base OAuth abstract class that all OAuth services should implement +* +* @package auth +*/ +abstract class phpbb_auth_provider_oauth_service_base implements phpbb_auth_provider_oauth_service_interface +{ + /** + * External OAuth service provider + * + * @var \OAuth\Common\Service\ServiceInterface + */ + protected $service_provider; + + /** + * {@inheritdoc} + */ + public function get_external_service_provider() + { + return $this->service_provider; + } + + /** + * {@inheritdoc} + */ + public function get_auth_scope() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider) + { + $this->service_provider = $service_provider; + } +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/bitly.php b/phpBB/phpbb/auth/provider/oauth/service/bitly.php new file mode 100644 index 0000000000..3bafdd59ce --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/bitly.php @@ -0,0 +1,98 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Bitly OAuth service +* +* @package auth +*/ +class phpbb_auth_provider_oauth_service_bitly extends phpbb_auth_provider_oauth_service_base +{ + /** + * phpBB config + * + * @var phpbb_config + */ + protected $config; + + /** + * phpBB request + * + * @var phpbb_request + */ + protected $request; + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_request $request + */ + public function __construct(phpbb_config $config, phpbb_request $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_bitly_key'], + 'secret' => $this->config['auth_oauth_bitly_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request from bitly, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('user/info'), true); + + // Return the unique identifier returned from bitly + return $result['data']['login']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('user/info'), true); + + // Return the unique identifier returned from bitly + return $result['data']['login']; + } +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/exception.php b/phpBB/phpbb/auth/provider/oauth/service/exception.php new file mode 100644 index 0000000000..23d3387951 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/exception.php @@ -0,0 +1,25 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* OAuth service exception class +* +* @package auth +*/ +class phpbb_auth_provider_oauth_service_exception extends RuntimeException +{ +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/facebook.php b/phpBB/phpbb/auth/provider/oauth/service/facebook.php new file mode 100644 index 0000000000..49206b7654 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/facebook.php @@ -0,0 +1,98 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Facebook OAuth service +* +* @package auth +*/ +class phpbb_auth_provider_oauth_service_facebook extends phpbb_auth_provider_oauth_service_base +{ + /** + * phpBB config + * + * @var phpbb_config + */ + protected $config; + + /** + * phpBB request + * + * @var phpbb_request + */ + protected $request; + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_request $request + */ + public function __construct(phpbb_config $config, phpbb_request $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_facebook_key'], + 'secret' => $this->config['auth_oauth_facebook_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('/me'), true); + + // Return the unique identifier + return $result['id']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('/me'), true); + + // Return the unique identifier + return $result['id']; + } +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/google.php b/phpBB/phpbb/auth/provider/oauth/service/google.php new file mode 100644 index 0000000000..d4ef6e1d42 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/google.php @@ -0,0 +1,109 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Google OAuth service +* +* @package auth +*/ +class phpbb_auth_provider_oauth_service_google extends phpbb_auth_provider_oauth_service_base +{ + /** + * phpBB config + * + * @var phpbb_config + */ + protected $config; + + /** + * phpBB request + * + * @var phpbb_request + */ + protected $request; + + /** + * Constructor + * + * @param phpbb_config $config + * @param phpbb_request $request + */ + public function __construct(phpbb_config $config, phpbb_request $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_auth_scope() + { + return array( + 'userinfo_email', + 'userinfo_profile', + ); + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_google_key'], + 'secret' => $this->config['auth_oauth_google_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true); + + // Return the unique identifier + return $result['id']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google)) + { + throw new phpbb_auth_provider_oauth_service_exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true); + + // Return the unique identifier + return $result['id']; + } +} diff --git a/phpBB/phpbb/auth/provider/oauth/service/interface.php b/phpBB/phpbb/auth/provider/oauth/service/interface.php new file mode 100644 index 0000000000..3bba7c0e2c --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/service/interface.php @@ -0,0 +1,77 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* OAuth service interface +* +* @package auth +*/ +interface phpbb_auth_provider_oauth_service_interface +{ + /** + * Returns an array of the scopes necessary for auth + * + * @return array An array of the required scopes + */ + public function get_auth_scope(); + + /** + * Returns the external library service provider once it has been set + * + * @param \OAuth\Common\Service\ServiceInterface|null + */ + public function get_external_service_provider(); + + /** + * Returns an array containing the service credentials belonging to requested + * service. + * + * @return array An array containing the 'key' and the 'secret' of the + * service in the form: + * array( + * 'key' => string + * 'secret' => string + * ) + */ + public function get_service_credentials(); + + /** + * Returns the results of the authentication in json format + * + * @throws phpbb_auth_provider_oauth_service_exception + * @return string The unique identifier returned by the service provider + * that is used to authenticate the user with phpBB. + */ + public function perform_auth_login(); + + /** + * Returns the results of the authentication in json format + * Use this function when the user already has an access token + * + * @throws phpbb_auth_provider_oauth_service_exception + * @return string The unique identifier returned by the service provider + * that is used to authenticate the user with phpBB. + */ + public function perform_token_auth(); + + /** + * Sets the external library service provider + * + * @param \OAuth\Common\Service\ServiceInterface $service + */ + public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider); +} diff --git a/phpBB/phpbb/auth/provider/oauth/token_storage.php b/phpBB/phpbb/auth/provider/oauth/token_storage.php new file mode 100644 index 0000000000..d21deb8999 --- /dev/null +++ b/phpBB/phpbb/auth/provider/oauth/token_storage.php @@ -0,0 +1,366 @@ +<?php +/** +* +* @package auth +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + + +use OAuth\OAuth1\Token\StdOAuth1Token; +use OAuth\Common\Token\TokenInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Storage\Exception\StorageException; +use OAuth\Common\Storage\Exception\TokenNotFoundException; + +/** +* OAuth storage wrapper for phpbb's cache +* +* @package auth +*/ +class phpbb_auth_provider_oauth_token_storage implements TokenStorageInterface +{ + /** + * Cache driver. + * + * @var phpbb_db_driver + */ + protected $db; + + /** + * phpBB user + * + * @var phpbb_user + */ + protected $user; + + /** + * OAuth token table + * + * @var string + */ + protected $auth_provider_oauth_table; + + /** + * @var object|TokenInterface + */ + protected $cachedToken; + + /** + * Creates token storage for phpBB. + * + * @param phpbb_db_driver $db + * @param phpbb_user $user + * @param string $auth_provider_oauth_table + */ + public function __construct(phpbb_db_driver $db, phpbb_user $user, $auth_provider_oauth_table) + { + $this->db = $db; + $this->user = $user; + $this->auth_provider_oauth_table = $auth_provider_oauth_table; + } + + /** + * {@inheritdoc} + */ + public function retrieveAccessToken($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken instanceOf TokenInterface) + { + return $this->cachedToken; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return $this->_retrieve_access_token($data); + } + + /** + * {@inheritdoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedToken = $token; + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + 'oauth_token' => $this->json_encode_token($token), + 'session_id' => $this->user->data['session_id'], + ); + + $sql = 'INSERT INTO ' . $this->auth_provider_oauth_table . ' + ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function hasAccessToken($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken) { + return true; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return $this->_has_acess_token($data); + } + + /** + * {@inheritdoc} + */ + public function clearToken($service) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedToken = null; + + $sql = 'DELETE FROM ' . $this->auth_provider_oauth_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . " + AND provider = '" . $this->db->sql_escape($service) . "'"; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function clearAllTokens() + { + $this->cachedToken = null; + + $sql = 'DELETE FROM ' . $this->auth_provider_oauth_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id']; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + } + + /** + * Updates the user_id field in the database assosciated with the token + * + * @param int $user_id + */ + public function set_user_id($user_id) + { + if (!$this->cachedToken) + { + return; + } + + $sql = 'UPDATE ' . $this->auth_provider_oauth_table . ' + SET ' . $this->db->sql_build_array('UPDATE', array( + 'user_id' => (int) $user_id + )) . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . " + AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + $this->db->sql_query($sql); + } + + /** + * Checks to see if an access token exists solely by the session_id of the user + * + * @return bool true if they have token, false if they don't + */ + public function has_access_token_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken) + { + return true; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return $this->_has_acess_token($data); + } + + /** + * A helper function that performs the query for has access token functions + * + * @param array $data + * @return bool + */ + protected function _has_acess_token($data) + { + return (bool) $this->get_access_token_row($data); + } + + public function retrieve_access_token_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken instanceOf TokenInterface) { + return $this->cachedToken; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return $this->_retrieve_access_token($data); + } + + /** + * A helper function that performs the query for retrieve access token functions + * Also checks if the token is a valid token + * + * @param array $data + * @return mixed + */ + protected function _retrieve_access_token($data) + { + $row = $this->get_access_token_row($data); + + if (!$row) + { + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED'); + } + + $token = $this->json_decode_token($row['oauth_token']); + + // Ensure that the token was serialized/unserialized correctly + if (!($token instanceof TokenInterface)) + { + $this->clearToken(); + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); + } + + $this->cachedToken = $token; + return $token; + } + + /** + * A helper function that performs the query for retrieving an access token + * + * @param array $data + * @return mixed + */ + protected function get_access_token_row($data) + { + $sql = 'SELECT oauth_token FROM ' . $this->auth_provider_oauth_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + public function json_encode_token(TokenInterface $token) + { + $members = array( + 'accessToken' => $token->getAccessToken(), + 'endOfLife' => $token->getEndOfLife(), + 'extraParams' => $token->getExtraParams(), + 'refreshToken' => $token->getRefreshToken(), + + 'token_class' => get_class($token), + ); + + // Handle additional data needed for OAuth1 tokens + if ($token instanceof StdOAuth1Token) + { + $members['requestToken'] = $token->getRequestToken(); + $members['requestTokenSecret'] = $token->getRequestTokenSecret(); + $members['accessTokenSecret'] = $token->getAccessTokenSecret(); + } + + return json_encode($members); + } + + public function json_decode_token($json) + { + $token_data = json_decode($json, true); + + if ($token_data === null) + { + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); + } + + $token_class = $token_data['token_class']; + $access_token = $token_data['accessToken']; + $refresh_token = $token_data['refreshToken']; + $endOfLife = $token_data['endOfLife']; + $extra_params = $token_data['extraParams']; + + // Create the token + $token = new $token_class($access_token, $refresh_token, TokenInterface::EOL_NEVER_EXPIRES, $extra_params); + $token->setEndOfLife($endOfLife); + + // Handle OAuth 1.0 specific elements + if ($token instanceof StdOAuth1Token) + { + $token->setRequestToken($token_data['requestToken']); + $token->setRequestTokenSecret($token_data['requestTokenSecret']); + $token->setAccessTokenSecret($token_data['accessTokenSecret']); + } + + return $token; + } + + /** + * Returns the name of the service as it must be stored in the database. + * + * @param string $service The name of the OAuth service + * @return string The name of the OAuth service as it needs to be stored + * in the database. + */ + protected function get_service_name_for_db($service) + { + // Enforce the naming convention for oauth services + if (strpos($service, 'auth.provider.oauth.service.') !== 0) + { + $service = 'auth.provider.oauth.service.' . strtolower($service); + } + + return $service; + } +} diff --git a/phpBB/phpbb/db/migration/data/310/auth_provider_oauth.php b/phpBB/phpbb/db/migration/data/310/auth_provider_oauth.php new file mode 100644 index 0000000000..cad1c16bb2 --- /dev/null +++ b/phpBB/phpbb/db/migration/data/310/auth_provider_oauth.php @@ -0,0 +1,71 @@ +<?php +/** +* +* @package migration +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License v2 +* +*/ + +class phpbb_db_migration_data_310_auth_provider_oauth extends phpbb_db_migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'auth_provider_oauth'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'oauth_tokens' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), // phpbb_users.user_id + 'session_id' => array('CHAR:32', ''), // phpbb_sessions.session_id used only when user_id not set + 'provider' => array('VCHAR', ''), // Name of the OAuth provider + 'oauth_token' => array('MTEXT', ''), // Serialized token + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'provider' => array('INDEX', 'provider'), + ), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'provider' => array('VCHAR', ''), + 'oauth_provider_id' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => array( + 'user_id', + 'provider', + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'oauth_tokens', + $this->table_prefix . 'oauth_accounts', + ), + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'ucp', + 'UCP_PROFILE', + array( + 'module_basename' => 'ucp_auth_link', + 'modes' => array('auth_link'), + ), + )), + ); + } +} diff --git a/phpBB/phpbb/request/interface.php b/phpBB/phpbb/request/interface.php index 741db35917..05f6c54d3f 100644 --- a/phpBB/phpbb/request/interface.php +++ b/phpBB/phpbb/request/interface.php @@ -136,4 +136,14 @@ interface phpbb_request_interface * Pay attention when using these, they are unsanitised! */ public function variable_names($super_global = phpbb_request_interface::REQUEST); + + /** + * Returns the original array of the requested super global + * + * @param phpbb_request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global which will be returned + * + * @return array The original array of the requested super global. + */ + public function get_super_global($super_global = phpbb_request_interface::REQUEST); } diff --git a/phpBB/phpbb/request/request.php b/phpBB/phpbb/request/request.php index ae3c526d89..ed2e8e2200 100644 --- a/phpBB/phpbb/request/request.php +++ b/phpBB/phpbb/request/request.php @@ -412,4 +412,12 @@ class phpbb_request implements phpbb_request_interface return $var; } + + /** + * {@inheritdoc} + */ + public function get_super_global($super_global = phpbb_request_interface::REQUEST) + { + return $this->input[$super_global]; + } } diff --git a/phpBB/phpbb/template/twig/environment.php b/phpBB/phpbb/template/twig/environment.php index b60cd72325..9a40dc2b15 100644 --- a/phpBB/phpbb/template/twig/environment.php +++ b/phpBB/phpbb/template/twig/environment.php @@ -137,4 +137,39 @@ class phpbb_template_twig_environment extends Twig_Environment return parent::loadTemplate($name, $index); } } + + /** + * Finds a template by name. + * + * @param string $name The template name + * @return string + */ + public function findTemplate($name) + { + if (strpos($name, '@') === false) + { + foreach ($this->getNamespaceLookUpOrder() as $namespace) + { + try + { + if ($namespace === '__main__') + { + return parent::getLoader()->getCacheKey($name); + } + + return parent::getLoader()->getCacheKey('@' . $namespace . '/' . $name); + } + catch (Twig_Error_Loader $e) + { + } + } + + // We were unable to load any templates + throw $e; + } + else + { + return parent::getLoader()->getCacheKey($name); + } + } } diff --git a/phpBB/phpbb/template/twig/lexer.php b/phpBB/phpbb/template/twig/lexer.php index 22e2c4ed3e..d47162951d 100644 --- a/phpBB/phpbb/template/twig/lexer.php +++ b/phpBB/phpbb/template/twig/lexer.php @@ -75,7 +75,7 @@ class phpbb_template_twig_lexer extends Twig_Lexer // Fix tokens that may have inline variables (e.g. <!-- DEFINE $TEST = '{FOO}') $code = $this->fix_inline_variable_tokens(array( - 'DEFINE.+=', + 'DEFINE \$[a-zA-Z0-9]+ =', 'INCLUDE', 'INCLUDEPHP', 'INCLUDEJS', @@ -240,7 +240,7 @@ class phpbb_template_twig_lexer extends Twig_Lexer return "<!-- {$matches[1]}IF{$inner}-->"; }; - return preg_replace_callback('#<!-- (ELSE)?IF((.*) \(?!?[\$|\.]([^\s]+)(.*))-->#', $callback, $code); + return preg_replace_callback('#<!-- (ELSE)?IF((.*?) \(?!?[\$|\.]([^\s]+)(.*?))-->#', $callback, $code); } /** @@ -264,10 +264,10 @@ class phpbb_template_twig_lexer extends Twig_Lexer */ // Replace <!-- DEFINE $NAME with {% DEFINE definition.NAME - $code = preg_replace('#<!-- DEFINE \$(.*)-->#', '{% DEFINE $1 %}', $code); + $code = preg_replace('#<!-- DEFINE \$(.*?) -->#', '{% DEFINE $1 %}', $code); // Changing UNDEFINE NAME to DEFINE NAME = null to save from creating an extra token parser/node - $code = preg_replace('#<!-- UNDEFINE \$(.*)-->#', '{% DEFINE $1= null %}', $code); + $code = preg_replace('#<!-- UNDEFINE \$(.*?)-->#', '{% DEFINE $1= null %}', $code); // Replace all of our variables, {$VARNAME}, with Twig style, {{ definition.VARNAME }} $code = preg_replace('#{\$([a-zA-Z0-9_\.]+)}#', '{{ definition.$1 }}', $code); diff --git a/phpBB/phpbb/template/twig/node/includeasset.php b/phpBB/phpbb/template/twig/node/includeasset.php index 1cab416c79..0808e2b10e 100644 --- a/phpBB/phpbb/template/twig/node/includeasset.php +++ b/phpBB/phpbb/template/twig/node/includeasset.php @@ -40,10 +40,10 @@ abstract class phpbb_template_twig_node_includeasset extends Twig_Node ->write("\$local_file = \$this->getEnvironment()->get_phpbb_root_path() . \$asset_path;\n") ->write("if (!file_exists(\$local_file)) {\n") ->indent() - ->write("\$local_file = \$this->getEnvironment()->getLoader()->getCacheKey(\$asset_path);\n") + ->write("\$local_file = \$this->getEnvironment()->findTemplate(\$asset_path);\n") ->write("\$asset->set_path(\$local_file, true);\n") ->outdent() - ->write("\$asset->add_assets_version({$config['assets_version']});\n") + ->write("\$asset->add_assets_version('{$config['assets_version']}');\n") ->write("\$asset_file = \$asset->get_url();\n") ->write("}\n") ->outdent() diff --git a/phpBB/search.php b/phpBB/search.php index 40c0b9a8ce..784e1f6398 100644 --- a/phpBB/search.php +++ b/phpBB/search.php @@ -964,14 +964,8 @@ if ($keywords || $author || $author_id || $search_id || $submit) } else { - // Second parse bbcode here - if ($row['bbcode_bitfield']) - { - $bbcode->bbcode_second_pass($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield']); - } - - $row['post_text'] = bbcode_nl2br($row['post_text']); - $row['post_text'] = smiley_text($row['post_text']); + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $row['post_text'] = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); if (!empty($attachments[$row['post_id']])) { diff --git a/phpBB/styles/prosilver/template/login_body.html b/phpBB/styles/prosilver/template/login_body.html index 89ef8acd6f..a1c2735ca9 100644 --- a/phpBB/styles/prosilver/template/login_body.html +++ b/phpBB/styles/prosilver/template/login_body.html @@ -47,6 +47,10 @@ </dl> </fieldset> </div> + + <!-- IF not S_ADMIN_AUTH and PROVIDER_TEMPLATE_FILE --> + <!-- INCLUDE {PROVIDER_TEMPLATE_FILE} --> + <!-- ENDIF --> </div> </div> diff --git a/phpBB/styles/prosilver/template/login_body_oauth.html b/phpBB/styles/prosilver/template/login_body_oauth.html new file mode 100644 index 0000000000..156485d211 --- /dev/null +++ b/phpBB/styles/prosilver/template/login_body_oauth.html @@ -0,0 +1,8 @@ +<div class="content"> + <!-- BEGIN oauth --> + <dl> + <dt> </dt> + <dd><a href="{oauth.REDIRECT_URL}" class="button2">{oauth.SERVICE_NAME}</a></dd> + </dl> + <!-- END oauth --> +</div> diff --git a/phpBB/styles/prosilver/template/mcp_post.html b/phpBB/styles/prosilver/template/mcp_post.html index 7b2ace6792..a205164e60 100644 --- a/phpBB/styles/prosilver/template/mcp_post.html +++ b/phpBB/styles/prosilver/template/mcp_post.html @@ -13,7 +13,7 @@ <div class="postbody"> <h3>{L_REPORT_REASON}{L_COLON} {REPORT_REASON_TITLE}</h3> <p class="author">{L_REPORTED} {L_POST_BY_AUTHOR} {REPORTER_FULL} « {REPORT_DATE}</p> - <!-- IF not S_POST_REPORTED --> + <!-- IF S_REPORT_CLOSED --> <p class="post-notice reported">{L_REPORT_CLOSED}</p> <!-- ENDIF --> <div class="content"> @@ -31,7 +31,7 @@ <form method="post" id="mcp_report" action="{S_CLOSE_ACTION}"> <fieldset class="submit-buttons"> - <!-- IF S_POST_REPORTED --> + <!-- IF not S_REPORT_CLOSED --> <input class="button1" type="submit" value="{L_CLOSE_REPORT}" name="action[close]" /> <!-- ENDIF --> <input class="button2" type="submit" value="{L_DELETE_REPORT}" name="action[delete]" /> diff --git a/phpBB/styles/prosilver/template/ucp_auth_link.html b/phpBB/styles/prosilver/template/ucp_auth_link.html new file mode 100644 index 0000000000..3c56415db0 --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_auth_link.html @@ -0,0 +1,13 @@ +<!-- INCLUDE ucp_header.html --> + +<h2>{L_UCP_AUTH_LINK_TITLE}</h2> + +<div class="panel"> + <div class="inner"> + <!-- IF ERROR --><dl><dd class="error">{ERROR}</dd></dl><!-- ENDIF --> + + <!-- INCLUDE {PROVIDER_TEMPLATE_FILE} --> + </div> +</div> + +<!-- INCLUDE ucp_footer.html --> diff --git a/phpBB/styles/prosilver/template/ucp_auth_link_oauth.html b/phpBB/styles/prosilver/template/ucp_auth_link_oauth.html new file mode 100644 index 0000000000..a3e27328cd --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_auth_link_oauth.html @@ -0,0 +1,29 @@ +<!-- BEGIN oauth --> + <form id="ucp" method="post" action="{S_UCP_ACTION}"> + <h3>{oauth.SERVICE_NAME}</h3> + + <fieldset class="fields2"> + <!-- IF oauth.UNIQUE_ID --> + <dl> + <dt>{L_UCP_AUTH_LINK_ID}{L_COLON}</dt> + <dd>{oauth.UNIQUE_ID}</dd> + </dl> + <dl> + <dt> </dt> + <dd><input type="submit" name="submit" tabindex="6" value="{L_UCP_AUTH_LINK_UNLINK}" class="button1" /></dd> + </dl> + <!-- ELSE --> + <dl> + <dd>{L_UCP_AUTH_LINK_ASK}</dd> + </dl> + <dl> + <dt> </dt> + <dd><input type="submit" name="submit" tabindex="6" value="{L_UCP_AUTH_LINK_LINK}" class="button1" /></dd> + </dl> + <!-- ENDIF--> + </fieldset> + {oauth.HIDDEN_FIELDS} + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + </form> +<!-- END oauth --> diff --git a/phpBB/styles/prosilver/template/ucp_login_link.html b/phpBB/styles/prosilver/template/ucp_login_link.html new file mode 100644 index 0000000000..d3c6931ce3 --- /dev/null +++ b/phpBB/styles/prosilver/template/ucp_login_link.html @@ -0,0 +1,58 @@ +<!-- INCLUDE overall_header.html --> + +<div class="panel"> + <div class="inner"> + + <h2>{SITENAME} - {L_LOGIN_LINK}</h2> + + <p>{L_LOGIN_LINK_EXPLAIN}</p> + + <!-- IF LOGIN_LINK_ERROR --><div class="content"> + <div class="error">{LOGIN_LINK_ERROR}</div> + </div><!-- ENDIF --> + + <div class="content"> + <h2>{L_REGISTER}</h2> + + <form action="{REGISTER_ACTION}" method="post" id="register"> + <fieldset class="fields1"> + <dl> + <dt> </dt> + <dd>{S_HIDDEN_FIELDS}<input type="submit" name="register" tabindex="1" value="{L_REGISTER}" class="button1" /></dd> + </dl> + </fieldset> + </form> + </div> + + <div class="content"> + <h2>{L_LOGIN}</h2> + + <form action="{LOGIN_ACTION}" method="post" id="login"> + <fieldset class="fields1"> + <!-- IF LOGIN_ERROR --><div class="error">{LOGIN_ERROR}</div><!-- ENDIF --> + <dl> + <dt><label for="{USERNAME_CREDENTIAL}">{L_USERNAME}{L_COLON}</label></dt> + <dd><input type="text" tabindex="2" name="{USERNAME_CREDENTIAL}" id="{USERNAME_CREDENTIAL}" size="25" value="{LOGIN_USERNAME}" class="inputbox autowidth" /></dd> + </dl> + <dl> + <dt><label for="{PASSWORD_CREDENTIAL}">{L_PASSWORD}{L_COLON}</label></dt> + <dd><input type="password" tabindex="3" id="{PASSWORD_CREDENTIAL}" name="{PASSWORD_CREDENTIAL}" size="25" class="inputbox autowidth" /></dd> + </dl> + <!-- IF CAPTCHA_TEMPLATE and S_CONFIRM_CODE --> + <!-- DEFINE $CAPTCHA_TAB_INDEX = 4 --> + <!-- INCLUDE {CAPTCHA_TEMPLATE} --> + <!-- ENDIF --> + + {S_LOGIN_REDIRECT} + <dl> + <dt> </dt> + <dd>{S_HIDDEN_FIELDS}<input type="submit" name="login" tabindex="5" value="{L_LOGIN}" class="button1" /></dd> + </dl> + </fieldset> + </form> + </div> + + </div> +</div> + +<!-- INCLUDE overall_footer.html --> diff --git a/phpBB/styles/subsilver2/template/login_body.html b/phpBB/styles/subsilver2/template/login_body.html index 2b895dda89..ed63e748cf 100644 --- a/phpBB/styles/subsilver2/template/login_body.html +++ b/phpBB/styles/subsilver2/template/login_body.html @@ -61,6 +61,9 @@ <td><input type="checkbox" class="radio" name="viewonline" tabindex="4" /> <span class="gensmall">{L_HIDE_ME}</span></td> </tr> <!-- ENDIF --> + <!-- IF not S_ADMIN_AUTH and PROVIDER_TEMPLATE_FILE --> + <!-- INCLUDE {PROVIDER_TEMPLATE_FILE} --> + <!-- ENDIF --> </table> </td> </tr> diff --git a/phpBB/styles/subsilver2/template/login_body_oauth.html b/phpBB/styles/subsilver2/template/login_body_oauth.html new file mode 100644 index 0000000000..6f374fa4f2 --- /dev/null +++ b/phpBB/styles/subsilver2/template/login_body_oauth.html @@ -0,0 +1,7 @@ +<!-- BEGIN oauth --> + <tr> + <td> + <a href="{oauth.REDIRECT_URL}">{oauth.SERVICE_NAME}</a> + </td> + </tr> +<!-- END oauth --> diff --git a/phpBB/styles/subsilver2/template/mcp_post.html b/phpBB/styles/subsilver2/template/mcp_post.html index a3ebb00d06..0f000ca931 100644 --- a/phpBB/styles/subsilver2/template/mcp_post.html +++ b/phpBB/styles/subsilver2/template/mcp_post.html @@ -28,7 +28,7 @@ </tr> <!-- ENDIF --> <tr> - <td class="cat" align="center" colspan="2"><!-- IF S_POST_REPORTED --><input class="btnmain" type="submit" value="{L_CLOSE_REPORT}" name="action[close]" /><!-- ELSE -->{L_REPORT_CLOSED}<!-- ENDIF --> <input class="btnlite" type="submit" value="{L_DELETE_REPORT}" name="action[delete]" /></td> + <td class="cat" align="center" colspan="2"><!-- IF not S_REPORT_CLOSED --><input class="btnmain" type="submit" value="{L_CLOSE_REPORT}" name="action[close]" /><!-- ELSE -->{L_REPORT_CLOSED}<!-- ENDIF --> <input class="btnlite" type="submit" value="{L_DELETE_REPORT}" name="action[delete]" /></td> </tr> </table> diff --git a/phpBB/styles/subsilver2/template/ucp_auth_link.html b/phpBB/styles/subsilver2/template/ucp_auth_link.html new file mode 100644 index 0000000000..c6e4ddd250 --- /dev/null +++ b/phpBB/styles/subsilver2/template/ucp_auth_link.html @@ -0,0 +1,17 @@ +<!-- INCLUDE ucp_header.html --> + +<table class="tablebg" width="100%" cellspacing="1"> + <tr> + <th colspan="4">{L_UCP_AUTH_LINK_TITLE}</th> + </tr> + + <!-- IF ERROR --> + <tr> + <td class="row1" colspan="2" align="center"><span class="genmed error">{ERROR}</span></td> + </tr> + <!-- ENDIF --> + + <!-- INCLUDE {PROVIDER_TEMPLATE_FILE} --> +</table> + +<!-- INCLUDE ucp_footer.html --> diff --git a/phpBB/styles/subsilver2/template/ucp_auth_link_oauth.html b/phpBB/styles/subsilver2/template/ucp_auth_link_oauth.html new file mode 100644 index 0000000000..56a4c89125 --- /dev/null +++ b/phpBB/styles/subsilver2/template/ucp_auth_link_oauth.html @@ -0,0 +1,34 @@ +<!-- BEGIN oauth --> + <tr> + <th>{oauth.SERVICE_NAME}</th> + </tr> + + <tr> + <td class="row1"> + <form id="ucp" method="post" action="{S_UCP_ACTION}"> + <table> + <!-- IF oauth.UNIQUE_ID --> + <tr> + <td class="row1">{L_UCP_AUTH_LINK_ID}{L_COLON}</td> + <td class="row1">{oauth.UNIQUE_ID}</td> + </tr> + <tr> + <td class="row1"> </td> + <td class="row1"><input type="submit" name="submit" tabindex="6" value="{L_UCP_AUTH_LINK_UNLINK}" class="button1" /></td> + </tr> + <!-- ELSE --> + <tr> + <td class="row1">{L_UCP_AUTH_LINK_ASK}</td> + </tr> + <tr> + <td class="row1"><input type="submit" name="submit" tabindex="6" value="{L_UCP_AUTH_LINK_LINK}" class="button1" /></td> + </tr> + <!-- ENDIF--> + </table> + {oauth.HIDDEN_FIELDS} + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + </form> + </td> + </tr> +<!-- END oauth --> diff --git a/phpBB/styles/subsilver2/template/ucp_login_link.html b/phpBB/styles/subsilver2/template/ucp_login_link.html new file mode 100644 index 0000000000..5d8e3ee27b --- /dev/null +++ b/phpBB/styles/subsilver2/template/ucp_login_link.html @@ -0,0 +1,74 @@ +<!-- INCLUDE overall_header.html --> + +<table class="tablebg" width="100%" cellspacing="1"> + <tr> + <th>{SITENAME} - {L_LOGIN_LINK}</th> + </tr> + + <tr> + <td class="row1" align="center"><span class="genmed">{L_LOGIN_LINK_EXPLAIN}</span></td> + </tr> + + <!-- IF LOGIN_LINK_ERROR --> + <tr> + <td class="row1" align="center"><span class="genmed error">{LOGIN_LINK_ERROR}</span></td> + </tr> + <!-- ENDIF --> + + <tr> + <td class="row1"> + <form action="{REGISTER_ACTION}" method="post" id="register"> + <table width="100%" cellspacing="1"> + <tr> + <th colspan="2">{L_REGISTER}</th> + </tr> + + <tr> + <td>{S_HIDDEN_FIELDS}<input type="submit" name="register" tabindex="1" value="{L_REGISTER}" class="button1" /></td> + </tr> + </table> + </form> + </td> + </tr> + + <tr> + <td class="row1"> + <form action="{LOGIN_ACTION}" method="post" id="login"> + <table width="100%" cellspacing="1"> + <tr> + <th colspan="2">{L_LOGIN}</th> + </tr> + + <!-- IF LOGIN_ERROR --> + <tr> + <td class="row1" align="center" colspan="2"><span class="genmed error">{LOGIN_ERROR}</span></td> + </tr> + <!-- ENDIF --> + + <tr> + <td><label for="{USERNAME_CREDENTIAL}">{L_USERNAME}{L_COLON}</label></td> + <td><input type="text" tabindex="2" name="{USERNAME_CREDENTIAL}" id="{USERNAME_CREDENTIAL}" size="25" value="{LOGIN_USERNAME}" class="inputbox autowidth" /></td> + </tr> + + <tr> + <td><label for="{PASSWORD_CREDENTIAL}">{L_PASSWORD}{L_COLON}</label></td> + <td><input type="password" tabindex="3" id="{PASSWORD_CREDENTIAL}" name="{PASSWORD_CREDENTIAL}" size="25" class="inputbox autowidth" /></td> + </tr> + + <!-- IF CAPTCHA_TEMPLATE and S_CONFIRM_CODE --> + <!-- DEFINE $CAPTCHA_TAB_INDEX = 4 --> + <!-- INCLUDE {CAPTCHA_TEMPLATE} --> + <!-- ENDIF --> + + {S_LOGIN_REDIRECT} + <tr> + <td> </td> + <td>{S_HIDDEN_FIELDS}<input type="submit" name="login" tabindex="5" value="{L_LOGIN}" class="button1" /></td> + </tr> + </table> + </form> + </td> + </tr> +</table> + +<!-- INCLUDE overall_footer.html --> diff --git a/phpBB/ucp.php b/phpBB/ucp.php index 7180c54de6..f1f8f2a829 100644 --- a/phpBB/ucp.php +++ b/phpBB/ucp.php @@ -21,7 +21,7 @@ require($phpbb_root_path . 'includes/functions_module.' . $phpEx); $id = request_var('i', ''); $mode = request_var('mode', ''); -if (in_array($mode, array('login', 'logout', 'confirm', 'sendpassword', 'activate'))) +if (in_array($mode, array('login', 'login_link', 'logout', 'confirm', 'sendpassword', 'activate'))) { define('IN_LOGIN', true); } @@ -80,6 +80,16 @@ switch ($mode) login_box(request_var('redirect', "index.$phpEx")); break; + case 'login_link': + if ($user->data['is_registered']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + $module->load('ucp', 'login_link'); + $module->display($user->lang['UCP_LOGIN_LINK']); + break; + case 'logout': if ($user->data['user_id'] != ANONYMOUS && $request->is_set('sid') && $request->variable('sid', '') === $user->session_id) { diff --git a/phpBB/viewtopic.php b/phpBB/viewtopic.php index bc54a249a9..af2056fdeb 100644 --- a/phpBB/viewtopic.php +++ b/phpBB/viewtopic.php @@ -1012,7 +1012,7 @@ while ($row = $db->sql_fetchrow($result)) } } - $rowset[$row['post_id']] = array( + $rowset_data = array( 'hide_post' => (($row['foe'] || $row['post_visibility'] == ITEM_DELETED) && ($view != 'show' || $post_id != $row['post_id'])) ? true : false, 'post_id' => $row['post_id'], @@ -1047,6 +1047,19 @@ while ($row = $db->sql_fetchrow($result)) 'foe' => $row['foe'], ); + /** + * Modify the post rowset containing data to be displayed with posts + * + * @event core.viewtopic_post_rowset_data + * @var array rowset_data Array with the rowset data for this post + * @var array row Array with original user and post data + * @since 3.1-A1 + */ + $vars = array('rowset_data', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_rowset_data', compact($vars))); + + $rowset[$row['post_id']] = $rowset_data; + // Define the global bbcode bitfield, will be used to load bbcodes $bbcode_bitfield = $bbcode_bitfield | base64_decode($row['bbcode_bitfield']); diff --git a/tests/auth/fixtures/oauth_tokens.xml b/tests/auth/fixtures/oauth_tokens.xml new file mode 100644 index 0000000000..9bfb5a4422 --- /dev/null +++ b/tests/auth/fixtures/oauth_tokens.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<dataset> + <table name="phpbb_oauth_tokens"> + <column>user_id</column> + <column>session_id</column> + <column>provider</column> + <column>oauth_token</column> + </table> +</dataset> + diff --git a/tests/auth/provider_oauth_token_storage_test.php b/tests/auth/provider_oauth_token_storage_test.php new file mode 100644 index 0000000000..401f049405 --- /dev/null +++ b/tests/auth/provider_oauth_token_storage_test.php @@ -0,0 +1,207 @@ +<?php +/** +* +* @package testing +* @copyright (c) 2013 phpBB Group +* @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2 +* +*/ + +use OAuth\OAuth2\Token\StdOAuth2Token; + +class phpbb_auth_provider_oauth_token_storage_test extends phpbb_database_test_case +{ + protected $db; + protected $service_name; + protected $session_id; + protected $token_storage; + protected $token_storage_table; + protected $user; + + protected function setup() + { + parent::setUp(); + + global $phpbb_root_path, $phpEx; + + $this->db = $this->new_dbal(); + $this->user = $this->getMock('phpbb_user'); + $this->service_name = 'auth.provider.oauth.service.testing'; + $this->token_storage_table = 'phpbb_oauth_tokens'; + + // Give the user a session_id that we will remember + $this->session_id = '12345'; + $this->user->data['session_id'] = $this->session_id; + + // Set the user id to anonymous + $this->user->data['user_id'] = ANONYMOUS; + + $this->token_storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->token_storage_table); + } + + public function getDataSet() + { + return $this->createXMLDataSet(dirname(__FILE__).'/fixtures/oauth_tokens.xml'); + } + + public static function retrieveAccessToken_data() + { + return array( + array(new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES, array('extra' => 'param')), null), + array(null, 'OAuth\Common\Storage\Exception\TokenNotFoundException'), + ); + } + + /** + * @dataProvider retrieveAccessToken_data + */ + public function test_retrieveAccessToken($cache_token, $exception) + { + if ($cache_token) + { + $this->token_storage->storeAccessToken($this->service_name, $cache_token); + $token = $cache_token; + } + + $this->setExpectedException($exception); + + $stored_token = $this->token_storage->retrieveAccessToken($this->service_name); + $this->assertEquals($token, $stored_token); + } + + public function test_retrieveAccessToken_from_db() + { + $expected_token = new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES); + + // Store a token in the database + $temp_storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->token_storage_table); + $temp_storage->storeAccessToken($this->service_name, $expected_token); + unset($temp_storage); + + // Test to see if the token can be retrieved + $stored_token = $this->token_storage->retrieveAccessToken($this->service_name); + $this->assertEquals($expected_token, $stored_token); + } + + /** + * @dataProvider retrieveAccessToken_data + */ + public function test_retrieve_access_token_by_session($cache_token, $exception) + { + if ($cache_token) + { + $this->token_storage->storeAccessToken($this->service_name, $cache_token); + $token = $cache_token; + } + + $this->setExpectedException($exception); + + $stored_token = $this->token_storage->retrieve_access_token_by_session($this->service_name); + $this->assertEquals($token, $stored_token); + } + + public function test_retrieve_access_token_by_session_from_db() + { + $expected_token = new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES); + + // Store a token in the database + $temp_storage = new phpbb_auth_provider_oauth_token_storage($this->db, $this->user, $this->token_storage_table); + $temp_storage->storeAccessToken($this->service_name, $expected_token); + unset($temp_storage); + + // Test to see if the token can be retrieved + $stored_token = $this->token_storage->retrieve_access_token_by_session($this->service_name); + $this->assertEquals($expected_token, $stored_token); + } + + public function test_storeAccessToken() + { + $token = new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES, array('extra' => 'param') ); + $this->token_storage->storeAccessToken($this->service_name, $token); + + // Confirm that the token is cached + $extraParams = $this->token_storage->retrieveAccessToken($this->service_name)->getExtraParams(); + $this->assertEquals( 'param', $extraParams['extra'] ); + $this->assertEquals( 'access', $this->token_storage->retrieveAccessToken($this->service_name)->getAccessToken() ); + + $row = $this->get_token_row_by_session_id($this->session_id); + + // The token is serialized before stored in the database + $this->assertEquals($this->token_storage->json_encode_token($token), $row['oauth_token']); + } + + public static function hasAccessToken_data() + { + return array( + array(null, false), + array(new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES, array('extra' => 'param') ), true), + ); + } + + /** + * @dataProvider hasAccessToken_data + */ + public function test_hasAccessToken($token, $expected) + { + if ($token) + { + $this->token_storage->storeAccessToken($this->service_name, $token); + } + + $has_access_token = $this->token_storage->hasAccessToken($this->service_name); + $this->assertEquals($expected, $has_access_token); + } + + /** + * @dataProvider hasAccessToken_data + */ + public function test_has_access_token_by_session($token, $expected) + { + if ($token) + { + $this->token_storage->storeAccessToken($this->service_name, $token); + } + + $has_access_token = $this->token_storage->has_access_token_by_session($this->service_name); + $this->assertEquals($expected, $has_access_token); + } + + public function test_clearToken() + { + $token = new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES, array('extra' => 'param') ); + $this->token_storage->storeAccessToken($this->service_name, $token); + + $this->token_storage->clearToken($this->service_name); + + // Check that the database has been cleared + $row = $this->get_token_row_by_session_id($this->session_id); + $this->assertFalse($row); + + // Check that the token is no longer in memory + $this->assertFalse($this->token_storage->hasAccessToken($this->service_name)); + } + + public function test_set_user_id() + { + $token = new StdOAuth2Token('access', 'refresh', StdOAuth2Token::EOL_NEVER_EXPIRES, array('extra' => 'param') ); + $this->token_storage->storeAccessToken($this->service_name, $token); + + $new_user_id = ANONYMOUS + 1; + $this->token_storage->set_user_id($new_user_id); + + $row = $this->get_token_row_by_session_id($this->session_id); + $this->assertEquals($new_user_id, $row['user_id']); + } + + protected function get_token_row_by_session_id($session_id) + { + // Test that the token is stored in the database + $sql = 'SELECT * FROM phpbb_oauth_tokens + WHERE session_id = \'' . $this->db->sql_escape($session_id) . '\''; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } +} diff --git a/tests/mock/request.php b/tests/mock/request.php index 2a272fc03b..d671621460 100644 --- a/tests/mock/request.php +++ b/tests/mock/request.php @@ -74,6 +74,11 @@ class phpbb_mock_request implements phpbb_request_interface return array_keys($this->data[$super_global]); } + public function get_super_global($super_global = phpbb_request_interface::REQUEST) + { + return $this->data[$super_global]; + } + /* custom methods */ public function set_header($header_name, $value) diff --git a/tests/template/template_test.php b/tests/template/template_test.php index bd5d997c09..1649604492 100644 --- a/tests/template/template_test.php +++ b/tests/template/template_test.php @@ -158,7 +158,7 @@ class phpbb_template_template_test extends phpbb_template_template_test_case array(), array('test_loop' => array(array(), array(), array(), array(), array(), array(), array()), 'test' => array(array()), 'test.deep' => array(array()), 'test.deep.defines' => array(array())), array(), - "xyz\nabc\n\$VALUE == 'abc'\n(\$VALUE == 'abc')\n!\$DOESNT_EXIST\n(!\$DOESNT_EXIST)\nabc\nbar\nbar\nabc\ntest!@#$%^&*()_-=+{}[]:;\",<.>/?[]", + "xyz\nabc\n\$VALUE == 'abc'\n(\$VALUE == 'abc')\n!\$DOESNT_EXIST\n(!\$DOESNT_EXIST)\nabc\nbar\nbar\nabc\ntest!@#$%^&*()_-=+{}[]:;\",<.>/?[]|foobar|", ), array( 'define_advanced.html', diff --git a/tests/template/templates/define.html b/tests/template/templates/define.html index 8405ea55fb..bc20c02ed1 100644 --- a/tests/template/templates/define.html +++ b/tests/template/templates/define.html @@ -25,3 +25,4 @@ $VALUE == 'abc' {$VALUE} <!-- DEFINE $VALUE = '' --> [{$VALUE}] +<!-- DEFINE $TEST -->foobar<!-- ENDDEFINE -->|{$TEST}| |