- 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:
parent
f5cc6d2618
commit
69bf185ca4
10 changed files with 441 additions and 15 deletions
78
plugins/owncloud/README
Normal file
78
plugins/owncloud/README
Normal 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.
|
||||
|
||||
|
|
@ -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>';
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
0.1.0
|
15
plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css
Executable file
15
plugins/owncloud/copy_to_owncload/themes/kolab/core/css/styles.css
Executable file
|
@ -0,0 +1,15 @@
|
|||
|
||||
#content,
|
||||
#controls,
|
||||
#navigation {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
#navigation #settings {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
#leftcontent, .leftcontent,
|
||||
#rightcontent, .rightcontent {
|
||||
top: 2.9em;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
|
@ -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>
|
94
plugins/owncloud/owncloud.js
Normal file
94
plugins/owncloud/owncloud.js
Normal 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(' ');
|
||||
}
|
||||
})
|
||||
|
|
@ -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');
|
||||
$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'],
|
||||
))), '='));
|
||||
|
||||
$src = preg_replace('/^(https?:\/\/)/',
|
||||
'\\1' . urlencode($user) . ':' . urlencode($pass) . '@', $src);
|
||||
return html::tag('iframe', array('id' => 'owncloudframe', 'src' => $src,
|
||||
'width' => "100%", 'height' => "100%", 'frameborder' => 0));
|
||||
}
|
||||
|
||||
return '<iframe id="owncloudframe" width="100%" height="100%" frameborder="0"'
|
||||
.' src="' . $src. '"></iframe>';
|
||||
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);
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue