FS-Mod/StudiRegistration/SpecialStudiRegistration.php

397 lines
12 KiB
PHP

<?php
/**
*
* @file
* @ingroup Extensions
* @author Bennet Bleßmann
* @author Einhard Leichtfuß
* @copyright © 2020 Bennet Bleßmann, Einhard Leichtfuß
* @license GNU General Public Licence 2.0 or later
*/
if (! defined('MEDIAWIKI'))
{
echo("Not a valid entry point.\n");
die(1);
}
require_once(dirname(__FILE__) . "/StudiRegistration.php");
require_once(dirname(__FILE__) . "/StudiRegistrationCommon.php");
/**
* Provides the StudiRegistration form
* @ingroup SpecialPage
*/
class SpecialStudiRegistration extends SpecialPage
{
/**
* Constructor
*/
public function __construct()
{
parent::__construct('StudiRegistration');
}
/**
* Main execution function
*
* @param $par Mixed: Parameters passed to the page
*/
public function execute($par) {
$this->checkPermissions();
$out = $this->getOutput();
$request = $this->getRequest();
$submit = $request->getVal('submit');
if ($request->getVal('submit') !== null)
{
$stu_name = $request->getVal('stu-name');
$password = $request->getVal('password');
$password_rep = $request->getVal('password-rep');
$ml_req = ($request->getVal('newsletter') !== null);
$this->handle_submission($out, $stu_name, $password, $password_rep,
$ml_req);
}
else if ($request->getVal('validate') !== null)
{
$token = $request->getVal('token');
$this->perform_validation($out, $token);
}
else
{
$this->print_form($out);
}
}
function handle_submission($out, $stu_name, $password, $password_rep, $ml_req) {
if ($stu_name === null)
{
$out->addHTML($this->msg('studi-reg_error-no-username')->text());
$this->print_form($out);
return false;
}
if (! StudiRegistrationCommon::validate_stu_name($stu_name))
{
$out->addHTML($this->msg('studi-reg_error-format-username')
->text());
$this->print_form($out, $stu_name);
return false;
}
if (mb_strlen($password) < MIN_PW_LEN)
{
$out->addHTML($this->msg('studi-reg_error-too-short-password',
MIN_PW_LEN)->parse());
$this->print_form($out, $stu_name);
return false;
}
if (mb_strlen($password) > MAX_PW_LEN)
{
$out->addHTML($this->msg('studi-reg_error-too-long-password',
MAX_PW_LEN)->parse());
$this->print_form($out, $stu_name);
return false;
}
if ($password === null || $password !== $password_rep)
{
$out->addHTML($this->msg('studi-reg_error-different-passwords')
->parse());
$this->print_form($out, $stu_name);
return false;
}
try
{
$pw_hash = self::crypt_hash($password);
if (StudiRegistration::stu_exists($stu_name))
{
$out->addHTML(
$this->msg(
'studi-reg_error-format-username',
ACC_PREFIX . $stu_name
)->parse()
);
return false;
}
$token = bin2hex(random_bytes(TOKEN_BYTES));
$token_hash = hash(TOKEN_HASH_ALGO, $token);
if (! StudiRegistration::create_registration($stu_name, $pw_hash,
$token_hash, $ml_req))
{
$out->addHTML($this->msg(
'studi-reg_error-internal-registration-creation')
->parse());
$this->print_form($out, $stu_name);
return false;
}
if (! $this->send_mail($stu_name, $token))
{
$out->addHTML($this->msg(
'studi-reg_error-internal-confirmation-mail')
->parse());
$this->print_form($out, $stu_name);
return false;
}
else
{
$out->addHTML($this->msg('studi-reg_success-confirmation-mail',
$stu_name . MAIL_SUFFIX, TOKEN_EXPIRY_MINUTES)
->parse());
return true;
}
}
catch (Exception $err)
{
error_log("StudiRegistration Internal Error: {$err}");
$out->addHTML($this->msg('studi-reg_error-internal-no-desc')
->parse());
$this->print_form($out, $stu_name);
return false;
}
}
function send_mail($stu_name, $token) {
$recipient = $stu_name . MAIL_SUFFIX;
$subject = $this->msg('studi-reg_mail-subject')->plain();
$url_token = urlencode($token);
$query = $this->msg('studi-reg_mail-additional-query-params')->text();
$link = SELF_URL . "?validate=true&token={$url_token}{$query}";
$message_pre = [
$this->msg('studi-reg_mail-greeting')->plain(),
""
];
$message_main = $this->msg('studi-reg_mail-main',
ACC_PREFIX . $stu_name)->plain();
$message_post = [
"",
" {$link}",
"",
$this->msg('studi-reg_mail-note-on-services-link')->plain(),
"",
" " . $this->msg('studi-reg_mail-services-link')->plain(),
"",
$this->msg('studi-reg_mail-regards')->plain()
];
$message = implode("\r\n", $message_pre) . "\r\n"
. wordwrap($message_main, MAIL_LINE_LEN, "\r\n") . "\r\n"
. implode("\r\n", $message_post) . "\r\n";
$header = array(
'From' => 'no-reply@fs-infmath.uni-kiel.de',
'Reply-To' => 'fachschaft@fs-infmath.uni-kiel.de',
'Content-type' => 'text/plain; charset=utf-8',
// The default is 7bit, i.e. US-ASCII only.
// Note that 8bit is not required to be supported.
// An alternative would be base64, which is ugly.
'Content-Transfer-Encoding' => '8bit'
);
return mail($recipient, $subject, $message, $header);
}
function perform_validation($out, $token) {
if ($token === null)
{
$out->addHTML($this->msg('studi-reg_error-no-token')->parse());
return false;
}
if (strlen($token) !== TOKEN_HEX_LEN)
{
$out->addHTML($this->msg('studi-reg_error-invalid-token-length')
->parse());
return false;
}
$token_hash = hash(TOKEN_HASH_ALGO, $token);
try
{
$acc_name = StudiRegistration::complete_registration($token_hash,
$ml_error);
if ($acc_name !== false)
{
$this->print_success($out, $acc_name, $ml_error);
return true;
}
else
{
$out->addHTML($this->msg('studi-reg_error-invalid-token')
->parse());
return false;
}
}
catch(Exception $err)
{
error_log("StudiRegistration Internal Error: {$err}");
$out->addHTML($this->msg('studi-reg_error-internal-no-desc')
->parse());
$this->print_form($out);
return false;
}
}
/**
* Hash a password with a random salt using SHA512.
*/
static function crypt_hash($password) {
// SHA512 requires a salt of (up to) 16 random characters in the set
// [a-zA-Z0-9./]. See crypt(3). base64 has a very similar character
// set (and an input-to-output ratio of 3-to-4).
$salt = base64_encode(random_bytes(12));
$salt = str_replace("+", ".", $salt);
// $6$ stands for SHA512.
return crypt($password, '$6$' . $salt);
}
function print_form($out, $stu_name = "") {
// heredocs cannot handle constants.
$max_stu_len = MAX_STU_LEN;
$acc_prefix = ACC_PREFIX;
$stu_name_prefix = STU_NAME_PREFIX;
$min_pw_len = MIN_PW_LEN;
$max_pw_len = MAX_PW_LEN;
$acc_prefix = ACC_PREFIX;
$msg_title = $this->msg("studi-reg_registration-title")->parse();
$msg_otherlang_info = $this->msg("studi-reg_otherlang-info")->plain();
$msg_note_not_for_ifi =
$this->msg("studi-reg_note-not-for-ifi")->plain();
$msg_stu_number = $this->msg("studi-reg_stu-number")->parse();
$msg_password = $this->msg("studi-reg_password")->parse();
$msg_repeat = $this->msg("studi-reg_repeat")->parse();
$msg_newsletter = $this->msg("studi-reg_newsletter")->parse();
$msg_privacy_note = $this->msg("studi-reg_privacy-note")->plain();
$msg_pw_guide_url = $this->msg("studi-reg_pw-guide-url")->plain();
$msg_password_assertion = $this->msg("studi-reg_password-assertion")
->plaintextParams($msg_pw_guide_url)
->text();
$msg_notes = $this->msg("studi-reg_notes")->text();
$msg_note1 = $this->msg("studi-reg_note1", ACC_PREFIX, STU_NAME_PREFIX)
->text();
$msg_note_target_mail_address =
$this->msg("studi-reg_note-target-mail-address")->parse();
$msg_note2 = $this->msg("studi-reg_note2")
->numParams(MIN_PW_LEN, MAX_PW_LEN)
->plaintextParams($msg_pw_guide_url)
->text();
$msg_note3 = $this->msg("studi-reg_note3")->plain();
$out->addHTML(<<<"EOF"
<h2>{$msg_title}</h2>
<p><em>{$msg_otherlang_info}</em></p>
<p>{$msg_note_not_for_ifi}</p>
<br />
<form method='POST' accept-charset='UTF-8'>
<table cellspacing='10'>
<tbody>
<tr>
<td><label for='stu-name'>{$msg_stu_number}</label>:</td>
<td align='right' style='padding: 1px; padding-left: 20px;'><strong>{$acc_prefix}</strong><input id='stu-name' type='name' name='stu-name' maxlength='{$max_stu_len}' value='{$stu_name}' placeholder='{$stu_name_prefix}XXXXXX' required='required' />
</tr>
<tr>
<td><label for='password'>{$msg_password}</label>:</td>
<td align='right' style='padding: 1px; padding-left: 20px;'><input id='password' type='password' name='password' minlength='{$min_pw_len}' maxlength='{$max_pw_len}' required='required' /></td>
</tr>
<tr>
<td><label for='password-rep'>{$msg_password} ({$msg_repeat})</label>:</td>
<td align='right' style='padding: 1px; padding-left: 20px;'><input id='password-rep' type='password' name='password-rep' maxlength='{$max_pw_len}' required='required' /></td>
</tr>
</tbody>
</table>
<br />
<label for='newsletter'>
<input type='checkbox' name='newsletter' value='subscribe' />
{$msg_newsletter}
</label><br />
<p>{$msg_privacy_note}</p>
<p>{$msg_password_assertion}</p>
<br />
<input type='submit' name='submit' />
<br />
<h3>{$msg_notes}:</h3>
<ul>
<li>{$msg_note1}</li>
<li>{$msg_note_target_mail_address}</li>
<li>{$msg_note2}</li>
<li>{$msg_note3}</li>
</ul>
</form>
EOF
);
}
function print_success($out, $acc_name, $ml_error) {
$stu_name = substr($acc_name, strlen(STU_NAME_PREFIX));
if ($ml_error)
{
$ml_err_msg = <<< "EOF"
<p>
{$this->msg('studi-reg_error-newsletter-subscribe')->parse()}
</p>
EOF;
}
else
{
$ml_err_msg = '';
}
$out->addHTML(<<<"EOF"
<p>
{$this->msg('studi-reg_success-confirmed-account', $acc_name)->text()}
</p>
{$ml_err_msg}
<p>
{$this->msg('studi-reg_success-confirmed-active', $acc_name)->text()}
{$this->msg('studi-reg_success-confirmed-chat', $acc_name)->text()}
{$this->msg('studi-reg_success-confirmed-list', $acc_name)->text()}
</p>
<p>
{$this->msg('studi-reg_success-confirmation-note', $acc_name, $stu_name)->text()}
</p>
EOF);
}
}
# vi: tw=80 fo+=t ts=8 sts=0 et sw=4 sta ai
?>