Example #1
0
 /**
  * Process an authorization request.
  *
  * Operations:
  *     - Auto creates users.
  *     - Sets up user object for linked accounts.
  *
  * @param string $oidcuniqid The OIDC unique identifier received.
  * @param array $tokenparams Received token parameters.
  * @param \auth_oidc\jwt $idtoken Received id token.
  * @return bool Success/Failure.
  */
 public function request_user_authorise($oidcuniqid, $tokenparams, $idtoken)
 {
     global $USER, $SESSION;
     $this->must_be_ready();
     $username = $oidcuniqid;
     $email = $idtoken->claim('email');
     $firstname = $idtoken->claim('given_name');
     $lastname = $idtoken->claim('family_name');
     // Office 365 uses "upn".
     $upn = $idtoken->claim('upn');
     if (!empty($upn)) {
         $username = $upn;
         $email = $upn;
     }
     $create = false;
     try {
         $user = new \User();
         $user->find_by_instanceid_username($this->instanceid, $username, true);
         if ($user->get('suspendedcusr')) {
             die_info(get_string('accountsuspended', 'mahara', strftime(get_string('strftimedaydate'), $user->get('suspendedctime')), $user->get('suspendedreason')));
         }
     } catch (\AuthUnknownUserException $e) {
         if ($this->can_auto_create_users() === true) {
             $institution = new \Institution($this->institution);
             if ($institution->isFull()) {
                 throw new \XmlrpcClientException('OpenID Connect login attempt failed because the institution is full.');
             }
             $user = new \User();
             $create = true;
         } else {
             return false;
         }
     }
     if ($create === true) {
         $user->passwordchange = 0;
         $user->active = 1;
         $user->deleted = 0;
         $user->expiry = null;
         $user->expirymailsent = 0;
         $user->lastlogin = time();
         $user->firstname = $firstname;
         $user->lastname = $lastname;
         $user->email = $email;
         $user->authinstance = $this->instanceid;
         db_begin();
         $user->username = get_new_username($username);
         $user->id = create_user($user, array(), $this->institution, $this, $username);
         $userobj = $user->to_stdclass();
         $userarray = (array) $userobj;
         db_commit();
         $user = new User();
         $user->find_by_id($userobj->id);
     }
     $user->commit();
     $USER->reanimate($user->id, $this->instanceid);
     $SESSION->set('authinstance', $this->instanceid);
     return true;
 }
Example #2
0
 /**
  * Return an XHTML string for the setting
  * @return string Returns an XHTML string
  */
 public function output_html($data, $query = '')
 {
     $tokens = get_config('local_o365', 'systemtokens');
     $setuser = '';
     if (!empty($tokens)) {
         $tokens = unserialize($tokens);
         if (isset($tokens['idtoken'])) {
             try {
                 $idtoken = \auth_oidc\jwt::instance_from_encoded($tokens['idtoken']);
                 $setuser = $idtoken->claim('upn');
             } catch (\Exception $e) {
                 // There is a check below for an empty $setuser.
             }
         }
     }
     $settinghtml = '<input type="hidden" id="' . $this->get_id() . '" name="' . $this->get_full_name() . '" value="0" />';
     $setuserurl = new \moodle_url('/local/o365/acp.php', ['mode' => 'setsystemuser']);
     if (!empty($setuser)) {
         $settinghtml .= get_string('settings_systemapiuser_userset', 'local_o365', $setuser) . ' ';
         $settinghtml .= \html_writer::link($setuserurl, get_string('settings_systemapiuser_change', 'local_o365'));
     } else {
         $settinghtml .= get_string('settings_systemapiuser_usernotset', 'local_o365') . ' ';
         $settinghtml .= \html_writer::link($setuserurl, get_string('settings_systemapiuser_setuser', 'local_o365'));
     }
     return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description);
 }
