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