diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3c86cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Created by .ignore support plugin (hsz.mobi) +### OSX template +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### Vagrant template +.vagrant/ +### Vim template +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags +### LibreOffice template +# LibreOffice locks +.~lock.*# +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + diff --git a/Classes/Controller/SsoController.php b/Classes/Controller/SsoController.php index 46d37af..c46dae0 100644 --- a/Classes/Controller/SsoController.php +++ b/Classes/Controller/SsoController.php @@ -26,70 +26,132 @@ namespace Jahnke\DiscourseSso\Controller; ***************************************************************/ use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; +use \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility; /** * Controller for the Member object - * - * @version $Id$ - * @copyright Copyright belongs to the respective authors - * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3 or later */ -class SsoController extends \TYPO3\CMS\Scheduler\Task\AbstractTask +class SsoController extends ActionController { - private function hashs_are_equal($data, $sig) { - if (!$data || !$sig || !is_string($data) || !is_string($sig)) - return false; - if (strlen($data) != strlen($sig)) - return false; - if (strcmp($data, $sig) === 0) - return true; - return false; - } + /** + * Compare if signed data matches given signature. + * + * @param string $data Signed data to be compared with. + * @param string $sig Signature to be compared with. + * + * @return boolean */ - public function authenticateAction() { - $user = NULL; - if (isset($GLOBALS['TSFE']) && isset($GLOBALS['TSFE']->fe_user) && isset($GLOBALS['TSFE']->fe_user->user)) { - $user = $GLOBALS['TSFE']->fe_user->user; + private function _hashsAreEqual($data, $sig) + { + if ($data === null || $sig === null || is_string($data) === false || is_string($sig) === false) { + return false; } - if (isset($user)) { - $sso = urldecode(GeneralUtility::_GP('sso')); - $sig = GeneralUtility::_GP('sig'); - $userId = $user['uid']; - $userEmail = $user['email']; - $userName = $user['username']; - $name = $user['name']; + if (strlen($data) !== strlen($sig)) { + return false; + } - if (!$this->hashs_are_equal(hash_hmac('sha256', $sso, $this->settings['discourse_sso_shared_key']), $sig)) { - header("HTTP/1.1 403 Forbidden"); - $this->throwStatus(403, "Bad SSO request"); - } else { - // valid $sso string available, convert it - parse_str(base64_decode($sso), $receivedPayload); - $nonce = $receivedPayload['nonce']; - $parameters = array( - 'nonce' => $nonce, - 'external_id' => $userId, - 'email' => $userEmail, - 'username' => $userName, - 'name' => $name - ); - $payload = base64_encode(http_build_query($parameters)); - $query = http_build_query(array('sso' => $payload, 'sig' => hash_hmac('sha256', $payload, $this->settings['discourse_sso_shared_key']))); - $statusCode = $this->settings['discourse_sso_redirect_statuscode']; - if (!$statusCode || ($statusCode < 300 || $statusCode > 308)) { - // set default: - $statusCode = 303; - } - $this->redirectToUri($this->settings['discourse_sso_redirect'] . '/session/sso_login?' . $query, 0, 302); - } + if (strcmp($data, $sig) === 0) { + return true; + } + + return false; + + }//end _hashsAreEqual() + + + /** + * Authenticate action. + * + * @return string + */ + public function authenticateAction() + { + /** @var \TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility $configurationUtility */ + $configurationUtility = $this->objectManager->get(\TYPO3\CMS\Extensionmanager\Utility\ConfigurationUtility::class); + $extensionConfiguration = $configurationUtility->getCurrentConfiguration('tx_dj_discourse_sso'); + + // Check mandatory settings. + if (isset($extensionConfiguration['redirect_url']) === false) { + $errorText = '
ERROR! ' + .'You should not see this message!
' + .'Could not find typoscript setting plugins.tx_dj_discourse_sso.redirect_url! ' + .'Please configure the plugin.'; + return $errorText; } else { - // no user logged in - // wrong setup! This plugin should be enabled only, if a user login exists - return "
ERROR! You should not see this message! This plugin should be made available only, if a Frontend User is logged in! Please change this in the setup of this content element."; + $redirectUrlRoot = $extensionConfiguration['redirect_url']; } - } + + if (isset($extensionConfiguration['shared_key']) === false) { + $errorText = '
ERROR! ' + .'You should not see this message!
' + .'Could not find typoscript setting plugins.tx_dj_discourse_sso.shared_key! ' + .'Please configure the plugin.'; + return $errorText; + } else { + $sharedKey = $extensionConfiguration['shared_key']; + } + + // Set some defaults. + if (isset($extensionConfiguration['redirect_status']) === true) { + $redirectStatus = $extensionConfiguration['redirect_status']; + } + + if ($redirectStatus === false || ($redirectStatus < 300 || $redirectStatus > 308)) { + // Set default. + $redirectStatus = 303; + } + + $hmac = hash_hmac('sha256', $sso, $this->settings['discourse_sso_shared_key']); + if ($this->_hashsAreEqual($hmac, $sig) === false) { + header('HTTP/1.1 403 Forbidden'); + $this->throwStatus(403, 'Bad SSO request'); + } else { + // Valid $sso string available, convert it. + parse_str(base64_decode($sso), $receivedPayload); + $user = null; + if (isset($GLOBALS['TSFE']) === true + && isset($GLOBALS['TSFE']->fe_user) === true + && isset($GLOBALS['TSFE']->fe_user->user) === true + ) { + $user = $GLOBALS['TSFE']->fe_user->user; + } + + if (isset($user) === true) { + $sso = urldecode(GeneralUtility::_GP('sso')); + $sig = GeneralUtility::_GP('sig'); + $userId = $user['uid']; + $userEmail = $user['email']; + $userName = $user['username']; + $name = $user['name']; + $nonce = $receivedPayload['nonce']; + $parameters = array( + 'nonce' => $nonce, + 'external_id' => $userId, + 'email' => $userEmail, + 'username' => $userName, + 'name' => $name, + ); + $payload = base64_encode(http_build_query($parameters)); + $signature = hash_hmac('sha256', $payload, $this->settings['discourse_sso_shared_key']); + $query = http_build_query(array('sso' => $payload, 'sig' => $signature)); + $redirectUrl = $redirectUrlRoot.'/session/sso_login?'.$query; + $this->redirectToUri($redirectUrl, 0, $redirectStatus); + } else { + // No user logged in. + // Wrong setup! This plugin should be enabled only, if a user login exists. + $errorText = '
ERROR! ' + .'You should not see this message!
' + .'This plugin should be made available only, if a Frontend User is logged in.
' + .'Please change this in the setup of this content element.'; + return $errorText; + }//end if + }//end if + + }//end authenticateAction() + + } -?> diff --git a/Configuration/TypoScript/setup.txt b/Configuration/TypoScript/setup.txt deleted file mode 100644 index cf26648..0000000 --- a/Configuration/TypoScript/setup.txt +++ /dev/null @@ -1,8 +0,0 @@ -plugin.tx_discoursesso { - settings { - # Discourse SSO - # discourse_sso_shared_key = MyFavKey - # discourse_sso_redirect = https://my.discourse.site - # discourse_sso_redirect = 302 - } -} diff --git a/ext_conf_template.txt b/ext_conf_template.txt new file mode 100644 index 0000000..9a3e9b2 --- /dev/null +++ b/ext_conf_template.txt @@ -0,0 +1,8 @@ +# cat=basic/links; type=string; label=Redirect URL:URL to the discourse instance e.g. https://my.discourse.net +redirect_url = + +# cat=basic/enable; type=string; label=Shared Key:The shared key as entered in the discourse configuration +shared_key = + +# cat=basic/enable; type=int [300-399]; label=Http Status:The SSO status to be sent back on successful authentication. Typically do not change this value. +redirect_status = 303 \ No newline at end of file diff --git a/ext_localconf.php b/ext_localconf.php index 96549e5..01abfb2 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,19 +1,13 @@ 'authenticate', - ), - array( - 'Sso' => 'authenticate', - ), - \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN + 'Jahnke.'.$_EXTKEY, + 'Sso', + array('Sso' => 'authenticate'), + array('Sso' => 'authenticate'), + \TYPO3\CMS\Extbase\Utility\ExtensionUtility::PLUGIN_TYPE_PLUGIN ); - -?> diff --git a/ext_tables.php b/ext_tables.php index ad0cbd4..54efc69 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -1,11 +1,8 @@