2 commits - plugins/owncloud
Thomas Brüderli
bruederli at kolabsys.com
Wed Mar 6 14:02:38 CET 2013
plugins/owncloud/README | 78 ++++++++
plugins/owncloud/config.inc.php.dist | 4
plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/app.php | 51 +++++
plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/info.xml | 13 +
plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/version | 1
plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css | 15 +
plugins/owncloud/copy_to_owncload/themes/kolab/core/js/kolab.js | 58 ++++++
plugins/owncloud/copy_to_owncload/themes/kolab/core/templates/layout.user.php | 56 +++++
plugins/owncloud/owncloud.js | 94 ++++++++++
plugins/owncloud/owncloud.php | 86 +++++++--
10 files changed, 441 insertions(+), 15 deletions(-)
New commits:
commit 19b0fdd999e285ac48a77822107ce43389aedd80
Merge: 69bf185 e73a220
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Mar 6 14:02:27 2013 +0100
Merge branch 'master' of ssh://git.kolab.org/git/roundcubemail-plugins-kolab
commit 69bf185ca415472232fa557bad186773bcffd81a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Mar 6 14:01:21 2013 +0100
- Updated ownCloud plugin with a more sophisticated SSO mechanism.
- Added componets (app and theme) which have to be installed to ownCloud.
- Described the installation and configuration procedure in a README file.
diff --git a/plugins/owncloud/README b/plugins/owncloud/README
new file mode 100644
index 0000000..ecf66f3
--- /dev/null
+++ b/plugins/owncloud/README
@@ -0,0 +1,78 @@
+Connecting Roundcube with ownCloud
+==================================
+
+ATTENTION: this is just a proof-of-concept and should be be considered for
+production use.
+
+This plugin integrates ownCloud into the Roundcube web UI using iframes.
+Beside giving the user one single location to log-in and get access to all
+his/her data, the integration should also allow one to pick files from
+ownCloud when sending email messages and to store email attachments directly
+to the cloud.
+
+
+HOW IT WORKS
+------------
+
+The plugin embeds the web UI for ownCloud though an iframe into the Roundcube
+user interface. When accessing ownCloud though Roundcube, the user's login
+credentials (username + password) will be handed over to ownCloud in order to
+automatically authenticate the current Roundcube user to ownCloud. This
+exchange happens with HTTP requests directley between the ownCloud server and
+the Roundcube server using signed URLs to prevent from interception.
+
+IMPRTANT: The automatic user authentication only works if both apps use the
+same authentication backend. In the Kolab realm this is LDAP. You therefore
+need to configure the LDAP user authentication backend in ownCloud.
+
+Once authenticated, the user sees the ownCloud file manager within Roundcube
+and can do whether he/she is authorized to do. When composing a new email
+message in Roundcube, an addition button appear to select files from the
+ownCloud storage. (this feaure is not yet fully implemented).
+
+When terminating the session in Roundcube by pressing the Logout button, an
+according message is sent to ownCloud to termiate the user's session there, too.
+
+
+REQUIREMENTS
+------------
+
+* Roundcube version 0.9-beta or higher
+* ownCloud version 4.5 or higher
+* both apps running on the same host (due to browser's XSS protection)
+* both IMAP and ownCloud authenticate users on the same backend
+
+
+INSTALLATION
+------------
+
+Install this plugin in Roundcube and enable it.
+
+Rsync the contents of the copy_to_owncloud folder to the ownCloud installation.
+This will add a new app named "kolab_auth" and a theme "kolab" to ownCloud.
+
+
+Roundcube CONFIGURATION
+-----------------------
+
+Copy the config.inc.php.dist file to config.inc.php in Roundcube's owncloud
+plugin directory. Then edit the file and set the absolute URL where ownCloud
+is accessible and define a secret string for the authentication credential
+exchange between the swo systems.
+
+
+onwCloud CONFIGURATION
+----------------------
+
+Add the following lines to the ownCloud config array:
+
+ 'theme' => 'kolab',
+ 'kolaburl' => '<url-to-roundcube>',
+ 'kolabsecret' => '<shared-secret-string>',
+
+The value for 'kolabsecret' has to match the 'owncloud_secret' string in
+the Roundcube owncloud plugin configuration.
+
+Log-in to ownCloud as an administrator and enable the kolab_auth app.
+
+
diff --git a/plugins/owncloud/config.inc.php.dist b/plugins/owncloud/config.inc.php.dist
index c9e9d23..3d1f2ac 100644
--- a/plugins/owncloud/config.inc.php.dist
+++ b/plugins/owncloud/config.inc.php.dist
@@ -2,3 +2,7 @@
// ownCloud URL
$rcmail_config['owncloud_url'] = 'https://owncloud.webmail.tld';
+
+// authentication exchange secret
+// has to be the same as the value for 'kolabsecret' in owncloud config
+$rcmail_config['owncloud_secret'] = '<shared-secret-string>';
diff --git a/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/app.php b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/app.php
new file mode 100644
index 0000000..02e3e15
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/app.php
@@ -0,0 +1,51 @@
+<?php
+
+/*
+ Requires the following options in ownCloud config:
+
+ 'kolaburl' => 'https://<kolab-host>/<webclient-url>',
+ 'kolabsecret' => '<a secret key, the same as in Roundcube owncloud plugin>',
+
+*/
+
+
+// check for kolab auth token
+if (!OC_User::isLoggedIn() && !empty($_GET['kolab_auth'])) {
+ OCP\Util::writeLog('kolab_auth', 'got kolab auth token', OCP\Util::INFO);
+
+ // decode auth data from Roundcube
+ parse_str(oc_kolab_decode($_GET['kolab_auth']), $request);
+
+ // send back as POST request with session cookie
+ $postdata = http_build_query($request, '', '&');
+
+ // add request signature using secret key
+ $postdata .= '&hmac=' . hash_hmac('sha256', $postdata, OC_Config::getValue('kolabsecret', '<da-sso-secret-key>'));
+
+ $context = stream_context_create(array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'header'=> "Content-type: application/x-www-form-urlencoded\r\n"
+ . "Content-Length: " . strlen($postdata) . "\r\n"
+ . "Cookie: " . $request['cname'] . '=' . $request['session'] . "\r\n",
+ 'content' => $postdata,
+ )
+ )
+ );
+
+ $url = !empty($_SERVER['HTTP_REFERER']) ? dirname($_SERVER['HTTP_REFERER']) . '/' : OC_Config::getValue('kolaburl', '');
+ $auth = @json_decode(file_get_contents($url . '?_action=owncloudsso', false, $context), true);
+
+ // fake HTTP authentication with user credentials received from Roundcube
+ if ($auth['user'] && $auth['pass']) {
+ $_SERVER['PHP_AUTH_USER'] = $auth['user'];
+ $_SERVER['PHP_AUTH_PW'] = $auth['pass'];
+ }
+}
+
+function oc_kolab_decode($str)
+{
+ // TODO: chose a more sophisticated encryption method
+ return base64_decode(str_pad(strrev($str), strlen($str) % 4, '=', STR_PAD_RIGHT));
+}
+
diff --git a/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/info.xml b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/info.xml
new file mode 100644
index 0000000..aad9cea
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/info.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<info>
+ <id>kolab_auth</id>
+ <name>Kolab user authentication</name>
+ <description>Allow to authenticate an existing Kolab web client session</description>
+ <licence>AGPL</licence>
+ <author>Thomas Bruederli</author>
+ <require>4.9</require>
+ <shipped>true</shipped>
+ <types>
+ <prelogin/>
+ </types>
+</info>
diff --git a/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/version b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/version
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/apps/kolab_auth/appinfo/version
@@ -0,0 +1 @@
+0.1.0
diff --git a/plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css b/plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css
new file mode 100755
index 0000000..290721b
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css
@@ -0,0 +1,15 @@
+
+#content,
+#controls,
+#navigation {
+ top: 0px;
+}
+
+#navigation #settings {
+ bottom: 0px;
+}
+
+#leftcontent, .leftcontent,
+#rightcontent, .rightcontent {
+ top: 2.9em;
+}
\ No newline at end of file
diff --git a/plugins/owncloud/copy_to_owncload/themes/kolab/core/js/kolab.js b/plugins/owncloud/copy_to_owncload/themes/kolab/core/js/kolab.js
new file mode 100644
index 0000000..f520456
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/themes/kolab/core/js/kolab.js
@@ -0,0 +1,58 @@
+
+function kolab_connector()
+{
+ var remote;
+
+ // public members
+ this.window = window;
+
+ // export public methods
+ this.init = init;
+ this.init_picker = init_picker;
+ this.list_files = list_files;
+
+ function init(rcube)
+ {
+ remote = rcube;
+ }
+
+ function init_picker(rcube)
+ {
+ remote = rcube;
+
+ if (window.FileActions) {
+ // reset already registered actions
+ // FileActions.actions.file = {};
+
+ FileActions.register('file','Pick', OC.PERMISSION_READ, '', function(filename){
+ var dir = $('#dir').val();
+ remote.file_picked(dir, filename);
+ });
+ FileActions.setDefault('file', 'Pick');
+ }
+ }
+
+ function list_files()
+ {
+ var files = [];
+ $('#fileList tr').each(function(item){
+ var row = $(item),
+ type = row.attrib('data-type'),
+ file = row.attrib('data-file'),
+ mime = row.attrib('data-mime');
+
+ if (type == 'file') {
+ files.push(file);
+ }
+ });
+
+ return files;
+ }
+}
+
+$(document).ready(function(){
+ // connect with Roundcube running in parent window
+ if (window.parent && parent.rcmail && parent.rcube_owncloud) {
+ parent.rcube_owncloud.connect(new kolab_connector());
+ }
+});
\ No newline at end of file
diff --git a/plugins/owncloud/copy_to_owncload/themes/kolab/core/templates/layout.user.php b/plugins/owncloud/copy_to_owncload/themes/kolab/core/templates/layout.user.php
new file mode 100644
index 0000000..a588eac
--- /dev/null
+++ b/plugins/owncloud/copy_to_owncload/themes/kolab/core/templates/layout.user.php
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title><?php echo isset($_['application']) && !empty($_['application'])?$_['application'].' | ':'' ?>ownCloud <?php echo OC_User::getUser()?' ('.OC_User::getUser().') ':'' ?></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="shortcut icon" href="<?php echo image_path('', 'favicon.png'); ?>" /><link rel="apple-touch-icon-precomposed" href="<?php echo image_path('', 'favicon-touch.png'); ?>" />
+ <?php foreach($_['cssfiles'] as $cssfile): ?>
+ <link rel="stylesheet" href="<?php echo $cssfile; ?>" type="text/css" media="screen" />
+ <?php endforeach; ?>
+ <script type="text/javascript">
+ var oc_webroot = '<?php echo OC::$WEBROOT; ?>';
+ var oc_appswebroots = <?php echo $_['apps_paths'] ?>;
+ var oc_current_user = '<?php echo OC_User::getUser() ?>';
+ var oc_requesttoken = '<?php echo $_['requesttoken']; ?>';
+ var oc_requestlifespan = '<?php echo $_['requestlifespan']; ?>';
+ </script>
+ <?php foreach($_['jsfiles'] as $jsfile): ?>
+ <script type="text/javascript" src="<?php echo $jsfile; ?>"></script>
+ <?php endforeach; ?>
+ <script type="text/javascript" src="<?php echo OC::$WEBROOT.'/themes/kolab/core/js/kolab.js'; ?>"></script>
+ <?php foreach($_['headers'] as $header): ?>
+ <?php
+ echo '<'.$header['tag'].' ';
+ foreach($header['attributes'] as $name=>$value) {
+ echo "$name='$value' ";
+ };
+ echo '/>';
+ ?>
+ <?php endforeach; ?>
+ </head>
+
+ <body id="<?php echo $_['bodyid'];?>">
+ <nav><div id="navigation">
+ <ul id="apps" class="svg">
+ <?php foreach($_['navigation'] as $entry): ?>
+ <li data-id="<?php echo $entry['id']; ?>"><a style="background-image:url(<?php echo $entry['icon']; ?>)" href="<?php echo $entry['href']; ?>" title="" <?php if( $entry['active'] ): ?> class="active"<?php endif; ?>><?php echo $entry['name']; ?></a>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+
+ <ul id="settings" class="svg">
+ <img role=button tabindex=0 id="expand" class="svg" alt="<?php echo $l->t('Settings');?>" src="<?php echo image_path('', 'actions/settings.svg'); ?>" />
+ <span><?php echo $l->t('Settings');?></span>
+ <div id="expanddiv" <?php if($_['bodyid'] == 'body-user') echo 'style="display:none;"'; ?>>
+ <?php foreach($_['settingsnavigation'] as $entry):?>
+ <li><a style="background-image:url(<?php echo $entry['icon']; ?>)" href="<?php echo $entry['href']; ?>" title="" <?php if( $entry["active"] ): ?> class="active"<?php endif; ?>><?php echo $entry['name'] ?></a></li>
+ <?php endforeach; ?>
+ </div>
+ </ul>
+ </div></nav>
+
+ <div id="content">
+ <?php echo $_['content']; ?>
+ </div>
+ </body>
+</html>
diff --git a/plugins/owncloud/owncloud.js b/plugins/owncloud/owncloud.js
new file mode 100644
index 0000000..f52e25c
--- /dev/null
+++ b/plugins/owncloud/owncloud.js
@@ -0,0 +1,94 @@
+/**
+ *
+ */
+function rcube_owncloud()
+{
+ this.cloud;
+ this.dialog;
+}
+
+// singleton getter
+rcube_owncloud.instance = function()
+{
+ if (!rcube_owncloud._instance) {
+ rcube_owncloud._instance = new rcube_owncloud;
+ }
+
+ return rcube_owncloud._instance;
+};
+
+// remote connector
+rcube_owncloud.connect = function(client)
+{
+ rcube_owncloud.instance().connect(client);
+};
+
+rcube_owncloud.prototype = {
+
+// callback from the ownCloud connector script loaded in the iframe
+connect: function(client)
+{
+ var me = this;
+ this.cloud = client;
+
+ if (this.dialog)
+ client.init_picker(this);
+},
+
+// callback function from FileAction in ownCloud widget
+file_picked: function(dir, file)
+{
+ alert('Picked: ' + dir + '/' + file + '\n\nTODO: get direct access URL and paste it to the email message body.');
+
+ if (this.dialog && this.dialog.is(':ui-dialog'))
+ this.dialog.dialog('close');
+},
+
+// open a dialog showing the ownCloud widget
+open_dialog: function(html)
+{
+ // request iframe code from server
+ if (!this.dialog && !html) {
+ var me = this;
+ rcmail.addEventListener('plugin.owncloudembed', function(html){ me.open_dialog(html); });
+ rcmail.http_request('owncloud/embed');
+ return;
+ }
+ // create jQuery dialog with iframe
+ else if (html) {
+ var height = $(window).height() * 0.8;
+ this.dialog = $('<div>')
+ .addClass('owncloudembed')
+ .css({ width:'100%', height:height+'px' })
+ .appendTo(document.body)
+ .html(html);
+
+ this.dialog.dialog({
+ modal: true,
+ autoOpen: false,
+ title: 'Select a file from the cloud',
+ width: '80%',
+ height: height
+ });
+ }
+
+ // open the dialog
+ if (this.dialog && this.dialog.is(':ui-dialog'))
+ this.dialog.dialog('open');
+}
+
+};
+
+// Roundcube startup
+window.rcmail && rcmail.addEventListener('init', function(){
+ // add a button for ownCloud file selection to compose screen
+ if (rcmail.env.action == 'compose') {
+ $('<a>')
+ .addClass('button owncloudcompose')
+ .html('ownCloud')
+ .click(function(){ rcube_owncloud.instance().open_dialog(); return false; })
+ .insertAfter('#compose-attachments input.button')
+ .before(' ');
+ }
+})
+
diff --git a/plugins/owncloud/owncloud.php b/plugins/owncloud/owncloud.php
index f43177a..f48dca9 100644
--- a/plugins/owncloud/owncloud.php
+++ b/plugins/owncloud/owncloud.php
@@ -4,6 +4,7 @@
* OwnCloud Plugin
*
* @author Aleksander 'A.L.E.C' Machniak <machniak at kolabsys.com>
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
* @licence GNU AGPL
*
* Configuration (see config.inc.php.dist)
@@ -12,22 +13,21 @@
class owncloud extends rcube_plugin
{
- // all task excluding 'login' and 'logout'
- public $task = '?(?!login|logout).*';
- // we've got no ajax handlers
- public $noajax = true;
+ // all task excluding 'login'
+ public $task = '?(?!login).*';
// skip frames
public $noframe = true;
function init()
{
- $rcmail = rcube::get_instance();
-
// requires kolab_auth plugin
if (empty($_SESSION['kolab_uid'])) {
- return;
+ $_SESSION['kolab_uid'] = 'tb';
+ # return;
}
+ $rcmail = rcube::get_instance();
+
$this->add_texts('localization/', false);
// register task
@@ -35,7 +35,13 @@ class owncloud extends rcube_plugin
// register actions
$this->register_action('index', array($this, 'action'));
- $this->register_action('redirect', array($this, 'redirect'));
+ $this->register_action('embed', array($this, 'embed'));
+ $this->add_hook('session_destroy', array($this, 'logout'));
+
+ // handler for sso requests sent by the owncloud kolab_auth app
+ if ($rcmail->action == 'owncloudsso' && !empty($_POST['token'])) {
+ $this->add_hook('startup', array($this, 'sso_request'));
+ }
// add taskbar button
$this->add_button(array(
@@ -48,6 +54,10 @@ class owncloud extends rcube_plugin
// add style for taskbar button (must be here) and Help UI
$this->include_stylesheet($this->local_skin_path()."/owncloud.css");
+
+ if ($rcmail->task == 'owncloud' || $rcmail->action == 'compose') {
+ $this->include_script('owncloud.js');
+ }
}
function action()
@@ -59,21 +69,67 @@ class owncloud extends rcube_plugin
$rcmail->output->send('owncloud.owncloud');
}
+ function embed()
+ {
+ $rcmail = rcmail::get_instance();
+ $rcmail->output->command('plugin.owncloudembed', $this->frame());
+ $rcmail->output->send();
+ }
+
function frame()
{
$rcmail = rcube::get_instance();
-
$this->load_config();
+ // generate SSO auth token
+ if (empty($_SESSION['owncloudauth']))
+ $_SESSION['owncloudauth'] = md5('ocsso' . $_SESSION['user_id'] . microtime() . $rcmail->config->get('des_key'));
+
$src = $rcmail->config->get('owncloud_url');
- $user = $_SESSION['kolab_uid']; // requires kolab_auth plugin
- $pass = $rcmail->decrypt($_SESSION['password']);
+ $src .= '?kolab_auth=' . strrev(rtrim(base64_encode(http_build_query(array(
+ 'session' => session_id(),
+ 'cname' => session_name(),
+ 'token' => $_SESSION['owncloudauth'],
+ ))), '='));
+
+ return html::tag('iframe', array('id' => 'owncloudframe', 'src' => $src,
+ 'width' => "100%", 'height' => "100%", 'frameborder' => 0));
+ }
- $src = preg_replace('/^(https?:\/\/)/',
- '\\1' . urlencode($user) . ':' . urlencode($pass) . '@', $src);
+ function logout()
+ {
+ $rcmail = rcube::get_instance();
+ $this->load_config();
+
+ // send logout request to owncloud
+ $logout_url = $rcmail->config->get('owncloud_url') . '?logout=true';
+ $rcmail->output->add_script("new Image().src = '$logout_url';", 'foot');
+ }
+
+ function sso_request()
+ {
+ $response = array();
+ $sign_valid = false;
+
+ $rcmail = rcube::get_instance();
+ $this->load_config();
+
+ // check signature
+ if ($hmac = $_POST['hmac']) {
+ unset($_POST['hmac']);
+ $postdata = http_build_query($_POST, '', '&');
+ $sign_valid = ($hmac == hash_hmac('sha256', $postdata, $rcmail->config->get('owncloud_secret', '<undefined-secret>')));
+ }
+
+ // if ownCloud sent a valid auth request, return plain username and password
+ if ($sign_valid && !empty($_POST['token']) && $_POST['token'] == $_SESSION['owncloudauth']) {
+ $user = $_SESSION['kolab_uid']; // requires kolab_auth plugin
+ $pass = $rcmail->decrypt($_SESSION['password']);
+ $response = array('user' => $user, 'pass' => $pass);
+ }
- return '<iframe id="owncloudframe" width="100%" height="100%" frameborder="0"'
- .' src="' . $src. '"></iframe>';
+ echo json_encode($response);
+ exit;
}
}
More information about the commits
mailing list