Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • perun/perun-proxyidp/simplesamlphp-module-campusmultiauth
1 result
Show changes
Showing
with 704 additions and 29 deletions
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth\Fingerprint\Header;
class DNT extends \SimpleSAML\Module\campusmultiauth\Fingerprint\Header
{
protected function getHeaderName()
{
return 'HTTP_DNT';
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth\Fingerprint\Header;
class UpgradeInsecureRequests extends \SimpleSAML\Module\campusmultiauth\Fingerprint\Header
{
protected function getHeaderName()
{
return 'HTTP_UPGRADE_INSECURE_REQUESTS';
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth\Fingerprint\Header;
class XRequestedWith extends \SimpleSAML\Module\campusmultiauth\Fingerprint\Header
{
protected function getHeaderName()
{
return 'HTTP_X_REQUESTED_WITH';
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth\Fingerprint;
class HeaderPresenceAndOrder extends \SimpleSAML\Module\campusmultiauth\Fingerprint
{
private const HEADERS = ['client-ip', 'x-forwarded-for', 'x-forwarded', 'x-cluster-client-ip', 'forwarded-for',
'forwarded', 'via', 'accept', 'accept-charset', 'accept-encoding', 'accept-language', 'connection',
'cookie', 'content-length', 'host', 'referer', 'user-agent', 'x-requested-with', 'dnt',
'upgrade-insecure-requests', ];
public function getValue()
{
return array_keys(
array_filter(
getallheaders(),
function ($var) {
return in_array(strtolower($var), self::HEADERS, true);
},
ARRAY_FILTER_USE_KEY
)
);
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth;
class Fingerprinting
{
/**
* Hash algorithm used for browser fingerprint.
*/
private const HASH_ALG = 'sha512';
/**
* Class prefix for fingerprint bits.
*/
private const CLASS_PREFIX = '\\SimpleSAML\\Module\\campusmultiauth\\Fingerprint\\';
/**
* Bits of information used for the fingerprint.
*/
private const BITS = [
'Header\\Accept',
'Header\\AcceptCharset',
'Header\\AcceptEncoding',
'Header\\AcceptLanguage',
'Header\\Connection',
'Header\\DNT',
'Header\\XRequestedWith',
'Header\\UpgradeInsecureRequests',
'Browser\\Name',
'Browser\\Platform',
];
public static function getBrowserFingerprint()
{
$info = [];
foreach (self::BITS as $bit) {
$className = self::CLASS_PREFIX . $bit;
$info[$bit] = (new $className())->getValue();
}
return hash(self::HASH_ALG, serialize($info));
}
}
<?php
namespace SimpleSAML\Module\campusmultiauth\Security;
interface Cipher
{
public function __construct();
/**
* Encrypt the data.
*
* @return string
*/
public function encrypt(string $data);
/**
* Decrypt the data.
*
* @return might return false if data is currupted, string otherwise
*/
public function decrypt(string $data);
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth\Security;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Core\JWKSet;
use Jose\Component\Encryption\Compression\CompressionMethodManager;
use Jose\Component\Encryption\Compression\Deflate;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\Encryption\JWEDecrypter;
use Jose\Component\Encryption\JWELoader;
use Jose\Component\Encryption\Serializer\JSONFlattenedSerializer as JWEJSONFlattenedSerializer;
use Jose\Component\Encryption\Serializer\JWESerializerManager;
use Jose\Component\NestedToken\NestedTokenBuilder;
use Jose\Component\NestedToken\NestedTokenLoader;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\JWSLoader;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\JSONFlattenedSerializer as JWSJSONFlattenedSerializer;
use Jose\Component\Signature\Serializer\JWSSerializerManager;
use SimpleSAML\Configuration;
use SimpleSAML\Logger;
class JWTCipher implements Cipher
{
private $builder;
private $loader;
private $signature_key;
private $signature_keyset;
private $encryption_key;
private $encryption_keyset;
private $signature_algorithm;
private $encryption_algorithm;
private $keywrap_algorithm;
/**
* @override
*/
public function __construct()
{
$moduleConfig = Configuration::getOptionalConfig('module_campusmultiauth.php')
->getConfigItem('remember_me', []);
$signatureKey = $moduleConfig->getArray('signature_key');
$this->signature_key = new JWK($signatureKey);
$this->signature_keyset = JWKSet::createFromKeyData([
'keys' => [$signatureKey],
]);
$encryptionKey = $moduleConfig->getArray('encryption_key');
$this->encryption_key = new JWK($encryptionKey);
$this->encryption_keyset = JWKSet::createFromKeyData([
'keys' => [$encryptionKey],
]);
$this->signature_algorithm = self::getAlgorithm(
'Signature\\Algorithm',
$moduleConfig->getString('signature_algorithm', 'HS512')
);
$this->encryption_algorithm = self::getAlgorithm(
'Encryption\\Algorithm\\ContentEncryption',
$moduleConfig->getString('encryption_algorithm', 'A256GCM')
);
$this->keywrap_algorithm = self::getAlgorithm(
'Encryption\\Algorithm\\KeyEncryption',
$moduleConfig->getString('keywrap_algorithm', 'A256GCMKW')
);
}
/**
* @override
*/
public function encrypt(string $data)
{
if (!$this->builder) {
$this->builder = $this->getBuilder();
}
$token = $this->builder->create(
// The payload to protect
$data,
// A list of signatures
[[
'key' => $this->signature_key,
'protected_header' => [
'alg' => $this->signature_algorithm->name(),
],
]],
// The serialization mode for the JWS
'jws_json_flattened',
// The shared protected header
[
'alg' => $this->keywrap_algorithm->name(),
'enc' => $this->encryption_algorithm->name(),
],
// The shared unprotected header
[],
// A list of recipients
[[
'key' => $this->encryption_key,
'header' => [],
]],
// The serialization mode for the JWE.
'jwe_json_flattened'
);
Logger::debug(sprintf('Encrypted JWT: %s', $token));
return $token;
}
/**
* @override
*/
public function decrypt(string $data)
{
if (!$this->loader) {
$this->loader = $this->getLoader();
}
$jws = $this->loader->load($data, $this->encryption_keyset, $this->signature_keyset);
$payload = $jws->getPayload();
Logger::debug(sprintf('Decrypted JWT: %s', $payload));
return $payload;
}
private static function getAlgorithm($path, $className)
{
$classPath = sprintf('Jose\\Component\\%s\\%s', $path, $className);
if (!class_exists($classPath)) {
throw new \Exception('Invalid algorithm specified: ' . $classPath);
}
return new $classPath();
}
/**
* @return JWSBuilder
*/
private function getJWSBuilder()
{
$algorithmManager = new AlgorithmManager([$this->signature_algorithm]);
return new JWSBuilder($algorithmManager);
}
/**
* @return JWEBuilder
*/
private function getJWEBuilder()
{
$keyEncryptionAlgorithmManager = new AlgorithmManager([$this->keywrap_algorithm]);
$contentEncryptionAlgorithmManager = new AlgorithmManager([$this->encryption_algorithm]);
$compressionMethodManager = new CompressionMethodManager([new Deflate()]);
return new JWEBuilder(
$keyEncryptionAlgorithmManager,
$contentEncryptionAlgorithmManager,
$compressionMethodManager
);
}
/**
* @return NestedTokenBuilder
*/
private function getBuilder()
{
$jweBuilder = $this->getJWEBuilder();
$jwsBuilder = $this->getJWSBuilder();
$jweSerializerManager = new JWESerializerManager([new JWEJSONFlattenedSerializer()]);
$jwsSerializerManager = new JWSSerializerManager([new JWSJSONFlattenedSerializer()]);
return new NestedTokenBuilder($jweBuilder, $jweSerializerManager, $jwsBuilder, $jwsSerializerManager);
}
/**
* @return JWELoader
*/
private function getJWELoader()
{
$keyEncryptionAlgorithmManager = new AlgorithmManager([$this->keywrap_algorithm]);
$contentEncryptionAlgorithmManager = new AlgorithmManager([$this->encryption_algorithm]);
$compressionMethodManager = new CompressionMethodManager([new Deflate()]);
$jweDecrypter = new JWEDecrypter(
$keyEncryptionAlgorithmManager,
$contentEncryptionAlgorithmManager,
$compressionMethodManager
);
$serializerManager = new JWESerializerManager([new JWEJSONFlattenedSerializer()]);
return new JWELoader($serializerManager, $jweDecrypter, null);
}
/**
* @return JWSLoader
*/
private function getJWSLoader()
{
$algorithmManager = new AlgorithmManager([$this->signature_algorithm]);
$jwsVerifier = new JWSVerifier($algorithmManager);
$serializerManager = new JWSSerializerManager([new JWSJSONFlattenedSerializer()]);
return new JWSLoader($serializerManager, $jwsVerifier, null);
}
/**
* @return NestedTokenLoader
*/
private function getLoader()
{
$jweLoader = $this->getJWELoader();
$jwsLoader = $this->getJWSLoader();
return new NestedTokenLoader($jweLoader, $jwsLoader);
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\campusmultiauth;
use SimpleSAML\Configuration;
class Utils
{
public static function getInterfaceInstance($interface, $optionName, $defaultClassPath)
{
$config = Configuration::getOptionalConfig('module_campusmultiauth.php')->getConfigItem('remember_me', []);
$classPath = $config->getString($optionName, $defaultClassPath);
if (!in_array($interface, class_implements($classPath), true)) {
throw new \Exception('Invalid ' . $optionName . ' specified: ' . $classPath);
}
return new $classPath();
}
public static function getSecurityImageOfUser($username)
{
$storage = self::getInterfaceInstance(
'SimpleSAML\\Module\\campusmultiauth\\Data\\Storage',
'storageClass',
'SimpleSAML\\Module\\campusmultiauth\\Data\\DatabaseStorage'
);
return $storage->getSecurityImageOfUser($username);
}
}
......@@ -78,3 +78,6 @@ msgstr "Z důvodu neaktivity je nutné stránku obnovit."
msgid "{campusmultiauth:refresh}"
msgstr "obnovit"
msgid "{campusmultiauth:different_account}"
msgstr "Přihlásit se jiným účtem"
......@@ -78,3 +78,6 @@ msgstr "Because of inactivity, it is needed to refresh the page."
msgid "{campusmultiauth:refresh}"
msgstr "refresh"
msgid "{campusmultiauth:different_account}"
msgstr "Use a different account to log in"
......@@ -12,6 +12,10 @@
{% endif %}
</h4>
{% if securityImage is defined %}
<img src='{{ securityImage|escape }}' class='security-image' alt=''>
{% endif %}
{% if wrongUserPass == true %}
{% if muni_jvs %}
<div class="message message--error" role="alert">
......@@ -28,22 +32,40 @@
{% endif %}
<div class="margin-bottom-24 text-left{% if wrongUserPass and muni_jvs %} error{% endif %}">
<label for="username" class="{% if muni_jvs %}color-{{ configuration.priority }}{% else %}form-label text-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}">
{% if attribute(configuration.username_label, currentLanguage) is defined %}{{ attribute(configuration.username_label, currentLanguage) }}
{% elseif configuration.username_label is defined and configuration.username_label is iterable and configuration.username_label is not empty %}{{ configuration.username_label | first }}
{% elseif configuration.username_label is defined and configuration.username_label is not iterable %}{{ configuration.username_label }}
{% else %}{{ '{campusmultiauth:username_label}'|trans }}
{% endif %}
</label>
<br>
{% if muni_jvs %}<span class="inp-fix">{% endif %}
<input id="username" class="{% if muni_jvs %}inp-text input-height border color-border-{{ configuration.priority }}{% else %}input-height form-control shadow-sm border-2{% if wrongUserPass %} is-invalid{% else %} border-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}{% endif %}"
type="text" name="username"{% if cookie_username is not null %} value="{{ cookie_username }}"{% endif %} placeholder="{% if attribute(configuration.username_placeholder, currentLanguage) is defined %}{{ attribute(configuration.username_placeholder, currentLanguage) }}
{% elseif configuration.username_placeholder is defined and configuration.username_placeholder is iterable and configuration.username_placeholder is not empty %}{{ configuration.username_placeholder | first }}
{% elseif configuration.username_placeholder is defined and configuration.username_placeholder is not iterable %}{{ configuration.username_placeholder }}
{% else %}{{ '{campusmultiauth:username_placeholder}'|trans }}
{% endif %}" required>
{% if muni_jvs %}</span>{% endif %}
{% if userInfo is same as false %}
<label for="username" class="{% if muni_jvs %}color-{{ configuration.priority }}{% else %}form-label text-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}">
{% if attribute(configuration.username_label, currentLanguage) is defined %}{{ attribute(configuration.username_label, currentLanguage) }}
{% elseif configuration.username_label is defined and configuration.username_label is iterable and configuration.username_label is not empty %}{{ configuration.username_label | first }}
{% elseif configuration.username_label is defined and configuration.username_label is not iterable %}{{ configuration.username_label }}
{% else %}{{ '{campusmultiauth:username_label}'|trans }}
{% endif %}
</label>
<br>
{% if muni_jvs %}<span class="inp-fix">{% endif %}
<input id="username" class="{% if muni_jvs %}inp-text input-height border color-border-{{ configuration.priority }}{% else %}input-height form-control shadow-sm border-2{% if wrongUserPass %} is-invalid{% else %} border-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}{% endif %}"
type="text"{% if autofocus == 'username' %} autofocus{% endif%} name="username" placeholder="{% if attribute(configuration.username_placeholder, currentLanguage) is defined %}{{ attribute(configuration.username_placeholder, currentLanguage) }}
{% elseif configuration.username_placeholder is defined and configuration.username_placeholder is iterable and configuration.username_placeholder is not empty %}{{ configuration.username_placeholder | first }}
{% elseif configuration.username_placeholder is defined and configuration.username_placeholder is not iterable %}{{ configuration.username_placeholder }}
{% else %}{{ '{campusmultiauth:username_placeholder}'|trans }}
{% endif %}" required>
{% if muni_jvs %}</span>{% endif %}
{% else %}
<div class="{% if muni_jvs %}box-bg box-bg--white-border u-pt-30 u-pb-30 inp-fix inp-icon inp-icon--after color-border-{{ configuration.priority }}{% else %}border-2 solid py-4 ps-4 position-relative {% if configuration.priority == 'primary' %}border-dark{% else %}border-muted{% endif %}{% endif %}">
<strong id="remembered-name">{{ userInfo.name|escape }}</strong>
<br>
<small>{{ uidName }} {{ username|escape }}</small>
<input class="hidden" readonly name="username" id="username" autocomplete="username" value="{{ username }}" />
{% if forceUsername is not defined or not forceUsername %}
<a {% if not muni_jvs %}class="position-absolute remember-me-times"{% endif%}href="{{ differentUsername }}" title="{{ '{campusmultiauth:different_account}'|trans }}">
{% if muni_jvs %}
<span class="icon icon-times"></span>
{% else %}
<i class="fas fa-times"></i>
{% endif %}
</a>
{% endif %}
</div>
{% endif %}
</div>
<div class="margin-bottom-24 text-left{% if wrongUserPass and muni_jvs %} error{% endif %}">
<label for="password" class="{% if muni_jvs %}color-{{ configuration.priority }}{% else %}form-label text-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}{% endif %}">
......@@ -69,7 +91,7 @@
class="form-control shadow-sm border-2 input-height{% if wrongUserPass %} is-invalid{% else %} border-muted{% endif %}"
{% endif %}
{% endif %}
type="password" name="password" autocomplete="current-password"{% if cookie_password is not null %} value="{{ cookie_password }}"{% endif %} placeholder="{% if attribute(configuration.password_placeholder, currentLanguage) is defined %}{{ attribute(configuration.password_placeholder, currentLanguage) }}
type="password" name="password"{% if autofocus == 'password' %} autofocus{% endif %} autocomplete="current-password" placeholder="{% if attribute(configuration.password_placeholder, currentLanguage) is defined %}{{ attribute(configuration.password_placeholder, currentLanguage) }}
{% elseif configuration.password_placeholder is defined and configuration.password_placeholder is iterable and configuration.password_placeholder is not empty %}{{ configuration.password_placeholder | first }}
{% elseif configuration.password_placeholder is defined and configuration.password_placeholder is not iterable %}{{ configuration.password_placeholder }}
{% else %}{{ '{campusmultiauth:password_placeholder}'|trans }}
......@@ -83,13 +105,14 @@
{% endif %}
</div>
{% if rememberme_enabled %}
{% if rememberme_enabled and userInfo is same as false %}
<div class="margin-bottom-24 text-left">
{% if muni_jvs %}
<label class="inp-item inp-item--checkbox" for="remember_me">
<input id="remember_me" type="checkbox" name="remember_me" value="Yes">
<input id="remember_me" type="checkbox"{% if dontRemember is same as false and rememberme_checked is same as true %} checked{% endif %} name="remember_me" value="Yes">
<span>{{ '{campusmultiauth:remember}'|trans }}</span>
</label>
<input type="hidden" id="dont_remember_me" name="dont_remember_me" value="{% if dontRemember is same as true or rememberme_checked is same as false %}Yes{% endif %}" />
{% else %}
<input type="checkbox" class="form-check-input border{% if configuration.priority == 'primary' %} border-dark{% else %} border-muted{% endif %}" id="remember_me" name="remember_me" value="Yes">
<label class="form-check-label margin-left-12 text-{% if configuration.priority == 'primary' %}dark{% else %}muted{% endif %}" for="remember_me">{{ '{campusmultiauth:remember}'|trans }}</label>
......@@ -97,7 +120,7 @@
</div>
{% endif %}
<div class="margin-bottom-24" id="submit">
<div class="margin-bottom-24{% if not rememberme_enabled or userInfo is not same as false %} margin-top-48{% endif %}" id="submit">
{% if muni_jvs %}
<button id="submit_button" class="btn {% if wayf_config.muni_faculty is defined %}btn-{{ wayf_config.muni_faculty }}{% else %}btn-primary {% endif %}{% if configuration.priority == 'secondary' %}btn-border{% endif %} btn-lg" type="submit">
<span>
......
......@@ -124,6 +124,14 @@ body {
background-color: #ffffff;
}
.security-image {
object-fit: scale-down;
margin: calc(1em - 25px) auto 1em;
display: block;
max-width: 500px;
max-height: 150px;
}
.individual-identity-logo {
width: 50px;
height: 50px;
......@@ -139,6 +147,11 @@ body {
padding: 0;
}
.remember-me-times {
right: 5%;
top: 40%;
}
.idp-text {
margin-bottom: 0;
margin-left: 25px;
......@@ -201,6 +214,10 @@ body {
margin-top: 12px;
}
.margin-top-48 {
margin-top: 48px;
}
.margin-left-12 {
margin-left: 12px;
}
......@@ -329,6 +346,10 @@ body {
text-align: center;
}
#username.hidden {
display: none;
}
#content {
margin: 0 36px 24px 36px;
}
......@@ -382,6 +403,10 @@ body {
padding-top: 24px;
}
.solid {
border-style: solid;
}
@media screen and (min-width: 768px) {
.wrap {
margin: 0 0 36px 0;
......@@ -458,6 +483,10 @@ body {
margin-top: -12px;
}
.security-image {
margin-top: calc(1em - 60px);
}
#content {
margin: 0;
}
......
import dialogPolyfill from "dialog-polyfill";
import PrivateWindow from "./extra/PrivateWindowCheck";
function hideElement(element) {
element.classList.add("vhide", "d-none");
......@@ -94,6 +95,17 @@ function selectizeLoad(query, callback) {
});
}
function isDNT() {
return (
(window.doNotTrack && window.doNotTrack === "1") ||
(navigator.doNotTrack &&
(navigator.doNotTrack === "yes" || navigator.doNotTrack === "1")) ||
(navigator.msDoNotTrack && navigator.msDoNotTrack === "1") ||
("msTrackingProtectionEnabled" in window.external &&
window.external.msTrackingProtectionEnabled())
);
}
document.addEventListener("DOMContentLoaded", function () {
// show dialog after the specified timeout to refresh the page
const dialog = document.getElementById("refresh-required-dialog");
......@@ -104,6 +116,29 @@ document.addEventListener("DOMContentLoaded", function () {
}, dialog.dataset.timeout * 1000);
}
// uncheck remember me in private windows and update dontRememberMe
const rememberMe = document.getElementById("remember_me");
if (rememberMe) {
const dontRememberMe = document.getElementById("dont_remember_me");
if (dontRememberMe) {
rememberMe.addEventListener("change", () => {
dontRememberMe.value = rememberMe.checked ? "" : "Yes";
});
}
if (rememberMe.checked) {
if (isDNT()) {
rememberMe.checked = false;
} else {
PrivateWindow.then((isPrivate) => {
if (isPrivate) {
rememberMe.checked = false;
}
});
}
}
}
var moreOptions = document.querySelectorAll(".more-options");
if (moreOptions) {
moreOptions.forEach(function (showButton) {
......
/* https://github.com/jLynx/PrivateWindowCheck/blob/ea5c4f8ede26e9fac34f87e9e8a606e336efbcb2/PrivateWindowCheck.js */
async function chrome76Detection() {
if ("storage" in navigator && "estimate" in navigator.storage) {
const { usage, quota } = await navigator.storage.estimate();
if (quota < 120000000) return true;
else return false;
} else {
return false;
}
}
function isNewChrome() {
var pieces = navigator.userAgent.match(
/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/
);
if (pieces == null || pieces.length != 5) {
return undefined;
}
major = pieces.map((piece) => parseInt(piece, 10))[1];
if (major >= 76) return true;
return false;
}
module.exports = new Promise(function (resolve, reject) {
try {
var isSafari =
navigator.vendor &&
navigator.vendor.indexOf("Apple") > -1 &&
navigator.userAgent &&
navigator.userAgent.indexOf("CriOS") == -1 &&
navigator.userAgent.indexOf("FxiOS") == -1;
if (isSafari) {
//Safari
var e = false;
if (window.safariIncognito) {
e = true;
} else {
try {
window.openDatabase(null, null, null, null);
window.localStorage.setItem("test", 1);
resolve(false);
} catch (t) {
e = true;
resolve(true);
}
void !e && ((e = !1), window.localStorage.removeItem("test"));
}
} else if (navigator.userAgent.includes("Firefox")) {
//Firefox
var db = indexedDB.open("test");
db.onerror = function () {
resolve(true);
};
db.onsuccess = function () {
resolve(false);
};
} else if (
navigator.userAgent.includes("Edge") ||
navigator.userAgent.includes("Trident") ||
navigator.userAgent.includes("msie")
) {
//Edge or IE
if (!window.indexedDB && (window.PointerEvent || window.MSPointerEvent))
resolve(true);
resolve(false);
} else {
//Normally ORP or Chrome
//Other
if (isNewChrome()) resolve(chrome76Detection());
const fs = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fs) resolve(null);
else {
fs(
window.TEMPORARY,
100,
function (fs) {
resolve(false);
},
function (err) {
resolve(true);
}
);
}
}
} catch (err) {
console.log(err);
resolve(null);
}
});
......@@ -6,9 +6,12 @@ use League\CommonMark\CommonMarkConverter;
use SimpleSAML\Auth\State;
use SimpleSAML\Configuration;
use SimpleSAML\Error\BadRequest;
use SimpleSAML\Logger;
use SimpleSAML\Metadata\MetaDataStorageHandler;
use SimpleSAML\Module;
use SimpleSAML\Module\campusmultiauth\Auth\Process\RememberMe;
use SimpleSAML\Module\campusmultiauth\Auth\Source\Campusidp;
use SimpleSAML\Module\campusmultiauth\Utils;
use SimpleSAML\XHTML\Template;
if (!array_key_exists('AuthState', $_REQUEST) && !array_key_exists('authstate', $_POST)) {
......@@ -131,13 +134,11 @@ if (array_key_exists('source', $_POST)) {
if (empty($_POST['username']) || empty($_POST['password'])) {
$_REQUEST['wrongUserPass'] = true;
} else {
if (
array_key_exists('remember_me', $_POST) &&
$_POST['remember_me'] === 'Yes' &&
Configuration::getInstance()->getBoolean('session.rememberme.enable', false)
) {
Campusidp::setCookie(Campusidp::COOKIE_USERNAME, $_POST['username']);
Campusidp::setCookie(Campusidp::COOKIE_PASSWORD, $_POST['password']);
if (!empty($_POST['dont_remember_me']) && $_POST['dont_remember_me'] === 'Yes') {
$state['DontRememberMe'] = true;
}
if (!empty($_POST['remember_me']) && $_POST['remember_me'] === 'Yes') {
$state['RememberMe'] = true;
}
Campusidp::delegateAuthentication($_POST['source'], $state);
......@@ -232,13 +233,12 @@ $t->data['authstate'] = $authStateId;
$t->data['currentUrl'] = htmlentities($_SERVER['PHP_SELF']);
$t->data['wayf_config'] = $wayfConfig;
$t->data['rememberme_enabled'] = Configuration::getInstance()->getBoolean('session.rememberme.enable', false);
$t->data['rememberme_checked'] = Configuration::getInstance()->getBoolean('session.rememberme.checked', false);
$t->data['muni_jvs'] = ($wayfConfig['css_framework'] ?? 'bootstrap5') === 'muni_jvs';
$t->data['idps'] = $idps;
$t->data['no_js_display_index'] = !empty($_POST['componentIndex']) ? $_POST['componentIndex'] : null;
$t->data['user_pass_source_name'] = $state[Campusidp::USER_PASS_SOURCE_NAME];
$t->data['sp_source_name'] = $state[Campusidp::SP_SOURCE_NAME];
$t->data['cookie_username'] = Campusidp::getCookie(Campusidp::COOKIE_USERNAME);
$t->data['cookie_password'] = Campusidp::getCookie(Campusidp::COOKIE_PASSWORD);
if (!empty($hintedIdps)) {
$t->data['idpsToShow'] = $hintedIdps;
......@@ -310,5 +310,94 @@ if (Campusidp::getCookie(Campusidp::COOKIE_PREVIOUS_IDPS) === null) {
);
}
$rememberMe = new RememberMe();
$dontRemember = filter_input(
INPUT_COOKIE,
$rememberMe->getDontCookieName(),
FILTER_DEFAULT,
[
'default' => '',
]
) === 'Yes';
$t->data['dontRemember'] = $dontRemember;
$config = Configuration::getOptionalConfig('module_campusmultiauth.php')->getConfigItem('remember_me', []);
$imagesConfig = $config->getConfigItem('security_images', []);
// verify cookie (if present), update counter and cookie
if (
empty($t->data['forceUsername'])
&& !empty($_GET[RememberMe::CLEAR_USERNAME_PARAM])
) {
$t->data['username'] = '';
$t->data['userInfo'] = false;
$rememberMe->deleteCookie();
}
$t->data['userInfo'] = $dontRemember ? false : $rememberMe->getUserInfo();
if ($t->data['userInfo']) {
if (empty($t->data['username']) || $t->data['userInfo']['username'] === $t->data['username']) {
$t->data['username'] = $t->data['userInfo']['username'];
$showFreshImage = $imagesConfig->getBoolean('showFreshImage', false);
if ($showFreshImage && (($t->data['userInfo']['security_image'] ?? true) !== false)) {
$t->data['securityImage'] = Utils::getSecurityImageOfUser($t->data['userInfo']['username']);
} elseif (!$showFreshImage && !empty($t->data['userInfo']['security_image'])) {
$t->data['securityImage'] = $t->data['userInfo']['security_image'];
}
$pictureDir = $imagesConfig->getString('pictureDir', null);
if ($t->data['securityImage'] && $pictureDir !== null) {
$pictureDataSrc = $t->data['securityImage'];
if (preg_match('~^data:image/(png|jpeg|gif);base64,(.*)$~', $pictureDataSrc, $matches)) {
list(, $pictureType, $pictureContent) = $matches;
$pictureContent = base64_decode($pictureContent, true);
if ($pictureContent !== false) {
$pictureFileName = sprintf(
'%s-%s.%s',
$t->data['username'],
hash('sha256', $imagesConfig->getString('securityImageSalt') . $t->data['username']),
$pictureType
);
if (!file_exists($pictureDir) && !mkdir($pictureDir, 0755, true)) {
throw new \Error('Folder for security images does not exist and could not be created.');
}
$pictureFilePath = rtrim($pictureDir, '/') . '/' . $pictureFileName;
file_put_contents($pictureFilePath, $pictureContent);
if (image_type_to_mime_type(exif_imagetype($pictureFilePath)) !== 'image/' . $pictureType) {
Logger::warning('Invalid security image, type mismatch: ' . $t->data['securityImage']);
unlink($pictureFilePath);
} else {
$t->data['securityImage'] = sprintf(
'%s/%s',
rtrim($imagesConfig->getString('pictureBaseURL'), '/'),
$pictureFileName
);
}
}
}
}
} elseif ($t->data['username'] !== $t->data['userInfo']['username']) {
$t->data['userInfo'] = false;
}
}
if (!empty($t->data['username'])) {
$t->data['autofocus'] = 'password';
} else {
$t->data['autofocus'] = 'username';
}
$t->data['uidName'] = $config->getString('uidName', '');
if ($t->data['userInfo'] !== false) {
$t->data['accessTarget'] = 'remembered-name';
}
$t->data['differentUsername'] = RememberMe::getOtherUsernameLink($authStateId);
$t->show();
exit();