Example #3
0
 /**
  * Test decode.
  *
  * @dataProvider dataprovider_decode
  */
 public function test_decode($encodedjwt, $expectedresult, $expectedexception)
 {
     if (!empty($expectedexception)) {
         $this->setExpectedException($expectedexception[0], $expectedexception[1]);
     }
     $actualresult = \auth_oidc\jwt::decode($encodedjwt);
     $this->assertEquals($expectedresult, $actualresult);
 }
 /**
  * Detect the proper auth instance based on received user information.
  *
  * @param \auth_oidc\jwt $idtoken JWT ID Token.
  * @return int|null The auth instance ID if found, or null if none found.
  */
 protected function detect_auth_instance($idtoken)
 {
     // Get auth instance.
     $sql = 'SELECT ai.id as instanceid, i.priority as institutionpriority
               FROM {auth_instance} ai
               JOIN {institution} i ON i.name = ai.institution
              WHERE ai.authname = \'oidc\'
           ORDER BY i.priority DESC, ai.priority ASC';
     $instances = get_records_sql_array($sql);
     $catchalls = array();
     $instanceid = null;
     foreach ($instances as $instance) {
         $reqattr = get_config_plugin_instance('auth', $instance->instanceid, 'institutionattribute');
         $reqval = get_config_plugin_instance('auth', $instance->instanceid, 'institutionvalue');
         if (empty($reqattr) || empty($reqval)) {
             $catchalls[$instance->institutionpriority][] = $instance;
         } else {
             // Check if we received specified attribute.
             $userattrval = $idtoken->claim($reqattr);
             if (!empty($userattrval)) {
                 // Match value.
                 if (preg_match('#' . trim($reqval) . '#', $userattrval)) {
                     $instanceid = $instance->instanceid;
                     break;
                 }
             }
         }
     }
     // If no match on attribute, get the instance id of the first catchall by priority.
     if (empty($instanceid)) {
         foreach ($catchalls as $priority => $instances) {
             foreach ($instances as $instance) {
                 $instanceid = $instance->instanceid;
                 break;
             }
             break;
         }
     }
     return $instanceid;
 }
Example #5
0
 /**
  * Handle a login event.
  *
  * @param string $oidcuniqid A unique identifier for the user.
  * @param array $authparams Parameters receieved from the auth request.
  * @param array $tokenparams Parameters received from the token request.
  * @param \auth_oidc\jwt $idtoken A JWT object representing the received id_token.
  */
 protected function handlelogin($oidcuniqid, $authparams, $tokenparams, $idtoken)
 {
     global $DB, $CFG;
     $tokenrec = $DB->get_record('auth_oidc_token', ['oidcuniqid' => $oidcuniqid]);
     if (!empty($tokenrec)) {
         $username = $tokenrec->username;
         $this->updatetoken($tokenrec->id, $authparams, $tokenparams);
     } else {
         // Use 'upn' if available for username (Azure-specific), or fall back to lower-case oidcuniqid.
         $username = $idtoken->claim('upn');
         if (empty($username)) {
             $username = strtolower($oidcuniqid);
         }
         $matchedwith = $this->check_for_matched($username);
         if (!empty($matchedwith)) {
             $matchedwith->aadupn = $username;
             throw new \moodle_exception('errorusermatched', 'local_o365', null, $matchedwith);
         }
         $tokenrec = $this->createtoken($oidcuniqid, $username, $authparams, $tokenparams, $idtoken);
     }
     $existinguserparams = ['username' => $username, 'mnethostid' => $CFG->mnet_localhost_id];
     if ($DB->record_exists('user', $existinguserparams) !== true) {
         // User does not exist. Create user if site allows, otherwise fail.
         if (empty($CFG->authpreventaccountcreation)) {
             $user = create_user_record($username, null, 'oidc');
         } else {
             // Trigger login failed event.
             $failurereason = AUTH_LOGIN_NOUSER;
             $eventdata = ['other' => ['username' => $username, 'reason' => $failurereason]];
             $event = \core\event\user_login_failed::create($eventdata);
             $event->trigger();
             throw new \moodle_exception('errorauthloginfailednouser', 'auth_oidc');
         }
     }
     $user = authenticate_user_login($username, null, true);
     if (empty($user)) {
         throw new \moodle_exception('errorauthloginfailednouser', 'auth_oidc');
     }
     complete_user_login($user);
     return true;
 }
 /**
  * Return an XHTML string for the setting
  * @return string Returns an XHTML string
  */
 public function output_html($data, $query = '')
 {
     global $OUTPUT;
     $tokens = get_config('local_o365', 'systemtokens');
     $setuser = '';
     if (!empty($tokens)) {
         $tokens = unserialize($tokens);
         if (isset($tokens['idtoken'])) {
             try {
                 $idtoken = \auth_oidc\jwt::instance_from_encoded($tokens['idtoken']);
                 $setuser = $idtoken->claim('upn');
             } catch (\Exception $e) {
                 // There is a check below for an empty $setuser.
             }
         }
     }
     $settinghtml = '<input type="hidden" id="' . $this->get_id() . '" name="' . $this->get_full_name() . '" value="0" />';
     $setuserurl = new \moodle_url('/local/o365/acp.php', ['mode' => 'setsystemuser']);
     if (!empty($setuser)) {
         $message = \html_writer::tag('span', get_string('settings_systemapiuser_userset', 'local_o365', $setuser)) . ' ';
         $linkstr = get_string('settings_systemapiuser_change', 'local_o365');
         $message .= \html_writer::link($setuserurl, $linkstr, ['class' => 'btn', 'style' => 'margin-left: 0.5rem']);
         $messageattrs = ['class' => 'local_o365_statusmessage alert-success'];
         $icon = $OUTPUT->pix_icon('t/check', 'success', 'moodle');
         $settinghtml .= \html_writer::tag('div', $icon . $message, $messageattrs);
     } else {
         $message = \html_writer::tag('span', get_string('settings_systemapiuser_usernotset', 'local_o365')) . ' ';
         $linkstr = get_string('settings_systemapiuser_setuser', 'local_o365');
         $message .= \html_writer::link($setuserurl, $linkstr, ['class' => 'btn', 'style' => 'margin-left: 0.5rem']);
         $messageattrs = ['class' => 'local_o365_statusmessage alert-info'];
         $icon = $OUTPUT->pix_icon('i/warning', 'warning', 'moodle');
         $settinghtml .= \html_writer::tag('div', $icon . $message, $messageattrs);
     }
     return format_admin_setting($this, $this->visiblename, $settinghtml, $this->description);
 }
Example #7
0
 /**
  * Create a token for a user, thus linking a Moodle user to an OpenID Connect user.
  *
  * @param string $oidcuniqid A unique identifier for the user.
  * @param array $username The username of the Moodle user to link to.
  * @param array $authparams Parameters receieved from the auth request.
  * @param array $tokenparams Parameters received from the token request.
  * @param \auth_oidc\jwt $idtoken A JWT object representing the received id_token.
  * @return \stdClass The created token database record.
  */
 protected function createtoken($oidcuniqid, $username, $authparams, $tokenparams, \auth_oidc\jwt $idtoken)
 {
     global $DB;
     // Determine remote username. Use 'upn' if available (Azure-specific), or fall back to standard 'sub'.
     $oidcusername = $idtoken->claim('upn');
     if (empty($oidcusername)) {
         $oidcusername = $idtoken->claim('sub');
     }
     // We should not fail here (idtoken was verified earlier to at least contain 'sub', but just in case...).
     if (empty($oidcusername)) {
         throw new \moodle_exception('errorauthinvalididtoken', 'auth_oidc');
     }
     $tokenrec = new \stdClass();
     $tokenrec->oidcuniqid = $oidcuniqid;
     $tokenrec->username = $username;
     $tokenrec->oidcusername = $oidcusername;
     $tokenrec->scope = $tokenparams['scope'];
     $tokenrec->resource = $tokenparams['resource'];
     $tokenrec->authcode = $authparams['code'];
     $tokenrec->token = $tokenparams['access_token'];
     $tokenrec->expiry = $tokenparams['expires_on'];
     $tokenrec->refreshtoken = $tokenparams['refresh_token'];
     $tokenrec->idtoken = $tokenparams['id_token'];
     $tokenrec->id = $DB->insert_record('auth_oidc_token', $tokenrec);
     return $tokenrec;
 }
Example #8
0
/**
 * Update plugin.
 *
 * @param int $oldversion the version we are upgrading from
 * @return bool result
 */
function xmldb_auth_oidc_upgrade($oldversion)
{
    global $DB;
    $dbman = $DB->get_manager();
    $result = true;
    if ($result && $oldversion < 2014111703) {
        // Lengthen field.
        $table = new xmldb_table('auth_oidc_token');
        $field = new xmldb_field('scope', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username');
        $dbman->change_field_type($table, $field);
        upgrade_plugin_savepoint($result, '2014111703', 'auth', 'oidc');
    }
    if ($result && $oldversion < 2015012702) {
        $table = new xmldb_table('auth_oidc_state');
        $field = new xmldb_field('additionaldata', XMLDB_TYPE_TEXT, null, null, null, null, null, 'timecreated');
        if (!$dbman->field_exists($table, $field)) {
            $dbman->add_field($table, $field);
        }
        upgrade_plugin_savepoint($result, '2015012702', 'auth', 'oidc');
    }
    if ($result && $oldversion < 2015012703) {
        $table = new xmldb_table('auth_oidc_token');
        $field = new xmldb_field('oidcusername', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null, 'username');
        if (!$dbman->field_exists($table, $field)) {
            $dbman->add_field($table, $field);
        }
        upgrade_plugin_savepoint($result, '2015012703', 'auth', 'oidc');
    }
    if ($result && $oldversion < 2015012704) {
        // Update OIDC users.
        $sql = 'SELECT u.id as userid,
                       u.username as username,
                       tok.id as tokenid,
                       tok.oidcuniqid as oidcuniqid,
                       tok.idtoken as idtoken,
                       tok.oidcusername as oidcusername
                  FROM {auth_oidc_token} tok
                  JOIN {user} u ON u.username = tok.username
                 WHERE u.auth = ? AND deleted = 0';
        $params = ['oidc'];
        $userstoupdate = $DB->get_recordset_sql($sql, $params);
        foreach ($userstoupdate as $user) {
            if (empty($user->idtoken)) {
                continue;
            }
            try {
                // Decode idtoken and determine oidc username.
                $idtoken = \auth_oidc\jwt::instance_from_encoded($user->idtoken);
                $oidcusername = $idtoken->claim('upn');
                if (empty($oidcusername)) {
                    $oidcusername = $idtoken->claim('sub');
                }
                // Populate token oidcusername.
                if (empty($user->oidcusername)) {
                    $updatedtoken = new \stdClass();
                    $updatedtoken->id = $user->tokenid;
                    $updatedtoken->oidcusername = $oidcusername;
                    $DB->update_record('auth_oidc_token', $updatedtoken);
                }
                // Update user username (if applicable), so user can use rocreds loginflow.
                if ($user->username == strtolower($user->oidcuniqid)) {
                    // Old username, update to upn/sub.
                    if ($oidcusername != $user->username) {
                        // Update username.
                        $updateduser = new \stdClass();
                        $updateduser->id = $user->userid;
                        $updateduser->username = $oidcusername;
                        $DB->update_record('user', $updateduser);
                        $updatedtoken = new \stdClass();
                        $updatedtoken->id = $user->tokenid;
                        $updatedtoken->username = $oidcusername;
                        $DB->update_record('auth_oidc_token', $updatedtoken);
                    }
                }
            } catch (\Exception $e) {
                continue;
            }
        }
        upgrade_plugin_savepoint($result, '2015012704', 'auth', 'oidc');
    }
    if ($result && $oldversion < 2015012707) {
        if (!$dbman->table_exists('auth_oidc_prevlogin')) {
            $dbman->install_one_table_from_xmldb_file(__DIR__ . '/install.xml', 'auth_oidc_prevlogin');
        }
        upgrade_plugin_savepoint($result, '2015012707', 'auth', 'oidc');
    }
    if ($result && $oldversion < 2015012710) {
        // Lengthen field.
        $table = new xmldb_table('auth_oidc_token');
        $field = new xmldb_field('scope', XMLDB_TYPE_TEXT, null, null, null, null, null, 'oidcusername');
        $dbman->change_field_type($table, $field);
        upgrade_plugin_savepoint($result, '2015012710', 'auth', 'oidc');
    }
    return $result;
}
Example #9
0
 /**
  * Process an idtoken, extract uniqid and construct jwt object.
  *
  * @param string $idtoken Encoded id token.
  * @param string $orignonce Original nonce to validate received nonce against.
  * @return array List of oidcuniqid and constructed idtoken jwt.
  */
 protected function process_idtoken($idtoken, $orignonce = '')
 {
     // Decode and verify idtoken.
     $idtoken = \auth_oidc\jwt::instance_from_encoded($idtoken);
     $sub = $idtoken->claim('sub');
     if (empty($sub)) {
         throw new \AuthInstanceException(get_string('errorauthinvalididtoken', 'auth.oidc'));
     }
     $receivednonce = $idtoken->claim('nonce');
     if (!empty($orignonce) && (empty($receivednonce) || $receivednonce !== $orignonce)) {
         throw new \AuthInstanceException(get_string('errorauthinvalididtoken', 'auth.oidc'));
     }
     // Use 'oid' if available (Azure-specific), or fall back to standard "sub" claim.
     $oidcuniqid = $idtoken->claim('oid');
     if (empty($oidcuniqid)) {
         $oidcuniqid = $idtoken->claim('sub');
     }
     return array($oidcuniqid, $idtoken);
 }