From 0abee6c0f095df6222e3e15c41806e9d2dbe5d43 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 23 Nov 2018 15:45:01 +0000 Subject: [PATCH] SSO + Chwala, and some improvements Requires: https://github.com/roundcube/roundcubemail/commit/edd9c31d6c69f4f https://git.kolab.org/rC76f9b695c55cb118e317d760965c6e23b44dff86 --- .../kolab_files/lib/kolab_files_engine.php | 4 + plugins/kolab_sso/README | 31 +++++-- plugins/kolab_sso/kolab_sso.php | 91 +++++++++++++------ 3 files changed, 89 insertions(+), 37 deletions(-) diff --git a/plugins/kolab_files/lib/kolab_files_engine.php b/plugins/kolab_files/lib/kolab_files_engine.php index d88ab97f..ede38542 100644 --- a/plugins/kolab_files/lib/kolab_files_engine.php +++ b/plugins/kolab_files/lib/kolab_files_engine.php @@ -999,6 +999,10 @@ class kolab_files_engine $url->setQueryVariables(array('method' => 'authenticate', 'version' => self::API_VERSION)); $request->setUrl($url); $request->setAuth($this->rc->user->get_username(), $this->rc->decrypt($_SESSION['password'])); + + // Allow plugins (e.g. kolab_sso) to modify the request + $this->rc->plugins->exec_hook('chwala_authenticate', array('request' => $request)); + $response = $request->send(); $status = $response->getStatus(); diff --git a/plugins/kolab_sso/README b/plugins/kolab_sso/README index 88ec94dc..3f0ad560 100644 --- a/plugins/kolab_sso/README +++ b/plugins/kolab_sso/README @@ -5,10 +5,8 @@ This plugin adds possibility to authenticate users via external authentication services. Currently the only supported method of authentication is OpenID Connect. Because Kolab backends do not support token authentication it is required -to use master user (sasl proxy) authentication, i.e. you have to put -master user credentials in plugin's config. For the same reason and also because -the same master user does not work in Postfix, you have to specify SMTP connection -parameters/user+password. +to use master user (sasl proxy) authentication where possible and service +user credentials in other places. See IMPLEMENTATION NOTES below for details. Plugin requires libkolab plugin and HTTP/Request2 library. Plugin contains BSD-licensed https://github.com/firebase/php-jwt (v5.0.0) library. @@ -36,13 +34,32 @@ For the above "alias" plugin configuration should include 'response_uri' = '/sso and on the provider side configured URI will be https://host/roundcubemail/sso. +IMPLEMENTATION NOTES +-------------------- + +[IMAP] Because Kolab backend do not support token authentication it is required +to use master user (sasl proxy) authentication, i.e. you have to put +master user credentials in plugin's config. + +[SMTP] For the same reason and also because the same master user does not work in Postfix, +you have to specify SMTP connection parameters/user+password. + +[Freebusy] Authentication into kolab-freebusy service is not yet implemented. A solution for +now is to add Roundcube host(s) into trustednetworks.allow option in kolab-freebusy service config. + +[Chwala] Authentication to Chwala will work if it uses the same (session) database as Roundcube. +Additionally set $config['fileapi_plugins'] = array('kolab_sso', 'kolab_auth', 'kolab_folders'); +Authenticating to Seafile/WebDAV storage is not supported (until it's custom user-defined +storage with saved password). + + TODO ---- -- SMTP auth - LDAP addressbook - kolab_delegation (LDAP auth) -- Chwala auth (+Seafile, +WebDAV) +- Chwala+Seafile +- Chwala+WebDAV - Freebusy auth - Mattermost auth -- Improved token validation +- Extended token validation diff --git a/plugins/kolab_sso/kolab_sso.php b/plugins/kolab_sso/kolab_sso.php index f07d7ddc..22ed5ac8 100644 --- a/plugins/kolab_sso/kolab_sso.php +++ b/plugins/kolab_sso/kolab_sso.php @@ -34,17 +34,15 @@ class kolab_sso extends rcube_plugin */ public function init() { - $this->rc = rcube::get_instance(); + // Roundcube or Chwala + if (defined('RCMAIL_VERSION') || defined('FILE_API_START')) { + $this->rc = rcube::get_instance(); - // Disable the plugin in Syncroton, iRony, etc. - if (!($this->rc instanceof rcmail)) { - return; + $this->add_hook('startup', array($this, 'startup')); + $this->add_hook('authenticate', array($this, 'authenticate')); + + $this->rc->add_shutdown_function(array($this, 'shutdown')); } - - $this->add_hook('startup', array($this, 'startup')); - $this->add_hook('ready', array($this, 'ready')); - $this->add_hook('login_after', array($this, 'ready')); - $this->add_hook('authenticate', array($this, 'authenticate')); } /** @@ -116,7 +114,41 @@ class kolab_sso extends rcube_plugin */ public function authenticate($args, $internal = false) { - if (!empty($this->data) && ($email = $this->data['email'])) { + // Chwala + if (defined('FILE_API_START') && !$internal && empty($args['pass']) && strpos($args['user'], 'RC:') === 0) { + // extract session ID and username from the token + list(, $sess_id, $user) = explode(':', $args['user']); + + // unset user, set invalid state + $args['valid'] = false; + $args['user'] = null; + + $session = rcube_session::factory($this->rc->config); + + if ($data = $session->read($sess_id)) { + // get SSO data from the existing session + $old_session = $_SESSION; + session_decode($data); + $session_user = $_SESSION['username']; + $data = $_SESSION['sso_data']; + $_SESSION = $old_session; + + // TODO: allow only configured REMOTE_ADDR? + if ($session_user == $user && $data && ($data = json_decode($this->rc->decrypt($data), true)) && ($mode = $data['mode'])) { + $driver = $this->get_driver($mode); + + // Session validation, token refresh, etc. + if ($this->data = $driver->validate_session($data)) { + $args['user'] = $user; + $args['pass'] = 'fake-sso-password'; + $args['valid'] = true; + $this->authenticate(array(), true); + } + } + } + } + // Roundcube + else if (!empty($this->data) && ($email = $this->data['email'])) { if (!$internal) { $args['user'] = $email; $args['pass'] = 'fake-sso-password'; @@ -130,38 +162,22 @@ class kolab_sso extends rcube_plugin $this->add_hook('storage_connect', array($this, 'storage_connect')); $this->add_hook('managesieve_connect', array($this, 'storage_connect')); $this->add_hook('smtp_connect', array($this, 'smtp_connect')); + $this->add_hook('chwala_authenticate', array($this, 'chwala_authenticate')); } return $args; } /** - * Login_after hook handler + * Shutdown handler */ - public function login_after($args) - { - // Between startup and authenticate the session is destroyed. - // So, we save the data later than that. Here, because of - // a redirect after successful login - if (!empty($this->data)) { - $_SESSION['sso_data'] = $this->rc->encrypt(json_encode($this->data)); - } - - return $args; - } - - /** - * Ready hook handler - */ - public function ready($args) + public function shutdown() { // Between startup and authenticate the session is destroyed. // So, we save the data later than that. - if (!empty($this->data)) { + if (!empty($this->data) && !empty($_SESSION['user_id'])) { $_SESSION['sso_data'] = $this->rc->encrypt(json_encode($this->data)); } - - return $args; } /** @@ -193,6 +209,21 @@ class kolab_sso extends rcube_plugin return $args; } + /** + * Chwala_authenticate hook handler + */ + public function chwala_authenticate($args) + { + // Instead of normal basic auth with user/pass we'll use + // Authorization: Bearer + $bearer = 'RC:' . session_id() . ':' . $_SESSION['username']; + + $args['request']->setAuth(null); + $args['request']->setHeader('Authorization', 'Bearer ' . base64_encode($bearer)); + + return $args; + } + /** * Login form object */