Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F9789007
kolab_sso.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
kolab_sso.php
View Options
<?php
/**
* Single Sign On Authentication for Kolab
*
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2018, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class
kolab_sso
extends
rcube_plugin
{
public
$rc
;
private
$data
;
private
$old_data
;
private
$driver
;
private
$logon_error
;
private
$debug
=
false
;
/**
* Plugin initialization
*/
public
function
init
()
{
// Roundcube or Chwala
if
(
defined
(
'RCMAIL_VERSION'
)
||
defined
(
'FILE_API_START'
))
{
$this
->
rc
=
rcube
::
get_instance
();
$this
->
add_hook
(
'startup'
,
array
(
$this
,
'startup'
));
$this
->
add_hook
(
'authenticate'
,
array
(
$this
,
'authenticate'
));
$this
->
rc
->
add_shutdown_function
(
array
(
$this
,
'shutdown'
));
}
}
/**
* Startup hook handler
*/
public
function
startup
(
$args
)
{
// On login or logout (or when the session expired)...
if
(
$args
[
'task'
]
==
'login'
||
$args
[
'task'
]
==
'logout'
)
{
$mode
=
$args
[
'action'
]
==
'sso'
?
$_SESSION
[
'sso_mode'
]
:
rcube_utils
::
get_input_value
(
'_sso'
,
rcube_utils
::
INPUT_GP
);
// Authorization
if
(
$mode
)
{
$driver
=
$this
->
get_driver
(
$mode
);
// This is where we handle redirections from the SSO provider
if
(
$args
[
'action'
]
==
'sso'
)
{
$this
->
data
=
$driver
->
response
();
if
(!
empty
(
$this
->
data
))
{
$this
->
data
[
'timezone'
]
=
$_SESSION
[
'sso_timezone'
];
$this
->
data
[
'url'
]
=
$_SESSION
[
'sso_url'
];
$this
->
data
[
'mode'
]
=
$mode
;
}
else
{
$this
->
logon_error
=
$driver
->
response_error
();
}
}
// This is where we handle clicking one of "Login by SSO" buttons
else
if
(
$_SESSION
[
'temp'
]
&&
$this
->
rc
->
check_request
())
{
// Remember some logon params for use on SSO response above
$_SESSION
[
'sso_timezone'
]
=
rcube_utils
::
get_input_value
(
'_timezone'
,
rcube_utils
::
INPUT_POST
);
$_SESSION
[
'sso_url'
]
=
rcube_utils
::
get_input_value
(
'_url'
,
rcube_utils
::
INPUT_POST
);
$_SESSION
[
'sso_mode'
]
=
$mode
;
$driver
->
authorize
();
}
$args
[
'action'
]
=
'login'
;
$args
[
'task'
]
=
'login'
;
}
}
// On valid session...
else
if
(
isset
(
$_SESSION
[
'user_id'
])
&&
(
$data
=
$_SESSION
[
'sso_data'
])
&&
(
$data
=
json_decode
(
$this
->
rc
->
decrypt
(
$data
),
true
))
&&
(
$mode
=
$data
[
'mode'
])
)
{
$driver
=
$this
->
get_driver
(
$mode
);
$this
->
old_data
=
$data
;
// Session validation, token refresh, etc.
if
(
$this
->
data
=
$driver
->
validate_session
(
$data
))
{
// register storage connection hooks
$this
->
authenticate
(
array
(),
true
);
}
else
{
// Destroy the session
$this
->
rc
->
kill_session
();
// TODO: error message beter explaining the reason
// $this->rc->output->show_message('sessionferror', 'error');
}
}
// Register login form modifications
$this
->
add_hook
(
'template_object_loginform'
,
array
(
$this
,
'login_form'
));
return
$args
;
}
/**
* Authenticate hook handler
*/
public
function
authenticate
(
$args
,
$internal
=
false
)
{
// 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'
;
$args
[
'valid'
]
=
true
;
$args
[
'cookiecheck'
]
=
false
;
$_POST
[
'_timezone'
]
=
$this
->
data
[
'timezone'
];
$_POST
[
'_url'
]
=
$this
->
data
[
'url'
];
}
$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
(
'ldap_connected'
,
array
(
$this
,
'ldap_connected'
));
$this
->
add_hook
(
'chwala_authenticate'
,
array
(
$this
,
'chwala_authenticate'
));
}
else
if
(
$this
->
logon_error
)
{
$args
[
'valid'
]
=
false
;
$args
[
'error'
]
=
$this
->
logon_error
;
}
return
$args
;
}
/**
* Shutdown handler
*/
public
function
shutdown
()
{
// Between startup and authenticate the session is destroyed.
// So, we save the data later than that.
if
(!
empty
(
$this
->
data
)
&&
!
empty
(
$_SESSION
[
'user_id'
])
// update session only when data changed
&&
(
empty
(
$this
->
old_data
)
||
$this
->
old_data
!=
$this
->
data
)
)
{
$_SESSION
[
'sso_data'
]
=
$this
->
rc
->
encrypt
(
json_encode
(
$this
->
data
));
}
}
/**
* Storage_connect/managesieve_connect hook handler
*/
public
function
storage_connect
(
$args
)
{
$user
=
$this
->
rc
->
config
->
get
(
'kolab_sso_imap_user'
);
$pass
=
$this
->
rc
->
config
->
get
(
'kolab_sso_imap_pass'
);
if
(
$user
&&
$pass
)
{
$args
[
'auth_cid'
]
=
$user
;
$args
[
'auth_pw'
]
=
$pass
;
$args
[
'auth_type'
]
=
'PLAIN'
;
}
return
$args
;
}
/**
* Smtp_connect hook handler
*/
public
function
smtp_connect
(
$args
)
{
foreach
(
array
(
'smtp_server'
,
'smtp_user'
,
'smtp_pass'
)
as
$prop
)
{
$args
[
$prop
]
=
$this
->
rc
->
config
->
get
(
"kolab_sso_$prop"
,
$args
[
$prop
]);
}
return
$args
;
}
/**
* ldap_connected hook handler
*/
public
function
ldap_connected
(
$args
)
{
$user
=
$this
->
rc
->
config
->
get
(
'kolab_sso_ldap_user'
);
$pass
=
$this
->
rc
->
config
->
get
(
'kolab_sso_ldap_pass'
);
if
(
$user
&&
$pass
&&
$args
[
'user_specific'
])
{
$args
[
'bind_dn'
]
=
$user
;
$args
[
'bind_pass'
]
=
$pass
;
$args
[
'search_filter'
]
=
null
;
}
return
$args
;
}
/**
* Chwala_authenticate hook handler
*/
public
function
chwala_authenticate
(
$args
)
{
// Instead of normal basic auth with user/pass we'll use
// Authorization: Bearer <roundcube session id>
$bearer
=
'RC:'
.
session_id
()
.
':'
.
$_SESSION
[
'username'
];
$args
[
'request'
]->
setAuth
(
null
);
$args
[
'request'
]->
setHeader
(
'Authorization'
,
'Bearer '
.
base64_encode
(
$bearer
));
return
$args
;
}
/**
* Login form object
*/
public
function
login_form
(
$args
)
{
$this
->
load_config
();
$options
=
(
array
)
$this
->
rc
->
config
->
get
(
'kolab_sso_options'
);
$disable_login
=
$this
->
rc
->
config
->
get
(
'kolab_sso_disable_login'
);
if
(
empty
(
$options
))
{
return
$args
;
}
$doc
=
new
DOMDocumentHelper
(
'1.0'
);
$doc
->
loadHTML
(
$args
[
'content'
]);
$body
=
$doc
->
getElementsByTagName
(
'body'
)->
item
(
0
);
if
(
$disable_login
)
{
// Remove login form inputs table
$table
=
$doc
->
getElementsByTagName
(
'table'
)->
item
(
0
);
$table
->
parentNode
->
removeChild
(
$table
);
// Remove original Submit button
$submit
=
$doc
->
getElementsByTagName
(
'button'
)->
item
(
0
);
$submit
->
parentNode
->
removeChild
(
$submit
);
}
if
(!
$this
->
driver
)
{
$this
->
add_texts
(
'localization/'
);
}
// Add SSO form elements
$form
=
$doc
->
createNode
(
'p'
,
null
,
array
(
'id'
=>
'sso-form'
,
'class'
=>
'formbuttons'
),
$body
);
foreach
(
$options
as
$idx
=>
$option
)
{
$label
=
array
(
'name'
=>
'loginby'
,
'vars'
=>
array
(
'provider'
=>
$option
[
'name'
]
?:
$this
->
gettext
(
'sso'
)));
$doc
->
createNode
(
'button'
,
$this
->
gettext
(
$label
),
array
(
'type'
=>
'button'
,
'value'
=>
$idx
,
'class'
=>
'button sso w-100 mb-1'
,
'onclick'
=>
'kolab_sso_submit(this)'
,
),
$form
);
}
$doc
->
createNode
(
'input'
,
null
,
array
(
'name'
=>
'_sso'
,
'type'
=>
'hidden'
),
$form
);
// Save the form content back and append script
$args
[
'content'
]
=
$doc
->
saveHTML
(
$body
)
.
"<script>"
.
"function kolab_sso_submit(button) {"
.
"
\$
('[name=_sso]').val(button.value);"
.
"
\$
('input[type=text],input[type=password]').attr('required', false);"
.
"rcmail.gui_objects.loginform.submit();"
.
"}"
.
"</script>"
;
return
$args
;
}
/**
* Debug function for drivers
*/
public
function
debug
(
$line
)
{
if
(
$this
->
debug
)
{
rcube
::
write_log
(
'sso'
,
$line
);
}
}
/**
* Initialize SSO driver object
*/
private
function
get_driver
(
$name
)
{
if
(
$this
->
driver
)
{
return
$this
->
driver
;
}
$this
->
load_config
();
$this
->
add_texts
(
'localization/'
);
$options
=
(
array
)
$this
->
rc
->
config
->
get
(
'kolab_sso_options'
);
$options
=
(
array
)
$options
[
$name
];
$driver
=
$options
[
'driver'
]
?:
'openidc'
;
$class
=
"kolab_sso_$driver"
;
if
(
empty
(
$options
)
||
!
file_exists
(
$this
->
home
.
"/drivers/$driver.php"
))
{
rcube
::
raise_error
(
array
(
'line'
=>
__LINE__
,
'file'
=>
__FILE__
,
'message'
=>
"Unable to find SSO driver"
),
true
,
true
);
}
// Add /lib to include_path
$include_path
=
$this
->
home
.
'/lib'
.
PATH_SEPARATOR
;
$include_path
.=
ini_get
(
'include_path'
);
set_include_path
(
$include_path
);
require_once
$this
->
home
.
"/drivers/$driver.php"
;
$this
->
debug
=
$this
->
rc
->
config
->
get
(
'kolab_sso_debug'
);
$this
->
driver
=
new
$class
(
$this
,
$options
);
return
$this
->
driver
;
}
}
/**
* DOMDocument wrapper with some shortcut method
*/
class
DOMDocumentHelper
extends
DOMDocument
{
public
function
loadHTML
(
$html
,
$options
=
0
)
{
return
parent
::
loadHTML
(
'<html><head><meta content="text/html; charset=utf-8" http-equiv="Content-Type"></head><body>'
.
$html
);
}
public
function
saveHTML
(
$node
=
null
)
{
return
preg_replace
(
'|</?body>|'
,
''
,
parent
::
saveHTML
(
$node
));
}
public
function
createNode
(
$name
,
$value
=
null
,
$args
=
array
(),
$parent
=
null
,
$prepend
=
false
)
{
$node
=
parent
::
createElement
(
$name
);
if
(
$value
)
{
$node
->
appendChild
(
new
DOMText
(
rcube
::
Q
(
$value
)));
}
foreach
(
$args
as
$attr_name
=>
$attr_value
)
{
$node
->
setAttribute
(
$attr_name
,
$attr_value
);
}
if
(
$parent
)
{
if
(
$prepend
&&
$parent
->
firstChild
)
{
$parent
->
insertBefore
(
$node
,
$parent
->
firstChild
);
}
else
{
$parent
->
appendChild
(
$node
);
}
}
return
$node
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Tue, Aug 18, 7:58 AM (17 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1262399
Default Alt Text
kolab_sso.php (13 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment