Commit 614a443c authored by Cecilia Vela Gurovic's avatar Cecilia Vela Gurovic Committed by Gerrit Code Review

Merge changes from topic 'WR325528'

* changes:
  Bug 1855560: SAML role prefix to only allow certain users login access
  Bug 1855622: Create institution on SAML login if needed
parents ffbade6d 2641c9be
......@@ -271,10 +271,108 @@ function auth_saml_loginlink_submit(Pieform $form, $values) {
* @return object authinstance record
*/
function auth_saml_find_authinstance($saml_attributes) {
// find the one (it should be only one) that has the right field, and the right field value for institution
// find the one (it should be only one) that has the right field, and the right field value for institution
$instance = false;
$institutions = array();
if (get_config('saml_create_institution') && get_config('saml_create_institution_default')) {
// We can try and make the institution if it doesn't exist
// First check if there is another institution that we can get default saml metadata from
// This institution will be defined in the config
$configs = false;
foreach ($defaults = explode(',', get_config('saml_create_institution_default')) as $default) {
if (!record_exists('institution', 'name', $default)) {
// institution does not exist
continue;
}
$auths = get_column_sql("SELECT ai.id FROM {auth_instance_config} aic
JOIN {auth_instance} ai ON ai.id = aic.instance
WHERE ai.authname = ? AND ai.active = 1
AND aic.field = 'institutionidpentityid' AND aic.value IS NOT NULL
AND ai.institution = ?", array('saml', $default));
if ($auths) {
foreach ($auths as $auth) {
// Select 'field' first so it is used as the array key
if ($configs = get_records_sql_assoc("SELECT field, value, instance FROM {auth_instance_config}
WHERE instance = ?", array($auth))) {
if (isset($saml_attributes[$configs['institutionattribute']->value]) &&
isset($saml_attributes[$configs['user_attribute']->value])) {
// Institution default SAML found
break;
}
else {
$configs = false;
continue;
}
}
}
}
}
// now we have the default configs
if ($configs) {
// Check there is a roleprefix set to see if they are allowed to try and login
// We do this here to avoid making an institution for a user that can't login
if (isset($configs['roleprefix']->value)) {
$roleallowed = false;
foreach ($saml_attributes[$configs['role']->value] as $index => $role) {
if (preg_match('/^' . $configs['roleprefix']->value . '/', $role)) {
$roleallowed = true;
}
}
if (!$roleallowed) {
log_debug('User authorisation request from SAML failed - no roles prefixed with "' . $configs['roleprefix']->value . '"');
return false;
}
}
foreach ($saml_attributes[$configs['institutionattribute']->value] as $index => $attr) {
// does this institution use a regex match against the institution check value?
if ($configvalue = $configs['institutionregex']->value) {
$is_regex = (boolean) $configvalue;
}
else {
$is_regex = false;
}
$instmatch = $is_regex ? "aic.value LIKE '%' || ? || '%'" : "aic.value = ?";
// If the institution exists and has active saml auth enabled
if ($inst = get_records_sql_array("SELECT ai.institution FROM {auth_instance} ai
JOIN {auth_instance_config} aic ON aic.instance = ai.id
WHERE aic.field = 'institutionvalue'
AND " . $instmatch . "
AND ai.authname = 'saml'
AND ai.active = 1", array($attr))) {
// All good so continue
log_debug('institution ' . $inst[0]->institution . ' is all ready');
continue;
}
else if ($inst = get_records_sql_array("SELECT ai.institution FROM {auth_instance} ai
JOIN {auth_instance_config} aic ON aic.instance = ai.id
WHERE aic.field = 'institutionvalue'
AND " . $instmatch . "
AND ai.authname = 'saml'", array($attr))) {
// Institution exists but SAML auth not active - so make it active
log_debug('institution ' . $inst[0]->institution . ' SAML auth is now active');
set_field('auth_instance', 'active', 1, 'institution', $inst[0]->institution, 'authname', 'saml');
}
// Because we can't find a SAML mapping of IdP institution to Mahara institution we will try and see if there is a Mahara institution with same name as IdP institution value
else if ($institution = get_record('institution', 'name', $attr)) {
// Institution exists but no SAML auth instance - so create one
add_institution_saml_auth($attr, $configs);
log_debug('institution ' . $attr . ' SAML auth is added');
}
else {
// Institution does not exist - create the institution and the SAML instance
$institution = institution_generate_name($attr);
$displayname = isset($configs['organisationname']) && !empty($saml_attributes[$configs['organisationname']->value][$index]) ? $saml_attributes[$configs['organisationname']->value][$index] : $attr;
$newinstitution = new Institution();
$newinstitution->initialise($institution, $displayname);
$newinstitution->commit();
add_institution_saml_auth($institution, $configs);
log_debug('institution ' . $attr . ' is now created and SAML auth is added');
}
}
}
}
// find all the possible institutions/auth instances of type saml
$instances = recordset_to_array(get_recordset_sql("SELECT * FROM {auth_instance_config} aic, {auth_instance} ai WHERE ai.id = aic.instance AND ai.authname = 'saml' AND ai.active = 1 AND aic.field = 'institutionattribute'"));
foreach ($instances as $row) {
......@@ -315,6 +413,25 @@ function auth_saml_find_authinstance($saml_attributes) {
return $instance;
}
function add_institution_saml_auth($institution, $configs=array()) {
$maxpriority = get_field_sql("SELECT MAX(priority) FROM {auth_instance} WHERE institution = ?", array($institution));
$priority = is_null($maxpriority) ? 0 : $maxpriority + 1;
$fordb = new stdClass();
$fordb->instancename = 'saml';
$fordb->priority = $priority;
$fordb->institution = $institution;
$fordb->authname = 'saml';
$fordb->active = 1;
$instanceid = insert_record('auth_instance', $fordb, 'id', true);
// Add in the configs for new institution saml auth
foreach ($configs as $k => $v) {
if ($k == 'institutionvalue') {
$v->value = $institution;
}
execute_sql("INSERT INTO {auth_instance_config} (instance, field, value) VALUES (?, ?, ?)", array($instanceid, $k, $v->value));
}
return $instanceid;
}
/**
* present the IdP discovery screen if there are more than one
......
......@@ -90,10 +90,12 @@ $string['samlfieldforstudentid'] = 'SSO field for student ID';
$string['samlfieldforavatar'] = 'SSO field for avatar icon';
$string['samlfieldforavatardescription'] = 'Supplied avatar needs to contain a base64 encoded image string';
$string['samlfieldforrole'] = 'SSO field for roles';
$string['samlfieldforroleprefix'] = 'SSO field for role prefix';
$string['samlfieldforrolesiteadmin'] = 'Role mapping for site administrator';
$string['samlfieldforrolesitestaff'] = 'Role mapping for site staff';
$string['samlfieldforroleinstadmin'] = 'Role mapping for institution administrator';
$string['samlfieldforroleinststaff'] = 'Role mapping for institution staff';
$string['samlfieldfororganisationname'] = 'SSO field for Organisation';
$string['samlfieldauthloginmsg'] = 'Wrong login message';
$string['spentityid'] = "Service Provider entityId";
$string['title'] = 'SAML';
......
<!-- @license http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later -->
<!-- @copyright For copyright information on Mahara, please see the README file distributed with this software. -->
<h3>SAML 2.0 field for role prefix</h3>
<p>If the Identity Provider (IdP) passes in role information for the
user logging in then you can set this 'prefix' field so that only
those roles starting with the prefix should be handled by Mahara.</p>
<p>This way the IdP can have different roles for different Service
Providers (SP) and if the user does not have any roles relating to
this prefix then they will not be allowed to login / create user
within Mahara.</p>
......@@ -94,10 +94,12 @@ class AuthSaml extends Auth {
$this->config['avatar'] = '';
$this->config['authloginmsg'] = '';
$this->config['role'] = '';
$this->config['roleprefix'] = '';
$this->config['rolesiteadmin'] = '';
$this->config['rolesitestaff'] = '';
$this->config['roleinstadmin'] = '';
$this->config['roleinststaff'] = '';
$this->config['organisationname'] = '';
$this->instanceid = $id;
if (!empty($id)) {
......@@ -157,6 +159,7 @@ class AuthSaml extends Auth {
$studentid = isset($attributes[$this->config['studentidfield']][0]) ? $attributes[$this->config['studentidfield']][0] : null;
$avatar = isset($attributes[$this->config['avatar']][0]) ? $attributes[$this->config['avatar']][0] : null;
$roles = isset($attributes[$this->config['role']]) ? $attributes[$this->config['role']] : array();
$roleprefix = isset($this->config['roleprefix']) ? $this->config['roleprefix'] : null;
$rolesiteadmin = isset($this->config['rolesiteadmin']) ? array_map('trim', explode(',', $this->config['rolesiteadmin'])) : array();
$rolesitestaff = isset($this->config['rolesitestaff']) ? array_map('trim', explode(',', $this->config['rolesitestaff'])) : array();
$roleinstadmin = isset($this->config['roleinstadmin']) ? array_map('trim', explode(',', $this->config['roleinstadmin'])) : array();
......@@ -165,7 +168,19 @@ class AuthSaml extends Auth {
$create = false;
$update = false;
// Check if a user needs a certain role to be allowed to login
if (!empty($roleprefix)) {
$roleallowed = false;
foreach ($roles as $index => $role) {
if (preg_match('/^' . $roleprefix . '/', $role)) {
$roleallowed = true;
}
}
if (!$roleallowed) {
log_debug('User authorisation request from SAML failed - no roles prefixed with "' . $roleprefix . '"');
return false;
}
}
// Retrieve a $user object. If that fails, create a blank one.
try {
$isremote = $this->config['remoteuser'] ? true : false;
......@@ -444,10 +459,12 @@ class PluginAuthSaml extends PluginAuth {
'firstnamefield' => '',
'surnamefield' => '',
'role' => '',
'roleprefix' => '',
'rolesiteadmin' => '',
'rolesitestaff' => '',
'roleinstadmin' => '',
'roleinststaff' => '',
'organisationname' => '',
'emailfield' => '',
'studentidfield' => '',
'updateuserinfoonlogin' => 1,
......@@ -1364,6 +1381,12 @@ EOF;
'defaultvalue' => self::$default_config['studentidfield'],
'help' => true,
),
'organisationname' => array(
'type' => 'text',
'title' => get_string('samlfieldfororganisationname', 'auth.saml'),
'defaultvalue' => self::$default_config['organisationname'],
'help' => false,
),
'avatar' => array(
'type' => 'text',
'title' => get_string('samlfieldforavatar', 'auth.saml'),
......@@ -1376,6 +1399,12 @@ EOF;
'defaultvalue' => self::$default_config['role'],
'help' => false,
),
'roleprefix' => array(
'type' => 'text',
'title' => get_string('samlfieldforroleprefix', 'auth.saml'),
'defaultvalue' => self::$default_config['roleprefix'],
'help' => true,
),
'rolesiteadmin' => array(
'type' => 'text',
'title' => get_string('samlfieldforrolesiteadmin', 'auth.saml'),
......@@ -1570,10 +1599,12 @@ EOF;
'emailfield' => $values['emailfield'],
'studentidfield' => $values['studentidfield'],
'role' => $values['role'],
'roleprefix' => trim($values['roleprefix']),
'rolesiteadmin' => $values['rolesiteadmin'],
'rolesitestaff' => $values['rolesitestaff'],
'roleinstadmin' => $values['roleinstadmin'],
'roleinststaff' => $values['roleinststaff'],
'organisationname' => $values['organisationname'],
'updateuserinfoonlogin' => $values['updateuserinfoonlogin'],
'institutionattribute' => $values['institutionattribute'],
'institutionvalue' => $values['institutionvalue'],
......
......@@ -831,3 +831,13 @@ $cfg->saml_log_attributes = false;
* To get passed this, by allowing just a unique identifier/username, we set the follwing flag
*/
// $cfg->saml_create_minimum_user=true;
/**
* Allow SAML to create an institution when creating a new user if the institution they need
* to belong to doesn't yet exist. The IdP can pass an institution display name through
* and be mapped via the SAML instance setting 'samlfieldfororganisationname'.
* To make a new institution you need to define what institution to fetch an existing SAML
* instance from to be used as the default 'template' SAML settings.
*/
//$cfg->saml_create_institution=true;
//$cfg->saml_create_institution_default = 'mahara';
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment