Merge branch '2fa' into master

This commit is contained in:
Jan Böhmer 2020-01-01 15:49:42 +01:00
commit 1016f0d4ee
65 changed files with 4769 additions and 320 deletions

View file

@ -90,6 +90,10 @@ require('./jquery.tristate.js');
require('darkmode-js');
window.ClipboardJS = require('clipboard');
window.QRCode = require('qrcode');
require('../ts_src/ajax_ui');
import {ajaxUI} from "../ts_src/ajax_ui";

134
assets/js/u2f_auth.js Normal file
View file

@ -0,0 +1,134 @@
import u2fApi from 'u2f-api'
'use strict'
window.u2fauth = window.u2fauth || {}
u2fauth.formId = 'u2fForm'
u2fauth.authCodeId = '_auth_code'
u2fauth.keynameId = 'u2fkeyname'
u2fauth.pressButtonId = 'u2fpressbutton'
u2fauth.errorId = 'u2fError'
u2fauth.timeout = 30
u2fauth.errorTranslation = {
1: 'Unknown Error',
2: 'Bad Request',
3: 'Client configuration not supported',
4: 'Device already registered or ineligible',
5: 'Timeout. Click to retry'
}
u2fauth.ready = function (fn) {
if (document.readyState !== 'loading') {
fn()
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', fn)
} else {
document.attachEvent('onreadystatechange', function () {
if (document.readyState !== 'loading') { fn() }
})
}
}
u2fauth.authenticate = function () {
u2fauth.clearError()
u2fauth.showPressButton()
var form = document.getElementById(u2fauth.formId)
var request = JSON.parse(form.dataset.request)
u2fApi.isSupported()
.then(function (supported) {
if (supported) {
return u2fApi.sign(request, u2fauth.timeout)
.then(response => {
u2fauth.hidePressButton()
u2fauth.submit(form, response)
})
} else {
alert('Browser not supported')
}
})
.catch(data => {
u2fauth.hidePressButton()
u2fauth.showError(data.metaData.code, u2fauth.authenticate)
})
}
u2fauth.register = function () {
u2fauth.clearError()
u2fauth.hideKeyname()
u2fauth.showPressButton()
var form = document.getElementById(u2fauth.formId)
var request = JSON.parse(form.dataset.request)
u2fApi.isSupported()
.then(function (supported) {
if (supported) {
return u2fApi.register(request[0], request[1], u2fauth.timeout)
.then(response => {
u2fauth.hidePressButton()
u2fauth.submit(form, response)
})
} else {
alert('Browser not supported')
}
})
.catch(data => {
console.info(data)
u2fauth.hidePressButton()
u2fauth.showError(data.metaData.code, u2fauth.register)
})
}
u2fauth.submit = function (form, data) {
var codeField = document.getElementById(u2fauth.authCodeId)
codeField.value = JSON.stringify(data)
form.submit()
}
u2fauth.hideKeyname = function () {
var keyname = document.getElementById(u2fauth.keynameId)
keyname.style.display = 'none'
}
u2fauth.hidePressButton = function () {
var pressButton = document.getElementById(u2fauth.pressButtonId)
pressButton.style.display = 'none'
}
u2fauth.showPressButton = function () {
var pressButton = document.getElementById(u2fauth.pressButtonId)
pressButton.style.display = 'block'
}
u2fauth.clearError = function () {
var errorDisplay = document.getElementById(u2fauth.errorId)
errorDisplay.style.display = 'none'
errorDisplay.innerText = ''
}
u2fauth.showError = function (error, callback) {
var errorDisplay = document.getElementById(u2fauth.errorId)
errorDisplay.style.display = 'block'
errorDisplay.innerText = u2fauth.errorTranslation[error]
errorDisplay.onclick = callback
}
u2fauth.ready(function () {
const form = document.getElementById('u2fForm')
if (!form) {
return
}
const type = form.dataset.action
if (type === 'auth') {
u2fauth.authenticate()
} else if (type === 'reg' && form.addEventListener) {
form.addEventListener('submit', function (event) {
event.preventDefault()
u2fauth.register()
}, false)
}
})

View file

@ -297,11 +297,17 @@ class AjaxUI {
/**
* Submits the given form via ajax.
* @param form The form that will be submmitted.
* @param btn The btn via which the form is submitted
*/
public submitForm(form)
public submitForm(form, btn = null)
{
let options = ajaxUI.getFormOptions();
if(btn) {
options.data = {};
options.data[$(btn).attr('name')] = $(btn).attr('value');
}
$(form).ajaxSubmit(options);
}
@ -363,6 +369,11 @@ class AjaxUI {
return;
}
//Ignore ajax errors with 200 code (like the ones during 2FA authentication)
if(request.status == 200) {
return;
}
console.error("Error getting the ajax data from server!");
console.log(event);
console.log(request);

View file

@ -23,6 +23,7 @@ import {ajaxUI} from "./ajax_ui";
import "bootbox";
import "marked";
import * as marked from "marked";
import "qrcode";
import {parse} from "marked";
/************************************
@ -184,26 +185,55 @@ $(document).on("ajaxUI:start ajaxUI:reload", function() {
});
$(document).on("ajaxUI:start ajaxUI:reload", function() {
$("[data-delete-form]").unbind('submit').submit(function(event) {
$("[data-delete-form]").unbind('submit').submit(function (event) {
event.preventDefault();
let form = this;
//Get the submit button
let btn = document.activeElement;
let title = $(this).data("title");
let message = $(this).data("message");
bootbox.confirm({
message: message,
title: title,
callback: function(result) {
message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form.
if(result) {
ajaxUI.submitForm(form);
if (result) {
ajaxUI.submitForm(form, btn);
}
}});
}
});
return false;
});
//Register for forms with delete-buttons
$("[data-delete-btn]").parents('form').unbind('submit').submit(function (event) {
event.preventDefault();
let form = this;
//Get the submit button
let btn = document.activeElement;
let title = $(btn).data("title");
let message = $(btn).data("message");
//If not the button with the message was pressed, then simply submit the form.
if(!btn.hasAttribute('data-delete-btn')) {
ajaxUI.submitForm(form, btn);
}
bootbox.confirm({
message: message, title: title, callback: function (result) {
//If the dialog was confirmed, then submit the form.
if (result) {
ajaxUI.submitForm(form, btn);
}
}
});
});
});
$(document).on("ajaxUI:start ajaxUI:reload", function() {
@ -458,6 +488,65 @@ $(document).on("ajaxUI:start ajaxUI:reload attachment:create", function() {
$('select.attachment_type_selector').change(updater).each(updater);
});
$(document).on("ajaxUI:start ajaxUI:reload", function() {
$('.qrcode').each(function() {
let canvas = $(this);
//@ts-ignore
QRCode.toCanvas(canvas[0], canvas.data('content'), function(error) {
if(error) console.error(error);
})
});
});
$(document).on("ajaxUI:start ajaxUI:reload", function() {
function setTooltip(btn, message) {
$(btn).tooltip('hide')
.attr('data-original-title', message)
.tooltip('show');
}
function hideTooltip(btn) {
setTimeout(function() {
$(btn).tooltip('hide');
}, 1000);
}
//@ts-ignore
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function(e) {
setTooltip(e.trigger, 'Copied!');
hideTooltip(e.trigger);
});
clipboard.on('error', function(e) {
setTooltip(e.trigger, 'Failed!');
hideTooltip(e.trigger);
});
});
//Register U2F on page reload too...
$(document).on("ajaxUI:reload", function() {
//@ts-ignore
window.u2fauth.ready(function () {
const form = document.getElementById('u2fForm')
if (!form) {
return
}
const type = form.dataset.action
if (type === 'auth') {
//@ts-ignore
u2fauth.authenticate()
} else if (type === 'reg' && form.addEventListener) {
form.addEventListener('submit', function (event) {
event.preventDefault()
//@ts-ignore
u2fauth.register()
}, false)
}
})
});
//Need for proper body padding, with every navbar height
$(window).resize(function () {
let height : number = $('#navbar').height() + 10;

View file

@ -20,8 +20,10 @@
"nyholm/psr7": "^1.1",
"ocramius/proxy-manager": "2.1.*",
"omines/datatables-bundle": "^0.3.1",
"r/u2f-two-factor-bundle": "dev-u2f-api",
"php-translation/symfony-bundle": "^0.9.1",
"s9e/text-formatter": "^2.1",
"scheb/two-factor-bundle": "^4.11",
"sensio/framework-extra-bundle": "^5.1",
"sensiolabs/security-checker": "^6.0",
"shivas/versioning-bundle": "^3.1",
@ -113,5 +115,11 @@
"allow-contrib": false,
"require": "4.4.*"
}
}
},
"repositories": [
{
"type": "vcs",
"url": "git@github.com:stephanvierkant/u2f-two-factor-bundle.git"
}
]
}

626
composer.lock generated
View file

@ -4,8 +4,70 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a59dc06a43af8bf7a925d52f820f8774",
"content-hash": "8aa86f40d5466e5d6f91f15664f3b633",
"packages": [
{
"name": "beberlei/assert",
"version": "v3.2.7",
"source": {
"type": "git",
"url": "https://github.com/beberlei/assert.git",
"reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/beberlei/assert/zipball/d63a6943fc4fd1a2aedb65994e3548715105abcf",
"reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"php": "^7"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan-shim": "*",
"phpunit/phpunit": ">=6.0.0 <8"
},
"suggest": {
"ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles"
},
"type": "library",
"autoload": {
"psr-4": {
"Assert\\": "lib/Assert"
},
"files": [
"lib/Assert/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"authors": [
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de",
"role": "Lead Developer"
},
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com",
"role": "Collaborator"
}
],
"description": "Thin assertion library for input validation in business models.",
"keywords": [
"assert",
"assertion",
"validation"
],
"time": "2019-12-19T17:51:41+00:00"
},
{
"name": "doctrine/annotations",
"version": "v1.8.0",
@ -1227,27 +1289,26 @@
},
{
"name": "egulias/email-validator",
"version": "2.1.12",
"version": "2.1.13",
"source": {
"type": "git",
"url": "https://github.com/egulias/EmailValidator.git",
"reference": "a6255605af39f2db7f5cb62e672bd8a7bad8d208"
"reference": "834593d5900615639208417760ba6a17299e2497"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/a6255605af39f2db7f5cb62e672bd8a7bad8d208",
"reference": "a6255605af39f2db7f5cb62e672bd8a7bad8d208",
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/834593d5900615639208417760ba6a17299e2497",
"reference": "834593d5900615639208417760ba6a17299e2497",
"shasum": ""
},
"require": {
"doctrine/lexer": "^1.0.1",
"php": ">= 5.5"
"php": ">=5.5"
},
"require-dev": {
"dominicsayers/isemail": "dev-master",
"phpunit/phpunit": "^4.8.35||^5.7||^6.0",
"satooshi/php-coveralls": "^1.0.1",
"symfony/phpunit-bridge": "^4.4@dev"
"dominicsayers/isemail": "^3.0.7",
"phpunit/phpunit": "^4.8.36|^7.5.15",
"satooshi/php-coveralls": "^1.0.1"
},
"suggest": {
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
@ -1281,7 +1342,7 @@
"validation",
"validator"
],
"time": "2019-12-20T12:49:39+00:00"
"time": "2019-12-30T08:14:25+00:00"
},
{
"name": "florianv/exchanger",
@ -1406,35 +1467,35 @@
},
{
"name": "friendsofsymfony/ckeditor-bundle",
"version": "2.1.0",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfSymfony/FOSCKEditorBundle.git",
"reference": "7ba78735ea75b9a6d4de403d7aced6c993a0557a"
"reference": "7e1cfe2a83faba0be02661d44289d35e940bb5ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSCKEditorBundle/zipball/7ba78735ea75b9a6d4de403d7aced6c993a0557a",
"reference": "7ba78735ea75b9a6d4de403d7aced6c993a0557a",
"url": "https://api.github.com/repos/FriendsOfSymfony/FOSCKEditorBundle/zipball/7e1cfe2a83faba0be02661d44289d35e940bb5ea",
"reference": "7e1cfe2a83faba0be02661d44289d35e940bb5ea",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-zip": "*",
"php": "^7.1",
"symfony/asset": "^3.4 || ^4.0",
"symfony/config": "^3.4 || ^4.0",
"symfony/dependency-injection": "^3.4 || ^4.0",
"symfony/expression-language": "^3.4 || ^4.0",
"symfony/form": "^3.4 || ^4.0",
"symfony/framework-bundle": "^3.4 || ^4.0",
"symfony/http-foundation": "^3.4 || ^4.0",
"symfony/http-kernel": "^3.4 || ^4.0",
"symfony/options-resolver": "^3.4 || ^4.0",
"symfony/property-access": "^3.4 || ^4.0",
"symfony/routing": "^3.4 || ^4.0",
"symfony/twig-bundle": "^3.4 || ^4.0",
"twig/twig": "^2.0"
"symfony/asset": "^3.4 || ^4.0 || ^5.0",
"symfony/config": "^3.4 || ^4.0 || ^5.0",
"symfony/dependency-injection": "^3.4 || ^4.0 || ^5.0",
"symfony/expression-language": "^3.4 || ^4.0 || ^5.0",
"symfony/form": "^3.4 || ^4.0 || ^5.0",
"symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0",
"symfony/http-foundation": "^3.4 || ^4.0 || ^5.0",
"symfony/http-kernel": "^3.4 || ^4.0 || ^5.0",
"symfony/options-resolver": "^3.4 || ^4.0 || ^5.0",
"symfony/property-access": "^3.4 || ^4.0 || ^5.0",
"symfony/routing": "^3.4 || ^4.0 || ^5.0",
"symfony/twig-bundle": "^3.4 || ^4.0 || ^5.0",
"twig/twig": "^2.4 || ^3.0"
},
"conflict": {
"sebastian/environment": "<1.3.4",
@ -1444,9 +1505,9 @@
"friendsofphp/php-cs-fixer": "^2.0",
"matthiasnoback/symfony-dependency-injection-test": "^1.0 || ^2.0",
"phpunit/phpunit": "^6.0",
"symfony/console": "^3.4 || ^4.0",
"symfony/phpunit-bridge": "^4.1",
"symfony/yaml": "^3.4 || ^4.0"
"symfony/console": "^3.4 || ^4.0 || ^5.0",
"symfony/phpunit-bridge": "^4.1 || ^5.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0"
},
"suggest": {
"egeloen/form-extra-bundle": "Allows to load CKEditor asynchronously"
@ -1480,7 +1541,7 @@
"keywords": [
"CKEditor"
],
"time": "2019-04-15T16:29:43+00:00"
"time": "2019-12-23T15:31:36+00:00"
},
{
"name": "gregwar/captcha",
@ -1700,17 +1761,72 @@
"time": "2014-01-12T16:20:24+00:00"
},
{
"name": "league/html-to-markdown",
"version": "4.9.0",
"name": "lcobucci/jwt",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/html-to-markdown.git",
"reference": "71319108e3db506250b8987721b13568fd9fa446"
"url": "https://github.com/lcobucci/jwt.git",
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/71319108e3db506250b8987721b13568fd9fa446",
"reference": "71319108e3db506250b8987721b13568fd9fa446",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-openssl": "*",
"php": "^5.6 || ^7.0"
},
"require-dev": {
"mikey179/vfsstream": "~1.5",
"phpmd/phpmd": "~2.2",
"phpunit/php-invoker": "~1.1",
"phpunit/phpunit": "^5.7 || ^7.3",
"squizlabs/php_codesniffer": "~2.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1-dev"
}
},
"autoload": {
"psr-4": {
"Lcobucci\\JWT\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Luís Otávio Cobucci Oblonczyk",
"email": "lcobucci@gmail.com",
"role": "Developer"
}
],
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
"keywords": [
"JWS",
"jwt"
],
"time": "2019-05-24T18:30:49+00:00"
},
{
"name": "league/html-to-markdown",
"version": "4.9.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/html-to-markdown.git",
"reference": "1dcd0f85de786f46a7f224a27cc3d709ddd2a68c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/1dcd0f85de786f46a7f224a27cc3d709ddd2a68c",
"reference": "1dcd0f85de786f46a7f224a27cc3d709ddd2a68c",
"shasum": ""
},
"require": {
@ -1761,7 +1877,7 @@
"html",
"markdown"
],
"time": "2019-11-02T14:54:14+00:00"
"time": "2019-12-28T01:32:28+00:00"
},
{
"name": "liip/imagine-bundle",
@ -2409,17 +2525,79 @@
"time": "2019-08-09T12:19:19+00:00"
},
{
"name": "php-http/discovery",
"version": "1.7.2",
"name": "paragonie/constant_time_encoding",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "16a3327861ae291006a2df8fc22e991806f720d7"
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/16a3327861ae291006a2df8fc22e991806f720d7",
"reference": "16a3327861ae291006a2df8fc22e991806f720d7",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
"shasum": ""
},
"require": {
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7",
"vimeo/psalm": "^1|^2|^3"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"time": "2019-11-06T19:20:29+00:00"
},
{
"name": "php-http/discovery",
"version": "1.7.3",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "2e0ad94833ec8473280454590a9011a0cd2dfc56"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/2e0ad94833ec8473280454590a9011a0cd2dfc56",
"reference": "2e0ad94833ec8473280454590a9011a0cd2dfc56",
"shasum": ""
},
"require": {
@ -2471,7 +2649,7 @@
"message",
"psr7"
],
"time": "2019-12-27T09:22:53+00:00"
"time": "2019-12-27T19:51:10+00:00"
},
{
"name": "php-http/httplug",
@ -2896,16 +3074,16 @@
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "4.3.3",
"version": "4.3.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "2ecaa9fef01634c83bfa8dc1fe35fb5cef223a62"
"reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/2ecaa9fef01634c83bfa8dc1fe35fb5cef223a62",
"reference": "2ecaa9fef01634c83bfa8dc1fe35fb5cef223a62",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
"reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
"shasum": ""
},
"require": {
@ -2917,6 +3095,7 @@
"require-dev": {
"doctrine/instantiator": "^1.0.5",
"mockery/mockery": "^1.0",
"phpdocumentor/type-resolver": "0.4.*",
"phpunit/phpunit": "^6.4"
},
"type": "library",
@ -2943,7 +3122,7 @@
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2019-12-20T13:40:23+00:00"
"time": "2019-12-28T18:55:12+00:00"
},
{
"name": "phpdocumentor/type-resolver",
@ -3382,6 +3561,71 @@
],
"time": "2017-10-23T01:57:42+00:00"
},
{
"name": "r/u2f-two-factor-bundle",
"version": "dev-u2f-api",
"source": {
"type": "git",
"url": "https://github.com/stephanvierkant/u2f-two-factor-bundle.git",
"reference": "81212afd7897911eb6bbf3f8b315ae336cb3e45b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/stephanvierkant/u2f-two-factor-bundle/zipball/81212afd7897911eb6bbf3f8b315ae336cb3e45b",
"reference": "81212afd7897911eb6bbf3f8b315ae336cb3e45b",
"shasum": ""
},
"require": {
"doctrine/collections": "^1.6",
"doctrine/common": "*",
"ext-json": "*",
"php": "^7.1.3",
"scheb/two-factor-bundle": "^3.2.0|^4.0.0",
"symfony/framework-bundle": "^3.4|^4.0",
"symfony/templating": "^3.4|^4.0",
"yubico/u2flib-server": "^1.0.0"
},
"conflict": {
"godzillante/u2f-two-factor-bundle": "*",
"tubssz/u2f-two-factor-bundle": "*"
},
"require-dev": {
"phpstan/phpstan": "^0.11.6"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"R\\U2FTwoFactorBundle\\": ""
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Nils Uliczka",
"email": "nils.uliczka@darookee.net"
},
{
"name": "Francesco De Francesco",
"email": "francesco.defrancesco@gmail.com"
}
],
"description": "Use U2F-Keys as 2FA for Symfony2, using scheb/two-factor-bundle",
"homepage": "https://github.com/darookee/u2f-two-factor-bundle",
"keywords": [
"authentication",
"fido",
"symfony2",
"two-factor",
"two-step",
"yubikey"
],
"support": {
"source": "https://github.com/stephanvierkant/u2f-two-factor-bundle/tree/u2f-api"
},
"time": "2019-07-17T10:00:04+00:00"
},
{
"name": "s9e/regexp-builder",
"version": "1.4.3",
@ -3489,17 +3733,85 @@
"time": "2019-12-26T19:14:01+00:00"
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.5.2",
"name": "scheb/two-factor-bundle",
"version": "v4.11.1",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
"reference": "92acfcc610e2180c52790ec3ff2e893f67e76b32"
"url": "https://github.com/scheb/two-factor-bundle.git",
"reference": "f9198cfcd5b2a92691926fd10406e8817232ac16"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/92acfcc610e2180c52790ec3ff2e893f67e76b32",
"reference": "92acfcc610e2180c52790ec3ff2e893f67e76b32",
"url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/f9198cfcd5b2a92691926fd10406e8817232ac16",
"reference": "f9198cfcd5b2a92691926fd10406e8817232ac16",
"shasum": ""
},
"require": {
"lcobucci/jwt": "^3.2",
"paragonie/constant_time_encoding": "^2.2",
"php": "^7.1.3",
"spomky-labs/otphp": "^9.1|^10.0",
"symfony/config": "^3.4|^4.0|^5.0",
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
"symfony/event-dispatcher": "^3.4|^4.0|^5.0",
"symfony/framework-bundle": "^3.4|^4.0|^5.0",
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/http-kernel": "^3.4|^4.0|^5.0",
"symfony/property-access": "^3.4|^4.0|^5.0",
"symfony/security-bundle": "^3.4|^4.0|^5.0",
"symfony/twig-bundle": "^3.4|^4.0|^5.0"
},
"require-dev": {
"doctrine/lexer": "^1.0.1",
"doctrine/orm": "^2.6",
"escapestudios/symfony2-coding-standard": "^3.9",
"phpunit/phpunit": "^7.0|^8.0",
"squizlabs/php_codesniffer": "^3.5",
"swiftmailer/swiftmailer": "^6.0",
"symfony/yaml": "^3.4|^4.0|^5.0"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"Scheb\\TwoFactorBundle\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Scheb",
"email": "me@christianscheb.de"
}
],
"description": "Provides two-factor authentication for Symfony applications",
"homepage": "https://github.com/scheb/two-factor-bundle",
"keywords": [
"Authentication",
"security",
"symfony",
"two-factor",
"two-step"
],
"time": "2019-12-19T12:07:42+00:00"
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.5.3",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git",
"reference": "98f0807137b13d0acfdf3c255a731516e97015de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/98f0807137b13d0acfdf3c255a731516e97015de",
"reference": "98f0807137b13d0acfdf3c255a731516e97015de",
"shasum": ""
},
"require": {
@ -3564,7 +3876,7 @@
"annotations",
"controllers"
],
"time": "2019-12-12T16:21:49+00:00"
"time": "2019-12-27T08:57:19+00:00"
},
{
"name": "sensiolabs/security-checker",
@ -3667,6 +3979,67 @@
],
"time": "2019-12-08T15:52:26+00:00"
},
{
"name": "spomky-labs/otphp",
"version": "v9.1.4",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/otphp.git",
"reference": "48d463cf909320399fe08eab2e1cd18d899d5068"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/48d463cf909320399fe08eab2e1cd18d899d5068",
"reference": "48d463cf909320399fe08eab2e1cd18d899d5068",
"shasum": ""
},
"require": {
"beberlei/assert": "^2.4|^3.0",
"paragonie/constant_time_encoding": "^2.0",
"php": "^7.1"
},
"require-dev": {
"phpunit/phpunit": "^6.0",
"satooshi/php-coveralls": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.0.x-dev"
}
},
"autoload": {
"psr-4": {
"OTPHP\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Florent Morselli",
"homepage": "https://github.com/Spomky"
},
{
"name": "All contributors",
"homepage": "https://github.com/Spomky-Labs/otphp/contributors"
}
],
"description": "A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator",
"homepage": "https://github.com/Spomky-Labs/otphp",
"keywords": [
"FreeOTP",
"RFC 4226",
"RFC 6238",
"google authenticator",
"hotp",
"otp",
"totp"
],
"time": "2019-03-18T10:08:51+00:00"
},
{
"name": "symfony/apache-pack",
"version": "v1.0.1",
@ -7646,16 +8019,16 @@
},
{
"name": "twig/cssinliner-extra",
"version": "v3.0.0",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/cssinliner-extra.git",
"reference": "431402520025e077268de4978a4206e5fb8c0103"
"reference": "d7c951e935d65c0cfd9a63bb08541a297f230f3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/431402520025e077268de4978a4206e5fb8c0103",
"reference": "431402520025e077268de4978a4206e5fb8c0103",
"url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/d7c951e935d65c0cfd9a63bb08541a297f230f3a",
"reference": "d7c951e935d65c0cfd9a63bb08541a297f230f3a",
"shasum": ""
},
"require": {
@ -7664,7 +8037,7 @@
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4@dev"
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
@ -7696,20 +8069,20 @@
"inlining",
"twig"
],
"time": "2019-10-17T07:27:07+00:00"
"time": "2019-12-27T07:33:44+00:00"
},
{
"name": "twig/extra-bundle",
"version": "v3.0.0",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/twig-extra-bundle.git",
"reference": "c56821429490e351003a09b7ed0c917feec2355f"
"reference": "ce5c97dd566d9acd5d1fbd5eb76b6d264614725a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/c56821429490e351003a09b7ed0c917feec2355f",
"reference": "c56821429490e351003a09b7ed0c917feec2355f",
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/ce5c97dd566d9acd5d1fbd5eb76b6d264614725a",
"reference": "ce5c97dd566d9acd5d1fbd5eb76b6d264614725a",
"shasum": ""
},
"require": {
@ -7719,11 +8092,11 @@
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"twig/cssinliner-extra": "^2.12|^3.0@dev",
"twig/html-extra": "^2.12@dev|^3.0@dev",
"twig/inky-extra": "^2.12@dev|^3.0@dev",
"twig/intl-extra": "^2.12@dev|^3.0@dev",
"twig/markdown-extra": "^2.12@dev|^3.0@dev"
"twig/cssinliner-extra": "^2.12|^3.0",
"twig/html-extra": "^2.12|^3.0",
"twig/inky-extra": "^2.12|^3.0",
"twig/intl-extra": "^2.12|^3.0",
"twig/markdown-extra": "^2.12|^3.0"
},
"type": "symfony-bundle",
"extra": {
@ -7755,20 +8128,20 @@
"extra",
"twig"
],
"time": "2019-10-17T07:30:08+00:00"
"time": "2019-12-28T07:09:27+00:00"
},
{
"name": "twig/inky-extra",
"version": "v3.0.0",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/inky-extra.git",
"reference": "7e33cb931f29e8cbc1f68eafa30e0ca7f7c6ad3b"
"reference": "68d7e3a00cb66dab07093c0c88059f4e02d71b39"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/inky-extra/zipball/7e33cb931f29e8cbc1f68eafa30e0ca7f7c6ad3b",
"reference": "7e33cb931f29e8cbc1f68eafa30e0ca7f7c6ad3b",
"url": "https://api.github.com/repos/twigphp/inky-extra/zipball/68d7e3a00cb66dab07093c0c88059f4e02d71b39",
"reference": "68d7e3a00cb66dab07093c0c88059f4e02d71b39",
"shasum": ""
},
"require": {
@ -7777,7 +8150,7 @@
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4@dev"
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
@ -7810,20 +8183,20 @@
"inky",
"twig"
],
"time": "2019-10-17T07:27:12+00:00"
"time": "2019-12-27T07:33:44+00:00"
},
{
"name": "twig/intl-extra",
"version": "v3.0.0",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/intl-extra.git",
"reference": "642552fa2834d9f56a727645f63e73d59672fb52"
"reference": "291d79ef98891da3efe14f0771fbe03a25fe6bec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/642552fa2834d9f56a727645f63e73d59672fb52",
"reference": "642552fa2834d9f56a727645f63e73d59672fb52",
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/291d79ef98891da3efe14f0771fbe03a25fe6bec",
"reference": "291d79ef98891da3efe14f0771fbe03a25fe6bec",
"shasum": ""
},
"require": {
@ -7832,7 +8205,7 @@
"twig/twig": "^2.4|^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4@dev"
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
@ -7863,20 +8236,20 @@
"intl",
"twig"
],
"time": "2019-11-15T20:33:33+00:00"
"time": "2019-12-28T07:09:27+00:00"
},
{
"name": "twig/markdown-extra",
"version": "v3.0.0",
"version": "v3.0.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/markdown-extra.git",
"reference": "609989e5bdc8cdb282e37fdc54e3591c244b5501"
"reference": "e3f6eb3f65eb2c165451c8417d918fb96fbd6d4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/609989e5bdc8cdb282e37fdc54e3591c244b5501",
"reference": "609989e5bdc8cdb282e37fdc54e3591c244b5501",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/e3f6eb3f65eb2c165451c8417d918fb96fbd6d4d",
"reference": "e3f6eb3f65eb2c165451c8417d918fb96fbd6d4d",
"shasum": ""
},
"require": {
@ -7888,7 +8261,7 @@
"league/commonmark": "^1.0",
"league/html-to-markdown": "^4.8",
"michelf/php-markdown": "^1.8",
"symfony/phpunit-bridge": "^4.4@dev"
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
@ -7920,20 +8293,20 @@
"markdown",
"twig"
],
"time": "2019-10-17T07:30:08+00:00"
"time": "2019-12-28T07:09:27+00:00"
},
{
"name": "twig/twig",
"version": "v2.12.2",
"version": "v2.12.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed"
"reference": "97b6311585cae66a26833b14b33785f5797f7d39"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed",
"reference": "d761fd1f1c6b867ae09a7d8119a6d95d06dc44ed",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/97b6311585cae66a26833b14b33785f5797f7d39",
"reference": "97b6311585cae66a26833b14b33785f5797f7d39",
"shasum": ""
},
"require": {
@ -7943,8 +8316,7 @@
},
"require-dev": {
"psr/container": "^1.0",
"symfony/debug": "^3.4|^4.2",
"symfony/phpunit-bridge": "^4.4@dev|^5.0"
"symfony/phpunit-bridge": "^4.4|^5.0"
},
"type": "library",
"extra": {
@ -7973,7 +8345,6 @@
},
{
"name": "Twig Team",
"homepage": "https://twig.symfony.com/contributors",
"role": "Contributors"
},
{
@ -7987,7 +8358,7 @@
"keywords": [
"templating"
],
"time": "2019-11-11T16:52:09+00:00"
"time": "2019-12-28T07:12:03+00:00"
},
{
"name": "webmozart/assert",
@ -8037,6 +8408,43 @@
],
"time": "2019-11-24T13:36:37+00:00"
},
{
"name": "yubico/u2flib-server",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/Yubico/php-u2flib-server.git",
"reference": "55d813acf68212ad2cadecde07551600d6971939"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939",
"reference": "55d813acf68212ad2cadecde07551600d6971939",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": ">= 1",
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"vimeo/psalm": "^0|^1|^2"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"description": "Library for U2F implementation",
"homepage": "https://developers.yubico.com/php-u2flib-server",
"time": "2018-09-07T08:16:44+00:00"
},
{
"name": "zendframework/zend-code",
"version": "3.4.1",
@ -8092,6 +8500,7 @@
"code",
"zf"
],
"abandoned": "laminas/laminas-code",
"time": "2019-12-10T19:21:15+00:00"
},
{
@ -8146,6 +8555,7 @@
"events",
"zf2"
],
"abandoned": "laminas/laminas-eventmanager",
"time": "2018-04-25T15:33:34+00:00"
}
],
@ -9495,22 +9905,23 @@
},
{
"name": "vimeo/psalm",
"version": "3.7.2",
"version": "3.8.1",
"source": {
"type": "git",
"url": "https://github.com/vimeo/psalm.git",
"reference": "d9cae720c1af31db9ba27c2bc1fcf9b0dd092fb0"
"reference": "8e54e3aa060fc490d86d0e2abbf62750516d40fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/d9cae720c1af31db9ba27c2bc1fcf9b0dd092fb0",
"reference": "d9cae720c1af31db9ba27c2bc1fcf9b0dd092fb0",
"url": "https://api.github.com/repos/vimeo/psalm/zipball/8e54e3aa060fc490d86d0e2abbf62750516d40fd",
"reference": "8e54e3aa060fc490d86d0e2abbf62750516d40fd",
"shasum": ""
},
"require": {
"amphp/amp": "^2.1",
"amphp/byte-stream": "^1.5",
"composer/xdebug-handler": "^1.1",
"ext-dom": "*",
"felixfbecker/advanced-json-rpc": "^3.0.3",
"felixfbecker/language-server-protocol": "^1.4",
"netresearch/jsonmapper": "^1.0",
@ -9531,6 +9942,7 @@
"ext-curl": "*",
"friendsofphp/php-cs-fixer": "^2.15",
"phpmyadmin/sql-parser": "^5.0",
"phpspec/prophecy": ">=1.9.0",
"phpunit/phpunit": "^7.5 || ^8.0",
"psalm/plugin-phpunit": "^0.6",
"slevomat/coding-standard": "^5.0",
@ -9561,7 +9973,8 @@
"Psalm\\": "src/Psalm"
},
"files": [
"src/functions.php"
"src/functions.php",
"src/spl_object_id.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -9579,7 +9992,7 @@
"inspection",
"php"
],
"time": "2019-12-03T13:33:31+00:00"
"time": "2019-12-29T16:11:07+00:00"
},
{
"name": "webmozart/glob",
@ -9679,6 +10092,7 @@
"minimum-stability": "stable",
"stability-flags": {
"gregwar/captcha-bundle": 20,
"r/u2f-two-factor-bundle": 20,
"roave/security-advisories": 20
},
"prefer-stable": false,

View file

@ -21,5 +21,7 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
R\U2FTwoFactorBundle\RU2FTwoFactorBundle::class => ['all' => true],
Translation\Bundle\TranslationBundle::class => ['all' => true],
];

View file

@ -0,0 +1,4 @@
ru2_f_two_factor:
formTemplate: "/security/U2F/u2f_login.html.twig"
registerTemplate: "/security/U2F/u2f_register.html.twig"
authCodeParameter: _auth_code

View file

@ -0,0 +1,25 @@
# See the configuration reference at https://github.com/scheb/two-factor-bundle/blob/master/Resources/doc/configuration.md
scheb_two_factor:
security_tokens:
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
# If you're using guard-based authentication, you have to use this one:
# - Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
google:
enabled: true # If Google Authenticator should be enabled, default false
server_name: '%partdb_title%' # Server name used in QR code
issuer: 'Part-DB' # Issuer name used in QR code
digits: 6 # Number of digits in authentication code
window: 1 # How many codes before/after the current one would be accepted as valid
template: security/2fa_form.html.twig
backup_codes:
enabled: true # If the backup code feature should be enabled
trusted_device:
enabled: true # If the trusted device feature should be enabled
lifetime: 5184000 # Lifetime of the trusted device token
extend_lifetime: false # Automatically extend lifetime of the trusted cookie on re-login
cookie_name: trusted_device # Name of the trusted device cookie
cookie_secure: false # Set the 'Secure' (HTTPS Only) flag on the trusted device cookie
cookie_same_site: "lax" # The same-site option of the cookie, can be "lax" or "strict"

View file

@ -18,6 +18,11 @@ security:
anonymous: true
user_checker: App\Security\UserChecker
two_factor:
auth_form_path: 2fa_login
check_path: 2fa_login_check
csrf_token_generator: security.csrf.token_manager
# activate different ways to authenticate
#http_basic: true
@ -42,5 +47,10 @@ security:
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
# This makes the logout route available during two-factor authentication, allows the user to cancel
- { path: ^/logout, role: IS_AUTHENTICATED_ANONYMOUSLY }
# This ensures that the form can only be accessed when two-factor authentication is in progress
- { path: "^/\\w{2}/2fa", role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
# We get into trouble with the U2F authentication, if the calls to the trees trigger an 2FA login
# This settings should not do much harm, because a read only access to show available data structures is not really critical
- { path: "^/\\w{2}/tree", role: IS_AUTHENTICATED_ANONYMOUSLY }

View file

@ -0,0 +1,11 @@
2fa_login:
path: /{_locale}/2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
2fa_login_check:
path: /{_locale}/2fa_check
r_u2f_register:
resource: "@RU2FTwoFactorBundle/Resources/config/routing.yml"
prefix: /{_locale}/user

View file

@ -26,6 +26,8 @@ services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
bool $demo_mode: '%demo_mode%'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
@ -120,6 +122,11 @@ services:
arguments:
$mimeTypes: '@mime_types'
App\Services\TFA\BackupCodeGenerator:
arguments:
$code_length: 8
$code_count: 15
App\Services\TranslationExtractor\PermissionExtractor:
tags:
- { name: 'translation.extractor', alias: 'permissionExtractor'}

View file

@ -28,6 +28,7 @@
"bootstrap-fileinput": "^5.0.1",
"bootstrap-select": "^1.13.8",
"bootswatch": "^4.3.1",
"clipboard": "^2.0.4",
"copy-webpack-plugin": "^5.0.4",
"corejs-typeahead": "^1.2.1",
"darkmode-js": "^1.5.3",
@ -43,7 +44,9 @@
"marked": "^0.7.0",
"patternfly-bootstrap-treeview": "^2.1.8",
"pdfmake": "^0.1.53",
"qrcode": "^1.4.4",
"ts-loader": "^5.3.3",
"typescript": "^3.3.4000"
"typescript": "^3.3.4000",
"u2f-api": "^1.1.1"
}
}

View file

@ -21,7 +21,11 @@
namespace App\Controller;
use App\Entity\Parts\Part;
use App\Entity\UserSystem\U2FKey;
use App\Entity\UserSystem\User;
use App\Services\PasswordResetManager;
use App\Services\TFA\BackupCodeManager;
use Doctrine\ORM\EntityManagerInterface;
use Gregwar\CaptchaBundle\Type\CaptchaType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -29,6 +33,7 @@ use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Mailer\MailerInterface;

View file

@ -25,16 +25,22 @@ use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\UserAttachment;
use App\Entity\UserSystem\User;
use App\Form\Permissions\PermissionsType;
use App\Form\TFAGoogleSettingsType;
use App\Form\UserAdminForm;
use App\Form\UserSettingsType;
use App\Services\EntityExporter;
use App\Services\EntityImporter;
use App\Services\StructuralElementRecursionHelper;
use App\Services\TFA\BackupCodeManager;
use Doctrine\ORM\EntityManagerInterface;
use \Exception;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
@ -61,6 +67,29 @@ class UserController extends AdminPages\BaseAdminController
*/
public function edit(User $entity, Request $request, EntityManagerInterface $em)
{
//Handle 2FA disabling
if($request->request->has('reset_2fa')) {
//Check if the admin has the needed permissions
$this->denyAccessUnlessGranted('set_password', $entity);
if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) {
//Disable Google authenticator
$entity->setGoogleAuthenticatorSecret(null);
$entity->setBackupCodes([]);
//Remove all U2F keys
foreach($entity->getU2FKeys() as $key) {
$em->remove($key);
}
//Invalidate trusted devices
$entity->invalidateTrustedDeviceTokens();
$em->flush();
$this->addFlash('success', 'user.edit.reset_success');
} else {
$this->addFlash('danger', 'csfr_invalid');
}
}
return $this->_edit($entity, $request, $em);
}
@ -76,7 +105,7 @@ class UserController extends AdminPages\BaseAdminController
}
/**
* @Route("/{id}", name="user_delete", methods={"DELETE"})
* @Route("/{id}", name="user_delete", methods={"DELETE"}, requirements={"id"="\d+"})
*/
public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper)
{
@ -147,94 +176,6 @@ class UserController extends AdminPages\BaseAdminController
]);
}
/**
* @Route("/settings", name="user_settings")
*/
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder)
{
/**
* @var User
*/
$user = $this->getUser();
$page_need_reload = false;
if (!$user instanceof User) {
return new \RuntimeException('This controller only works only for Part-DB User objects!');
}
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
/***************************
* User settings form
***************************/
$form = $this->createForm(UserSettingsType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Check if user theme setting has changed
if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) {
$page_need_reload = true;
}
$em->flush();
$this->addFlash('success', 'user.settings.saved_flash');
}
/*****************************
* Password change form
****************************/
$demo_mode = $this->getParameter('demo_mode');
$pw_form = $this->createFormBuilder()
->add('old_password', PasswordType::class, [
'label' => 'user.settings.pw_old.label',
'disabled' => $demo_mode,
'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted.
->add('new_password', RepeatedType::class, [
'disabled' => $demo_mode,
'type' => PasswordType::class,
'first_options' => ['label' => 'user.settings.pw_new.label'],
'second_options' => ['label' => 'user.settings.pw_confirm.label'],
'invalid_message' => 'password_must_match',
'constraints' => [new Length([
'min' => 6,
'max' => 128,
])],
])
->add('submit', SubmitType::class, ['label' => 'save'])
->getForm();
$pw_form->handleRequest($request);
//Check if password if everything was correct, then save it to User and DB
if ($pw_form->isSubmitted() && $pw_form->isValid()) {
$password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData());
$user->setPassword($password);
//After the change reset the password change needed setting
$user->setNeedPwChange(false);
$em->persist($user);
$em->flush();
$this->addFlash('success', 'user.settings.pw_changed_flash');
}
/******************************
* Output both forms
*****************************/
return $this->render('Users/user_settings.html.twig', [
'settings_form' => $form->createView(),
'pw_form' => $pw_form->createView(),
'page_need_reload' => $page_need_reload,
]);
}
/**
* Get either a Gravatar URL or complete image tag for a specified email address.
*

View file

@ -0,0 +1,308 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Controller;
use App\Entity\UserSystem\U2FKey;
use App\Entity\UserSystem\User;
use App\Form\TFAGoogleSettingsType;
use App\Form\UserSettingsType;
use App\Services\TFA\BackupCodeManager;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
use Symfony\Component\Validator\Constraints\Length;
/**
* @Route("/user")
* @package App\Controller
*/
class UserSettingsController extends AbstractController
{
protected $demo_mode;
public function __construct(bool $demo_mode)
{
$this->demo_mode = $demo_mode;
}
/**
* @Route("/2fa_backup_codes", name="show_backup_codes")
*/
public function showBackupCodes()
{
$user = $this->getUser();
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$user instanceof User) {
return new \RuntimeException('This controller only works only for Part-DB User objects!');
}
if (empty($user->getBackupCodes())) {
$this->addFlash('error', 'tfa_backup.no_codes_enabled');
throw new Exception('You do not have any backup codes enabled, therefore you can not view them!');
}
return $this->render('Users/backup_codes.html.twig', [
'user' => $user
]);
}
/**
* @Route("/u2f_delete", name="u2f_delete", methods={"DELETE"})
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function removeU2FToken(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager)
{
if($this->demo_mode) {
throw new \RuntimeException('You can not do 2FA things in demo mode');
}
$user = $this->getUser();
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$user instanceof User) {
throw new \RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) {
if($request->request->has('key_id')) {
$key_id = $request->request->get('key_id');
$key_repo = $entityManager->getRepository(U2FKey::class);
/** @var U2FKey|null $u2f */
$u2f = $key_repo->find($key_id);
if($u2f === null) {
$this->addFlash('danger','tfa_u2f.u2f_delete.not_existing');
throw new \RuntimeException('Key not existing!');
}
//User can only delete its own U2F keys
if ($u2f->getUser() !== $user) {
$this->addFlash('danger', 'tfa_u2f.u2f_delete.access_denied');
throw new \RuntimeException('You can only delete your own U2F keys!');
}
$backupCodeManager->disableBackupCodesIfUnused($user);
$entityManager->remove($u2f);
$entityManager->flush();
$this->addFlash('success', 'tfa.u2f.u2f_delete.success');
}
} else {
$this->addFlash('error','csfr_invalid');
}
return $this->redirectToRoute('user_settings');
}
/**
* @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"})
*/
public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager)
{
if($this->demo_mode) {
throw new \RuntimeException('You can not do 2FA things in demo mode');
}
$user = $this->getUser();
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$user instanceof User) {
return new \RuntimeException('This controller only works only for Part-DB User objects!');
}
if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) {
$user->invalidateTrustedDeviceTokens();
$entityManager->flush();
$this->addFlash('success', 'tfa_trustedDevice.invalidate.success');
} else {
$this->addFlash('error','csfr_invalid');
}
return $this->redirectToRoute('user_settings');
}
/**
* @Route("/settings", name="user_settings")
*/
public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordEncoderInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager)
{
/**
* @var User
*/
$user = $this->getUser();
$page_need_reload = false;
//When user change its settings, he should be logged in fully.
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
if (!$user instanceof User) {
throw new \RuntimeException('This controller only works only for Part-DB User objects!');
}
/***************************
* User settings form
***************************/
$form = $this->createForm(UserSettingsType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() && !$this->demo_mode) {
//Check if user theme setting has changed
if ($user->getTheme() !== $em->getUnitOfWork()->getOriginalEntityData($user)['theme']) {
$page_need_reload = true;
}
$em->flush();
$this->addFlash('success', 'user.settings.saved_flash');
}
/*****************************
* Password change form
****************************/
$pw_form = $this->createFormBuilder()
//Username field for autocomplete
->add('username', TextType::class, [
'data' => $user->getName(),
'attr' => ['autocomplete' => 'username'],
'disabled' => true,
'row_attr' => ['class' => 'd-none']
])
->add('old_password', PasswordType::class, [
'label' => 'user.settings.pw_old.label',
'disabled' => $this->demo_mode,
'attr' => ['autocomplete' => 'current-password'],
'constraints' => [new UserPassword()], ]) //This constraint checks, if the current user pw was inputted.
->add('new_password', RepeatedType::class, [
'disabled' => $this->demo_mode,
'type' => PasswordType::class,
'first_options' => ['label' => 'user.settings.pw_new.label'],
'second_options' => ['label' => 'user.settings.pw_confirm.label'],
'invalid_message' => 'password_must_match',
'options' => [
'attr' => ['autocomplete' => 'new-password']
],
'constraints' => [new Length([
'min' => 6,
'max' => 128,
])],
])
->add('submit', SubmitType::class, ['label' => 'save'])
->getForm();
$pw_form->handleRequest($request);
//Check if password if everything was correct, then save it to User and DB
if ($pw_form->isSubmitted() && $pw_form->isValid() && !$this->demo_mode) {
$password = $passwordEncoder->encodePassword($user, $pw_form['new_password']->getData());
$user->setPassword($password);
//After the change reset the password change needed setting
$user->setNeedPwChange(false);
$em->persist($user);
$em->flush();
$this->addFlash('success', 'user.settings.pw_changed_flash');
}
//Handle 2FA things
$google_form = $this->createForm(TFAGoogleSettingsType::class, $user);
$google_enabled = $user->isGoogleAuthenticatorEnabled();
if (!$form->isSubmitted() && !$google_enabled) {
$user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret());
$google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret());
}
$google_form->handleRequest($request);
if($google_form->isSubmitted() && $google_form->isValid() && !$this->demo_mode) {
if (!$google_enabled) {
//Save 2FA settings (save secrets)
$user->setGoogleAuthenticatorSecret($google_form->get('googleAuthenticatorSecret')->getData());
$backupCodeManager->enableBackupCodes($user);
$em->flush();
$this->addFlash('success', 'user.settings.2fa.google.activated');
return $this->redirectToRoute('user_settings');
} elseif ($google_enabled) {
//Remove secret to disable google authenticator
$user->setGoogleAuthenticatorSecret(null);
$backupCodeManager->disableBackupCodesIfUnused($user);
$em->flush();
$this->addFlash('success', 'user.settings.2fa.google.disabled');
return $this->redirectToRoute('user_settings');
}
}
$backup_form = $this->get('form.factory')->createNamedBuilder('backup_codes')->add('reset_codes', SubmitType::class,[
'label' => 'tfa_backup.regenerate_codes',
'attr' => ['class' => 'btn-danger'],
'disabled' => empty($user->getBackupCodes())
])->getForm();
$backup_form->handleRequest($request);
if ($backup_form->isSubmitted() && $backup_form->isValid() && !$this->demo_mode) {
$backupCodeManager->regenerateBackupCodes($user);
$em->flush();
$this->addFlash('success', 'user.settings.2fa.backup_codes.regenerated');
}
/******************************
* Output both forms
*****************************/
return $this->render('Users/user_settings.html.twig', [
'user' => $user,
'settings_form' => $form->createView(),
'pw_form' => $pw_form->createView(),
'page_need_reload' => $page_need_reload,
'google_form' => $google_form->createView(),
'backup_form' => $backup_form->createView(),
'tfa_google' => [
'enabled' => $google_enabled,
'qrContent' => $googleAuthenticator->getQRContent($user),
'secret' => $user->getGoogleAuthenticatorSecret(),
'username' => $user->getGoogleAuthenticatorUsername()
]
]);
}
}

View file

@ -53,9 +53,9 @@ trait MasterAttachmentTrait
* Sets the new master picture for this part.
*
* @param Attachment|null $new_master_attachment
* @return Part
* @return $this
*/
public function setMasterPictureAttachment(?Attachment $new_master_attachment): self
public function setMasterPictureAttachment(?Attachment $new_master_attachment)
{
$this->master_picture_attachment = $new_master_attachment;

View file

@ -107,11 +107,11 @@ class Storelocation extends PartsContainingDBElement
protected $storage_type;
/**
* @ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
* @ORM\JoinTable(name="part_lots",
* joinColumns={@ORM\JoinColumn(name="id_store_location", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="id_part", referencedColumnName="id")}
* )
* //@ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
* //@ORM\JoinTable(name="part_lots",
* // joinColumns={@ORM\JoinColumn(name="id_store_location", referencedColumnName="id")},
* // inverseJoinColumns={@ORM\JoinColumn(name="id_part", referencedColumnName="id")}
* //)
*/
protected $parts;

View file

@ -106,11 +106,11 @@ class Supplier extends Company
protected $shipping_costs;
/**
* @ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
* @ORM\JoinTable(name="orderdetails",
* joinColumns={@ORM\JoinColumn(name="id_supplier", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="part_id", referencedColumnName="id")}
* )
* //@ORM\ManyToMany(targetEntity="Part", fetch="EXTRA_LAZY")
* //@ORM\JoinTable(name="orderdetails",
* // joinColumns={@ORM\JoinColumn(name="id_supplier", referencedColumnName="id")},
* // inverseJoinColumns={@ORM\JoinColumn(name="part_id", referencedColumnName="id")}
* //)
*/
protected $parts;

View file

@ -64,12 +64,40 @@ class Group extends StructuralDBElement implements HasPermissionsInterface
*/
protected $permissions;
/**
* @var bool If true all users associated with this group must have enabled some kind of 2 factor authentication
* @ORM\Column(type="boolean", name="enforce_2fa")
*/
protected $enforce2FA = false;
public function __construct()
{
parent::__construct();
$this->permissions = new PermissionsEmbed();
}
/**
* Check if the users of this group are enforced to have two factor authentification (2FA) enabled.
* @return bool
*/
public function isEnforce2FA(): bool
{
return $this->enforce2FA;
}
/**
* Sets if the user of this group are enforced to have two factor authentification enabled.
* @param bool $enforce2FA True, if the users of this group are enforced to have 2FA enabled.
* @return $this
*/
public function setEnforce2FA(bool $enforce2FA): Group
{
$this->enforce2FA = $enforce2FA;
return $this;
}
/**
* Returns the ID as an string, defined by the element class.
* This should have a form like P000014, for a part with ID 14.

View file

@ -0,0 +1,183 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Entity\UserSystem;
use App\Entity\Base\TimestampTrait;
use Doctrine\ORM\Mapping as ORM;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use u2flib_server\Registration;
/**
* @ORM\Entity
* @ORM\Table(name="u2f_keys",
* uniqueConstraints={@ORM\UniqueConstraint(name="user_unique",columns={"user_id",
* "key_handle"})})
* @ORM\HasLifecycleCallbacks()
*/
class U2FKey implements TwoFactorKeyInterface
{
use TimestampTrait;
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string")
* @var string
**/
public $keyHandle;
/**
* @ORM\Column(type="string")
* @var string
**/
public $publicKey;
/**
* @ORM\Column(type="text")
* @var string
**/
public $certificate;
/**
* @ORM\Column(type="string")
* @var int
**/
public $counter;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="u2fKeys")
* @var User
**/
protected $user;
/**
* @ORM\Column(type="string")
* @var string
**/
protected $name;
public function fromRegistrationData(Registration $data): void
{
$this->keyHandle = $data->keyHandle;
$this->publicKey = $data->publicKey;
$this->certificate = $data->certificate;
$this->counter = $data->counter;
}
/** @inheritDoc */
public function getKeyHandle()
{
return $this->keyHandle;
}
/** @inheritDoc */
public function setKeyHandle($keyHandle)
{
$this->keyHandle = $keyHandle;
}
/** @inheritDoc */
public function getPublicKey()
{
return $this->publicKey;
}
/** @inheritDoc */
public function setPublicKey($publicKey)
{
$this->publicKey = $publicKey;
}
/** @inheritDoc */
public function getCertificate()
{
return $this->certificate;
}
/** @inheritDoc */
public function setCertificate($certificate)
{
$this->certificate = $certificate;
}
/** @inheritDoc */
public function getCounter()
{
return $this->counter;
}
/** @inheritDoc */
public function setCounter($counter)
{
$this->counter = $counter;
}
/** @inheritDoc */
public function getName()
{
return $this->name;
}
/** @inheritDoc */
public function setName($name)
{
$this->name = $name;
}
/**
* Gets the user, this U2F key belongs to.
* @return User
*/
public function getUser() : User
{
return $this->user;
}
/**
* The primary key ID of this key
* @return int
*/
public function getID() : int
{
return $this->id;
}
/**
* Sets the user this U2F key belongs to.
* @param TwoFactorInterface $new_user
* @return $this
*/
public function setUser(TwoFactorInterface $new_user) : self
{
$this->user = $new_user;
return $this;
}
}

View file

