Skip to content
Draft

2fa #204

Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions forum/qa-plugin/q2a-googleauthenticator-login/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "event15/q2a-googleauthenticator-login",
"type": "library",
"license": "GPLv2",
"authors": [
{
"name": "Marek Woś",
"email": "[email protected]"
}
],
"minimum-stability": "dev",
"autoload": {
"psr-4": {
"CodersCommunity\\": "src/"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

myślę, że namespace powinien być bardziej konkretny, tzn. zawierać jeszcze chociażby nazwę plugina, ale tu jest cała kwestia uzywania composera, opisałem w komentarzu

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

miałem na caly ten kod zupełnie inny pomysł z początku, ale zostałem uprzedzony i kod z obiektowego stał się q2a'owy. Przy takim stanie rzeczy przyjąłem to co już było i nie starałem się diametralnie wszystkiego zmieniać. Jeśli się uda dodać do composera głównego zależność a jej tu nie budować, to być może to zniknie, z resztą o ile mam dobrą pamięć, to ten namespace jest nieużyty, a na pewno nie tak, jak powinien.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nawet jeśli ten byłby nie użyty to CodersCommunity kojarzy się z nazwą całej organizacji forumowej, więc czemu ma należeć do jednego plugina. Tym bardziej już gdyby coś było w globalnym lepiej byłoby dać chociażby CodersCommunity\GoogleAuthenticatorLogin czy cokolwiek innego, co jednoznacznie wskazuje, że chodzi o dany plugin, nie całą grupę.

}
},
"require": {
"robthree/twofactorauth": "dev-master"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nie lepiej dla bezpieczeństwa wskazać konkretną wersję o jaką nam chodzi?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

}
}
71 changes: 71 additions & 0 deletions forum/qa-plugin/q2a-googleauthenticator-login/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions forum/qa-plugin/q2a-googleauthenticator-login/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "Google Authenticator 2-factor authentication for Q2A",
"description": "This plugin provides a Google 2FA security for users on forum",
"version": "1.0",
"date": "2018-05-30",
"author": "Marek Woś",
"author_uri": "http://github.com/event15",
"license": "GPLv2",
"update_uri": "Web address for Q2A to check for updates",
"min_q2a": "1.7",
"min_php": "5.4",
"load_order": "Bootstrap moment in which the plugin will be loaded"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

require_once GOOGLEAUTHENTICATOR_BASIC_PATH . '/src/Init.php';

