Prepared DB and models for Two Factor authentication

This commit is contained in:
Jan Böhmer 2019-12-14 16:35:19 +01:00
parent 235baf32d5
commit 2fa0963374
13 changed files with 935 additions and 39 deletions

View file

@ -20,7 +20,9 @@
"nyholm/psr7": "^1.1",
"ocramius/proxy-manager": "2.1.*",
"omines/datatables-bundle": "^0.3.1",
"r/u2f-two-factor-bundle": "^0.7.0",
"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",

410
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": "b2a4dbd5f3b049839c54b31fc4098eed",
"content-hash": "addae3263b772b9d16dce2392f8af0e7",
"packages": [
{
"name": "beberlei/assert",
"version": "v3.2.6",
"source": {
"type": "git",
"url": "https://github.com/beberlei/assert.git",
"reference": "99508be011753690fe108ded450f5caaae180cfa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/beberlei/assert/zipball/99508be011753690fe108ded450f5caaae180cfa",
"reference": "99508be011753690fe108ded450f5caaae180cfa",
"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-10-10T10:33:57+00:00"
},
{
"name": "doctrine/annotations",
"version": "v1.8.0",
@ -1695,6 +1757,61 @@
],
"time": "2014-01-12T16:20:24+00:00"
},
{
"name": "lcobucci/jwt",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18"
},
"dist": {
"type": "zip",
"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.0",
@ -2305,6 +2422,68 @@
],
"time": "2019-08-09T12:19:19+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2"
},
"dist": {
"type": "zip",
"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.0",
@ -3067,6 +3246,69 @@
],
"time": "2017-10-23T01:57:42+00:00"
},
{
"name": "r/u2f-two-factor-bundle",
"version": "0.7.0",
"source": {
"type": "git",
"url": "https://github.com/darookee/u2f-two-factor-bundle.git",
"reference": "dcf391e694a8f237883b4c39cfe7367c344c1556"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/darookee/u2f-two-factor-bundle/zipball/dcf391e694a8f237883b4c39cfe7367c344c1556",
"reference": "dcf391e694a8f237883b4c39cfe7367c344c1556",
"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\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"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",
"Symfony2",
"fido",
"two-factor",
"two-step",
"yubikey"
],
"time": "2019-06-05T14:42:26+00:00"
},
{
"name": "s9e/regexp-builder",
"version": "1.4.3",
@ -3173,6 +3415,74 @@
],
"time": "2019-11-17T16:03:56+00:00"
},
{
"name": "scheb/two-factor-bundle",
"version": "v4.11.0",
"source": {
"type": "git",
"url": "https://github.com/scheb/two-factor-bundle.git",
"reference": "eadac02014233ab45dac215d42fd06aaf629b09a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/eadac02014233ab45dac215d42fd06aaf629b09a",
"reference": "eadac02014233ab45dac215d42fd06aaf629b09a",
"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-08T16:03:05+00:00"
},
{
"name": "sensio/framework-extra-bundle",
"version": "v5.5.1",
@ -3352,6 +3662,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",
@ -7721,6 +8092,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",

View file

@ -21,4 +21,6 @@ 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],
];

View file

@ -0,0 +1,24 @@
# 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
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

@ -0,0 +1,7 @@
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
2fa_login_check:
path: /2fa_check

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;
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,153 @@
<?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\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
**/
protected $keyHandle;
/**
* @ORM\Column(type="string")
* @var string
**/
protected $publicKey;
/**
* @ORM\Column(type="text")
* @var string
**/
protected $certificate;
/**
* @ORM\Column(type="string")
* @var int
**/
protected $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;
}
}

View file

@ -53,28 +53,37 @@ 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\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
{
use MasterAttachmentTrait;
/** The User id of the anonymous user */
public const ID_ANONYMOUS = 1;
@ -172,6 +181,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;
/** @var Collection<TwoFactorKeyInterface>
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user")
*/
protected $u2fKeys;
/**
* @var array
* @ORM\Column(type="json")
@ -227,6 +263,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
{
parent::__construct();
$this->permissions = new PermissionsEmbed();
$this->u2fKeys = new ArrayCollection();
}
/**
@ -457,6 +494,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 +510,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 +519,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 +532,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 +541,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 +554,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* Gets the department of this user
* @return string
*/
public function getDepartment(): ?string
@ -516,8 +563,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 +575,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
}
/**
* Get the email of the user.
* @return string
*/
public function getEmail(): ?string
@ -536,9 +584,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 +596,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 +606,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 +628,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 +640,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 +649,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 +661,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 +682,148 @@ 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;
$this->backupCodesGenerationDate = new \DateTime();
return $this;
}
/**
* Return the date when the backup codes were generated.
* @return \DateTime
*/
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++;
}
public function isU2FAuthEnabled(): bool
{
return count($this->u2fKeys) > 0;
}
/** @return Collection<TwoFactorKeyInterface> */
public function getU2FKeys(): Collection
{
return $this->u2fKeys;
}
public function addU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->add($key);
}
public function removeU2FKey(TwoFactorKeyInterface $key): void
{
$this->u2fKeys->remove($key);
}
}

View file

@ -0,0 +1,40 @@
<?php
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

@ -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"
},
@ -226,6 +232,9 @@
"openlss/lib-array2xml": {
"version": "1.0.0"
},
"paragonie/constant_time_encoding": {
"version": "v2.3.0"
},
"php": {
"version": "7.1.3"
},
@ -274,6 +283,9 @@
"psr/simple-cache": {
"version": "1.0.1"
},
"r/u2f-two-factor-bundle": {
"version": "0.7.0"
},
"roave/security-advisories": {
"version": "dev-master"
},
@ -283,6 +295,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"
},
@ -313,6 +338,9 @@
"shivas/versioning-bundle": {
"version": "3.1.3"
},
"spomky-labs/otphp": {
"version": "v9.1.4"
},
"symfony/apache-pack": {
"version": "1.0",
"recipe": {
@ -724,6 +752,9 @@
"webmozart/path-util": {
"version": "2.3.0"
},
"yubico/u2flib-server": {
"version": "1.0.2"
},
"zendframework/zend-code": {
"version": "3.3.1"
},