Skip to content
Snippets Groups Projects
Unverified Commit 66856386 authored by lukasmatusiewicz's avatar lukasmatusiewicz Committed by GitHub
Browse files

Merge pull request #121 from melanger/csp

move inline JS and CSS to files, bug fixes
parents 362053bd 7c4d8faa
No related branches found
No related tags found
No related merge requests found
...@@ -38,6 +38,27 @@ ...@@ -38,6 +38,27 @@
"u2fNotWorking": { "u2fNotWorking": {
"en": "U2F is currently not working." "en": "U2F is currently not working."
}, },
"alternate_login_options": {
"en": "Alternate login options:"
},
"alert_webauthn_insecure_context": {
"en": "Unable to proceed with Web Authn because the context is insecure!"
},
"alert_webauthn_unavailable": {
"en": "Could not load WebAuthn library. Please try again or use other token."
},
"alert_webAuthnSignRequest_error": {
"en": "Error while signing WebAuthnSignRequest:"
},
"alert_u2f_insecure_context": {
"en": "Unable to proceed with U2F because the context is insecure!"
},
"alert_u2f_unavailable": {
"en": "Could not load U2F library. Please try again or use other token."
},
"alert_U2FSignRequest_error": {
"en": "Error while signing U2FSignRequest:"
},
"tryAgain": { "tryAgain": {
"en": "Try Again" "en": "Try Again"
} }
......
...@@ -46,6 +46,10 @@ if (strlen($this->data['username']) > 0) ...@@ -46,6 +46,10 @@ if (strlen($this->data['username']) > 0)
$this->data['autofocus'] = 'username'; $this->data['autofocus'] = 'username';
} }
$this->data['head'] .= '<link rel="stylesheet" href="'
. htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/css/loginform.css'), ENT_QUOTES)
. '" media="screen" />';
$this->includeAtTemplateBase('includes/header.php'); $this->includeAtTemplateBase('includes/header.php');
// Prepare error case to show it in UI if needed // Prepare error case to show it in UI if needed
...@@ -53,9 +57,9 @@ if ($this->data['errorCode'] !== NULL) ...@@ -53,9 +57,9 @@ if ($this->data['errorCode'] !== NULL)
{ {
?> ?>
<div style="border-left: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8; background: #f5f5f5"> <div class="error-dialog">
<img src="/<?php echo htmlspecialchars($this->data['baseurlpath'], ENT_QUOTES); ?>resources/icons/experience/gtk-dialog-error.48x48.png" <img src="/<?php echo htmlspecialchars($this->data['baseurlpath'], ENT_QUOTES); ?>resources/icons/experience/gtk-dialog-error.48x48.png"
class="float-l erroricon" style="margin: 15px " alt="gtk-dialog-error"/> class="float-l erroricon" alt="gtk-dialog-error"/>
<h2><?php echo $this->t('{login:error_header}'); ?></h2> <h2><?php echo $this->t('{login:error_header}'); ?></h2>
<p> <p>
<strong><?php echo htmlspecialchars("Error " . $this->data['errorCode'] . ": " . $this->data['errorMessage']); ?></strong> <strong><?php echo htmlspecialchars("Error " . $this->data['errorCode'] . ": " . $this->data['errorMessage']); ?></strong>
...@@ -92,7 +96,7 @@ if ($this->data['errorCode'] !== NULL) ...@@ -92,7 +96,7 @@ if ($this->data['errorCode'] !== NULL)
if ($this->data['forceUsername']) if ($this->data['forceUsername'])
{ {
?> ?>
<strong style="font-size: medium"><?php echo htmlspecialchars($this->data['username']) ?></strong> <h3><?php echo htmlspecialchars($this->data['username']) ?></h3>
<input type="hidden" id="username" name="username" <input type="hidden" id="username" name="username"
value="<?php echo htmlspecialchars($this->data['username'], ENT_QUOTES) ?>"/> value="<?php echo htmlspecialchars($this->data['username'], ENT_QUOTES) ?>"/>
<?php <?php
...@@ -101,7 +105,6 @@ if ($this->data['errorCode'] !== NULL) ...@@ -101,7 +105,6 @@ if ($this->data['errorCode'] !== NULL)
?> ?>
<label for="username"></label> <label for="username"></label>
<input type="text" id="username" tabindex="1" name="username" <input type="text" id="username" tabindex="1" name="username"
style="width:322px; margin:25px 15px 15px"
value="<?php echo htmlspecialchars($this->data['username'], ENT_QUOTES) ?>" value="<?php echo htmlspecialchars($this->data['username'], ENT_QUOTES) ?>"
placeholder="<?php echo htmlspecialchars($this->t('{login:username}'), ENT_QUOTES) ?>" placeholder="<?php echo htmlspecialchars($this->t('{login:username}'), ENT_QUOTES) ?>"
/> />
...@@ -145,18 +148,16 @@ if ($this->data['errorCode'] !== NULL) ...@@ -145,18 +148,16 @@ if ($this->data['errorCode'] !== NULL)
<!-- Pass and OTP fields --> <!-- Pass and OTP fields -->
<label for="password"></label> <label for="password"></label>
<input id="password" name="password" tabindex="1" type="password" value="" class="text" <input id="password" name="password" tabindex="1" type="password" value="" class="text"
placeholder="<?php echo htmlspecialchars($passHint, ENT_QUOTES) ?>" placeholder="<?php echo htmlspecialchars($passHint, ENT_QUOTES) ?>"/>
style="width:322px; margin:15px"/>
<label for="otp" class="block"><strong <strong id="message"><?php echo $this->data['message'] ?></strong>
id="message"><?php echo $this->data['message'] ?></strong></label>
<br> <label for="otp"></label>
<input id="otp" name="otp" tabindex="1" type="password" value="" class="text" <input id="otp" name="otp" tabindex="1" type="password" value="" class="text"
placeholder="<?php echo htmlspecialchars($otpHint, ENT_QUOTES) ?>" placeholder="<?php echo htmlspecialchars($otpHint, ENT_QUOTES) ?>"/>
style="width:322px; margin: 25px 15px 7px"/>
<br> <br>
<input id="submitButton" tabindex="1" class="rc-button rc-button-submit" type="submit" <input id="submitButton" tabindex="1" class="rc-button rc-button-submit" type="submit"
name="Submit" style="width:210px; margin:0 15px 7px" name="Submit"
value="<?php echo htmlspecialchars($this->t('{login:login_button}'), ENT_QUOTES) ?>"/> value="<?php echo htmlspecialchars($this->t('{login:login_button}'), ENT_QUOTES) ?>"/>
<!-- Hidden input which store the info about changes for future use in backend--> <!-- Hidden input which store the info about changes for future use in backend-->
...@@ -179,68 +180,9 @@ if ($this->data['errorCode'] !== NULL) ...@@ -179,68 +180,9 @@ if ($this->data['errorCode'] !== NULL)
value="<?php echo $this->data['loadCounter'] ?>"/> value="<?php echo $this->data['loadCounter'] ?>"/>
<!-- Additional input to persist the message --> <!-- Additional input to persist the message -->
<input id="message" type="hidden" name="message" <input type="hidden" name="message"
value="<?php echo $this->data['message'] ?>"/> value="<?php echo $this->data['message'] ?>"/>
<script>
// Helper functions
function value(id)
{
const element = document.getElementById(id);
if (element != null)
{
return element.value;
} else
{
console.log(id + " is null!");
}
return "";
}
function set(id, value)
{
const element = document.getElementById(id);
if (element != null)
{
element.value = value;
} else
{
console.log(id + " is null!");
}
}
function disable(id)
{
const element = document.getElementById(id);
if (element != null)
{
element.style.display = "none";
} else
{
console.log(id + " is null!");
}
}
function enable(id)
{
const element = document.getElementById(id);
if (element != null)
{
element.style.display = "initial";
} else
{
console.log(id + " is null!");
}
}
function changeMode(newMode)
{
document.getElementById("mode").value = newMode;
document.getElementById("modeChanged").value = "1";
document.forms["piLoginForm"].submit();
}
</script>
<?php <?php
// If enrollToken load QR Code // If enrollToken load QR Code
if (isset($this->data['tokenQR'])) if (isset($this->data['tokenQR']))
...@@ -299,20 +241,14 @@ if ($this->data['errorCode'] !== NULL) ...@@ -299,20 +241,14 @@ if ($this->data['errorCode'] !== NULL)
</div> <!-- slide-out--> </div> <!-- slide-out-->
</div> <!-- form-panel --> </div> <!-- form-panel -->
<div id="AlternateLoginOptions" style="margin-top:35px" class="groupMargin"> <div id="AlternateLoginOptions" class="groupMargin">
<label><strong>Alternate login options:</strong></label> <h3><label><?php echo $this->t('{privacyidea:privacyidea:alternate_login_options}'); ?></label></h3>
<br>
<!-- Alternate Login Options--> <!-- Alternate Login Options-->
<input id="useWebAuthnButton" name="useWebAuthnButton" type="button" value="WebAuthn" <input id="useWebAuthnButton" name="useWebAuthnButton" type="button" value="WebAuthn"/>
onclick="doWebAuthn()" style="width:140px; margin:15px 10px 7px"/> <input id="usePushButton" name="usePushButton" type="button" value="Push"/>
<input id="usePushButton" name="usePushButton" type="button" value="Push" <input id="useOTPButton" name="useOTPButton" type="button" value="OTP"/>
onclick="changeMode('push')" style="width:140px; margin:15px 10px 7px"/> <input id="useU2FButton" name="useU2FButton" type="button" value="U2F"/>
<input id="useOTPButton" name="useOTPButton" style="width:140px; margin:15px 15px 7px" type="button"
value="OTP" onclick="changeMode('otp')"/>
<input id="useU2FButton" name="useU2FButton" type="button" value="U2F" onclick="doU2F()"
style="width:140px; margin:15px 10px 7px"/>
</div> </div>
<br><br>
</form> </form>
<?php <?php
...@@ -329,15 +265,13 @@ if ($this->data['errorCode'] !== NULL) ...@@ -329,15 +265,13 @@ if ($this->data['errorCode'] !== NULL)
<?php <?php
if (!empty($this->data['links'])) if (!empty($this->data['links']))
{ {
echo '<ul class="links" style="margin-top: 2em">'; echo '<ul class="links">';
foreach ($this->data['links'] as $l) foreach ($this->data['links'] as $l)
{ {
echo '<li><a href="' . htmlspecialchars($l['href'], ENT_QUOTES) . '">' . htmlspecialchars($this->t($l['text'])) . '</a></li>'; echo '<li><a href="' . htmlspecialchars($l['href'], ENT_QUOTES) . '">' . htmlspecialchars($this->t($l['text'])) . '</a></li>';
} }
echo '</ul>'; echo '</ul>';
} }
$this->includeAtTemplateBase('includes/footer.php');
?> ?>
<script src="<?php echo htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/js/webauthn.js'), ENT_QUOTES) ?>"> <script src="<?php echo htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/js/webauthn.js'), ENT_QUOTES) ?>">
...@@ -346,206 +280,28 @@ $this->includeAtTemplateBase('includes/footer.php'); ...@@ -346,206 +280,28 @@ $this->includeAtTemplateBase('includes/footer.php');
<script src="<?php echo htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/js/u2f-api.js'), ENT_QUOTES) ?>"> <script src="<?php echo htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/js/u2f-api.js'), ENT_QUOTES) ?>">
</script> </script>
<!--We need to open a new script tag up here--> <meta id="privacyidea-step" name="privacyidea-step" content="<?php echo $this->data['step'] ?>">
<meta id="privacyidea-hide-alternate" name="privacyidea-hide-alternate" content="<?php echo (
<script> !$this->data['pushAvailable']
const step = '<?php echo $this->data['step'] ?>'; && (($this->data['u2fSignRequest']) == "")
&& (($this->data['webAuthnSignRequest']) == "")
if (step > "1") ) ? 'true' : 'false'; ?>">
{
disable("username"); <meta id="privacyidea-translations" name="privacyidea-translations" content="<?php
disable("password"); $translations = [];
} else $translation_keys = [
{ 'alert_webauthn_insecure_context', 'alert_webauthn_unavailable', 'alert_webAuthnSignRequest_error',
disable("otp"); 'alert_u2f_insecure_context', 'alert_u2f_unavailable', 'alert_U2FSignRequest_error',
disable("message"); ];
disable("AlternateLoginOptions"); foreach ($translation_keys as $translation_key) {
} $translations[$translation_key] = $this->t(sprintf('{privacyidea:privacyidea:%s}', $translation_key));
}
// Set alternate token button visibility echo htmlspecialchars(json_encode($translations));
if (value("webAuthnSignRequest") === "") ?>">
{
disable("useWebAuthnButton");
}
if (value("u2fSignRequest") === "")
{
disable("useU2FButton");
}
if (value("pushAvailable") !== "1")
{
disable("usePushButton");
}
if (value("otpAvailable") !== "1")
{
disable("useOTPButton");
}
if (value("pushAvailable") === "0" && value("webAuthnSignRequest") === "" && value("u2fSignRequest") === "")
{
disable("alternateTokenDiv");
}
if (value("mode") === "otp")
{
disable("useOTPButton");
}
if (value("mode") === "webauthn")
{
doWebAuthn();
}
if (value("mode") === "u2f")
{
doU2F();
}
if (value("mode") === "push")
{
const pollingIntervals = [4, 3, 2, 1];
disable("otp");
disable("usePushButton");
disable("submitButton");
if (value("loadCounter") > (pollingIntervals.length - 1))
{
refreshTime = pollingIntervals[(pollingIntervals.length - 1)];
} else
{
refreshTime = pollingIntervals[Number(value("loadCounter") - 1)];
}
refreshTime *= 1000;
setTimeout(() =>
{
document.forms["piLoginForm"].submit();
}, refreshTime);
}
function doWebAuthn()
{
// If mode is push, we have to change it, otherwise the site will refresh while doing webauthn
if (value("mode") === "push")
{
changeMode("webauthn");
}
if (!window.isSecureContext)
{
alert("Unable to proceed with Web Authn because the context is insecure!");
console.log("Insecure context detected: Aborting Web Authn authentication!")
changeMode("otp");
return;
}
if (!window.pi_webauthn)
{
alert("Could not load WebAuthn library. Please try again or use other token.");
changeMode("otp");
return;
}
const requestStr = value("webAuthnSignRequest");
// Set origin
if (!window.location.origin)
{
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
}
set("origin", window.origin);
try
{
const requestjson = JSON.parse(requestStr);
const webAuthnSignResponse = window.pi_webauthn.sign(requestjson);
webAuthnSignResponse.then((webauthnresponse) =>
{
const response = JSON.stringify(webauthnresponse);
set("webAuthnSignResponse", response);
set("mode", "webauthn");
document.forms["piLoginForm"].submit();
});
} catch (err)
{
console.log("Error while signing WebAuthnSignRequest: " + err);
alert("Error while signing WebAuthnSignRequest: " + err);
}
}
function doU2F()
{
// If mode is push, we have to change it, otherwise the site will refresh while doing webauthn
if (value("mode") === "push")
{
changeMode("u2f");
}
if (!window.isSecureContext)
{
alert("Unable to proceed with U2F because the context is insecure!");
console.log("Insecure context detected: Aborting U2F authentication!")
changeMode("otp");
return;
}
const requestStr = value("u2fSignRequest");
if (requestStr === null)
{
alert("Could not load U2F library. Please try again or use other token.");
changeMode("otp");
return;
}
try
{
const requestjson = JSON.parse(requestStr);
sign_u2f_request(requestjson);
} catch (err)
{
console.log("Error while signing U2FSignRequest: " + err);
alert("Error while signing U2FSignRequest: " + err);
}
}
function sign_u2f_request(signRequest)
{
let appId = signRequest["appId"];
let challenge = signRequest["challenge"];
let registeredKeys = [];
registeredKeys.push({
version: "U2F_V2",
keyHandle: signRequest["keyHandle"]
});
u2f.sign(appId, challenge, registeredKeys, function (result) <script src="<?php echo htmlspecialchars(SimpleSAML_Module::getModuleUrl('privacyidea/js/loginform.js'), ENT_QUOTES) ?>">
{
const stringResult = JSON.stringify(result);
if (stringResult.includes("clientData") && stringResult.includes("signatureData"))
{
set("u2fSignResponse", stringResult);
set("mode", "u2f");
document.forms["piLoginForm"].submit();
}
})
}
</script> </script>
<?php <?php
if (!$this->data['pushAvailable'] $this->includeAtTemplateBase('includes/footer.php');
&& (($this->data['u2fSignRequest']) == "") ?>
&& (($this->data['webAuthnSignRequest']) == ""))
{
?>
<script>
document.getElementById("AlternateLoginOptions").style.display = "none";
</script>
<?php } ?>
\ No newline at end of file
.error-dialog {
border-left: 1px solid #e8e8e8;
border-bottom: 1px solid #e8e8e8;
background: #f5f5f5;
}
.erroricon {
margin: 15px;
}
#username[type=text], #password, #otp {
width:322px;
}
#username[type=text] {
margin:25px 15px 15px;
}
#password {
margin:15px;
}
#message {
display: block;
}
label[for=otp] {
font-weight: bold;
}
#otp {
margin: 25px 15px 7px;
}
#submitButton {
width:210px;
margin:0 15px 7px;
}
#AlternateLoginOptions {
margin-top:35px;
padding-bottom: 2em;
}
#useWebAuthnButton, #usePushButton, #useOTPButton, #useU2FButton {
width:140px;
margin:15px 10px 7px;
}
#useOTPButton {
margin-left: 15px;
margin-right: 15px;
}
.links {
margin-top: 2em;
}
.hidden {
display: none;
}
// Helper functions
function value(id)
{
const element = document.getElementById(id);
if (element != null)
{
return element.value;
} else
{
console.log(id + " is null!");
}
return "";
}
function set(id, value)
{
const element = document.getElementById(id);
if (element != null)
{
element.value = value;
} else
{
console.log(id + " is null!");
}
}
function disable(id)
{
const element = document.getElementById(id);
if (element != null)
{
element.classList.add("hidden");
} else
{
console.log(id + " is null!");
}
}
function enable(id)
{
const element = document.getElementById(id);
if (element != null)
{
element.classList.remove("hidden");
} else
{
console.log(id + " is null!");
}
}
function changeMode(newMode)
{
document.getElementById("mode").value = newMode;
document.getElementById("modeChanged").value = "1";
document.forms["piLoginForm"].submit();
}
function t(key) {
return JSON.parse(document.getElementById("privacyidea-translations").content)[key];
}
const step = document.getElementById("privacyidea-step").content;
if (step > "1")
{
disable("username");
disable("password");
} else
{
disable("otp");
disable("message");
disable("AlternateLoginOptions");
}
// Set alternate token button visibility
if (value("webAuthnSignRequest") === "")
{
disable("useWebAuthnButton");
}
if (value("u2fSignRequest") === "")
{
disable("useU2FButton");
}
if (value("pushAvailable") !== "1")
{
disable("usePushButton");
}
if (value("otpAvailable") !== "1")
{
disable("useOTPButton");
}
if (value("pushAvailable") === "0" && value("webAuthnSignRequest") === "" && value("u2fSignRequest") === "")
{
disable("alternateTokenDiv");
}
if (value("mode") === "otp")
{
disable("useOTPButton");
}
if (value("mode") === "webauthn")
{
doWebAuthn();
}
if (value("mode") === "u2f")
{
doU2F();
}
if (value("mode") === "push")
{
const pollingIntervals = [4, 3, 2, 1];
disable("otp");
disable("usePushButton");
disable("submitButton");
if (value("loadCounter") > (pollingIntervals.length - 1))
{
refreshTime = pollingIntervals[(pollingIntervals.length - 1)];
} else
{
refreshTime = pollingIntervals[Number(value("loadCounter") - 1)];
}
refreshTime *= 1000;
setTimeout(() =>
{
document.forms["piLoginForm"].submit();
}, refreshTime);
}
function doWebAuthn()
{
// If mode is push, we have to change it, otherwise the site will refresh while doing webauthn
if (value("mode") === "push")
{
changeMode("webauthn");
}
if (!window.isSecureContext)
{
alert(t("alert_webauthn_insecure_context"));
console.log("Insecure context detected: Aborting Web Authn authentication!")
changeMode("otp");
return;
}
if (!window.pi_webauthn)
{
alert(t("alert_webauthn_unavailable"));
changeMode("otp");
return;
}
const requestStr = value("webAuthnSignRequest");
// Set origin
if (!window.location.origin)
{
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port : '');
}
set("origin", window.origin);
try
{
const requestjson = JSON.parse(requestStr);
const webAuthnSignResponse = window.pi_webauthn.sign(requestjson);
webAuthnSignResponse.then((webauthnresponse) =>
{
const response = JSON.stringify(webauthnresponse);
set("webAuthnSignResponse", response);
set("mode", "webauthn");
document.forms["piLoginForm"].submit();
});
} catch (err)
{
console.log("Error while signing WebAuthnSignRequest: " + err);
alert(t("alert_webAuthnSignRequest_error") + " " + err);
}
}
function doU2F()
{
// If mode is push, we have to change it, otherwise the site will refresh while doing webauthn
if (value("mode") === "push")
{
changeMode("u2f");
}
if (!window.isSecureContext)
{
alert(t("alert_u2f_insecure_context"));
console.log("Insecure context detected: Aborting U2F authentication!")
changeMode("otp");
return;
}
const requestStr = value("u2fSignRequest");
if (requestStr === null)
{
alert(t("alert_u2f_unavailable"));
changeMode("otp");
return;
}
try
{
const requestjson = JSON.parse(requestStr);
sign_u2f_request(requestjson);
} catch (err)
{
console.log("Error while signing U2FSignRequest: " + err);
alert(t("alert_U2FSignRequest_error") + " " + err);
}
}
function sign_u2f_request(signRequest)
{
let appId = signRequest["appId"];
let challenge = signRequest["challenge"];
let registeredKeys = [];
registeredKeys.push({
version: "U2F_V2",
keyHandle: signRequest["keyHandle"]
});
u2f.sign(appId, challenge, registeredKeys, function (result)
{
const stringResult = JSON.stringify(result);
if (stringResult.includes("clientData") && stringResult.includes("signatureData"))
{
set("u2fSignResponse", stringResult);
set("mode", "u2f");
document.forms["piLoginForm"].submit();
}
})
}
if (document.getElementById("privacyidea-hide-alternate").content == "true") {
document.getElementById("AlternateLoginOptions").classList.add("hidden");
}
document.addEventListener("DOMContentLoaded", (event) => {
document.getElementById("useWebAuthnButton").addEventListener("click", doWebAuthn);
document.getElementById("usePushButton").addEventListener("click", function(){changeMode("push");});
document.getElementById("useOTPButton").addEventListener("click", function(){changeMode("otp");});
document.getElementById("useU2FButton").addEventListener("click", doU2F);
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment