Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117751080
kolab_2fa.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
29 KB
Referenced Files
None
Subscribers
None
kolab_2fa.php
View Options
<?php
/**
* Kolab 2-Factor-Authentication plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2015, 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_2fa
extends
rcube_plugin
{
public
$task
=
'(login|settings)'
;
protected
$login_verified
=
null
;
protected
$login_factors
=
[];
protected
$drivers
=
[];
protected
$storage
;
/**
* Plugin init
*/
public
function
init
()
{
$this
->
load_config
();
$this
->
add_hook
(
'startup'
,
[
$this
,
'startup'
]);
}
/**
* Startup hook
*/
public
function
startup
(
$args
)
{
$rcmail
=
rcmail
::
get_instance
();
// register library namespace to autoloader
$loader
=
include
(
INSTALL_PATH
.
'vendor/autoload.php'
);
$loader
->
set
(
'Kolab2FA'
,
[
$this
->
home
.
'/lib'
]);
if
(
$args
[
'task'
]
===
'login'
&&
$this
->
api
->
output
)
{
$this
->
add_texts
(
'localization/'
,
false
);
$this
->
add_hook
(
'authenticate'
,
[
$this
,
'authenticate'
]);
// process 2nd factor auth step after regular login
if
(
$args
[
'action'
]
===
'plugin.kolab-2fa-login'
/* || !empty($_SESSION['kolab_2fa_factors']) */
)
{
return
$this
->
login_verify
(
$args
);
}
}
elseif
(
$args
[
'task'
]
===
'settings'
)
{
$this
->
add_texts
(
'localization/'
,
!
$this
->
api
->
output
->
ajax_call
);
$this
->
add_hook
(
'settings_actions'
,
[
$this
,
'settings_actions'
]);
$this
->
register_action
(
'plugin.kolab-2fa'
,
[
$this
,
'settings_view'
]);
$this
->
register_action
(
'plugin.kolab-2fa-data'
,
[
$this
,
'settings_data'
]);
$this
->
register_action
(
'plugin.kolab-2fa-save'
,
[
$this
,
'settings_save'
]);
$this
->
register_action
(
'plugin.kolab-2fa-verify'
,
[
$this
,
'settings_verify'
]);
}
return
$args
;
}
/**
* Handler for 'authenticate' plugin hook.
*
* ATTENTION: needs to be called *after* kolab_auth::authenticate()
*/
public
function
authenticate
(
$args
)
{
// nothing to be done for me
if
(
$args
[
'abort'
]
||
$this
->
login_verified
!==
null
)
{
return
$args
;
}
// Single Sign On authentication, disable 2FA (Roundcube > 1.6)
if
(!
empty
(
$args
[
'sso'
]))
{
return
$args
;
}
$rcmail
=
rcmail
::
get_instance
();
// parse $host URL
$a_host
=
parse_url
(
$args
[
'host'
]);
$hostname
=
$_SESSION
[
'hostname'
]
=
$a_host
[
'host'
]
?:
$args
[
'host'
];
$username
=
!
empty
(
$_SESSION
[
'kolab_auth_admin'
])
?
$_SESSION
[
'kolab_auth_admin'
]
:
$args
[
'user'
];
// Check if we need to add/force domain to username
$username_domain
=
$rcmail
->
config
->
get
(
'username_domain'
);
if
(!
empty
(
$username_domain
))
{
$domain
=
''
;
if
(
is_array
(
$username_domain
))
{
if
(!
empty
(
$username_domain
[
$hostname
]))
{
$domain
=
$username_domain
[
$hostname
];
}
}
else
{
$domain
=
$username_domain
;
}
if
(
$domain
=
rcube_utils
::
parse_host
((
string
)
$domain
,
$hostname
))
{
$pos
=
strpos
(
$username
,
'@'
);
// force configured domains
if
(
$pos
!==
false
&&
$rcmail
->
config
->
get
(
'username_domain_forced'
))
{
$username
=
substr
(
$username
,
0
,
$pos
)
.
'@'
.
$domain
;
}
// just add domain if not specified
elseif
(
$pos
===
false
)
{
$username
.=
'@'
.
$domain
;
}
}
}
// Convert username to lowercase. Copied from rcmail::login()
$login_lc
=
$rcmail
->
config
->
get
(
'login_lc'
,
2
);
if
(
$login_lc
)
{
if
(
$login_lc
==
2
||
$login_lc
===
true
)
{
$username
=
mb_strtolower
(
$username
);
}
elseif
(
strpos
(
$username
,
'@'
))
{
// lowercase domain name
[
$local
,
$domain
]
=
explode
(
'@'
,
$username
);
$username
=
$local
.
'@'
.
mb_strtolower
(
$domain
);
}
}
// 2a. let plugins provide the list of active authentication factors
$lookup
=
$rcmail
->
plugins
->
exec_hook
(
'kolab_2fa_lookup'
,
[
'user'
=>
$username
,
'host'
=>
$hostname
,
'factors'
=>
null
,
'check'
=>
$rcmail
->
config
->
get
(
'kolab_2fa_check'
,
true
),
]);
$factors
=
[];
if
(
isset
(
$lookup
[
'factors'
]))
{
$factors
=
(
array
)
$lookup
[
'factors'
];
}
// 2b. check storage if this user has 2FA enabled
elseif
(
$lookup
[
'check'
]
!==
false
&&
(
$storage
=
$this
->
get_storage
(
$username
)))
{
$factors
=
(
array
)
$storage
->
enumerate
();
}
if
(
count
(
$factors
)
>
0
)
{
$args
[
'abort'
]
=
true
;
$factors
=
array_unique
(
$factors
);
// 3. flag session for 2nd factor verification
$_SESSION
[
'kolab_2fa_time'
]
=
time
();
$_SESSION
[
'kolab_2fa_nonce'
]
=
bin2hex
(
openssl_random_pseudo_bytes
(
32
));
$_SESSION
[
'kolab_2fa_factors'
]
=
$factors
;
$_SESSION
[
'username'
]
=
$username
;
$_SESSION
[
'host'
]
=
$args
[
'host'
];
$_SESSION
[
'password'
]
=
$rcmail
->
encrypt
(
$args
[
'pass'
]);
// 4. render to 2nd auth step
$this
->
login_step
(
$factors
);
}
return
$args
;
}
/**
* Handler for the additional login step requesting the 2FA verification code
*/
public
function
login_step
(
$factors
)
{
// replace handler for login form
$this
->
login_factors
=
array_values
(
$factors
);
$this
->
api
->
output
->
add_handler
(
'loginform'
,
[
$this
,
'auth_form'
]);
// focus the code input field on load
$this
->
api
->
output
->
add_script
(
'$("input.kolab2facode").first().select();'
,
'docready'
);
$this
->
api
->
output
->
send
(
'login'
);
}
/**
* Process the 2nd factor code verification form submission
*/
public
function
login_verify
(
$args
)
{
$this
->
login_verified
=
false
;
$rcmail
=
rcmail
::
get_instance
();
$time
=
$_SESSION
[
'kolab_2fa_time'
];
$nonce
=
$_SESSION
[
'kolab_2fa_nonce'
];
$factors
=
(
array
)
$_SESSION
[
'kolab_2fa_factors'
];
$expired
=
$time
<
time
()
-
$rcmail
->
config
->
get
(
'kolab_2fa_timeout'
,
120
);
$username
=
!
empty
(
$_SESSION
[
'kolab_auth_admin'
])
?
$_SESSION
[
'kolab_auth_admin'
]
:
$_SESSION
[
'username'
];
if
(!
empty
(
$factors
)
&&
!
empty
(
$nonce
)
&&
!
$expired
)
{
// TODO: check signature
// try to verify each configured factor
foreach
(
$factors
as
$factor
)
{
[
$method
]
=
explode
(
':'
,
$factor
,
2
);
// verify the submitted code
$code
=
rcube_utils
::
get_input_value
(
"_${nonce}_${method}"
,
rcube_utils
::
INPUT_POST
);
$this
->
login_verified
=
$this
->
verify_factor_auth
(
$factor
,
$code
,
$username
);
// accept first successful method
if
(
$this
->
login_verified
)
{
break
;
}
}
}
if
(
$this
->
login_verified
)
{
// restore POST data from session
$_POST
[
'_user'
]
=
$_SESSION
[
'username'
];
$_POST
[
'_host'
]
=
$_SESSION
[
'host'
];
$_POST
[
'_pass'
]
=
$rcmail
->
decrypt
(
$_SESSION
[
'password'
]);
if
(!
empty
(
$_SESSION
[
'kolab_auth_admin'
]))
{
$_POST
[
'_user'
]
=
$_SESSION
[
'kolab_auth_admin'
];
$_POST
[
'_loginas'
]
=
$_SESSION
[
'username'
];
}
}
// proceed with regular login ...
$args
[
'action'
]
=
'login'
;
// session data will be reset in index.php thus additional
// auth attempts with intercepted data will be rejected
// $rcmail->kill_session();
// we can't display any custom error messages on failed login
// but that's actually desired to expose as little information as possible
return
$args
;
}
/**
* Helper method to verify the given method/code tuple
*/
protected
function
verify_factor_auth
(
$method
,
$code
,
$username
)
{
if
(
strlen
(
$code
)
&&
(
$driver
=
$this
->
get_driver
(
$method
,
$username
)))
{
try
{
// verify the submitted code
return
$driver
->
verify
(
$code
,
$_SESSION
[
'kolab_2fa_time'
]);
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
$e
,
true
,
false
);
}
}
return
false
;
}
/**
* Render 2nd factor authentication form in place of the regular login form
*/
public
function
auth_form
(
$attrib
=
[])
{
$form_name
=
!
empty
(
$attrib
[
'form'
])
?
$attrib
[
'form'
]
:
'form'
;
$nonce
=
$_SESSION
[
'kolab_2fa_nonce'
];
$methods
=
array_unique
(
array_map
(
function
(
$factor
)
{
[
$method
,
$id
]
=
explode
(
':'
,
$factor
);
return
$method
;
},
$this
->
login_factors
));
// forward these values as the regular login screen would submit them
$input_task
=
new
html_hiddenfield
([
'name'
=>
'_task'
,
'value'
=>
'login'
]);
$input_action
=
new
html_hiddenfield
([
'name'
=>
'_action'
,
'value'
=>
'plugin.kolab-2fa-login'
]);
$input_tzone
=
new
html_hiddenfield
([
'name'
=>
'_timezone'
,
'id'
=>
'rcmlogintz'
,
'value'
=>
rcube_utils
::
get_input_value
(
'_timezone'
,
rcube_utils
::
INPUT_POST
)]);
$input_url
=
new
html_hiddenfield
([
'name'
=>
'_url'
,
'id'
=>
'rcmloginurl'
,
'value'
=>
rcube_utils
::
get_input_value
(
'_url'
,
rcube_utils
::
INPUT_POST
)]);
// create HTML table with two cols
$table
=
new
html_table
([
'cols'
=>
2
]);
$required
=
count
(
$methods
)
>
1
?
null
:
'required'
;
$row
=
0
;
// render input for each configured auth method
foreach
(
$methods
as
$i
=>
$method
)
{
if
(
$row
++
>
0
)
{
$table
->
add
(
[
'colspan'
=>
2
,
'class'
=>
'title hint'
,
'style'
=>
'text-align:center'
],
$this
->
gettext
(
'or'
)
);
}
$field_id
=
"rcmlogin2fa$method"
;
$input_code
=
new
html_inputfield
([
'name'
=>
"_${nonce}_${method}"
,
'class'
=>
'kolab2facode'
,
'id'
=>
$field_id
,
'required'
=>
$required
,
'autocomplete'
=>
'off'
,
'data-icon'
=>
'key'
,
// for Elastic
]
+
$attrib
);
$table
->
add
(
'title'
,
html
::
label
(
$field_id
,
html
::
quote
(
$this
->
gettext
(
$method
))));
$table
->
add
(
'input'
,
$input_code
->
show
(
''
));
}
$out
=
$input_task
->
show
();
$out
.=
$input_action
->
show
();
$out
.=
$input_tzone
->
show
();
$out
.=
$input_url
->
show
();
$out
.=
$table
->
show
();
// add submit button
if
(
rcube_utils
::
get_boolean
(
$attrib
[
'submit'
]))
{
$out
.=
html
::
p
(
'formbuttons'
,
html
::
tag
(
'button'
,
[
'type'
=>
'submit'
,
'id'
=>
'rcmloginsubmit'
,
'class'
=>
'button mainaction save'
,
],
$this
->
gettext
(
'continue'
))
);
}
// surround html output with a form tag
if
(
empty
(
$attrib
[
'form'
]))
{
$out
=
$this
->
api
->
output
->
form_tag
([
'name'
=>
$form_name
,
'method'
=>
'post'
],
$out
);
}
return
$out
;
}
/**
* Load driver class for the given authentication factor
*
* @param string $factor Factor identifier (<method>:<id>)
* @param string $username Username (email)
*
* @return Kolab2FA\Driver\Base|false
*/
public
function
get_driver
(
$factor
,
$username
=
null
)
{
[
$method
]
=
explode
(
':'
,
$factor
,
2
);
$rcmail
=
rcmail
::
get_instance
();
if
(!
empty
(
$this
->
drivers
[
$factor
]))
{
return
$this
->
drivers
[
$factor
];
}
$config
=
$rcmail
->
config
->
get
(
'kolab_2fa_'
.
$method
,
[]);
// use product name as "issuer"
if
(
empty
(
$config
[
'issuer'
]))
{
$config
[
'issuer'
]
=
$rcmail
->
config
->
get
(
'product_name'
);
}
if
(
empty
(
$username
)
&&
$rcmail
->
user
->
ID
)
{
$username
=
$rcmail
->
get_user_name
();
}
try
{
$storage
=
$this
->
get_storage
(
$username
);
$driver
=
\Kolab2FA\Driver\Base
::
factory
(
$storage
,
$factor
,
$config
);
$this
->
drivers
[
$factor
]
=
$driver
;
return
$driver
;
}
catch
(
Exception
$e
)
{
$error
=
strval
(
$e
);
}
rcube
::
raise_error
(
[
'code'
=>
600
,
'type'
=>
'php'
,
'file'
=>
__FILE__
,
'line'
=>
__LINE__
,
'message'
=>
$error
],
true
,
false
);
return
false
;
}
/**
* Getter for a storage instance singleton
*/
public
function
get_storage
(
$for
=
null
)
{
if
(!
isset
(
$this
->
storage
)
||
(!
empty
(
$for
)
&&
$this
->
storage
->
username
!==
$for
))
{
$rcmail
=
rcmail
::
get_instance
();
try
{
$this
->
storage
=
\Kolab2FA\Storage\Base
::
factory
(
$rcmail
->
config
->
get
(
'kolab_2fa_storage'
,
'roundcube'
),
$rcmail
->
config
->
get
(
'kolab_2fa_storage_config'
,
[])
);
$this
->
storage
->
set_username
(
$for
);
$this
->
storage
->
set_logger
(
new
\Kolab2FA\Log\RcubeLogger
());
// set user properties from active session
if
(!
empty
(
$_SESSION
[
'kolab_dn'
]))
{
$this
->
storage
->
userdn
=
$_SESSION
[
'kolab_dn'
];
}
}
catch
(
Exception
$e
)
{
$this
->
storage
=
false
;
rcube
::
raise_error
(
[
'code'
=>
600
,
'type'
=>
'php'
,
'file'
=>
__FILE__
,
'line'
=>
__LINE__
,
'message'
=>
$e
->
getMessage
()],
true
,
false
);
}
}
return
$this
->
storage
;
}
/**
* Handler for 'settings_actions' hook
*/
public
function
settings_actions
(
$args
)
{
// register as settings action
$args
[
'actions'
][]
=
[
'action'
=>
'plugin.kolab-2fa'
,
'class'
=>
'twofactorauth'
,
'label'
=>
'settingslist'
,
'title'
=>
'settingstitle'
,
'domain'
=>
'kolab_2fa'
,
];
return
$args
;
}
/**
* Handler for settings/plugin.kolab-2fa requests
*/
public
function
settings_view
()
{
$this
->
register_handler
(
'plugin.settingsform'
,
[
$this
,
'settings_form'
]);
$this
->
register_handler
(
'plugin.settingslist'
,
[
$this
,
'settings_list'
]);
$this
->
register_handler
(
'plugin.factoradder'
,
[
$this
,
'settings_factoradder'
]);
$this
->
register_handler
(
'plugin.highsecuritydialog'
,
[
$this
,
'settings_highsecuritydialog'
]);
$this
->
include_script
(
'kolab2fa.js'
);
$this
->
include_stylesheet
(
$this
->
local_skin_path
()
.
'/kolab2fa.css'
);
$this
->
api
->
output
->
set_env
(
'session_secured'
,
$this
->
check_secure_mode
());
$this
->
api
->
output
->
add_label
(
'save'
,
'cancel'
);
$this
->
api
->
output
->
set_pagetitle
(
$this
->
gettext
(
'settingstitle'
));
$this
->
api
->
output
->
send
(
'kolab_2fa.config'
);
}
/**
* Render the menu to add another authentication factor
*/
public
function
settings_factoradder
(
$attrib
)
{
$rcmail
=
rcmail
::
get_instance
();
$attrib
[
'id'
]
=
'kolab2fa-add'
;
$select
=
new
html_select
(
$attrib
);
$select
->
add
(
$this
->
gettext
(
'addfactor'
)
.
'...'
,
''
);
foreach
((
array
)
$rcmail
->
config
->
get
(
'kolab_2fa_drivers'
,
[])
as
$method
)
{
$select
->
add
(
$this
->
gettext
(
$method
),
$method
);
}
return
$select
->
show
();
}
/**
* Render a list of active factor this user has configured
*/
public
function
settings_list
(
$attrib
=
[])
{
$attrib
[
'id'
]
=
'kolab2fa-factors'
;
$table
=
new
html_table
([
'cols'
=>
3
]);
$table
->
add_header
(
'name'
,
$this
->
gettext
(
'factor'
));
$table
->
add_header
(
'created'
,
$this
->
gettext
(
'created'
));
$table
->
add_header
(
'actions'
,
''
);
return
$table
->
show
(
$attrib
);
}
/**
* Render the settings form template object
*/
public
function
settings_form
(
$attrib
=
[])
{
$rcmail
=
rcmail
::
get_instance
();
$storage
=
$this
->
get_storage
(
$rcmail
->
get_user_name
());
$factors
=
$storage
?
(
array
)
$storage
->
enumerate
()
:
[];
$drivers
=
(
array
)
$rcmail
->
config
->
get
(
'kolab_2fa_drivers'
,
[]);
$out
=
''
;
$env_methods
=
[];
foreach
(
$drivers
as
$j
=>
$method
)
{
$out
.=
$this
->
settings_factor
(
$method
,
$attrib
);
$env_methods
[
$method
]
=
[
'name'
=>
$this
->
gettext
(
$method
),
'active'
=>
0
,
];
}
$me
=
$this
;
$factors
=
array_combine
(
$factors
,
array_map
(
function
(
$id
)
use
(
$me
,
&
$env_methods
)
{
$props
=
[
'id'
=>
$id
];
if
(
$driver
=
$me
->
get_driver
(
$id
))
{
$props
+=
$this
->
format_props
(
$driver
->
props
());
$props
[
'method'
]
=
$driver
->
method
;
$props
[
'name'
]
=
$me
->
gettext
(
$driver
->
method
);
$env_methods
[
$driver
->
method
][
'active'
]++;
}
return
$props
;
},
$factors
)
);
$this
->
api
->
output
->
set_env
(
'kolab_2fa_methods'
,
$env_methods
);
$this
->
api
->
output
->
set_env
(
'kolab_2fa_factors'
,
!
empty
(
$factors
)
?
$factors
:
null
);
return
html
::
div
([
'id'
=>
'kolab2fapropform'
],
$out
);
}
/**
* Render the settings UI for the given method/driver
*/
protected
function
settings_factor
(
$method
,
$attrib
)
{
$out
=
''
;
$rcmail
=
rcmail
::
get_instance
();
$attrib
+=
[
'class'
=>
'propform'
];
if
(
$driver
=
$this
->
get_driver
(
$method
))
{
$table
=
new
html_table
([
'cols'
=>
2
,
'class'
=>
$attrib
[
'class'
]]);
foreach
(
$driver
->
props
()
as
$field
=>
$prop
)
{
if
(!
$prop
[
'editable'
])
{
continue
;
}
switch
(
$prop
[
'type'
])
{
case
'boolean'
:
case
'checkbox'
:
$input
=
new
html_checkbox
([
'value'
=>
'1'
]);
break
;
case
'enum'
:
case
'select'
:
$input
=
new
html_select
([
'disabled'
=>
!
empty
(
$prop
[
'readonly'
])]);
$input
->
add
(
array_map
([
$this
,
'gettext'
],
$prop
[
'options'
]),
$prop
[
'options'
]);
break
;
default
:
$input
=
new
html_inputfield
([
'size'
=>
!
empty
(
$prop
[
'size'
])
?
$prop
[
'size'
]
:
30
,
'disabled'
=>
empty
(
$prop
[
'editable'
]),
]);
}
$explain_label
=
$field
.
'explain'
.
$method
;
$explain_html
=
$rcmail
->
text_exists
(
$explain_label
,
'kolab_2fa'
)
?
html
::
div
(
'explain form-text'
,
$this
->
gettext
(
$explain_label
))
:
''
;
$field_id
=
'rcmk2fa'
.
$method
.
$field
;
$table
->
add
(
'title'
,
html
::
label
(
$field_id
,
$this
->
gettext
(
$field
)));
$table
->
add
(
null
,
$input
->
show
(
''
,
[
'id'
=>
$field_id
,
'name'
=>
"_prop[$field]"
])
.
$explain_html
);
}
// add row for displaying the QR code
if
(
method_exists
(
$driver
,
'get_provisioning_uri'
))
{
$gif
=
'data:image/gif;base64,R0lGODlhDwAPAIAAAMDAwAAAACH5BAEAAAAALAAAAAAPAA8AQAINhI+py+0Po5y02otnAQA7'
;
$table
->
add
(
'title'
,
$this
->
gettext
(
'qrcode'
));
$table
->
add
(
'pl-3 pr-3'
,
html
::
div
(
'explain form-text'
,
$this
->
gettext
(
"qrcodeexplain$method"
))
.
html
::
tag
(
'img'
,
[
'src'
=>
$gif
,
'class'
=>
'qrcode mt-2'
,
'rel'
=>
$method
])
);
// add row for testing the factor
$field_id
=
'rcmk2faverify'
.
$method
;
$table
->
add
(
'title'
,
html
::
label
(
$field_id
,
$this
->
gettext
(
'verifycode'
)));
$table
->
add
(
null
,
html
::
tag
(
'input'
,
[
'type'
=>
'text'
,
'name'
=>
'_verify_code'
,
'id'
=>
$field_id
,
'class'
=>
'k2fa-verify'
,
'size'
=>
20
,
'required'
=>
true
])
.
html
::
div
(
'explain form-text'
,
$this
->
gettext
(
"verifycodeexplain$method"
))
);
}
$input_id
=
new
html_hiddenfield
([
'name'
=>
'_prop[id]'
,
'value'
=>
''
]);
$out
.=
html
::
tag
(
'form'
,
[
'method'
=>
'post'
,
'action'
=>
'#'
,
'id'
=>
'kolab2fa-prop-'
.
$method
,
'style'
=>
'display:none'
,
'class'
=>
'propform'
,
],
html
::
tag
(
'fieldset'
,
[],
html
::
tag
(
'legend'
,
[],
$this
->
gettext
(
$method
))
.
html
::
div
(
'factorprop'
,
$table
->
show
())
.
$input_id
->
show
()
)
);
}
return
$out
;
}
/**
* Render the high-security-dialog content
*/
public
function
settings_highsecuritydialog
(
$attrib
=
[])
{
$attrib
+=
[
'id'
=>
'kolab2fa-highsecuritydialog'
];
$field_id
=
'rcmk2facode'
;
$input
=
new
html_inputfield
([
'name'
=>
'_code'
,
'id'
=>
$field_id
,
'class'
=>
'verifycode'
,
'size'
=>
20
]);
$label
=
html
::
label
([
'for'
=>
$field_id
,
'class'
=>
'col-form-label col-sm-4'
],
'$name'
);
return
html
::
div
(
$attrib
,
html
::
div
(
'explain form-text'
,
$this
->
gettext
(
'highsecuritydialog'
))
.
html
::
div
(
'propform row form-group'
,
$label
.
html
::
div
(
'col-sm-8'
,
$input
->
show
(
''
)))
);
}
/**
* Handler for settings/plugin.kolab-2fa-save requests
*/
public
function
settings_save
()
{
$method
=
rcube_utils
::
get_input_value
(
'_method'
,
rcube_utils
::
INPUT_POST
);
$data
=
@
json_decode
(
rcube_utils
::
get_input_value
(
'_data'
,
rcube_utils
::
INPUT_POST
),
true
);
$rcmail
=
rcmail
::
get_instance
();
$storage
=
$this
->
get_storage
(
$rcmail
->
get_user_name
());
$success
=
false
;
$errors
=
0
;
$save_data
=
[];
if
(
$driver
=
$this
->
get_driver
(
$method
))
{
if
(
$data
===
false
)
{
if
(
$this
->
check_secure_mode
())
{
// remove method from active factors and clear stored settings
$success
=
$driver
->
clear
();
}
else
{
$errors
++;
}
}
else
{
// verify the submitted code before saving
$verify_code
=
rcube_utils
::
get_input_value
(
'_verify_code'
,
rcube_utils
::
INPUT_POST
);
$timestamp
=
intval
(
rcube_utils
::
get_input_value
(
'_timestamp'
,
rcube_utils
::
INPUT_POST
));
if
(!
empty
(
$verify_code
))
{
if
(!
$driver
->
verify
(
$verify_code
,
$timestamp
))
{
$this
->
api
->
output
->
command
(
'plugin.verify_response'
,
[
'id'
=>
$driver
->
id
,
'method'
=>
$driver
->
method
,
'success'
=>
false
,
'message'
=>
str_replace
(
'$method'
,
$this
->
gettext
(
$driver
->
method
),
$this
->
gettext
(
'codeverificationfailed'
)),
]);
$this
->
api
->
output
->
send
();
}
}
foreach
(
$data
as
$prop
=>
$value
)
{
if
(!
$driver
->
set
(
$prop
,
$value
))
{
$errors
++;
}
}
$driver
->
set
(
'active'
,
true
);
}
// commit changes to the user properties
if
(!
$errors
)
{
if
(
$success
=
$driver
->
commit
())
{
$save_data
=
$data
!==
false
?
$this
->
format_props
(
$driver
->
props
())
:
[];
}
else
{
$errors
++;
}
}
}
if
(
$success
)
{
$this
->
api
->
output
->
show_message
(
$data
===
false
?
$this
->
gettext
(
'factorremovesuccess'
)
:
$this
->
gettext
(
'factorsavesuccess'
),
'confirmation'
);
$this
->
api
->
output
->
command
(
'plugin.save_success'
,
[
'method'
=>
$method
,
'active'
=>
$data
!==
false
,
'id'
=>
$driver
->
id
]
+
$save_data
);
}
elseif
(
$errors
)
{
$this
->
api
->
output
->
show_message
(
$this
->
gettext
(
'factorsaveerror'
),
'error'
);
$this
->
api
->
output
->
command
(
'plugin.reset_form'
,
$data
!==
false
?
$method
:
null
);
}
$this
->
api
->
output
->
send
();
}
/**
* Handler for settings/plugin.kolab-2fa-data requests
*/
public
function
settings_data
()
{
$method
=
rcube_utils
::
get_input_value
(
'_method'
,
rcube_utils
::
INPUT_POST
);
if
(
$driver
=
$this
->
get_driver
(
$method
))
{
$data
=
[
'method'
=>
$method
,
'id'
=>
$driver
->
id
];
foreach
(
$driver
->
props
(
true
)
as
$field
=>
$prop
)
{
$data
[
$field
]
=
$prop
[
'text'
]
?:
$prop
[
'value'
];
}
// generate QR code for provisioning URI
if
(
method_exists
(
$driver
,
'get_provisioning_uri'
))
{
try
{
$uri
=
$driver
->
get_provisioning_uri
();
// Some OTP apps have an issue with algorithm character case
// So we make sure we use upper-case per the spec.
$uri
=
str_replace
(
'algorithm=sha'
,
'algorithm=SHA'
,
$uri
);
$qr
=
new
Endroid\QrCode\QrCode
();
$qr
->
setText
(
$uri
)
->
setSize
(
240
)
->
setPadding
(
10
)
->
setErrorCorrection
(
'high'
)
->
setForegroundColor
([
'r'
=>
0
,
'g'
=>
0
,
'b'
=>
0
,
'a'
=>
0
])
->
setBackgroundColor
([
'r'
=>
255
,
'g'
=>
255
,
'b'
=>
255
,
'a'
=>
0
]);
$data
[
'qrcode'
]
=
base64_encode
(
$qr
->
get
());
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
$e
,
true
,
false
);
}
}
$this
->
api
->
output
->
command
(
'plugin.render_data'
,
$data
);
}
$this
->
api
->
output
->
send
();
}
/**
* Handler for settings/plugin.kolab-2fa-verify requests
*/
public
function
settings_verify
()
{
$method
=
rcube_utils
::
get_input_value
(
'_method'
,
rcube_utils
::
INPUT_POST
);
$timestamp
=
intval
(
rcube_utils
::
get_input_value
(
'_timestamp'
,
rcube_utils
::
INPUT_POST
));
$success
=
false
;
if
(
$driver
=
$this
->
get_driver
(
$method
))
{
$data
=
@
json_decode
(
rcube_utils
::
get_input_value
(
'_data'
,
rcube_utils
::
INPUT_POST
),
true
);
if
(
is_array
(
$data
))
{
foreach
(
$data
as
$key
=>
$value
)
{
if
(
$value
!==
'******'
)
{
$driver
->
set
(
$key
,
$value
,
false
);
}
}
}
$success
=
$driver
->
verify
(
rcube_utils
::
get_input_value
(
'_code'
,
rcube_utils
::
INPUT_POST
),
$timestamp
);
$method
=
$driver
->
method
;
}
// put session into high-security mode
if
(
$success
&&
!
empty
(
$_POST
[
'_session'
]))
{
$_SESSION
[
'kolab_2fa_secure_mode'
]
=
time
();
}
$this
->
api
->
output
->
command
(
'plugin.verify_response'
,
[
'method'
=>
$method
,
'success'
=>
$success
,
'message'
=>
str_replace
(
'$method'
,
$this
->
gettext
(
$method
),
$this
->
gettext
(
$success
?
'codeverificationpassed'
:
'codeverificationfailed'
)
),
]);
$this
->
api
->
output
->
send
();
}
/**
*
*/
protected
function
format_props
(
$props
)
{
$rcmail
=
rcmail
::
get_instance
();
$values
=
[];
foreach
(
$props
as
$key
=>
$prop
)
{
switch
(
$prop
[
'type'
])
{
case
'datetime'
:
$value
=
$rcmail
->
format_date
(
$prop
[
'value'
]);
break
;
default
:
$value
=
$prop
[
'value'
];
}
$values
[
$key
]
=
$value
;
}
return
$values
;
}
/**
* Check whether the session is secured with 2FA (excluding the logon)
*/
protected
function
check_secure_mode
()
{
// Allow admins that used kolab_auth's "login as" feature to act without
// being asked for the user's second factor
if
(!
empty
(
$_SESSION
[
'kolab_auth_admin'
])
&&
!
empty
(
$_SESSION
[
'kolab_auth_password'
]))
{
return
true
;
}
if
(!
empty
(
$_SESSION
[
'kolab_2fa_secure_mode'
])
&&
$_SESSION
[
'kolab_2fa_secure_mode'
]
>
time
()
-
180
)
{
return
$_SESSION
[
'kolab_2fa_secure_mode'
];
}
return
false
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Sat, Apr 4, 2:49 AM (5 d, 2 h ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
a4/bb/e578b5c56901295dd81ae0752ab7
Default Alt Text
kolab_2fa.php (29 KB)
Attached To
Mode
rRPK roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline