- 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.
This commit is contained in:
Thomas Bruederli 2013-03-06 14:01:21 +01:00
parent f5cc6d2618
commit 69bf185ca4
10 changed files with 441 additions and 15 deletions

78
plugins/owncloud/README Normal file
View file

@ -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.

View file

@ -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>';

View file

@ -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));
}

View file

@ -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>

View file

@ -0,0 +1 @@
0.1.0

View file

@ -0,0 +1,15 @@
#content,
#controls,
#navigation {
top: 0px;
}
#navigation #settings {
bottom: 0px;
}
#leftcontent, .leftcontent,
#rightcontent, .rightcontent {
top: 2.9em;
}

View file

@ -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());
}
});

View file

@ -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>

View file

@ -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('&nbsp;');
}
})

View file

@ -4,6 +4,7 @@
* OwnCloud Plugin
*
* @author Aleksander 'A.L.E.C' Machniak <machniak@kolabsys.com>
* @author Thomas Bruederli <bruederli@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');
$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));
}
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);
}
$src = preg_replace('/^(https?:\/\/)/',
'\\1' . urlencode($user) . ':' . urlencode($pass) . '@', $src);
return '<iframe id="owncloudframe" width="100%" height="100%" frameborder="0"'
.' src="' . $src. '"></iframe>';
echo json_encode($response);
exit;
}
}