class qa_html_theme_layer extends qa_html_theme_base
{
public function doctype()
{
parent::doctype();

if ('account' === $this->request && true === (bool) qa_opt('googleauthenticator_login')) {
$content = [
'tags' => 'method="post" action="' . qa_self_html() . '"',
'style' => 'wide',
'title' => qa_lang_html('plugin_2fa/title')
];

$content += $this->enablePlugin();

$this->content['form_2fa'] = $content;
qa_html_theme_base::doctype();
}
}

private function getUserQuery($userId)
{
// Return the user with the specified userid (should return one user or null)
$users = qa_db_read_all_assoc(
qa_db_query_sub(
'SELECT us.userid, us.2fa_enabled, us.email, us.handle, us.2fa_change_date, up.points FROM ^users us LEFT JOIN ^userpoints up ON us.userid = up.userid WHERE us.userid=$',
$userId
)
);

return empty($users) ? null : $users[0];
}

/**
* This method enables 2fa on selected user.
*
* @param $userId
* @param $isEnabled
* @param $secret
* @param $recoveryCode
*
* @return mixed
*/
private function updateUserEnable2FA($userId, $isEnabled, $secret = null, $recoveryCode = null)
{
$time = new DateTime('now');
$formatter = new IntlDateFormatter('pl_PL', IntlDateFormatter::SHORT, IntlDateFormatter::SHORT);
$formatter->setPattern('EEEE, dd MMMM yyyy, HH:mm:ss');

$result = qa_db_query_sub(
'UPDATE ^users SET 2fa_enabled=#, 2fa_change_date=$, 2fa_secret=$, 2fa_recovery_code=$ WHERE userid=#',
$isEnabled,
$formatter->format($time),
$secret,
$recoveryCode,
$userId
);

if (true === $result) {
return $isEnabled;
}

user_error('Nie udało się włączyć autoryzacji dwuetapowej. Zgłoś ten problem administratorowi.');
}

private function enablePlugin()
{
$userAccount = $this->getUser();
$userActive2fa = $userAccount['2fa_enabled'];

if (qa_clicked('doenable2fa')) {
$this->init = new Init();
$this->init->createSecret();
$recoveryCode = $this->init->getRandomRecoveryCode();
$secret = $this->init->getSecret();

$userActive2fa = $this->updateUserEnable2FA($userAccount['userid'], true, $secret, $recoveryCode);
} elseif (qa_clicked('dodisable2fa')) {
$userActive2fa = $this->updateUserEnable2FA($userAccount['userid'], false);
}

$userAccount = $this->getUser();
if (true === (bool) $userActive2fa) {

$result = $this->render2FAEnabledForm($userAccount['2fa_change_date']);

if (isset($this->init)) {
$note = qa_lang_html('plugin_2fa/2fa_data_info');
$note = str_replace('{{ QR_CODE }}', '<br><center><img src="' . $this->init->getQRCode() . '"></center><br>', $note);
$note = str_replace('{{ SECRET }}', '<code>' . $secret . '</code>', $note);
$note = str_replace('{{ RECOVERY_CODE }}', '<code>' . $recoveryCode . '</code>', $note);
$note = str_replace('{{ ERROR_START }}', '<br><div class="qa-error">', $note);
$note = str_replace('{{ ERROR_END }}', '</div><br>', $note);

$result['fields'][] = [
'style' => 'tall',
'type' => 'static',
'note' => $note
];
}

} else {
$result = $this->render2FADisabledForm($userAccount['2fa_change_date']);
}

return $result;
}

private function render2FAEnabledForm($date)
{
return [
'fields' => [
'old' => [
'label' => qa_lang_html('plugin_2fa/plugin_is_enabled'),
'tags' => 'name="oldpassword" disabled',
'value' => $date,
'type' => 'input'
],
],
'buttons' => [
'enable' => [
'label' => qa_lang_html('plugin_2fa/disable_plugin')
]
],
'hidden' => [
'dodisable2fa' => '1',
'code' => qa_get_form_security_code('2faform')
]
];
}

private function render2FADisabledForm($date)
{
return [
'fields' => [
'old' => [
'label' => qa_lang_html('plugin_2fa/plugin_is_disabled'),
'value' => '',
'type' => 'static'
]
],
'buttons' => [
'enable_2fa' => [
'label' => qa_lang_html('plugin_2fa/enable_plugin')
]
],
'hidden' => [
'doenable2fa' => '1',
'code' => qa_get_form_security_code('2faform')
]
];
}

/**
* @return null
*/
private function getUser()
{
$userId = qa_get_logged_in_userid();

if (!isset($userId)) {
qa_redirect('login');
}

$userAccount = $this->getUserQuery($userId);

return $userAccount;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

function qa_set_logged_in_user($userId, $handle = '', $remember = false, $source = null)
{
require_once QA_INCLUDE_DIR . 'app/cookies.php';

qa_start_session();

if (!isset($userId)) {
// logout

qa_report_event(
'u_logout',
qa_get_logged_in_userid(),
qa_get_logged_in_handle(),
qa_cookie_get()
);

qa_clear_session_cookie();
qa_clear_session_user();

return;
}

// login

require_once QA_INCLUDE_DIR . 'db/selects.php';

$result = qa_db_read_all_assoc(qa_db_query_sub(
'SELECT 2fa_enabled FROM ^users WHERE userid = #',
$userId
));

if (count($result) != 1) {
echo 'Invalid num_rows';
die;
}

$usingTwoFactorAuth = (bool) $result[0]['2fa_enabled'];

if ($usingTwoFactorAuth && '2fa' !== $source) {
echo '
<form method="post" id="form" action="./2fa-auth">
<input name="login" type="hidden" value="' . qa_post_text('emailhandle') . '">
<input name="password" type="hidden" value="' . qa_post_text('password') . '">
<input name="remember" type="hidden" value="' . qa_post_text('remember') . '">
<input name="redirect" type="hidden" value="' . $_GET['to'] . '">
</form>
Trwa przekierowanie...
<script>
document.getElementById("form").submit();
</script>
';
exit;
}

$userInfo = qa_db_single_select(
qa_db_user_account_selectspec(
$userId,
true
)
);

if (empty($userInfo['sessioncode']) || $source !== $userInfo['sessionsource'] && '2fa' !== $source) {
$sessionCode = qa_db_user_rand_sessioncode();

qa_db_user_set(
$userId,
[
'sessioncode' => $sessionCode,
'sessionsource' => $sessionSource,
]
);
} else {
$sessionCode = $userInfo['sessioncode'];
}

qa_db_user_logged_in($userId, qa_remote_ip_address());
qa_set_session_cookie($handle, $sessionCode, $remember);

qa_report_event('u_login', $userId, $userInfo['handle'], qa_cookie_get());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brak entera

Loading