@ -53,28 +53,38 @@ namespace App\Entity\UserSystem;
use App\Entity\Attachments\AttachmentContainingDBElement;
use App\Entity\Attachments\UserAttachment;
use App\Entity\Base\MasterAttachmentTrait;
use App\Entity\Base\NamedDBElement;
use App\Entity\PriceInformations\Currency;
use App\Security\Interfaces\HasPermissionsInterface;
use App\Validator\Constraints\Selectable;
use App\Validator\Constraints\ValidPermission;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorKeyInterface;
use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
use Scheb\TwoFactorBundle\Model\PreferredProviderInterface;
use Scheb\TwoFactorBundle\Model\TrustedDeviceInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use R\U2FTwoFactorBundle\Model\U2F\TwoFactorInterface as U2FTwoFactorInterface;
/**
* This entity represents a user, which can log in and have permissions.
* Also this entity is able to save some informations about the user, like the names, email-address and other info.
* Also this entity is able to save some informations about the user, like the names, email-address and other info.
*
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @ORM\Table("`users`")
* @UniqueEntity("name", message="validator.user.username_already_used")
*/
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface
class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface,
TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, U2FTwoFactorInterface, PreferredProviderInterface
{
use MasterAttachmentTrait;
/** The User id of the anonymous user */
public const ID_ANONYMOUS = 1;
@ -172,6 +182,33 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/
protected $group;
/**
* @var string|null The secret used for google authenticator
* @ORM\Column(name="google_authenticator_secret", type="string", nullable=true)
*/
protected $googleAuthenticatorSecret;
/**
* @var string[]|null A list of backup codes that can be used, if the user has no access to its Google Authenticator device
* @ORM\Column(type="json")
*/
protected $backupCodes = [];
/** @var \DateTime The time when the backup codes were generated
* @ORM\Column(type="datetime", nullable=true)
*/
protected $backupCodesGenerationDate;
/** @var int The version of the trusted device cookie. Used to invalidate all trusted device cookies at once.
* @ORM\Column(type="integer")
*/
protected $trustedDeviceCookieVersion = 0;
/** @var Collection<TwoFactorKeyInterface>
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
*/
protected $u2fKeys;
/**
* @var array
* @ORM\Column(type="json")
@ -227,6 +264,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
{
parent::__construct();
$this->permissions = new PermissionsEmbed();
$this->u2fKeys = new ArrayCollection();
}
/**
@ -457,6 +495,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
return sprintf('%s %s', $this->getFirstName(), $this->getLastName());
}
/**
* Change the username of this user
* @param string $new_name The new username.
* @return $this
*/
public function setName(string $new_name): NamedDBElement
{
// Anonymous user is not allowed to change its username
@ -468,7 +511,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @return string
* Get the first name of the user.
* @return string|null
*/
public function getFirstName(): ?string
{
@ -476,9 +520,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $first_name
* Change the first name of the user
* @param string $first_name The new first name
*
* @return User
* @return $this
*/
public function setFirstName(?string $first_name): self
{
@ -488,7 +533,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @return string
* Get the last name of the user
* @return string|null
*/
public function getLastName(): ?string
{
@ -496,9 +542,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $last_name
* Change the last name of the user
* @param string $last_name The new last name
*
* @return User
* @return $this
*/
public function setLastName(?string $last_name): self
{
@ -508,6 +555,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* Gets the department of this user
* @return string
*/
public function getDepartment(): ?string
@ -516,8 +564,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $department
*
* Change the department of the user
* @param string $department The new department
* @return User
*/
public function setDepartment(?string $department): self
@ -528,6 +576,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* Get the email of the user.
* @return string
*/
public function getEmail(): ?string
@ -536,9 +585,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $email
*
* @return User
* Change the email of the user
* @param string $email The new email adress
* @return $this
*/
public function setEmail(?string $email): self
{
@ -548,7 +597,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @return string
* Gets the language the user prefers (as 2 letter ISO code).
* @return string|null The 2 letter ISO code of the preferred language (e.g. 'en' or 'de').
* If null is returned, the user has not specified a language and the server wide language should be used.
*/
public function getLanguage(): ?string
{
@ -556,19 +607,21 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $language
*
* Change the language the user prefers.
* @param string|null $language The new language as 2 letter ISO code (e.g. 'en' or 'de').
* Set to null, to use the system wide language.
* @return User
*/
public function setLanguage(?string $language): self
{
$this->language = $language;
return $this;
}
/**
* @return string
* Gets the timezone of the user
* @return string|null The timezone of the user (e.g. 'Europe/Berlin') or null if the user has not specified
* a timezone (then the global one should be used)
*/
public function getTimezone(): ?string
{
@ -576,9 +629,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $timezone
*
* @return User
* Change the timezone of this user.
* @param string $timezone|null The new timezone (e.g. 'Europe/Berlin') or null to use the system wide one.
* @return $this
*/
public function setTimezone(?string $timezone): self
{
@ -588,7 +641,8 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @return string
* Gets the theme the users wants to see. See self::AVAILABLE_THEMES for valid values.
* @return string|null The name of the theme the user wants to see, or null if the system wide should be used.
*/
public function getTheme(): ?string
{
@ -596,9 +650,10 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* @param string $theme
*
* @return User
* Change the theme the user wants to see.
* @param string|null $theme The name of the theme (See See self::AVAILABLE_THEMES for valid values). Set to null
* if the system wide theme should be used.
* @return $this
*/
public function setTheme(?string $theme): self
{
@ -607,11 +662,20 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
return $this;
}
/**
* Gets the group to which this user belongs to.
* @return Group|null The group of this user. Null if this user does not have a group.
*/
public function getGroup(): ?Group
{
return $this->group;
}
/**
* Sets the group of this user.
* @param Group|null $group The new group of this user. Set to null if this user should not have a group.
* @return $this
*/
public function setGroup(?Group $group): self
{
$this->group = $group;
@ -619,10 +683,181 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
return $this;
}
/**
* Returns a string representation of this user (the full name).
* E.g. 'Jane Doe (j.doe) [DISABLED]
* @return string
*/
public function __toString()
{
$tmp = $this->isDisabled() ? ' [DISABLED]' : '';
return $this->getFullName(true).$tmp;
}
/**
* Return true if the user should do two-factor authentication.
*
* @return bool
*/
public function isGoogleAuthenticatorEnabled(): bool
{
return $this->googleAuthenticatorSecret ? true : false;
}
/**
* Return the user name that should be shown in Google Authenticator.
* @return string
*/
public function getGoogleAuthenticatorUsername(): string
{
return $this->getUsername();
}
/**
* Return the Google Authenticator secret
* When an empty string is returned, the Google authentication is disabled.
*
* @return string|null
*/
public function getGoogleAuthenticatorSecret(): ?string
{
return $this->googleAuthenticatorSecret;
}
/**
* Sets the secret used for Google Authenticator. Set to null to disable Google Authenticator.
* @param string|null $googleAuthenticatorSecret
* @return $this
*/
public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): self
{
$this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
return $this;
}
/**
* Check if the given code is a valid backup code.
*
* @param string $code The code that should be checked.
* @return bool True if the backup code is valid.
*/
public function isBackupCode(string $code): bool
{
return in_array($code, $this->backupCodes);
}
/**
* Invalidate a backup code.
*
* @param string $code The code that should be invalidated
*/
public function invalidateBackupCode(string $code): void
{
$key = array_search($code, $this->backupCodes);
if ($key !== false){
unset($this->backupCodes[$key]);
}
}
/**
* Returns the list of all valid backup codes
* @return string[] An array with all backup codes
*/
public function getBackupCodes() : array
{
return $this->backupCodes ?? [];
}
/**
* Set the backup codes for this user. Existing backup codes are overridden.
* @param string[] $codes A
* @return $this
*/
public function setBackupCodes(array $codes) : self
{
$this->backupCodes = $codes;
if(empty($codes)) {
$this->backupCodesGenerationDate = null;
} else {
$this->backupCodesGenerationDate = new \DateTime();
}
return $this;
}
/**
* Return the date when the backup codes were generated.
* @return \DateTime|null
*/
public function getBackupCodesGenerationDate() : ?\DateTime
{
return $this->backupCodesGenerationDate;
}
/**
* Return version for the trusted device token. Increase version to invalidate all trusted token of the user.
* @return int The version of trusted device token
*/
public function getTrustedTokenVersion(): int
{
return $this->trustedDeviceCookieVersion;
}
/**
* Invalidate all trusted device tokens at once, by incrementing the token version.
* You have to flush the changes to database afterwards.
*/
public function invalidateTrustedDeviceTokens() : void
{
$this->trustedDeviceCookieVersion++;
}
/**
* Check if U2F is enabled
* @return bool
*/
public function isU2FAuthEnabled(): bool
{
return count($this->u2fKeys) > 0;
}
/**
* Get all U2F Keys that are associated with this user
* @return Collection<TwoFactorKeyInterface>
*/
public function getU2FKeys(): Collection
{
return $this->u2fKeys;
}
/**
* Add a U2F key to this user.
* @param TwoFactorKeyInterface $key
*/
public function addU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->add($key);
}
/**
* Remove a U2F key from this user.
* @param TwoFactorKeyInterface $key
*/
public function removeU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->removeElement($key);
}
/**
* @inheritDoc
*/
public function getPreferredTwoFactorProvider(): ?string
{
//If U2F is available then prefer it
if($this->isU2FAuthEnabled()) {
return 'u2f_two_factor';
}
//Otherwise use other methods
return null;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\EventSubscriber;
use App\Entity\UserSystem\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\HttpUtils;
/**
* This event subscriber redirects a user to its settings page, when it needs to change its password or is enforced
* to setup a 2FA method (enforcement can be set per group).
* In this cases the user is unable to access sites other than the whitelisted (see ALLOWED_ROUTES).
* @package App\EventSubscriber
*/
class PasswordChangeNeededSubscriber implements EventSubscriberInterface
{
protected $security;
protected $flashBag;
protected $httpUtils;
/**
* @var string[] The routes the user is allowed to access without being redirected.
* This should be only routes related to login/logout and user settings
*/
public const ALLOWED_ROUTES = [
'2fa_login',
'2fa_login_check',
'user_settings',
'club_base_register_u2f',
'logout',
];
/** @var string The route the user will redirected to, if he needs to change this password */
public const REDIRECT_TARGET = 'user_settings';
public function __construct(Security $security, FlashBagInterface $flashBag, HttpUtils $httpUtils)
{
$this->security = $security;
$this->flashBag = $flashBag;
$this->httpUtils = $httpUtils;
}
/**
* This function is called when the kernel encounters a request.
* It checks if the user must change its password or add an 2FA mehtod and redirect it to the user settings page,
* if needed.
* @param RequestEvent $event
*/
public function redirectToSettingsIfNeeded(RequestEvent $event) : void
{
$user = $this->security->getUser();
$request = $event->getRequest();
if(!$event->isMasterRequest()) {
return;
}
if(!$user instanceof User) {
return;
}
//Abort if we dont need to redirect the user.
if (!$user->isNeedPwChange() && !static::TFARedirectNeeded($user)) {
return;
}
//Check for a whitelisted URL
foreach (static::ALLOWED_ROUTES as $route) {
//Dont do anything if we encounter an allowed route
if ($this->httpUtils->checkRequestPath($request, $route)) {
return;
}
}
/* Dont redirect tree endpoints, as this would cause trouble and creates multiple flash
warnigs for one page reload */
if(strpos($request->getUri(), '/tree/') !== false) {
return;
}
//Show appropriate message to user about the reason he was redirected
if($user->isNeedPwChange()) {
$this->flashBag->add('warning', 'user.pw_change_needed.flash');
}
if(static::TFARedirectNeeded($user)) {
$this->flashBag->add('warning', 'user.2fa_needed.flash');
}
$event->setResponse($this->httpUtils->createRedirectResponse($request, static::REDIRECT_TARGET));
}
/**
* Check if a redirect because of a missing 2FA method is needed.
* That is the case if the group of the user enforces 2FA, but the user has neither Google Authenticator nor an
* U2F key setup.
* @param User $user The user for which should be checked if it needs to be redirected.
* @return bool True if the user needs to be redirected.
*/
public static function TFARedirectNeeded(User $user) : bool
{
$tfa_enabled = $user->isU2FAuthEnabled() || $user->isGoogleAuthenticatorEnabled();
if ($user->getGroup() !== null && $user->getGroup()->isEnforce2FA() && !$tfa_enabled) {
return true;
}
return false;
}
/**
* @inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'redirectToSettingsIfNeeded',
];
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\EventSubscriber;
use App\Entity\UserSystem\U2FKey;
use Doctrine\ORM\EntityManagerInterface;
use R\U2FTwoFactorBundle\Event\RegisterEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class U2FRegistrationSubscriber implements EventSubscriberInterface
{
/** @var UrlGeneratorInterface */
private $router;
protected $em;
protected $demo_mode;
protected $flashBag;
public function __construct(UrlGeneratorInterface $router, EntityManagerInterface $entityManager, FlashBagInterface $flashBag, bool $demo_mode)
{
$this->router = $router;
$this->em = $entityManager;
$this->demo_mode = $demo_mode;
$this->flashBag = $flashBag;
}
/** @return string[] **/
public static function getSubscribedEvents(): array
{
return array(
'r_u2f_two_factor.register' => 'onRegister',
);
}
public function onRegister(RegisterEvent $event): void
{
//Skip adding of U2F key on demo mode
if (!$this->demo_mode) {
$user = $event->getUser();
$registration = $event->getRegistration();
$newKey = new U2FKey();
$newKey->fromRegistrationData($registration);
$newKey->setUser($user);
$newKey->setName($event->getKeyName());
// persist the new key
$this->em->persist($newKey);
$this->em->flush();
$this->flashBag->add('success', 'tfa_u2f.key_added_successful');
}
// generate new response, here we redirect the user to the fos user
// profile
$response = new RedirectResponse($this->router->generate('user_settings'));
$event->setResponse($response);
}
}

View file

@ -23,12 +23,22 @@ namespace App\Form\AdminPages;
use App\Entity\Base\NamedDBElement;
use App\Form\Permissions\PermissionsType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
class GroupAdminForm extends BaseEntityAdminForm
{
protected function additionalFormElements(FormBuilderInterface $builder, array $options, NamedDBElement $entity)
{
$is_new = null === $entity->getID();
$builder->add('enforce2FA', CheckboxType::class, ['required' => false,
'label' => 'group.edit.enforce_2fa',
'help' => 'entity.edit.enforce_2fa.help',
'label_attr' => ['class' => 'checkbox-custom'],
'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity)
]);
$builder->add('permissions', PermissionsType::class, [
'mapped' => false,
'data' => $builder->getData(),

View file

@ -0,0 +1,94 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Form;
use App\Entity\UserSystem\User;
use App\Validator\Constraints\ValidGoogleAuthCode;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
class TFAGoogleSettingsType extends AbstractType
{
protected $translator;
public function __construct()
{
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$form = $event->getForm();
/** @var User $user */
$user = $event->getData();
//Only show setup fields, when google authenticator is not enabled
if(!$user->isGoogleAuthenticatorEnabled()) {
$form->add(
'google_confirmation',
TextType::class,
[
'mapped' => false,
'attr' => ['maxlength' => '6', 'minlength' => '6', 'pattern' => '\d*', 'autocomplete' => 'off'],
'constraints' => [new ValidGoogleAuthCode()]
]
);
$form->add(
'googleAuthenticatorSecret',
HiddenType::class,
[
'disabled' => false,
]
);
$form->add('submit', SubmitType::class, [
'label' => 'tfa_google.enable'
]);
} else {
$form->add('submit', SubmitType::class, [
'label' =>'tfa_google.disable',
'attr' => ['class' => 'btn-danger']
]);
}
});
//$builder->add('cancel', ResetType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20191214153125 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE u2f_keys (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, key_handle VARCHAR(255) NOT NULL, public_key VARCHAR(255) NOT NULL, certificate LONGTEXT NOT NULL, counter VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, INDEX IDX_4F4ADB4BA76ED395 (user_id), UNIQUE INDEX user_unique (user_id, key_handle), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE u2f_keys ADD CONSTRAINT FK_4F4ADB4BA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)');
$this->addSql('ALTER TABLE `groups` ADD enforce_2fa TINYINT(1) NOT NULL');
$this->addSql('ALTER TABLE users ADD google_authenticator_secret VARCHAR(255) DEFAULT NULL, ADD backup_codes LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', ADD backup_codes_generation_date DATETIME DEFAULT NULL, ADD trusted_device_cookie_version INT NOT NULL');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE u2f_keys');
$this->addSql('ALTER TABLE `groups` DROP enforce_2fa');
$this->addSql('ALTER TABLE `users` DROP google_authenticator_secret, DROP backup_codes, DROP backup_codes_generation_date, DROP trusted_device_cookie_version');
}
}

View file

@ -0,0 +1,77 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Services\TFA;
/**
* This class generates random backup codes for two factor authentication
* @package App\Services\TFA
*/
class BackupCodeGenerator
{
protected $code_length;
protected $code_count;
/**
* BackupCodeGenerator constructor.
* @param int $code_length How many characters a single code should have.
* @param int $code_count How many codes are generated for a whole backup set.
*/
public function __construct(int $code_length, int $code_count)
{
if ($code_length > 32) {
throw new \RuntimeException('Backup code can have maximum 32 digits!');
}
if ($code_length < 6) {
throw new \RuntimeException('Code must have at least 6 digits to ensure security!');
}
$this->code_count = $code_count;
$this->code_length = $code_length;
}
/**
* Generates a single backup code.
* It is a random hexadecimal value with the digit count configured in constructor
* @return string The generated backup code (e.g. 1f3870be2)
* @throws \Exception If no entropy source is available.
*/
public function generateSingleCode() : string
{
$bytes = random_bytes(32);
return substr(md5($bytes), 0, $this->code_length);
}
/**
* Returns a full backup code set. The code count can be configured in the constructor
* @return string[] An array containing different backup codes.
*/
public function generateCodeSet() : array
{
$array = [];
for($n=0; $n<$this->code_count; $n++) {
$array[] = $this->generateSingleCode();
}
return $array;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Services\TFA;
use App\Entity\UserSystem\User;
/**
* This services offers methods to manage backup codes for two factor authentication
* @package App\Services\TFA
*/
class BackupCodeManager
{
protected $backupCodeGenerator;
public function __construct(BackupCodeGenerator $backupCodeGenerator)
{
$this->backupCodeGenerator = $backupCodeGenerator;
}
/**
* Enable backup codes for the given user, by generating a set of backup codes.
* If the backup codes were already enabled before, they a
* @param User $user
*/
public function enableBackupCodes(User $user)
{
if(empty($user->getBackupCodes())) {
$this->regenerateBackupCodes($user);
}
}
/**
* Disable (remove) the backup codes when no other 2 factor authentication methods are enabled.
* @param User $user
*/
public function disableBackupCodesIfUnused(User $user)
{
if($user->isGoogleAuthenticatorEnabled()) {
return;
}
$user->setBackupCodes([]);
}
/**
* Generates a new set of backup codes for the user. If no backup codes were available before, new ones are
* generated.
* @param User $user The user for which the backup codes should be regenerated
*/
public function regenerateBackupCodes(User $user)
{
$codes = $this->backupCodeGenerator->generateCodeSet();
$user->setBackupCodes($codes);
}
}

View file

@ -33,6 +33,7 @@ use App\Services\MoneyFormatter;
use App\Services\SIFormatter;
use App\Services\TreeBuilder;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
@ -49,13 +50,14 @@ class AppExtension extends AbstractExtension
protected $amountFormatter;
protected $attachmentURLGenerator;
protected $FAIconGenerator;
protected $translator;
public function __construct(EntityURLGenerator $entityURLGenerator, MarkdownParser $markdownParser,
SerializerInterface $serializer, TreeBuilder $treeBuilder,
MoneyFormatter $moneyFormatter,
SIFormatter $SIFormatter, AmountFormatter $amountFormatter,
AttachmentURLGenerator $attachmentURLGenerator,
FAIconGenerator $FAIconGenerator)
SerializerInterface $serializer, TreeBuilder $treeBuilder,
MoneyFormatter $moneyFormatter,
SIFormatter $SIFormatter, AmountFormatter $amountFormatter,
AttachmentURLGenerator $attachmentURLGenerator,
FAIconGenerator $FAIconGenerator, TranslatorInterface $translator)
{
$this->entityURLGenerator = $entityURLGenerator;
$this->markdownParser = $markdownParser;
@ -66,6 +68,7 @@ class AppExtension extends AbstractExtension
$this->amountFormatter = $amountFormatter;
$this->attachmentURLGenerator = $attachmentURLGenerator;
$this->FAIconGenerator = $FAIconGenerator;
$this->translator = $translator;
}
public function getFilters()

View file

@ -0,0 +1,30 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class ValidGoogleAuthCode extends Constraint
{
}

View file

@ -0,0 +1,83 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Validator\Constraints;
use App\Entity\UserSystem\User;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class ValidGoogleAuthCodeValidator extends ConstraintValidator
{
protected $googleAuthenticator;
public function __construct(GoogleAuthenticator $googleAuthenticator)
{
$this->googleAuthenticator = $googleAuthenticator;
}
/**
* @inheritDoc
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof ValidGoogleAuthCode) {
throw new UnexpectedTypeException($constraint, ValidGoogleAuthCode::class);
}
if (null === $value || '' === $value) {
return;
}
if (!\is_string($value)) {
throw new UnexpectedValueException($value, 'string');
}
if(!ctype_digit($value)) {
$this->context->addViolation('validator.google_code.only_digits_allowed');
}
//Number must have 6 digits
if(strlen($value) !== 6) {
$this->context->addViolation('validator.google_code.wrong_digit_count');
}
//Try to retrieve the user we want to check
if($this->context->getObject() instanceof FormInterface &&
$this->context->getObject()->getParent() instanceof FormInterface
&& $this->context->getObject()->getParent()->getData() instanceof User) {
$user = $this->context->getObject()->getParent()->getData();
//Check if the given code is valid
if(!$this->googleAuthenticator->checkCode($user, $value)) {
$this->context->addViolation('validator.google_code.wrong_code');
}
}
}
}

View file

@ -5,6 +5,9 @@
"amphp/byte-stream": {
"version": "v1.6.1"
},
"beberlei/assert": {
"version": "v3.2.6"
},
"composer/xdebug-handler": {
"version": "1.3.3"
},
@ -165,6 +168,9 @@
"jdorn/sql-formatter": {
"version": "v1.2.17"
},
"lcobucci/jwt": {
"version": "3.3.1"
},
"league/html-to-markdown": {
"version": "4.8.2"
},
@ -229,6 +235,9 @@
"openlss/lib-array2xml": {
"version": "1.0.0"
},
"paragonie/constant_time_encoding": {
"version": "v2.3.0"
},
"php": {
"version": "7.1.3"
},
@ -301,6 +310,9 @@
"psr/simple-cache": {
"version": "1.0.1"
},
"r/u2f-two-factor-bundle": {
"version": "0.7.0"
},
"roave/security-advisories": {
"version": "dev-master"
},
@ -310,6 +322,19 @@
"s9e/text-formatter": {
"version": "2.1.2"
},
"scheb/two-factor-bundle": {
"version": "3.16",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "master",
"version": "3.16",
"ref": "b5789cd9710e2ee555bf361079b991068a0f640b"
},
"files": [
"./config/packages/scheb_two_factor.yaml",
"./config/routes/scheb_two_factor.yaml"
]
},
"sebastian/diff": {
"version": "3.0.2"
},
@ -340,6 +365,9 @@
"shivas/versioning-bundle": {
"version": "3.1.3"
},
"spomky-labs/otphp": {
"version": "v9.1.4"
},
"symfony/apache-pack": {
"version": "1.0",
"recipe": {
@ -751,6 +779,9 @@
"webmozart/path-util": {
"version": "2.3.0"
},
"yubico/u2flib-server": {
"version": "1.0.2"
},
"zendframework/zend-code": {
"version": "3.3.1"
},

View file

@ -12,6 +12,10 @@
{% block additional_panes %}
<div class="tab-pane" id="tab_permissions">
{{ form_row(form.permissions) }}
{{ form_row(form.permissions) }}
</div>
{% endblock %}
{% block additional_controls %}
{{ form_row(form.enforce2FA) }}
{% endblock %}

View file

@ -1,5 +1,9 @@
{% extends "AdminPages/EntityAdminBase.html.twig" %}
{% import "helper.twig" as helper %}
{# @var entity \App\Entity\UserSystem\User #}
{% block card_title %}
<i class="fas fa-user fa-fw"></i> {% trans %}user.edit.caption{% endtrans %}
{% endblock %}
@ -34,6 +38,42 @@
{{ form_row(form.new_password) }}
{{ form_row(form.need_pw_change) }}
{{ form_row(form.disabled) }}
{% if entity.id is not null %}
<div class="offset-3 mb-3">
<hr>
<h6>{% trans %}user.edit.tfa.caption{% endtrans %}</h6>
<p><b>{% trans %}user.edit.tfa.google_active{% endtrans %}:</b> {{ helper.boolean(entity.googleAuthenticatorEnabled) }}</p>
<p class="mb-0"><b>{% trans %}tfa_backup.remaining_tokens{% endtrans %}:</b> {{ entity.backupCodes | length }}</p>
<p><b>{% trans %}tfa_backup.generation_date{% endtrans %}:</b>
{% if entity.backupCodesGenerationDate is not null %}
{{ entity.backupCodesGenerationDate | format_datetime }}
{% else %}
{% trans %}user.edit.tfa.disabled{% endtrans %}
{% endif %}
</p>
<p><b>{% trans %}user.edit.tfa.u2f_keys_count{% endtrans %}:</b>
{% if entity.u2FAuthEnabled %}
{{ entity.u2FKeys | length }}
{% else %}
{% trans %}user.edit.tfa.disabled{% endtrans %}
{% endif %}
</p>
{% set tfa_disable_disabled = not is_granted('set_password', entity) %}
{# Disable button when he has no 2FA activated #}
{% if not entity.u2FAuthEnabled and not entity.googleAuthenticatorEnabled and entity.backupCodes is empty %}
{% set tfa_disable_disabled = true %}
{% endif %}
<input type="hidden" name="_token" value="{{ csrf_token('reset_2fa' ~ entity.id) }}">
<button class="btn btn-warning" {% if tfa_disable_disabled %}disabled="disabled"{% endif %}
data-delete-btn data-title="{% trans %}user.edit.tfa.disable_tfa_title{% endtrans %}" data-message="{% trans %}user.edit.tfa.disable_tfa_message{% endtrans %}"
type="submit" name="reset_2fa" value="reset_2fa">{% trans %}user.edit.tfa.disable_tfa.btn{% endtrans %}</button>
</div>
{% endif %}
</div>
<div class="tab-pane" id="permissions">

View file

@ -0,0 +1,162 @@
{# @var user \App\Entity\UserSystem\User #}
<div class="card mt-4">
<div class="card-header">
<i class="fa fa-shield-alt fa-fw" aria-hidden="true"></i>
{% trans %}user.settings.2fa_settings{% endtrans %}
</div>
<div class="card-body">
<ul class="nav nav-tabs" id="tfa-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="google-tab" data-toggle="tab" href="#tfa-google" role="tab"
aria-controls="home" aria-selected="true">{% trans %}tfa.settings.google.tab{% endtrans %}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="backup-tab" data-toggle="tab" href="#tfa-backup" role="tab"
aria-controls="profile" aria-selected="false">{% trans %}tfa.settings.bakup.tab{% endtrans %}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="u2f-tab" data-toggle="tab" href="#tfa-u2f" role="tab"
aria-controls="profile" aria-selected="false">{% trans %}tfa.settings.u2f.tab{% endtrans %}</a>
</li>
<li class="nav-item">
<a class="nav-link" id="trustedDevices-tab" data-toggle="tab" href="#tfa-trustedDevices" role="tab"
aria-controls="profile" aria-selected="false">{% trans %}tfa.settings.trustedDevices.tab{% endtrans %}</a>
</li>
</ul>
<div class="tab-content mt-3 mb-3" id="tfa-tabs-content">
<div class="tab-pane fade show active" id="tfa-google" role="tabpanel" aria-labelledby="google-tab">
{% set google_form_attr = {} %}
{% if tfa_google.enabled %}
{% set google_form_attr = { 'data-delete-form': true,
'data-title': 'tfa_google.disable.confirm_title' | trans, 'data-message': 'tfa_google.disable.confirm_message'|trans} %}
{% endif %}
{{ form_start(google_form, { 'attr': google_form_attr}) }}
{% if not tfa_google.enabled %}
<div class="offset-3">
<h6>{% trans %}tfa_google.disabled_message{% endtrans %}</h6>
</div>
<div class="offset-3 row">
<div class="col-3">
<canvas class="qrcode" data-content="{{ tfa_google.qrContent }}"></canvas>
</div>
<div class="col-9 my-auto">
<ol class="">
<li>{% trans %}tfa_google.step.download{% endtrans %}</li>
<li>{% trans %}tfa_google.step.scan{% endtrans %}</li>
<li>{% trans %}tfa_google.step.input_code{% endtrans %}</li>
<li>{% trans %}tfa_google.step.download_backup{% endtrans %}</li>
</ol>
</div>
</div>
<div class="offset-3">
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#manualSetupCollapse" aria-expanded="false" aria-controls="manualSetupCollapse">
{% trans %}tfa_google.manual_setup{% endtrans %}
</button>
<div class="collapse" id="manualSetupCollapse">
<div class="card card-body mb-2">
<p><b>{% trans %}tfa_google.manual_setup.type{% endtrans %}</b>: TOTP</p>
<p><b>{% trans %}tfa_google.manual_setup.username{% endtrans %}</b>: {{ tfa_google.username }}</p>
<p><b>{% trans %}tfa_google.manual_setup.secret{% endtrans %}</b>: {{ tfa_google.secret }}</p>
<p><b>{% trans %}tfa_google.manual_setup.digit_count{% endtrans %}</b>: 6</p>
</div>
</div>
</div>
{{ form_row(google_form.google_confirmation) }}
{% else %}
<div class="offset-3">
<h6>{% trans %}tfa_google.enabled_message{% endtrans %}</h6>
</div>
{% endif %}
{{ form_row(google_form.submit) }}
{{ form_end(google_form) }}
</div>
<div class="tab-pane fade" id="tfa-backup" role="tabpanel" aria-labelledby="backup-tab">
{% if user.backupCodes is empty %}
<div class="offset-3">
<h6>{% trans %}tfa_backup.disabled{% endtrans %}</h6>
<span>{% trans %}tfa_backup.explanation{% endtrans %}</span>
</div>
{% else %}
{% set backup_form_attr = { 'data-delete-form': true,
'data-title': 'tfa_backup.reset_codes.confirm_title' | trans, 'data-message': 'tfa_backup.reset_codes.confirm_message' | trans} %}
{{ form_start(backup_form, { 'attr': backup_form_attr}) }}
<div class="offset-3">
<h6>{% trans %}tfa_backup.enabled{% endtrans %}</h6>
<span>{% trans %}tfa_backup.explanation{% endtrans %}</span>
</div>
<div class="offset-3 mt-2">
<p class="mb-0"><b>{% trans %}tfa_backup.remaining_tokens{% endtrans %}:</b> {{ user.backupCodes | length }}</p>
<p><b>{% trans %}tfa_backup.generation_date{% endtrans %}:</b> {{ user.backupCodesGenerationDate | format_datetime }}</p>
</div>
<div class="offset-3">
<a href="{{ url('show_backup_codes') }}" target="_blank" data-no-ajax class="btn btn-primary">{% trans %}tfa_backup.show_codes{% endtrans %}</a>
</div>
<div class="offset-3 mt-2">
{{ form_widget(backup_form.reset_codes) }}
</div>
{{ form_end(backup_form) }}
{% endif %}
</div>
<div class="tab-pane fade" id="tfa-u2f" role="tabpanel" aria-labelledby="u2f-tab">
<p>{% trans %}tfa_u2f.explanation{% endtrans %}</p>
{% if user.u2FKeys is not empty %}
<b>{% trans %}tfa_u2f.table_caption{% endtrans %}:</b>
<form action="{{ url('u2f_delete') }}" method="post" data-delete-form data-title="{% trans %}tfa_u2f.delete_u2f.confirm_title{% endtrans %}"
data-message="{% trans %}tfa_u2f.delete_u2f.confirm_message{% endtrans %}">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ user.id) }}">
<table class="table table-striped table-bordered table-sm mt-2">
<thead>
<tr>
<th>#</th>
<th>{% trans %}tfa_u2f.keys.name{% endtrans %}</th>
<th>{% trans %}tfa_u2f.keys.added_date{% endtrans %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for key in user.u2FKeys %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ key.name }}</td>
<td>{{ key.addedDate | format_datetime }}</td>
<td><button type="submit" class="btn btn-danger btn-sm" name="key_id" value="{{ key.id }}"><i class="fas fa-trash-alt fa-fw"></i> {% trans %}tfa_u2f.key_delete{% endtrans %}</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% else %}
<p><b>{% trans %}tfa_u2f.no_keys_registered{% endtrans %}</b></p>
{% endif %}
<a href="{{ url('club_base_register_u2f') }}" class="btn btn-success"><i class="fas fa-plus-square fa-fw"></i> {% trans %}tfa_u2f.add_new_key{% endtrans %}</a>
</div>
<div class="tab-pane fade" id="tfa-trustedDevices" role="tabpanel" aria-labelledby="trustedDevices-tab-tab">
<p>{% trans %}tfa_trustedDevices.explanation{% endtrans %}</p>
<form action="{{ url('tfa_trustedDevices_invalidate') }}" method="post" data-delete-form data-title="{% trans %}tfa_trustedDevices.invalidate.confirm_title{% endtrans %}"
data-message="{% trans %}tfa_trustedDevices.invalidate.confirm_message{% endtrans %}">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token('devices_reset' ~ user.id) }}">
<button class="btn btn-danger" type="submit">{% trans %}tfa_trustedDevices.invalidate.btn{% endtrans %}</button>
</form>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,40 @@
{% extends "base.html.twig" %}
{% block title %}{{ partdb_title }} {% trans %}tfa_backup.codes.title{% endtrans %}{% endblock %}
{% block body %}
<div class="container">
<div class="card">
<div class="card-header">
{{ partdb_title }} {% trans %}tfa_backup.codes.title{% endtrans %}
</div>
<div class="card-body">
<h5 class="card-title">{% trans %}tfa_backup.codes.explanation{% endtrans %}</h5>
<p class="card-text">{% trans %}tfa_backup.codes.help{% endtrans %}</p>
<p class="mb-0"><b>{% trans %}tfa_backup.remaining_tokens{% endtrans %}:</b> {{ user.backupCodes | length }}</p>
<p class="mb-0"><b>{% trans %}tfa_backup.username{% endtrans %}:</b> {{ user.name }}</p>
<p><b>{% trans %}tfa_backup.generation_date{% endtrans %}:</b> {{ user.backupCodesGenerationDate | format_datetime }}</p>
<div class="alert border-dark">
<div class="card-body">
<ul class="row list-unstyled" id="backup_codes_list">
{% for code in user.backupCodes %}
<h4 class="col-6"><li><i class="far fa-square fa-fw"></i><span class="text-monospace text-dark ml-1">{{ code }}</span></li></h4>
{% endfor %}
</ul>
</div>
</div>
<p><small>{% trans with {'%date%': "now" | format_datetime} %}tfa_backup.codes.page_generated_on{% endtrans %}</small></p>
<a href="javascript:window.print()" class="btn btn-outline-dark d-print-none">
<i class="fas fa-print fa-fw"></i> {% trans %}tfa_backup.codes.print{% endtrans %}
</a>
<button class="btn btn-outline-dark d-print-none" data-clipboard-text="{{ user.backupCodes | join('\n') }}">
<i class="fas fa-copy"></i> {% trans %}tfa_backup.codes.copy_clipboard{% endtrans %}
</button>
</div>
</div>
</div>
{% endblock %}

View file

@ -47,6 +47,8 @@
{% block content %}
{{ parent() }}
{% include "Users/_2fa_settings.html.twig" %}
<div class="card mt-4">
<div class="card-header">
<i class="fa fa-key fa-fw" aria-hidden="true"></i>

View file

@ -123,6 +123,10 @@
<script src="{{ preload(asset(js), {as: 'script'}) }}"></script>
{% endfor %}
{% for js in encore_entry_js_files('ru2ftwofactor') %}
<script src="{{ preload(asset(js), {as: 'script'}) }}"></script>
{% endfor %}
{% endblock %}
{% block scripts %}

View file

@ -80,4 +80,12 @@
{% endfor %}
</ol>
</nav>
{% endmacro %}
{% macro bool_icon(bool) %}
{% if bool %}
<i class="fas fa-check-circle fa-fw" title="{% trans %}Yes{% endtrans %}"></i>
{% else %}
<i class="fas fa-times-circle fa-fw" title="{% trans %}No{% endtrans %}"></i>
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,58 @@
{% extends "main_card.html.twig" %}
{% block title %}{% trans %}tfa.check.title{% endtrans %}{% endblock %}
{% block card_title %}<i class="fas fa-shield-alt"></i> {% trans %}tfa.check.title{% endtrans %}{% endblock %}
{% block content %}
{% if authenticationError %}
<div class="alert alert-danger" role="alert">
<strong>{{ authenticationError|trans(authenticationErrorData, 'SchebTwoFactorBundle') }}}</strong>
</div>
{% endif %}
{{ parent() }}
{% endblock %}
{% block card_content %}
<ul class="nav nav-pills mb-4">
{% for provider in availableTwoFactorProviders %}
<li class="nav-item">
<a class="nav-link {% if provider == twoFactorProvider %}active{% endif %}"
href="{{ path("2fa_login", {"preferProvider": provider}) }}">{{ ('tfa.provider.'~provider) | trans }}</a>
</li>
{% endfor %}
</ul>
{# Display current two-factor provider #}
<form class="form form-horizontal" action="{{ path("2fa_login_check") }}" method="post" {% block form_attributes %}{% endblock %}>
{% block form %}
{% endblock %}
{% if displayTrustedOption %}
<div class="form-group row mt-3">
<div class="offset-3">
<div class="custom-checkbox custom-control ml-2">
<input id="_trusted" class="custom-control-input" type="checkbox" name="{{ trustedParameterName }}" />
<label class="custom-control-label" for="_trusted">{% trans %}tfa.code.trusted_pc{% endtrans %}</label>
</div>
</div>
</div>
{% endif %}
{% if isCsrfProtectionEnabled %}
<input type="hidden" name="{{ csrfParameterName }}" value="{{ csrf_token(csrfTokenId) }}">
{% endif %}
{% block submit_btn %}
<div class="form-group-row">
<div class="offset-3">
<button type="submit" class="btn btn-primary" value="{{ "login"|trans({}, 'SchebTwoFactorBundle') }}">{% trans %}login.btn{% endtrans %}</button>
<a class="ml-2" href="{{ logoutPath }}">{% trans %}user.logout{% endtrans %}</a>
</div>
</div>
{% endblock %}
</form>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "security/2fa_base_form.html.twig" %}
{% block form %}
<div class="form-group row">
<label for="_auth_code" class="col-form-label col-3">{% trans %}tfa.check.code.label{% endtrans %}</label>
<div class="col-9">
<input id="_auth_code" class="form-control" type="text" autocomplete="off" name="{{ authCodeParameterName }}" autofocus />
<small id="passwordHelpBlock" class="form-text text-muted">
{% trans %}tfa.check.code.help{% endtrans %}
</small>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends "security/2fa_base_form.html.twig" %}
{% block form_attributes %}id="u2fForm" data-action="auth" data-request='{{ authenticationData|raw }}'{% endblock %}
{% block form %}
{% if not app.request.secure %}
<p class="text-warning"><b><i class="fas fa-exclamation-triangle fa-fw"></i> {% trans %}tfa_u2f.http_warning{% endtrans %}</b></p>
{% endif %}
<div id="u2fpressbutton" style="display: none;" class="h4 text-center">{{ 'r_u2f_two_factor.pressbutton'|trans }}</div>
<div id="u2fError"></div>
<p class="widget"><input id="_auth_code" type="hidden" autocomplete="off" name="_auth_code" /></p>
<a class="ml-2" href="{{ logoutPath }}">{% trans %}user.logout{% endtrans %}</a>
{% endblock %}
{% block submit_btn %}
{% endblock %}

View file

@ -0,0 +1,28 @@
{% extends "main_card.html.twig" %}
{% block card_title %}<i class="fas fa-plus-square fa-fw"></i> {% trans %}tfa_u2f.add_key.title{% endtrans %}{% endblock %}
{% block card_content %}
<p>{% trans %}tfa_u2f.explanation{% endtrans %}</p>
<p><b>{% trans %}tfa_u2f.add_key.backup_hint{% endtrans %}</b></p>
{% if not app.request.secure %}
<p class="text-warning"><b><i class="fas fa-exclamation-triangle fa-fw"></i> {% trans %}tfa_u2f.http_warning{% endtrans %}</b></p>
{% endif %}
<form method="post" class="form" action="{{ path('club_base_register_u2f') }}" id="u2fForm" data-action="reg" data-request='{{ registrationRequest|raw }}'>
<div id="u2fkeyname" class="form-group row">
<div class="col-9">
<input type="text" class="form-control " name="keyName" id="keyName" placeholder="{{ 'r_u2f_two_factor.name'|trans }}"/>
</div>
<div class="col-3">
<button type="button" class="btn btn-success" onclick="u2fauth.register()">{% trans %}tfa_u2f.add_key.add_button{% endtrans %}</button>
</div>
</div>
<div id="u2fpressbutton" style="display: none;" class="text-center h4">{{ 'r_u2f_two_factor.pressbutton'|trans }}</div>
<input type="hidden" name="_auth_code" id="_auth_code" />
<div id="u2fError" style="display: none;" class="text-center h4"></div>
</form>
<a href="{{ url('user_settings') }}">{% trans %}tfa_u2f.add_key.back_to_settings{% endtrans %}</a>
{% endblock %}

View file

@ -26,6 +26,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* @group slow
* @group DB
*/
abstract class AbstractAdminControllerTest extends WebTestCase
{

View file

@ -25,6 +25,7 @@ use App\Entity\Attachments\AttachmentType;
/**
* @group slow
* @group DB
*/
class AttachmentTypeControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Category;
/**
* @group slow
* @group DB
*/
class CategoryControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Devices\Device;
/**
* @group slow
* @group DB
*/
class DeviceControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Footprint;
/**
* @group slow
* @group DB
*/
class FootprintControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Manufacturer;
/**
* @group slow
* @group DB
*/
class ManufacturerControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\MeasurementUnit;
/**
* @group slow
* @group DB
*/
class MeasurementUnitControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Storelocation;
/**
* @group slow
* @group DB
*/
class StorelocationControllerTest extends AbstractAdminControllerTest
{

View file

@ -25,6 +25,7 @@ use App\Entity\Parts\Supplier;
/**
* @group slow
* @group DB
*/
class SupplierControllerTest extends AbstractAdminControllerTest
{

View file

@ -27,6 +27,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @group slow
* @group DB
*/
class RedirectControllerTest extends WebTestCase
{

View file

@ -21,6 +21,7 @@
namespace App\Tests\Entity\UserSystem;
use App\Entity\UserSystem\U2FKey;
use App\Entity\UserSystem\User;
use PHPUnit\Framework\TestCase;
@ -36,4 +37,91 @@ class UserTest extends TestCase
$this->assertEquals('John Doe', $user->getFullName(false));
$this->assertEquals('John Doe (username)', $user->getFullName(true));
}
public function googleAuthenticatorEnabledDataProvider() : array
{
return [
[null, false],
['', false],
['SSSk38498', true]
];
}
/**
* @dataProvider googleAuthenticatorEnabledDataProvider
*/
public function testIsGoogleAuthenticatorEnabled(?string $secret, bool $expected)
{
$user = new User();
$user->setGoogleAuthenticatorSecret($secret);
$this->assertSame($expected ,$user->isGoogleAuthenticatorEnabled());
}
/**
* @requires PHPUnit 8
*/
public function testSetBackupCodes()
{
$user = new User();
$codes = ["test", "invalid", "test"];
$user->setBackupCodes($codes);
// Backup Codes generation date must be changed!
$this->assertEqualsWithDelta(new \DateTime(), $user->getBackupCodesGenerationDate(), 0.1);
$this->assertEquals($codes, $user->getBackupCodes());
//Test what happens if we delete the backup keys
$user->setBackupCodes([]);
$this->assertEmpty($user->getBackupCodes());
$this->assertNull($user->getBackupCodesGenerationDate());
}
public function testIsBackupCode()
{
$user = new User();
$codes = ['aaaa', 'bbbb', 'cccc', 'dddd'];
$user->setBackupCodes($codes);
$this->assertTrue($user->isBackupCode('aaaa'));
$this->assertTrue($user->isBackupCode('cccc'));
$this->assertFalse($user->isBackupCode(''));
$this->assertFalse($user->isBackupCode('zzzz'));
}
public function testInvalidateBackupCode()
{
$user = new User();
$codes = ['aaaa', 'bbbb', 'cccc', 'dddd'];
$user->setBackupCodes($codes);
//Ensure the code is valid
$this->assertTrue($user->isBackupCode('aaaa'));
$this->assertTrue($user->isBackupCode('bbbb'));
//Invalidate code, afterwards the code has to be invalid!
$user->invalidateBackupCode('bbbb');
$this->assertFalse($user->isBackupCode('bbbb'));
$this->assertTrue($user->isBackupCode('aaaa'));
//No exception must happen, when we try to invalidate an not existing backup key!
$user->invalidateBackupCode('zzzz');
}
public function testInvalidateTrustedDeviceTokens()
{
$user = new User();
$old_value = $user->getTrustedTokenVersion();
//To invalidate the token, the new value must be bigger than the old value
$user->invalidateTrustedDeviceTokens();
$this->assertGreaterThan($old_value, $user->getTrustedTokenVersion());
}
public function testIsU2fEnabled()
{
$user = new User();
$user->addU2FKey(new U2FKey());
$this->assertTrue($user->isU2FAuthEnabled());
$user->getU2FKeys()->clear();
$this->assertFalse($user->isU2FAuthEnabled());
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Tests\EventSubscriber;
use App\Entity\UserSystem\Group;
use App\Entity\UserSystem\U2FKey;
use App\Entity\UserSystem\User;
use App\EventSubscriber\PasswordChangeNeededSubscriber;
use PHPUnit\Framework\TestCase;
class PasswordChangeNeededSubscriberTest extends TestCase
{
public function testTFARedirectNeeded()
{
$user = new User();
$group = new Group();
//A user without a group must not redirect
$user->setGroup(null);
$this->assertFalse(PasswordChangeNeededSubscriber::TFARedirectNeeded($user));
//When the group does not enforce the redirect the user must not be redirected
$user->setGroup($group);
$this->assertFalse(PasswordChangeNeededSubscriber::TFARedirectNeeded($user));
//The user must be redirected if the group enforces 2FA and it does not have a method
$group->setEnforce2FA(true);
$this->assertTrue(PasswordChangeNeededSubscriber::TFARedirectNeeded($user));
//User must not be redirect if google authenticator is setup
$user->setGoogleAuthenticatorSecret('abcd');
$this->assertFalse(PasswordChangeNeededSubscriber::TFARedirectNeeded($user));
//User must not be redirect if 2FA is setup
$user->setGoogleAuthenticatorSecret(null);
$user->addU2FKey(new U2FKey());
$this->assertFalse(PasswordChangeNeededSubscriber::TFARedirectNeeded($user));
}
}

View file

@ -28,6 +28,9 @@ use App\Services\ElementTypeNameGenerator;
use App\Services\EntityImporter;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @group DB
*/
class EntityImporterTest extends WebTestCase
{
/**

View file

@ -0,0 +1,75 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Tests\Services\TFA;
use App\Services\TFA\BackupCodeGenerator;
use PHPUnit\Framework\TestCase;
class BackupCodeGeneratorTest extends TestCase
{
/**
* Test if an exception is thrown if you are using a too high code length
*/
public function testLengthUpperLimit()
{
$this->expectException(\RuntimeException::class);
new BackupCodeGenerator(33, 10);
}
/**
* Test if an exception is thrown if you are using a too high code length
*/
public function testLengthLowerLimit()
{
$this->expectException(\RuntimeException::class);
new BackupCodeGenerator(4, 10);
}
public function codeLengthDataProvider()
{
return [[6], [8], [10], [16]];
}
/**
* @dataProvider codeLengthDataProvider
*/
public function testGenerateSingleCode(int $code_length)
{
$generator = new BackupCodeGenerator($code_length, 10);
$this->assertRegExp("/^([a-f0-9]){{$code_length}}\$/", $generator->generateSingleCode());
}
public function codeCountDataProvider()
{
return [[2], [8], [10]];
}
/**
* @dataProvider codeCountDataProvider
*/
public function testGenerateCodeSet(int $code_count)
{
$generator = new BackupCodeGenerator(8, $code_count);
$this->assertCount($code_count, $generator->generateCodeSet());
}
}

View file

@ -0,0 +1,83 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Tests\Services\TFA;
use App\Entity\UserSystem\U2FKey;
use App\Entity\UserSystem\User;
use App\Services\TFA\BackupCodeManager;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BackupCodeManagerTest extends WebTestCase
{
/**
* @var BackupCodeManager $service
*/
protected $service;
public function setUp(): void
{
self::bootKernel();
$this->service = self::$container->get(BackupCodeManager::class);
}
public function testRegenerateBackupCodes()
{
$user = new User();
$old_codes = ['aaaa', 'bbbb'];
$user->setBackupCodes($old_codes);
$this->service->regenerateBackupCodes($user);
$this->assertNotEquals($old_codes, $user->getBackupCodes());
}
public function testEnableBackupCodes()
{
$user = new User();
//Check that nothing is changed, if there are already backup codes
$old_codes = ['aaaa', 'bbbb'];
$user->setBackupCodes($old_codes);
$this->service->enableBackupCodes($user);
$this->assertEquals($old_codes, $user->getBackupCodes());
//When no old codes are existing, it should generate a set
$user->setBackupCodes([]);
$this->service->enableBackupCodes($user);
$this->assertNotEmpty($user->getBackupCodes());
}
public function testDisableBackupCodesIfUnused()
{
$user = new User();
//By default nothing other 2FA is activated, so the backup codes should be disabled
$codes = ['aaaa', 'bbbb'];
$user->setBackupCodes($codes);
$this->service->disableBackupCodesIfUnused($user);
$this->assertEmpty($user->getBackupCodes());
$user->setBackupCodes($codes);
$user->setGoogleAuthenticatorSecret('jskf');
$this->service->disableBackupCodesIfUnused($user);
$this->assertEquals($codes, $user->getBackupCodes());
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="de">
<file id="SchebTwoFactorBundle.de">
<unit id="QoghNQ6" name="login">
<segment>
<source>login</source>
<target>Login</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en" trgLang="en">
<file id="SchebTwoFactorBundle.en">
<unit id="QoghNQ6" name="login">
<segment>
<source>login</source>
<target>Login</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -265,7 +265,7 @@
<unit id="iK5P0V5" name="user.edit.permissions">
<notes>
<note category="file-source" priority="1">templates\AdminPages\GroupAdmin.html.twig:9</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:12</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:16</note>
</notes>
<segment>
<source>user.edit.permissions</source>
@ -318,7 +318,7 @@
</unit>
<unit id=".YoS4pi" name="user.edit.caption">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:4</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:8</note>
</notes>
<segment>
<source>user.edit.caption</source>
@ -327,7 +327,7 @@
</unit>
<unit id="eDE4Z9X" name="user.edit.configuration">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:10</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:14</note>
</notes>
<segment>
<source>user.edit.configuration</source>
@ -336,13 +336,101 @@
</unit>
<unit id="3HIFZxy" name="user.edit.password">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:11</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:15</note>
</notes>
<segment>
<source>user.edit.password</source>
<target state="translated">Passwort</target>
</segment>
</unit>
<unit id="CpSdWDM" name="user.edit.tfa.caption">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:45</note>
</notes>
<segment>
<source>user.edit.tfa.caption</source>
<target>Zwei-Faktor-Authentifizierung</target>
</segment>
</unit>
<unit id="QAuf3JI" name="user.edit.tfa.google_active">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:47</note>
</notes>
<segment>
<source>user.edit.tfa.google_active</source>
<target>Authentifizierungsapp aktiv</target>
</segment>
</unit>
<unit id="7v_PSOf" name="tfa_backup.remaining_tokens">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:48</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:15</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:95</note>
</notes>
<segment>
<source>tfa_backup.remaining_tokens</source>
<target>Verbleibende Backupcodes</target>
</segment>
</unit>
<unit id="xhNIm7L" name="tfa_backup.generation_date">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:49</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:17</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:96</note>
</notes>
<segment>
<source>tfa_backup.generation_date</source>
<target>Erzeugungsdatum der Backupcodes</target>
</segment>
</unit>
<unit id="_m6S_4Y" name="user.edit.tfa.disabled">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:53</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:60</note>
</notes>
<segment>
<source>user.edit.tfa.disabled</source>
<target>Methode deaktiviert</target>
</segment>
</unit>
<unit id="TagZ6I5" name="user.edit.tfa.u2f_keys_count">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:56</note>
</notes>
<segment>
<source>user.edit.tfa.u2f_keys_count</source>
<target>Aktive Sicherheitsschlüssel</target>
</segment>
</unit>
<unit id=".K9hu_c" name="user.edit.tfa.disable_tfa_title">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:72</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa_title</source>
<target>Wirklich fortfahren?</target>
</segment>
</unit>
<unit id="xORuIU4" name="user.edit.tfa.disable_tfa_message">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:72</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa_message</source>
<target><![CDATA[Dies wird <b>alle aktiven Zwei-Faktor-Authentifizierungsmethoden des Nutzers deaktivieren</b> und die <b>Backupcodes löschen</b>! <br>
Der Benutzer wird alle Zwei-Faktor-Authentifizierungmethoden neu einrichten müssen und neue Backupcodes ausdrucken müssen! <br><br>
<b>Führen sie dies nur durch, wenn Sie über die Identität des (um Hilfe suchenden) Benutzers absolut sicher sind, da ansonsten eine Kompromittierung des Accounts durch einen Angreifer erfolgen könnte!</b>]]></target>
</segment>
</unit>
<unit id="02HvwiS" name="user.edit.tfa.disable_tfa.btn">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:73</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa.btn</source>
<target>Alle Zwei-Faktor-Authentifizierungsmethoden deaktivieren</target>
</segment>
</unit>
<unit id="0EjI8FY" name="attachment.delete">
<notes>
<note category="file-source" priority="1">templates\AdminPages\_attachments.html.twig:4</note>
@ -761,6 +849,24 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target state="translated">Nein</target>
</segment>
</unit>
<unit id="haOas0X" name="Yes">
<notes>
<note category="file-source" priority="1">templates\helper.twig:87</note>
</notes>
<segment>
<source>Yes</source>
<target>Ja</target>
</segment>
</unit>
<unit id="HqRCoTS" name="No">
<notes>
<note category="file-source" priority="1">templates\helper.twig:89</note>
</notes>
<segment>
<source>No</source>
<target>Nein</target>
</segment>
</unit>
<unit id="Qj.Hpb." name="version.caption">
<notes>
<note category="file-source" priority="1">templates\homepage.html.twig:7</note>
@ -1790,6 +1896,63 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target state="translated">Anzahl an Unterelementen</target>
</segment>
</unit>
<unit id="3WtQFdr" name="tfa.check.title">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:3</note>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:5</note>
</notes>
<segment>
<source>tfa.check.title</source>
<target>Zwei-Faktor-Authentifizierung benötigt</target>
</segment>
</unit>
<unit id="FPvJfPr" name="tfa.code.trusted_pc">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:39</note>
</notes>
<segment>
<source>tfa.code.trusted_pc</source>
<target>Dies ist ein vertrauenswürdiger Computer (wenn dies aktiviert ist, werden auf diesem Computer keine weiteren Zwei-Faktor-Abfragen durchgeführt)</target>
</segment>
</unit>
<unit id="XtrGxkd" name="login.btn">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:52</note>
<note category="file-source" priority="1">templates\security\login.html.twig:58</note>
</notes>
<segment>
<source>login.btn</source>
<target>Login</target>
</segment>
</unit>
<unit id="okzpfQC" name="user.logout">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:53</note>
<note category="file-source" priority="1">templates\_navbar.html.twig:103</note>
</notes>
<segment>
<source>user.logout</source>
<target state="translated">Ausloggen</target>
</segment>
</unit>
<unit id="INlwXoR" name="tfa.check.code.label">
<notes>
<note category="file-source" priority="1">templates\security\2fa_form.html.twig:6</note>
</notes>
<segment>
<source>tfa.check.code.label</source>
<target>Authenticator App Code</target>
</segment>
</unit>
<unit id="TUbr0_A" name="tfa.check.code.help">
<notes>
<note category="file-source" priority="1">templates\security\2fa_form.html.twig:10</note>
</notes>
<segment>
<source>tfa.check.code.help</source>
<target>Geben Sie hier den 6-stelligen Code aus ihrer Authenticator App ein oder einen ihrer Backupcodes, wenn der Authenticator nicht verfügbar ist.</target>
</segment>
</unit>
<unit id="Kd99AFq" name="login.title">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:3</note>
@ -1860,16 +2023,6 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target>Eingeloggt bleiben (nicht empfohlen auf geteilten Computern)</target>
</segment>
</unit>
<unit id="XtrGxkd" name="login.btn">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:58</note>
<note priority="1">templates\security\login.html.twig:58</note>
</notes>
<segment>
<source>login.btn</source>
<target>Login</target>
</segment>
</unit>
<unit id="GO.ZhTX" name="pw_reset.password_forget">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:64</note>
@ -1897,6 +2050,145 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target state="translated">Neues Passwort anfordern</target>
</segment>
</unit>
<unit id="J1aHED." name="tfa_u2f.http_warning">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_login.html.twig:7</note>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:10</note>
</notes>
<segment>
<source>tfa_u2f.http_warning</source>
<target>Sie greifen auf diese Seite über das unsichere HTTP-Verfahren zu, daher wird U2F sehr wahrscheinlich nicht funktionieren (Fehlermeldung Bad Request). Bitten Sie einen Adminstrator das sichere HTTPS Verfahren einzurichten, wenn Sie Sicherheitsschlüssel benutzen möchten.</target>
</segment>
</unit>
<unit id="Do6QuMa" name="r_u2f_two_factor.pressbutton">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_login.html.twig:10</note>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:22</note>
</notes>
<segment>
<source>r_u2f_two_factor.pressbutton</source>
<target>Bitte Sicherheitsschlüssel einstecken und Button drücken!</target>
</segment>
</unit>
<unit id="T4i5SE4" name="tfa_u2f.add_key.title">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:3</note>
</notes>
<segment>
<source>tfa_u2f.add_key.title</source>
<target>Sicherheitsschlüssel hinzufügen</target>
</segment>
</unit>
<unit id="rIJ.Flq" name="tfa_u2f.explanation">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:6</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:111</note>
</notes>
<segment>
<source>tfa_u2f.explanation</source>
<target>Mithilfe eines U2F/FIDO kompatiblem Sicherheitsschlüssel (z.B. YubiKey oder NitroKey) kann eine benutzerfreundliche und sichere Zwei-Faktor-Authentifizierung ermöglicht. Die Sicherheitsschlüssel können hier registriert werden, und wird eine Zwei-Faktor-Überprüfung benötigt, so muss der Schlüssel nur per USB angesteckt oder per NFC gegen das Gerät getippt werden.</target>
</segment>
</unit>
<unit id="DMC2yTT" name="tfa_u2f.add_key.backup_hint">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:7</note>
</notes>
<segment>
<source>tfa_u2f.add_key.backup_hint</source>
<target>Um den Zugang auch bei Verlust des Schlüssels zu gewährleisten, ist es empfehlenswert einen zweiten Schlüssel als Backup zu registrieren und diesen an einem sicherem Ort zu lagern!</target>
</segment>
</unit>
<unit id="ktQ_kY9" name="r_u2f_two_factor.name">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:16</note>
</notes>
<segment>
<source>r_u2f_two_factor.name</source>
<target>Anzeigename des Schlüssels (z.B. Backup)</target>
</segment>
</unit>
<unit id="HI4_6dF" name="tfa_u2f.add_key.add_button">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:19</note>
</notes>
<segment>
<source>tfa_u2f.add_key.add_button</source>
<target>Schlüssel hinzufügen</target>
</segment>
</unit>
<unit id="JyEfylJ" name="tfa_u2f.add_key.back_to_settings">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:27</note>
</notes>
<segment>
<source>tfa_u2f.add_key.back_to_settings</source>
<target>Zurück zu den Einstellungen</target>
</segment>
</unit>
<unit id="xd6VhOm" name="tfa_backup.codes.title">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:3</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:9</note>
</notes>
<segment>
<source>tfa_backup.codes.title</source>
<target>Backupcodes</target>
</segment>
</unit>
<unit id="DE6_hcj" name="tfa_backup.codes.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:12</note>
</notes>
<segment>
<source>tfa_backup.codes.explanation</source>
<target>Drucken Sie diese Codes aus und bewahren Sie sie an einem sicherem Ort auf!</target>
</segment>
</unit>
<unit id="q3Apy0z" name="tfa_backup.codes.help">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:13</note>
</notes>
<segment>
<source>tfa_backup.codes.help</source>
<target>Wenn Sie keinen Zugriff auf ihr Gerät mit der Authenticator App mehr haben sollten (Smartphone verloren, Datenverlust, etc.) können Sie einen dieser Codes benutzen, um Zugriff auf ihren Account zu erhalten und evtl. eine neue Authenticator App einzurichten. Jeder dieser Codes lässt sich einmal einsetzen, es empfiehlt sich benutzte Codes zu streichen. Jeder mit Zugriff auf diese Codes kann potentiell auf ihren Account zugreifen, daher bewahren Sie sie an einem sicheren Ort auf.</target>
</segment>
</unit>
<unit id="iPcPo4t" name="tfa_backup.username">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:16</note>
</notes>
<segment>
<source>tfa_backup.username</source>
<target>Benutzername</target>
</segment>
</unit>
<unit id="g7XKOBR" name="tfa_backup.codes.page_generated_on">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:29</note>
</notes>
<segment>
<source>tfa_backup.codes.page_generated_on</source>
<target>Codes abgerufen am %date%</target>
</segment>
</unit>
<unit id="3Qg6WkA" name="tfa_backup.codes.print">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:32</note>
</notes>
<segment>
<source>tfa_backup.codes.print</source>
<target>Drucken</target>
</segment>
</unit>
<unit id="CJiKz6Q" name="tfa_backup.codes.copy_clipboard">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:35</note>
</notes>
<segment>
<source>tfa_backup.codes.copy_clipboard</source>
<target>In die Zwischenablage kopieren</target>
</segment>
</unit>
<unit id="_M8LV3f" name="user.info.label">
<notes>
<note category="file-source" priority="1">templates\Users\user_info.html.twig:3</note>
@ -2027,7 +2319,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="v3ayVni" name="user.settings.change_pw">
<notes>
<note category="file-source" priority="1">templates\Users\user_settings.html.twig:53</note>
<note category="file-source" priority="1">templates\Users\user_settings.html.twig:55</note>
<note priority="1">templates\Users\user_settings.html.twig:48</note>
</notes>
<segment>
@ -2035,6 +2327,333 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target>Passwort ändern</target>
</segment>
</unit>
<unit id="2g9WpFv" name="user.settings.2fa_settings">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:6</note>
</notes>
<segment>
<source>user.settings.2fa_settings</source>
<target>Zwei-Faktor-Authentifizierung</target>
</segment>
</unit>
<unit id="4Ubnv0V" name="tfa.settings.google.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:13</note>
</notes>
<segment>
<source>tfa.settings.google.tab</source>
<target>Authenticator App</target>
</segment>
</unit>
<unit id="A0dMe4r" name="tfa.settings.bakup.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:17</note>
</notes>
<segment>
<source>tfa.settings.bakup.tab</source>
<target>Backupcodes</target>
</segment>
</unit>
<unit id="dxSnk50" name="tfa.settings.u2f.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:21</note>
</notes>
<segment>
<source>tfa.settings.u2f.tab</source>
<target>Sicherheitsschlüssel (U2F)</target>
</segment>
</unit>
<unit id="6nafcsi" name="tfa.settings.trustedDevices.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:25</note>
</notes>
<segment>
<source>tfa.settings.trustedDevices.tab</source>
<target>Vertrauenswürdige Geräte</target>
</segment>
</unit>
<unit id="AYt4Z2k" name="tfa_google.disable.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:33</note>
</notes>
<segment>
<source>tfa_google.disable.confirm_title</source>
<target>Möchten Sie die Authenticator App wirklich deaktivieren?</target>
</segment>
</unit>
<unit id="lTwm.J0" name="tfa_google.disable.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:33</note>
</notes>
<segment>
<source>tfa_google.disable.confirm_message</source>
<target><![CDATA[Wenn Sie die Authenticator App deaktivieren, werden alle Backupcodes gelöscht, daher sie müssen sie evtl. neu ausdrucken.<br>
Beachten Sie außerdem, dass ihr Account ohne Zwei-Faktor-Authentifizierung nicht mehr so gut gegen Angreifer geschützt ist!]]></target>
</segment>
</unit>
<unit id="VYOAO2c" name="tfa_google.disabled_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:39</note>
</notes>
<segment>
<source>tfa_google.disabled_message</source>
<target>Authenticator App deaktiviert</target>
</segment>
</unit>
<unit id="cW3GnIC" name="tfa_google.step.download">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:48</note>
</notes>
<segment>
<source>tfa_google.step.download</source>
<target><![CDATA[Laden Sie eine Authenticator App herunter (z.B. <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> oder <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">FreeOTP Authenticator</a>)]]></target>
</segment>
</unit>
<unit id="TXv.pXw" name="tfa_google.step.scan">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:49</note>
</notes>
<segment>
<source>tfa_google.step.scan</source>
<target>Scannen Sie den nebenstehenden QR-Code mit der App oder geben Sie die Daten manuell ein</target>
</segment>
</unit>
<unit id="A9Ex8CO" name="tfa_google.step.input_code">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:50</note>
</notes>
<segment>
<source>tfa_google.step.input_code</source>
<target>Geben Sie den erzeugten Code in das untere Feld ein und bestätigen Sie</target>
</segment>
</unit>
<unit id="RqoGsnx" name="tfa_google.step.download_backup">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:51</note>
</notes>
<segment>
<source>tfa_google.step.download_backup</source>
<target>Drucken Sie ihre Backupcodes aus und lagern sie an einem sicherem Ort</target>
</segment>
</unit>
<unit id="EygadTb" name="tfa_google.manual_setup">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:58</note>
</notes>
<segment>
<source>tfa_google.manual_setup</source>
<target>Manuelle Einrichtung</target>
</segment>
</unit>
<unit id="4jE4fvm" name="tfa_google.manual_setup.type">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:62</note>
</notes>
<segment>
<source>tfa_google.manual_setup.type</source>
<target>Typ</target>
</segment>
</unit>
<unit id="NqQTgh6" name="tfa_google.manual_setup.username">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:63</note>
</notes>
<segment>
<source>tfa_google.manual_setup.username</source>
<target>Benutzername</target>
</segment>
</unit>
<unit id="pkupOqy" name="tfa_google.manual_setup.secret">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:64</note>
</notes>
<segment>
<source>tfa_google.manual_setup.secret</source>
<target>Secret</target>
</segment>
</unit>
<unit id="zabzs3X" name="tfa_google.manual_setup.digit_count">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:65</note>
</notes>
<segment>
<source>tfa_google.manual_setup.digit_count</source>
<target>Anzahl Stellen</target>
</segment>
</unit>
<unit id="FvB0dIm" name="tfa_google.enabled_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:74</note>
</notes>
<segment>
<source>tfa_google.enabled_message</source>
<target>Authenticator App aktiv</target>
</segment>
</unit>
<unit id="atzHEe7" name="tfa_backup.disabled">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:83</note>
</notes>
<segment>
<source>tfa_backup.disabled</source>
<target>Backupcodes deaktiviert. Authenticator App einrichten, um Backupcodes zu aktivieren.</target>
</segment>
</unit>
<unit id=".T4.dPM" name="tfa_backup.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:84</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:92</note>
</notes>
<segment>
<source>tfa_backup.explanation</source>
<target>Mithilfe dieser Backupcodes können Sie auf ihren Account zugreifen, selbst wenn Sie das Gerät mit der Authenticator App verlieren sollten. Drucken Sie die Codes aus und bewahren Sie sie an einem sicherem Ort auf.</target>
</segment>
</unit>
<unit id="H10CC4E" name="tfa_backup.reset_codes.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:88</note>
</notes>
<segment>
<source>tfa_backup.reset_codes.confirm_title</source>
<target>Codes wirklich zurücksetzen?</target>
</segment>
</unit>
<unit id="5Fu2DpJ" name="tfa_backup.reset_codes.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:88</note>
</notes>
<segment>
<source>tfa_backup.reset_codes.confirm_message</source>
<target>Dies wird alle bisherigen Codes löschen und einen Satz neuer Codes generieren. Dies lässt sich nicht rückgängig machen. Denken Sie daran die neuen Codes auszudrucken und an einem sicheren Ort zu hinterlegen!</target>
</segment>
</unit>
<unit id="o5LtJ9_" name="tfa_backup.enabled">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:91</note>
</notes>
<segment>
<source>tfa_backup.enabled</source>
<target>Backupcodes aktiviert</target>
</segment>
</unit>
<unit id="kqmz5H_" name="tfa_backup.show_codes">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:99</note>
</notes>
<segment>
<source>tfa_backup.show_codes</source>
<target>Backupcodes anzeigen</target>
</segment>
</unit>
<unit id="7g11_KD" name="tfa_u2f.table_caption">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:114</note>
</notes>
<segment>
<source>tfa_u2f.table_caption</source>
<target>Registrierte Sicherheitsschlüssel</target>
</segment>
</unit>
<unit id="UUr4y_o" name="tfa_u2f.delete_u2f.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:115</note>
</notes>
<segment>
<source>tfa_u2f.delete_u2f.confirm_title</source>
<target>Diesen Sicherheitsschlüssel wirklich entfernen?</target>
</segment>
</unit>
<unit id="5ggwjiF" name="tfa_u2f.delete_u2f.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:116</note>
</notes>
<segment>
<source>tfa_u2f.delete_u2f.confirm_message</source>
<target>Wenn Sie diesen Schlüssel entfernen, dann wird kein Login mehr mit diesem möglich sein. Wenn keine Sicherheitsschlüssel verleiben, wird die Zwei-Faktor-Authentifizierung deaktiviert.</target>
</segment>
</unit>
<unit id="QS7fv4V" name="tfa_u2f.keys.name">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:123</note>
</notes>
<segment>
<source>tfa_u2f.keys.name</source>
<target>Name des Schlüssels</target>
</segment>
</unit>
<unit id="4qB9rjg" name="tfa_u2f.keys.added_date">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:124</note>
</notes>
<segment>
<source>tfa_u2f.keys.added_date</source>
<target>Datum der Registrierung</target>
</segment>
</unit>
<unit id="EDTcMLO" name="tfa_u2f.key_delete">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:134</note>
</notes>
<segment>
<source>tfa_u2f.key_delete</source>
<target>Schlüssel löschen</target>
</segment>
</unit>
<unit id="h0wV1h2" name="tfa_u2f.no_keys_registered">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:141</note>
</notes>
<segment>
<source>tfa_u2f.no_keys_registered</source>
<target>Keine Sicherheitsschlüssel registriert</target>
</segment>
</unit>
<unit id="HuO06yQ" name="tfa_u2f.add_new_key">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:144</note>
</notes>
<segment>
<source>tfa_u2f.add_new_key</source>
<target>Neuen Sicherheitsschlüssel registrieren</target>
</segment>
</unit>
<unit id="JNqROE2" name="tfa_trustedDevices.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:148</note>
</notes>
<segment>
<source>tfa_trustedDevices.explanation</source>
<target><![CDATA[Bei der Überprüfung des zweiten Faktors, kann der aktuelle Computer als vertrauenswürdig gekennzeichnet werden, daher es werden keine Zwei-Faktor-Überprüfungen mehr an diesem Computer benötigt.
Wenn Sie dies fehlerhafterweise gemacht haben oder ein Computer nicht mehr vertrauenswürdig ist, können Sie hier den Status <i>aller </i>Computer zurücksetzen.]]></target>
</segment>
</unit>
<unit id="xRB9q2I" name="tfa_trustedDevices.invalidate.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:149</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.confirm_title</source>
<target>Wirklich alle vertrauenswürdigen Computer entfernen?</target>
</segment>
</unit>
<unit id="r7Z3.L4" name="tfa_trustedDevices.invalidate.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:150</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.confirm_message</source>
<target>Sie werden auf allen Rechnern erneut eine Zwei-Faktor-Authentifizierung durchführen müssen. Achten Sie darauf, dass Sie ihr Zwei-Faktor-Gerät zur Hand haben.</target>
</segment>
</unit>
<unit id="jiNvzqA" name="tfa_trustedDevices.invalidate.btn">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:154</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.btn</source>
<target>Alle vertrauenswürdigen Geräte entfernen</target>
</segment>
</unit>
<unit id="cElTSD4" name="sidebar.toggle">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:4</note>
@ -2150,16 +2769,6 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target state="translated">Eingeloggt als</target>
</segment>
</unit>
<unit id="okzpfQC" name="user.logout">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:103</note>
<note priority="1">templates\base.html.twig:101</note>
</notes>
<segment>
<source>user.logout</source>
<target state="translated">Ausloggen</target>
</segment>
</unit>
<unit id="rruB2OR" name="user.login">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:105</note>
@ -2335,6 +2944,9 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<unit id="2bvWUoo" name="csfr_invalid">
<notes>
<note category="file-source" priority="1">src\Controller\AdminPages\BaseAdminController.php:257</note>
<note category="file-source" priority="1">src\Controller\UserController.php:89</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:125</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:155</note>
</notes>
<segment>
<source>csfr_invalid</source>
@ -2403,7 +3015,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="B1T9Kt6" name="pw_reset.user_or_email">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:85</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:90</note>
</notes>
<segment>
<source>pw_reset.user_or_email</source>
@ -2412,7 +3024,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="C_43E5l" name="pw_reset.request.success">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:102</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:107</note>
</notes>
<segment>
<source>pw_reset.request.success</source>
@ -2421,7 +3033,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="Ytlen4L" name="pw_reset.username">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:127</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:132</note>
</notes>
<segment>
<source>pw_reset.username</source>
@ -2430,7 +3042,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="kCND5gR" name="pw_reset.token">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:130</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:135</note>
</notes>
<segment>
<source>pw_reset.token</source>
@ -2439,7 +3051,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="Kz.sA0j" name="pw_reset.new_pw.error">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:155</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:160</note>
</notes>
<segment>
<source>pw_reset.new_pw.error</source>
@ -2448,16 +3060,70 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="Uy8yPX1" name="pw_reset.new_pw.success">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:157</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:162</note>
</notes>
<segment>
<source>pw_reset.new_pw.success</source>
<target state="translated">Passwort wurde erfolgreich zurückgesetzt. Sie können sich nun mit dem neuen Passwort einloggen.</target>
</segment>
</unit>
<unit id="L8g8bFy" name="user.edit.reset_success">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:87</note>
</notes>
<segment>
<source>user.edit.reset_success</source>
<target>Alle Zwei-Faktor-Authentisierungsmethoden wurden erfolgreich deaktiviert.</target>
</segment>
</unit>
<unit id="_OcjXRe" name="tfa_backup.no_codes_enabled">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:72</note>
</notes>
<segment>
<source>tfa_backup.no_codes_enabled</source>
<target>Es sind keine Backupcodes aktiviert!</target>
</segment>
</unit>
<unit id="9UBDL1p" name="tfa_u2f.u2f_delete.not_existing">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:109</note>
</notes>
<segment>
<source>tfa_u2f.u2f_delete.not_existing</source>
<target>Es existiert kein Sicherheitsschlüssel mit dieser ID!</target>
</segment>
</unit>
<unit id="6waUw7j" name="tfa_u2f.u2f_delete.access_denied">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:115</note>
</notes>
<segment>
<source>tfa_u2f.u2f_delete.access_denied</source>
<target>Sie können nur ihre eigenen Sicherheitsschlüssel löschen!</target>
</segment>
</unit>
<unit id="Kn9dPI5" name="tfa.u2f.u2f_delete.success">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:122</note>
</notes>
<segment>
<source>tfa.u2f.u2f_delete.success</source>
<target>Sicherheitsschlüssel erfolgreich entfernt.</target>
</segment>
</unit>
<unit id="h.BcrtU" name="tfa_trustedDevice.invalidate.success">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:153</note>
</notes>
<segment>
<source>tfa_trustedDevice.invalidate.success</source>
<target>Vertrauenswürdige Geräte erfolgreich zurückgesetzt.</target>
</segment>
</unit>
<unit id="PBKICdd" name="user.settings.saved_flash">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:184</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:195</note>
<note priority="1">src\Controller\UserController.php:98</note>
<note priority="1">new</note>
</notes>
@ -2468,7 +3134,7 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
</unit>
<unit id="mPAd5JY" name="user.settings.pw_changed_flash">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:224</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:244</note>
<note priority="1">src\Controller\UserController.php:130</note>
<note priority="1">new</note>
</notes>
@ -2477,6 +3143,33 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target>Passwort geändert!</target>
</segment>
</unit>
<unit id="xy.2mkA" name="user.settings.2fa.google.activated">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:262</note>
</notes>
<segment>
<source>user.settings.2fa.google.activated</source>
<target>Authenticator App erfolgreich aktiviert.</target>
</segment>
</unit>
<unit id="RXjFD7H" name="user.settings.2fa.google.disabled">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:269</note>
</notes>
<segment>
<source>user.settings.2fa.google.disabled</source>
<target>Authenticator App erfolgreich deaktiviert.</target>
</segment>
</unit>
<unit id="4ZUxld3" name="user.settings.2fa.backup_codes.regenerated">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:284</note>
</notes>
<segment>
<source>user.settings.2fa.backup_codes.regenerated</source>
<target>Neue Backupcodes erfolgreich erzeugt.</target>
</segment>
</unit>
<unit id="3ZnUiT_" name="attachment.edit.name">
<notes>
<note category="file-source" priority="1">src\DataTables\AttachmentDataTable.php:89</note>
@ -3303,6 +3996,33 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target state="translated">Verschiedene</target>
</segment>
</unit>
<unit id="wPnvtWt" name="tfa_google.enable">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:74</note>
</notes>
<segment>
<source>tfa_google.enable</source>
<target>Authenticator App aktivieren</target>
</segment>
</unit>
<unit id="g7Bwb96" name="tfa_google.disable">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:78</note>
</notes>
<segment>
<source>tfa_google.disable</source>
<target>Authenticator App deaktivieren</target>
</segment>
</unit>
<unit id="j7YxSXm" name="google_confirmation">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:56</note>
</notes>
<segment>
<source>google_confirmation</source>
<target>Bestätigungscode</target>
</segment>
</unit>
<unit id="CPJxiHz" name="M">
<notes>
<note category="file-source" priority="1">src\Form\Type\SIUnitType.php:117</note>
@ -3711,6 +4431,15 @@ Subelemente werden beim Löschen nach oben verschoben.</target>
<target>Neues Element</target>
</segment>
</unit>
<unit id="n13qerD" name="tfa_backup.regenerate_codes">
<notes>
<note category="state" priority="1">obsolete</note>
</notes>
<segment>
<source>tfa_backup.regenerate_codes</source>
<target>Neue Backupcodes erzeugen</target>
</segment>
</unit>
<unit id="lZvhKYu" name="validator.noneofitschild.self">
<notes>
<note category="state" priority="1">obsolete</note>
@ -5497,7 +6226,7 @@ Element 3</target>
</notes>
<segment>
<source>user.settings.pw_old.label</source>
<target>Old password</target>
<target>Altes Passwort</target>
</segment>
</unit>
<unit id="Ed3ydow" name="pw_reset.submit">
@ -5509,5 +6238,29 @@ Element 3</target>
<target state="translated">Passwort zurücksetzen</target>
</segment>
</unit>
<unit id="MzwE3RU" name="bootstrap">
<segment>
<source>bootstrap</source>
<target>bootstrap</target>
</segment>
</unit>
<unit id="Gb0vp3u" name="cerulean">
<segment>
<source>cerulean</source>
<target>cerulean</target>
</segment>
</unit>
<unit id="XTe6pUU" name="tfa.provider.google">
<segment>
<source>tfa.provider.google</source>
<target>Authenticator App</target>
</segment>
</unit>
<unit id="tST2X1R" name="tfa.provider.u2f_two_factor">
<segment>
<source>tfa.provider.u2f_two_factor</source>
<target>Sicherheitsschlüssel</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -264,7 +264,7 @@
<unit id="iK5P0V5" name="user.edit.permissions">
<notes>
<note category="file-source" priority="1">templates\AdminPages\GroupAdmin.html.twig:9</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:12</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:16</note>
</notes>
<segment>
<source>user.edit.permissions</source>
@ -315,7 +315,7 @@
</unit>
<unit id=".YoS4pi" name="user.edit.caption">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:4</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:8</note>
</notes>
<segment>
<source>user.edit.caption</source>
@ -324,7 +324,7 @@
</unit>
<unit id="eDE4Z9X" name="user.edit.configuration">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:10</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:14</note>
</notes>
<segment>
<source>user.edit.configuration</source>
@ -333,13 +333,102 @@
</unit>
<unit id="3HIFZxy" name="user.edit.password">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:11</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:15</note>
</notes>
<segment>
<source>user.edit.password</source>
<target state="translated">Password</target>
</segment>
</unit>
<unit id="CpSdWDM" name="user.edit.tfa.caption">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:45</note>
</notes>
<segment>
<source>user.edit.tfa.caption</source>
<target>Two-factor authentication</target>
</segment>
</unit>
<unit id="QAuf3JI" name="user.edit.tfa.google_active">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:47</note>
</notes>
<segment>
<source>user.edit.tfa.google_active</source>
<target>Authenticator app active</target>
</segment>
</unit>
<unit id="7v_PSOf" name="tfa_backup.remaining_tokens">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:48</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:15</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:95</note>
</notes>
<segment>
<source>tfa_backup.remaining_tokens</source>
<target>Remaining backup codes count</target>
</segment>
</unit>
<unit id="xhNIm7L" name="tfa_backup.generation_date">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:49</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:17</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:96</note>
</notes>
<segment>
<source>tfa_backup.generation_date</source>
<target>Generation date of the backup codes</target>
</segment>
</unit>
<unit id="_m6S_4Y" name="user.edit.tfa.disabled">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:53</note>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:60</note>
</notes>
<segment>
<source>user.edit.tfa.disabled</source>
<target>Method not enabled</target>
</segment>
</unit>
<unit id="TagZ6I5" name="user.edit.tfa.u2f_keys_count">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:56</note>
</notes>
<segment>
<source>user.edit.tfa.u2f_keys_count</source>
<target>Active security keys</target>
</segment>
</unit>
<unit id=".K9hu_c" name="user.edit.tfa.disable_tfa_title">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:72</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa_title</source>
<target>Do you really want to proceed?</target>
</segment>
</unit>
<unit id="xORuIU4" name="user.edit.tfa.disable_tfa_message">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:72</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa_message</source>
<target><![CDATA[This will disable <b>all active two-factor authentication methods of the user</b> and delete the <b>backup codes</b>!
<br>
The user will have to set up all two-factor authentication methods again and print new backup codes! <br><br>
<b>Only do this if you are absolutely sure about the identity of the user (seeking help), otherwise the account could be compromised by an attacker!</b>]]></target>
</segment>
</unit>
<unit id="02HvwiS" name="user.edit.tfa.disable_tfa.btn">
<notes>
<note category="file-source" priority="1">templates\AdminPages\UserAdmin.html.twig:73</note>
</notes>
<segment>
<source>user.edit.tfa.disable_tfa.btn</source>
<target>Disable all two-factor authentication methods</target>
</segment>
</unit>
<unit id="0EjI8FY" name="attachment.delete">
<notes>
<note category="file-source" priority="1">templates\AdminPages\_attachments.html.twig:4</note>
@ -757,6 +846,24 @@ Subelements will be moved upwards.]]></target>
<target state="translated">False</target>
</segment>
</unit>
<unit id="haOas0X" name="Yes">
<notes>
<note category="file-source" priority="1">templates\helper.twig:87</note>
</notes>
<segment>
<source>Yes</source>
<target>Yes</target>
</segment>
</unit>
<unit id="HqRCoTS" name="No">
<notes>
<note category="file-source" priority="1">templates\helper.twig:89</note>
</notes>
<segment>
<source>No</source>
<target>No</target>
</segment>
</unit>
<unit id="Qj.Hpb." name="version.caption">
<notes>
<note category="file-source" priority="1">templates\homepage.html.twig:7</note>
@ -1781,6 +1888,63 @@ Subelements will be moved upwards.]]></target>
<target state="translated">Count of children elements</target>
</segment>
</unit>
<unit id="3WtQFdr" name="tfa.check.title">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:3</note>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:5</note>
</notes>
<segment>
<source>tfa.check.title</source>
<target>Two-factor authentication needed</target>
</segment>
</unit>
<unit id="FPvJfPr" name="tfa.code.trusted_pc">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:39</note>
</notes>
<segment>
<source>tfa.code.trusted_pc</source>
<target>This is a trusted computer (if this is enabled, no further two-factor queries are performed on this computer)</target>
</segment>
</unit>
<unit id="XtrGxkd" name="login.btn">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:52</note>
<note category="file-source" priority="1">templates\security\login.html.twig:58</note>
</notes>
<segment>
<source>login.btn</source>
<target>Login</target>
</segment>
</unit>
<unit id="okzpfQC" name="user.logout">
<notes>
<note category="file-source" priority="1">templates\security\2fa_base_form.html.twig:53</note>
<note category="file-source" priority="1">templates\_navbar.html.twig:103</note>
</notes>
<segment>
<source>user.logout</source>
<target state="translated">Logout</target>
</segment>
</unit>
<unit id="INlwXoR" name="tfa.check.code.label">
<notes>
<note category="file-source" priority="1">templates\security\2fa_form.html.twig:6</note>
</notes>
<segment>
<source>tfa.check.code.label</source>
<target>Enter the 6-digit code from your Authenticator App or one of your backup codes if the Authenticator is not available.</target>
</segment>
</unit>
<unit id="TUbr0_A" name="tfa.check.code.help">
<notes>
<note category="file-source" priority="1">templates\security\2fa_form.html.twig:10</note>
</notes>
<segment>
<source>tfa.check.code.help</source>
<target>Authenticator app code</target>
</segment>
</unit>
<unit id="Kd99AFq" name="login.title">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:3</note>
@ -1851,16 +2015,6 @@ Subelements will be moved upwards.]]></target>
<target>Remember me (should not be used on shared computers)</target>
</segment>
</unit>
<unit id="XtrGxkd" name="login.btn">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:58</note>
<note priority="1">templates\security\login.html.twig:58</note>
</notes>
<segment>
<source>login.btn</source>
<target>Login</target>
</segment>
</unit>
<unit id="GO.ZhTX" name="pw_reset.password_forget">
<notes>
<note category="file-source" priority="1">templates\security\login.html.twig:64</note>
@ -1888,6 +2042,145 @@ Subelements will be moved upwards.]]></target>
<target state="translated">Request a new password</target>
</segment>
</unit>
<unit id="J1aHED." name="tfa_u2f.http_warning">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_login.html.twig:7</note>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:10</note>
</notes>
<segment>
<source>tfa_u2f.http_warning</source>
<target>You are accessing this page using the insecure HTTP method, so U2F will most likely not work (Bad Request error message). Ask an administrator to set up the secure HTTPS method if you want to use security keys.</target>
</segment>
</unit>
<unit id="Do6QuMa" name="r_u2f_two_factor.pressbutton">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_login.html.twig:10</note>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:22</note>
</notes>
<segment>
<source>r_u2f_two_factor.pressbutton</source>
<target>Please plug in your security key and press its button!</target>
</segment>
</unit>
<unit id="T4i5SE4" name="tfa_u2f.add_key.title">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:3</note>
</notes>
<segment>
<source>tfa_u2f.add_key.title</source>
<target>Add security key</target>
</segment>
</unit>
<unit id="rIJ.Flq" name="tfa_u2f.explanation">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:6</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:111</note>
</notes>
<segment>
<source>tfa_u2f.explanation</source>
<target>With the help of a U2F/FIDO compatible security key (e.g. YubiKey or NitroKey), user-friendly and secure two-factor authentication can be achieved. The security keys can be registered here, and if two-factor verification is required, the key only needs to be inserted via USB or typed against the device via NFC.</target>
</segment>
</unit>
<unit id="DMC2yTT" name="tfa_u2f.add_key.backup_hint">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:7</note>
</notes>
<segment>
<source>tfa_u2f.add_key.backup_hint</source>
<target>To ensure access even if the key is lost, it is recommended to register a second key as backup and store it in a safe place!</target>
</segment>
</unit>
<unit id="ktQ_kY9" name="r_u2f_two_factor.name">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:16</note>
</notes>
<segment>
<source>r_u2f_two_factor.name</source>
<target>Shown key name (e.g. Backup)</target>
</segment>
</unit>
<unit id="HI4_6dF" name="tfa_u2f.add_key.add_button">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:19</note>
</notes>
<segment>
<source>tfa_u2f.add_key.add_button</source>
<target>Add security key</target>
</segment>
</unit>
<unit id="JyEfylJ" name="tfa_u2f.add_key.back_to_settings">
<notes>
<note category="file-source" priority="1">templates\security\U2F\u2f_register.html.twig:27</note>
</notes>
<segment>
<source>tfa_u2f.add_key.back_to_settings</source>
<target>Back to settings</target>
</segment>
</unit>
<unit id="xd6VhOm" name="tfa_backup.codes.title">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:3</note>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:9</note>
</notes>
<segment>
<source>tfa_backup.codes.title</source>
<target>Backup codes</target>
</segment>
</unit>
<unit id="DE6_hcj" name="tfa_backup.codes.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:12</note>
</notes>
<segment>
<source>tfa_backup.codes.explanation</source>
<target>Print out these codes and keep them in a safe place!</target>
</segment>
</unit>
<unit id="q3Apy0z" name="tfa_backup.codes.help">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:13</note>
</notes>
<segment>
<source>tfa_backup.codes.help</source>
<target>If you no longer have access to your device with the Authenticator App (lost smartphone, data loss, etc.) you can use one of these codes to access your account and possibly set up a new Authenticator App. Each of these codes can be used once, it is recommended to delete used codes. Anyone with access to these codes can potentially access your account, so keep them in a safe place.</target>
</segment>
</unit>
<unit id="iPcPo4t" name="tfa_backup.username">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:16</note>
</notes>
<segment>
<source>tfa_backup.username</source>
<target>Username</target>
</segment>
</unit>
<unit id="g7XKOBR" name="tfa_backup.codes.page_generated_on">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:29</note>
</notes>
<segment>
<source>tfa_backup.codes.page_generated_on</source>
<target>Page generated on %date%</target>
</segment>
</unit>
<unit id="3Qg6WkA" name="tfa_backup.codes.print">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:32</note>
</notes>
<segment>
<source>tfa_backup.codes.print</source>
<target>Print</target>
</segment>
</unit>
<unit id="CJiKz6Q" name="tfa_backup.codes.copy_clipboard">
<notes>
<note category="file-source" priority="1">templates\Users\backup_codes.html.twig:35</note>
</notes>
<segment>
<source>tfa_backup.codes.copy_clipboard</source>
<target>Copy to clipboard</target>
</segment>
</unit>
<unit id="_M8LV3f" name="user.info.label">
<notes>
<note category="file-source" priority="1">templates\Users\user_info.html.twig:3</note>
@ -2018,7 +2311,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="v3ayVni" name="user.settings.change_pw">
<notes>
<note category="file-source" priority="1">templates\Users\user_settings.html.twig:53</note>
<note category="file-source" priority="1">templates\Users\user_settings.html.twig:55</note>
<note priority="1">templates\Users\user_settings.html.twig:48</note>
</notes>
<segment>
@ -2026,6 +2319,333 @@ Subelements will be moved upwards.]]></target>
<target>Change password</target>
</segment>
</unit>
<unit id="2g9WpFv" name="user.settings.2fa_settings">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:6</note>
</notes>
<segment>
<source>user.settings.2fa_settings</source>
<target>Two-Factor Authentication</target>
</segment>
</unit>
<unit id="4Ubnv0V" name="tfa.settings.google.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:13</note>
</notes>
<segment>
<source>tfa.settings.google.tab</source>
<target>Authenticator app</target>
</segment>
</unit>
<unit id="A0dMe4r" name="tfa.settings.bakup.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:17</note>
</notes>
<segment>
<source>tfa.settings.bakup.tab</source>
<target>Backup codes</target>
</segment>
</unit>
<unit id="dxSnk50" name="tfa.settings.u2f.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:21</note>
</notes>
<segment>
<source>tfa.settings.u2f.tab</source>
<target>Security keys (U2F)</target>
</segment>
</unit>
<unit id="6nafcsi" name="tfa.settings.trustedDevices.tab">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:25</note>
</notes>
<segment>
<source>tfa.settings.trustedDevices.tab</source>
<target>Trusted devices</target>
</segment>
</unit>
<unit id="AYt4Z2k" name="tfa_google.disable.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:33</note>
</notes>
<segment>
<source>tfa_google.disable.confirm_title</source>
<target>Do you really want to disable the Authenticator App?</target>
</segment>
</unit>
<unit id="lTwm.J0" name="tfa_google.disable.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:33</note>
</notes>
<segment>
<source>tfa_google.disable.confirm_message</source>
<target><![CDATA[If you disable the Authenticator App, all backup codes will be deleted, so you may need to reprint them.<br>
Also note that without two-factor authentication your account is not as well protected against attackers!]]></target>
</segment>
</unit>
<unit id="VYOAO2c" name="tfa_google.disabled_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:39</note>
</notes>
<segment>
<source>tfa_google.disabled_message</source>
<target>Authenticator app deactivated!</target>
</segment>
</unit>
<unit id="cW3GnIC" name="tfa_google.step.download">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:48</note>
</notes>
<segment>
<source>tfa_google.step.download</source>
<target><![CDATA[Download an authenticator app (e.g. <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Google Authenticator</a> oder <a class="link-external" target="_blank" href="https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp">FreeOTP Authenticator</a>)]]></target>
</segment>
</unit>
<unit id="TXv.pXw" name="tfa_google.step.scan">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:49</note>
</notes>
<segment>
<source>tfa_google.step.scan</source>
<target>Scan the adjoining QR Code with the app or enter the data manually</target>
</segment>
</unit>
<unit id="A9Ex8CO" name="tfa_google.step.input_code">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:50</note>
</notes>
<segment>
<source>tfa_google.step.input_code</source>
<target>Enter the generated code in the field below and confirm</target>
</segment>
</unit>
<unit id="RqoGsnx" name="tfa_google.step.download_backup">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:51</note>
</notes>
<segment>
<source>tfa_google.step.download_backup</source>
<target>Print out your backup codes and store them in a safe place</target>
</segment>
</unit>
<unit id="EygadTb" name="tfa_google.manual_setup">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:58</note>
</notes>
<segment>
<source>tfa_google.manual_setup</source>
<target>Manual setup</target>
</segment>
</unit>
<unit id="4jE4fvm" name="tfa_google.manual_setup.type">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:62</note>
</notes>
<segment>
<source>tfa_google.manual_setup.type</source>
<target>Type</target>
</segment>
</unit>
<unit id="NqQTgh6" name="tfa_google.manual_setup.username">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:63</note>
</notes>
<segment>
<source>tfa_google.manual_setup.username</source>
<target>Username</target>
</segment>
</unit>
<unit id="pkupOqy" name="tfa_google.manual_setup.secret">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:64</note>
</notes>
<segment>
<source>tfa_google.manual_setup.secret</source>
<target>Secret</target>
</segment>
</unit>
<unit id="zabzs3X" name="tfa_google.manual_setup.digit_count">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:65</note>
</notes>
<segment>
<source>tfa_google.manual_setup.digit_count</source>
<target>Digit count</target>
</segment>
</unit>
<unit id="FvB0dIm" name="tfa_google.enabled_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:74</note>
</notes>
<segment>
<source>tfa_google.enabled_message</source>
<target>Authenticator App enabled</target>
</segment>
</unit>
<unit id="atzHEe7" name="tfa_backup.disabled">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:83</note>
</notes>
<segment>
<source>tfa_backup.disabled</source>
<target>Backup codes disabled. Setup authenticator app to enable backup codes.</target>
</segment>
</unit>
<unit id=".T4.dPM" name="tfa_backup.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:84</note>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:92</note>
</notes>
<segment>
<source>tfa_backup.explanation</source>
<target>You can use these backup codes to access your account even if you lose the device with the Authenticator App. Print out the codes and keep them in a safe place.</target>
</segment>
</unit>
<unit id="H10CC4E" name="tfa_backup.reset_codes.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:88</note>
</notes>
<segment>
<source>tfa_backup.reset_codes.confirm_title</source>
<target>Really reset codes?</target>
</segment>
</unit>
<unit id="5Fu2DpJ" name="tfa_backup.reset_codes.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:88</note>
</notes>
<segment>
<source>tfa_backup.reset_codes.confirm_message</source>
<target>This will delete all previous codes and generate a set of new codes. This cannot be undone. Remember to print out the new codes and store them in a safe place!</target>
</segment>
</unit>
<unit id="o5LtJ9_" name="tfa_backup.enabled">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:91</note>
</notes>
<segment>
<source>tfa_backup.enabled</source>
<target>Backup codes enabled</target>
</segment>
</unit>
<unit id="kqmz5H_" name="tfa_backup.show_codes">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:99</note>
</notes>
<segment>
<source>tfa_backup.show_codes</source>
<target>Show backup codes</target>
</segment>
</unit>
<unit id="7g11_KD" name="tfa_u2f.table_caption">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:114</note>
</notes>
<segment>
<source>tfa_u2f.table_caption</source>
<target>Registered security keys</target>
</segment>
</unit>
<unit id="UUr4y_o" name="tfa_u2f.delete_u2f.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:115</note>
</notes>
<segment>
<source>tfa_u2f.delete_u2f.confirm_title</source>
<target>Really remove this security key?</target>
</segment>
</unit>
<unit id="5ggwjiF" name="tfa_u2f.delete_u2f.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:116</note>
</notes>
<segment>
<source>tfa_u2f.delete_u2f.confirm_message</source>
<target>If you remove this key, then no more login with this key will be possible. If no security keys remain, two-factor authentication will be disabled.</target>
</segment>
</unit>
<unit id="QS7fv4V" name="tfa_u2f.keys.name">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:123</note>
</notes>
<segment>
<source>tfa_u2f.keys.name</source>
<target>Key name</target>
</segment>
</unit>
<unit id="4qB9rjg" name="tfa_u2f.keys.added_date">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:124</note>
</notes>
<segment>
<source>tfa_u2f.keys.added_date</source>
<target>Registration date</target>
</segment>
</unit>
<unit id="EDTcMLO" name="tfa_u2f.key_delete">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:134</note>
</notes>
<segment>
<source>tfa_u2f.key_delete</source>
<target>Delete key</target>
</segment>
</unit>
<unit id="h0wV1h2" name="tfa_u2f.no_keys_registered">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:141</note>
</notes>
<segment>
<source>tfa_u2f.no_keys_registered</source>
<target>No keys registered yet.</target>
</segment>
</unit>
<unit id="HuO06yQ" name="tfa_u2f.add_new_key">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:144</note>
</notes>
<segment>
<source>tfa_u2f.add_new_key</source>
<target>Register new security key</target>
</segment>
</unit>
<unit id="JNqROE2" name="tfa_trustedDevices.explanation">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:148</note>
</notes>
<segment>
<source>tfa_trustedDevices.explanation</source>
<target><![CDATA[When checking the second factor, the current computer can be marked as trustworthy, so no more two-factor checks on this computer are needed.
If you have done this incorrectly or if a computer is no longer trusted, you can reset the status of <i>all </i>computers here.]]></target>
</segment>
</unit>
<unit id="xRB9q2I" name="tfa_trustedDevices.invalidate.confirm_title">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:149</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.confirm_title</source>
<target>Really remove all trusted computers?</target>
</segment>
</unit>
<unit id="r7Z3.L4" name="tfa_trustedDevices.invalidate.confirm_message">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:150</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.confirm_message</source>
<target>You will have to perform two-factor authentication again on all computers. Make sure you have your two-factor device at hand.</target>
</segment>
</unit>
<unit id="jiNvzqA" name="tfa_trustedDevices.invalidate.btn">
<notes>
<note category="file-source" priority="1">templates\Users\_2fa_settings.html.twig:154</note>
</notes>
<segment>
<source>tfa_trustedDevices.invalidate.btn</source>
<target>Reset trusted devices</target>
</segment>
</unit>
<unit id="cElTSD4" name="sidebar.toggle">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:4</note>
@ -2141,16 +2761,6 @@ Subelements will be moved upwards.]]></target>
<target state="translated">Logged in as</target>
</segment>
</unit>
<unit id="okzpfQC" name="user.logout">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:103</note>
<note priority="1">templates\base.html.twig:101</note>
</notes>
<segment>
<source>user.logout</source>
<target state="translated">Logout</target>
</segment>
</unit>
<unit id="rruB2OR" name="user.login">
<notes>
<note category="file-source" priority="1">templates\_navbar.html.twig:105</note>
@ -2325,6 +2935,9 @@ Subelements will be moved upwards.]]></target>
<unit id="2bvWUoo" name="csfr_invalid">
<notes>
<note category="file-source" priority="1">src\Controller\AdminPages\BaseAdminController.php:257</note>
<note category="file-source" priority="1">src\Controller\UserController.php:89</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:125</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:155</note>
</notes>
<segment>
<source>csfr_invalid</source>
@ -2391,7 +3004,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="B1T9Kt6" name="pw_reset.user_or_email">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:85</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:90</note>
</notes>
<segment>
<source>pw_reset.user_or_email</source>
@ -2400,7 +3013,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="C_43E5l" name="pw_reset.request.success">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:102</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:107</note>
</notes>
<segment>
<source>pw_reset.request.success</source>
@ -2409,7 +3022,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="Ytlen4L" name="pw_reset.username">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:127</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:132</note>
</notes>
<segment>
<source>pw_reset.username</source>
@ -2418,7 +3031,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="kCND5gR" name="pw_reset.token">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:130</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:135</note>
</notes>
<segment>
<source>pw_reset.token</source>
@ -2427,7 +3040,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="Kz.sA0j" name="pw_reset.new_pw.error">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:155</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:160</note>
</notes>
<segment>
<source>pw_reset.new_pw.error</source>
@ -2436,16 +3049,70 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="Uy8yPX1" name="pw_reset.new_pw.success">
<notes>
<note category="file-source" priority="1">src\Controller\SecurityController.php:157</note>
<note category="file-source" priority="1">src\Controller\SecurityController.php:162</note>
</notes>
<segment>
<source>pw_reset.new_pw.success</source>
<target state="translated">Password was reset successfully. You can now login with your new password.</target>
</segment>
</unit>
<unit id="L8g8bFy" name="user.edit.reset_success">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:87</note>
</notes>
<segment>
<source>user.edit.reset_success</source>
<target>All two-factor authentication methods were successfully disabled.</target>
</segment>
</unit>
<unit id="_OcjXRe" name="tfa_backup.no_codes_enabled">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:72</note>
</notes>
<segment>
<source>tfa_backup.no_codes_enabled</source>
<target>No backup codes enabled!</target>
</segment>
</unit>
<unit id="9UBDL1p" name="tfa_u2f.u2f_delete.not_existing">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:109</note>
</notes>
<segment>
<source>tfa_u2f.u2f_delete.not_existing</source>
<target>No security key with this ID is existing.</target>
</segment>
</unit>
<unit id="6waUw7j" name="tfa_u2f.u2f_delete.access_denied">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:115</note>
</notes>
<segment>
<source>tfa_u2f.u2f_delete.access_denied</source>
<target>You can not delete the security keys of other users!</target>
</segment>
</unit>
<unit id="Kn9dPI5" name="tfa.u2f.u2f_delete.success">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:122</note>
</notes>
<segment>
<source>tfa.u2f.u2f_delete.success</source>
<target>Security key successfully removed.</target>
</segment>
</unit>
<unit id="h.BcrtU" name="tfa_trustedDevice.invalidate.success">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:153</note>
</notes>
<segment>
<source>tfa_trustedDevice.invalidate.success</source>
<target>Trusted devices successfully reset.</target>
</segment>
</unit>
<unit id="PBKICdd" name="user.settings.saved_flash">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:184</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:195</note>
<note priority="1">src\Controller\UserController.php:98</note>
</notes>
<segment>
@ -2455,7 +3122,7 @@ Subelements will be moved upwards.]]></target>
</unit>
<unit id="mPAd5JY" name="user.settings.pw_changed_flash">
<notes>
<note category="file-source" priority="1">src\Controller\UserController.php:224</note>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:244</note>
<note priority="1">src\Controller\UserController.php:130</note>
</notes>
<segment>
@ -2463,6 +3130,33 @@ Subelements will be moved upwards.]]></target>
<target>Password changed!</target>
</segment>
</unit>
<unit id="xy.2mkA" name="user.settings.2fa.google.activated">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:262</note>
</notes>
<segment>
<source>user.settings.2fa.google.activated</source>
<target>Authenticator App successfully activated.</target>
</segment>
</unit>
<unit id="RXjFD7H" name="user.settings.2fa.google.disabled">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:269</note>
</notes>
<segment>
<source>user.settings.2fa.google.disabled</source>
<target>Authenticator App erfolgreich deaktiviert.</target>
</segment>
</unit>
<unit id="4ZUxld3" name="user.settings.2fa.backup_codes.regenerated">
<notes>
<note category="file-source" priority="1">src\Controller\UserSettingsController.php:284</note>
</notes>
<segment>
<source>user.settings.2fa.backup_codes.regenerated</source>
<target>New backup codes successfully generated.</target>
</segment>
</unit>
<unit id="3ZnUiT_" name="attachment.edit.name">
<notes>
<note category="file-source" priority="1">src\DataTables\AttachmentDataTable.php:89</note>
@ -3289,6 +3983,33 @@ Subelements will be moved upwards.]]></target>
<target state="translated">Miscellaneous</target>
</segment>
</unit>
<unit id="wPnvtWt" name="tfa_google.enable">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:74</note>
</notes>
<segment>
<source>tfa_google.enable</source>
<target>Enable authenticator app</target>
</segment>
</unit>
<unit id="g7Bwb96" name="tfa_google.disable">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:78</note>
</notes>
<segment>
<source>tfa_google.disable</source>
<target>Deactivate authenticator app</target>
</segment>
</unit>
<unit id="j7YxSXm" name="google_confirmation">
<notes>
<note category="file-source" priority="1">src\Form\TFAGoogleSettingsType.php:56</note>
</notes>
<segment>
<source>google_confirmation</source>
<target>Confirmation code</target>
</segment>
</unit>
<unit id="CPJxiHz" name="M">
<notes>
<note category="file-source" priority="1">src\Form\Type\SIUnitType.php:117</note>
@ -3689,6 +4410,15 @@ Subelements will be moved upwards.]]></target>
<target>New Element</target>
</segment>
</unit>
<unit id="n13qerD" name="tfa_backup.regenerate_codes">
<notes>
<note category="state" priority="1">obsolete</note>
</notes>
<segment>
<source>tfa_backup.regenerate_codes</source>
<target>Generate new backup codes</target>
</segment>
</unit>
<unit id="lZvhKYu" name="validator.noneofitschild.self">
<notes>
<note category="state" priority="1">obsolete</note>
@ -5477,5 +6207,35 @@ Element 3</target>
<target state="translated">Reset password</target>
</segment>
</unit>
<unit id="E8uyrqE" name="u2f_two_factor">
<segment>
<source>u2f_two_factor</source>
<target>Security key (U2F)</target>
</segment>
</unit>
<unit id="u976KVD" name="google">
<segment>
<source>google</source>
<target>google</target>
</segment>
</unit>
<unit id="tST2X1R" name="tfa.provider.u2f_two_factor">
<segment>
<source>tfa.provider.u2f_two_factor</source>
<target>Security key (U2F)</target>
</segment>
</unit>
<unit id="XTe6pUU" name="tfa.provider.google">
<segment>
<source>tfa.provider.google</source>
<target>Authenticator app</target>
</segment>
</unit>
<unit id="iGSYnmL" name="Login successful">
<segment>
<source>Login successful</source>
<target>Login successful</target>
</segment>
</unit>
</file>
</xliff>

View file

@ -40,6 +40,8 @@ Encore
* and one CSS file (e.g. app.css) if you JavaScript imports CSS.
*/
.addEntry('app', './assets/js/app.js')
.addEntry('ru2ftwofactor', './assets/js/u2f_auth.js')
//.addEntry('ajaxUI', './assets/ts_src/ajax_ui.ts')
//.addEntry('page1', './assets/js/page1.js')
//.addEntry('page2', './assets/js/page2.js')

110
yarn.lock
View file

@ -1490,12 +1490,30 @@ browserslist@^4.0.0, browserslist@^4.6.0, browserslist@^4.8.2:
electron-to-chromium "^1.3.322"
node-releases "^1.1.42"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-equal@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b"
integrity sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=
buffer-from@^1.0.0:
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer-from@^1.0.0, buffer-from@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
@ -1519,6 +1537,14 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffer@^5.4.3:
version "5.4.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@ -1679,6 +1705,15 @@ clean-webpack-plugin@^0.1.19:
dependencies:
rimraf "^2.6.1"
clipboard@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
cliui@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49"
@ -2379,6 +2414,11 @@ del@^4.1.1:
pify "^4.0.1"
rimraf "^2.6.3"
delegate@^3.1.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -2431,6 +2471,11 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
dijkstrajs@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.1.tgz#d3cd81221e3ea40742cfcde556d4e99e98ddc71b"
integrity sha1-082BIh4+pAdCz83lVtTpnpjdxxs=
dir-glob@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
@ -3322,6 +3367,13 @@ globby@^7.1.1:
pify "^3.0.0"
slash "^1.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
dependencies:
delegate "^3.1.2"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
@ -3934,6 +3986,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isarray@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -5060,6 +5117,11 @@ png-js@^1.0.0:
resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.0.0.tgz#e5484f1e8156996e383aceebb3789fd75df1874d"
integrity sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==
pngjs@^3.3.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
popper.js@>=1.12.9, popper.js@^1.14.1, popper.js@^1.14.7:
version "1.16.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.0.tgz#2e1816bcbbaa518ea6c2e15a466f4cb9c6e2fbb3"
@ -5512,6 +5574,19 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qrcode@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
integrity sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==
dependencies:
buffer "^5.4.3"
buffer-alloc "^1.2.0"
buffer-from "^1.1.1"
dijkstrajs "^1.0.1"
isarray "^2.0.1"
pngjs "^3.3.0"
yargs "^13.2.4"
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@ -5902,6 +5977,11 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=
selfsigned@^1.10.7:
version "1.10.7"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b"
@ -6509,6 +6589,11 @@ timsort@^0.3.0:
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
tiny-emitter@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-inflate@^1.0.0, tiny-inflate@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
@ -6617,6 +6702,11 @@ typescript@^3.3.4000:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69"
integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==
u2f-api@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-1.1.1.tgz#594f600ff7bb49e8bb03e9f533e1f7bfc95f2e0f"
integrity sha512-tbmMBmg9eaFv+cdcBwja/hbpdXwcvBV0YSQh674nmP3HI0hNWcNAp9LRJ0jl9HGWLF7gF/3UKHzIxlXCW8j7kw==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@ -7086,7 +7176,7 @@ yargs-parser@^12.0.0:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^13.1.0:
yargs-parser@^13.1.0, yargs-parser@^13.1.1:
version "13.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
@ -7128,3 +7218,19 @@ yargs@13.2.4:
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.0"
yargs@^13.2.4:
version "13.3.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.1"