LDAP authentication support (#815)
This commit is contained in:
43
src/Configuration/LdapConfiguration.php
Normal file
43
src/Configuration/LdapConfiguration.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Configuration;
|
||||
|
||||
class LdapConfiguration
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
public function __construct(array $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function isActivated(): bool
|
||||
{
|
||||
return (bool) $this->settings['active'];
|
||||
}
|
||||
|
||||
public function getRoleParameters(): array
|
||||
{
|
||||
return (array) $this->settings['role'];
|
||||
}
|
||||
|
||||
public function getUserParameters(): array
|
||||
{
|
||||
return (array) $this->settings['user'];
|
||||
}
|
||||
|
||||
public function getConnectionParameters(): array
|
||||
{
|
||||
return (array) $this->settings['connection'];
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,12 @@ class AppExtension extends Extension
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
} catch (InvalidConfigurationException $e) {
|
||||
trigger_error('Found invalid "kimai" configuration: ' . $e->getMessage());
|
||||
$config = [];
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// @deprecated since 0.9, duration_only will be removed with 1.0
|
||||
if (isset($config['timesheet']['duration_only'])) {
|
||||
trigger_error('Configuration "kimai.timesheet.duration_only" is deprecated, please remove it', E_USER_DEPRECATED);
|
||||
@trigger_error('Configuration "kimai.timesheet.duration_only" is deprecated, please remove it', E_USER_DEPRECATED);
|
||||
if (true === $config['timesheet']['duration_only'] && 'duration_only' !== $config['timesheet']['mode']) {
|
||||
trigger_error('Found ambiguous configuration. Please remove "kimai.timesheet.duration_only" and set "kimai.timesheet.mode" instead.');
|
||||
}
|
||||
@@ -60,6 +60,25 @@ class AppExtension extends Extension
|
||||
$container->setParameter('kimai.timesheet', $config['timesheet']);
|
||||
$container->setParameter('kimai.timesheet.rates', $config['timesheet']['rates']);
|
||||
$container->setParameter('kimai.timesheet.rounding', $config['timesheet']['rounding']);
|
||||
|
||||
$this->setLdapParameter($config['ldap'], $container);
|
||||
}
|
||||
|
||||
protected function setLdapParameter(array $config, ContainerBuilder $container)
|
||||
{
|
||||
if (!isset($config['connection']['baseDn'])) {
|
||||
$config['connection']['baseDn'] = $config['user']['baseDn'];
|
||||
}
|
||||
|
||||
if (empty($config['connection']['accountFilterFormat']) && $config['connection']['bindRequiresDn']) {
|
||||
$filter = '';
|
||||
if (!empty($config['user']['filter'])) {
|
||||
$filter = $config['user']['filter'];
|
||||
}
|
||||
$config['connection']['accountFilterFormat'] = '(&' . $filter . '(' . $config['user']['usernameAttribute'] . '=%s))';
|
||||
}
|
||||
|
||||
$container->setParameter('kimai.ldap', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,7 @@ class Configuration implements ConfigurationInterface
|
||||
->append($this->getWidgetsNode())
|
||||
->append($this->getDefaultsNode())
|
||||
->append($this->getPermissionsNode())
|
||||
->append($this->getLdapNode())
|
||||
->end()
|
||||
->end();
|
||||
|
||||
@@ -100,7 +101,7 @@ class Configuration implements ConfigurationInterface
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->integerNode('begin')
|
||||
@@ -141,7 +142,7 @@ class Configuration implements ConfigurationInterface
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->floatNode('factor')
|
||||
@@ -258,7 +259,7 @@ class Configuration implements ConfigurationInterface
|
||||
->children()
|
||||
->arrayNode('days')
|
||||
->requiresAtLeastOneElement()
|
||||
->prototype('integer')->end()
|
||||
->integerPrototype()->end()
|
||||
->defaultValue([1, 2, 3, 4, 5])
|
||||
->end()
|
||||
->scalarNode('begin')->defaultValue('08:00')->end()
|
||||
@@ -449,7 +450,7 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->end()
|
||||
@@ -459,7 +460,7 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->end()
|
||||
@@ -469,7 +470,7 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->scalarPrototype()->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->defaultValue([
|
||||
@@ -484,4 +485,130 @@ class Configuration implements ConfigurationInterface
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
protected function getLdapNode()
|
||||
{
|
||||
$treeBuilder = new TreeBuilder('ldap');
|
||||
$node = $treeBuilder->getRootNode();
|
||||
|
||||
$node
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->booleanNode('active')->defaultFalse()->end()
|
||||
->arrayNode('connection')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('host')->defaultNull()->end()
|
||||
->scalarNode('port')->defaultValue(389)->end()
|
||||
->scalarNode('useStartTls')->defaultFalse()->end()
|
||||
->scalarNode('useSsl')->defaultFalse()->end()
|
||||
->scalarNode('username')->end()
|
||||
->scalarNode('password')->end()
|
||||
->scalarNode('bindRequiresDn')->defaultTrue()->end()
|
||||
->scalarNode('baseDn')->end()
|
||||
->scalarNode('accountCanonicalForm')->end()
|
||||
->scalarNode('accountDomainName')->end()
|
||||
->scalarNode('accountDomainNameShort')->end()
|
||||
->scalarNode('accountFilterFormat')
|
||||
->defaultNull()
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
if (empty($v)) {
|
||||
return false;
|
||||
}
|
||||
if ($v[0] !== '(' || (substr_count($v, '(') !== substr_count($v, ')'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (substr_count($v, '%s') !== 1);
|
||||
})
|
||||
->thenInvalid('The accountFilterFormat must be enclosed by a matching number of parentheses "()" and contain one "%%s" replacer for the username')
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('allowEmptyPassword')->end()
|
||||
->scalarNode('optReferrals')->end()
|
||||
->scalarNode('tryUsernameSplit')->end()
|
||||
->scalarNode('networkTimeout')->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
return $v['useSsl'] && $v['useStartTls'];
|
||||
})
|
||||
->thenInvalid('The ldap.connection.useSsl and ldap.connection.useStartTls options are mutually exclusive.')
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('user')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('baseDn')->defaultNull()->end()
|
||||
->scalarNode('filter')
|
||||
->defaultValue('')
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
if (empty($v)) {
|
||||
return false;
|
||||
}
|
||||
if ($v[0] !== '(' || (substr_count($v, '(') !== substr_count($v, ')'))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (stripos($v, '%s') !== false);
|
||||
})
|
||||
->thenInvalid('The ldap.user.filter must be enclosed by a matching number of parentheses "()" and must NOT contain a "%%s" replacer')
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('usernameAttribute')->defaultValue('uid')->end()
|
||||
->arrayNode('attributes')
|
||||
->defaultValue([])
|
||||
->arrayPrototype()
|
||||
->children()
|
||||
->scalarNode('ldap_attr')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('user_method')->isRequired()->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('role')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('baseDn')->defaultNull()->end()
|
||||
->scalarNode('filter')->end()
|
||||
->scalarNode('usernameAttribute')->defaultValue('dn')->end()
|
||||
->scalarNode('nameAttribute')->defaultValue('cn')->end()
|
||||
->scalarNode('userDnAttribute')->defaultValue('member')->end()
|
||||
->arrayNode('groups')
|
||||
->defaultValue([])
|
||||
->arrayPrototype()
|
||||
->children()
|
||||
->scalarNode('ldap_value')->isRequired()->cannotBeEmpty()->end()
|
||||
->scalarNode('role')->isRequired()->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
return $v['active'] && !extension_loaded('ldap');
|
||||
})
|
||||
->thenInvalid('LDAP is activated, but the LDAP PHP extension is not loaded.')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
return $v['active'] && empty($v['connection']['host']);
|
||||
})
|
||||
->thenInvalid('The "ldap.connection.host" config must be set if LDAP is activated.')
|
||||
->end()
|
||||
->validate()
|
||||
->ifTrue(static function ($v) {
|
||||
return $v['active'] && empty($v['user']['baseDn']);
|
||||
})
|
||||
->thenInvalid('The "ldap.user.baseDn" config must be set if LDAP is activated.')
|
||||
->end()
|
||||
;
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -28,7 +30,7 @@ class Activity
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var Project
|
||||
* @var Project|null
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="activities")
|
||||
* @ORM\JoinColumn(onDelete="CASCADE")
|
||||
@@ -60,7 +62,7 @@ class Activity
|
||||
private $visible = true;
|
||||
|
||||
/**
|
||||
* @var Timesheet[]
|
||||
* @var Timesheet[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Timesheet", mappedBy="activity")
|
||||
*/
|
||||
@@ -70,94 +72,68 @@ class Activity
|
||||
use RatesTrait;
|
||||
use ColorTrait;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function __construct()
|
||||
{
|
||||
$this->timesheets = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timesheet[]
|
||||
* @return Collection<Timesheet>
|
||||
*/
|
||||
public function getTimesheets(): array
|
||||
public function getTimesheets(): Collection
|
||||
{
|
||||
return $this->timesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project
|
||||
*/
|
||||
public function getProject()
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project $project
|
||||
* @return Activity
|
||||
*/
|
||||
public function setProject($project)
|
||||
public function setProject(?Project $project): Activity
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Activity
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): Activity
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
* @return Activity
|
||||
*/
|
||||
public function setComment($comment)
|
||||
public function setComment(?string $comment): Activity
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $visible
|
||||
* @return Activity
|
||||
*/
|
||||
public function setVisible($visible)
|
||||
public function setVisible(bool $visible): Activity
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getVisible()
|
||||
public function getVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
@@ -48,10 +48,7 @@ class Configuration
|
||||
*/
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
@@ -76,14 +73,17 @@ class Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* Given $value will not be serialized before its stored, so it should be a scalar type.
|
||||
* Given $value will not be serialized before its stored, so it should be a scalar type
|
||||
* that can be casted to string.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string|null|int|bool $value
|
||||
* @return Configuration
|
||||
*/
|
||||
public function setValue($value): Configuration
|
||||
{
|
||||
$this->value = $value;
|
||||
if (null !== $value) {
|
||||
$this->value = (string) $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -53,7 +55,7 @@ class Customer
|
||||
private $comment;
|
||||
|
||||
/**
|
||||
* @var Project[]
|
||||
* @var Project[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Project", mappedBy="customer")
|
||||
*/
|
||||
@@ -151,362 +153,200 @@ class Customer
|
||||
use RatesTrait;
|
||||
use ColorTrait;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function __construct()
|
||||
{
|
||||
$this->projects = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name
|
||||
* @return Customer
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): Customer
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @return Customer
|
||||
*/
|
||||
public function setNumber(string $number)
|
||||
public function setNumber(?string $number): Customer
|
||||
{
|
||||
$this->number = $number;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNumber(): ?string
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comment
|
||||
*
|
||||
* @param string $comment
|
||||
* @return Customer
|
||||
*/
|
||||
public function setComment($comment)
|
||||
public function setComment(?string $comment): Customer
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comment
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set visible
|
||||
*
|
||||
* @param bool $visible
|
||||
* @return Customer
|
||||
*/
|
||||
public function setVisible($visible)
|
||||
public function setVisible(bool $visible): Customer
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get visible
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getVisible()
|
||||
public function getVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set company
|
||||
*
|
||||
* @param string $company
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCompany($company)
|
||||
public function setCompany(?string $company): Customer
|
||||
{
|
||||
$this->company = $company;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get company
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCompany()
|
||||
public function getCompany(): ?string
|
||||
{
|
||||
return $this->company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact
|
||||
*
|
||||
* @param string $contact
|
||||
* @return Customer
|
||||
*/
|
||||
public function setContact($contact)
|
||||
public function setContact(?string $contact): Customer
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contact
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContact()
|
||||
public function getContact(): ?string
|
||||
{
|
||||
return $this->contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
* @return Customer
|
||||
*/
|
||||
public function setAddress($address)
|
||||
public function setAddress(?string $address): Customer
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress()
|
||||
public function getAddress(): ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set country
|
||||
*
|
||||
* @param string $country
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCountry($country)
|
||||
public function setCountry(string $country): Customer
|
||||
{
|
||||
$this->country = $country;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get country
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCountry()
|
||||
public function getCountry(): ?string
|
||||
{
|
||||
return $this->country;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currency
|
||||
* @return Customer
|
||||
*/
|
||||
public function setCurrency($currency)
|
||||
public function setCurrency(string $currency): Customer
|
||||
{
|
||||
$this->currency = $currency;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency()
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set phone
|
||||
*
|
||||
* @param string $phone
|
||||
* @return Customer
|
||||
*/
|
||||
public function setPhone($phone)
|
||||
public function setPhone(?string $phone): Customer
|
||||
{
|
||||
$this->phone = $phone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get phone
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPhone()
|
||||
public function getPhone(): ?string
|
||||
{
|
||||
return $this->phone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fax
|
||||
*
|
||||
* @param string $fax
|
||||
* @return Customer
|
||||
*/
|
||||
public function setFax($fax)
|
||||
public function setFax(?string $fax): Customer
|
||||
{
|
||||
$this->fax = $fax;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fax
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFax()
|
||||
public function getFax(): ?string
|
||||
{
|
||||
return $this->fax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mobile
|
||||
*
|
||||
* @param string $mobile
|
||||
* @return Customer
|
||||
*/
|
||||
public function setMobile($mobile)
|
||||
public function setMobile(?string $mobile): Customer
|
||||
{
|
||||
$this->mobile = $mobile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mobile
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMobile()
|
||||
public function getMobile(): ?string
|
||||
{
|
||||
return $this->mobile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mail
|
||||
*
|
||||
* @param string $mail
|
||||
* @return Customer
|
||||
*/
|
||||
public function setEmail($mail)
|
||||
public function setEmail(?string $mail): Customer
|
||||
{
|
||||
$this->email = $mail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mail
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail()
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set homepage
|
||||
*
|
||||
* @param string $homepage
|
||||
* @return Customer
|
||||
*/
|
||||
public function setHomepage($homepage)
|
||||
public function setHomepage(?string $homepage): Customer
|
||||
{
|
||||
$this->homepage = $homepage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get homepage
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHomepage()
|
||||
public function getHomepage(): ?string
|
||||
{
|
||||
return $this->homepage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set timezone
|
||||
*
|
||||
* @param string $timezone
|
||||
* @return Customer
|
||||
*/
|
||||
public function setTimezone($timezone)
|
||||
public function setTimezone(string $timezone): Customer
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTimezone()
|
||||
public function getTimezone(): ?string
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project[] $projects
|
||||
* @return Customer
|
||||
* @return Collection<Project>
|
||||
*/
|
||||
public function setProjects($projects)
|
||||
{
|
||||
$this->projects = $projects;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project[]
|
||||
*/
|
||||
public function getProjects()
|
||||
public function getProjects(): Collection
|
||||
{
|
||||
return $this->projects;
|
||||
}
|
||||
|
||||
@@ -109,202 +109,125 @@ class InvoiceTemplate
|
||||
*/
|
||||
private $paymentTerms;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): InvoiceTemplate
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setTitle(string $title)
|
||||
public function setTitle(string $title): InvoiceTemplate
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress(): ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setAddress($address)
|
||||
public function setAddress(?string $address): InvoiceTemplate
|
||||
{
|
||||
$this->address = $address;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getNumberGenerator(): ?string
|
||||
public function getNumberGenerator(): string
|
||||
{
|
||||
return $this->numberGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $numberGenerator
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setNumberGenerator(string $numberGenerator)
|
||||
public function setNumberGenerator(string $numberGenerator): InvoiceTemplate
|
||||
{
|
||||
$this->numberGenerator = $numberGenerator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDueDays(): ?int
|
||||
public function getDueDays(): int
|
||||
{
|
||||
return $this->dueDays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $dueDays
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setDueDays(int $dueDays)
|
||||
public function setDueDays(int $dueDays): InvoiceTemplate
|
||||
{
|
||||
$this->dueDays = $dueDays;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getVat(): ?float
|
||||
public function getVat(): float
|
||||
{
|
||||
return $this->vat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $vat
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setVat(float $vat)
|
||||
public function setVat(float $vat): InvoiceTemplate
|
||||
{
|
||||
$this->vat = $vat;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCompany(): ?string
|
||||
{
|
||||
return $this->company;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $company
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setCompany(string $company)
|
||||
public function setCompany(string $company): InvoiceTemplate
|
||||
{
|
||||
$this->company = $company;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRenderer(): string
|
||||
{
|
||||
return $this->renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $renderer
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setRenderer(string $renderer)
|
||||
public function setRenderer(string $renderer): InvoiceTemplate
|
||||
{
|
||||
$this->renderer = $renderer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCalculator(): string
|
||||
{
|
||||
return $this->calculator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $calculator
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setCalculator(string $calculator)
|
||||
public function setCalculator(string $calculator): InvoiceTemplate
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPaymentTerms(): ?string
|
||||
{
|
||||
return $this->paymentTerms;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $paymentTerms
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function setPaymentTerms(?string $paymentTerms)
|
||||
public function setPaymentTerms(?string $paymentTerms): InvoiceTemplate
|
||||
{
|
||||
$this->paymentTerms = $paymentTerms;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -77,7 +79,7 @@ class Project
|
||||
private $budget = 0.00;
|
||||
|
||||
/**
|
||||
* @var Activity[]
|
||||
* @var Activity[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Activity", mappedBy="project")
|
||||
*/
|
||||
@@ -88,54 +90,43 @@ class Project
|
||||
use ColorTrait;
|
||||
|
||||
/**
|
||||
* @var Timesheet[]
|
||||
* @var Timesheet[]|ArrayCollection
|
||||
*
|
||||
* @ORM\OneToMany(targetEntity="App\Entity\Timesheet", mappedBy="project")
|
||||
*/
|
||||
private $timesheets;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function __construct()
|
||||
{
|
||||
$this->activities = new ArrayCollection();
|
||||
$this->timesheets = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
*/
|
||||
public function getCustomer()
|
||||
public function getCustomer(): ?Customer
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer $customer
|
||||
* @return Project
|
||||
*/
|
||||
public function setCustomer($customer)
|
||||
public function setCustomer(Customer $customer): Project
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return Project
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): Project
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
@@ -144,26 +135,19 @@ class Project
|
||||
* @param string $comment
|
||||
* @return Project
|
||||
*/
|
||||
public function setComment($comment)
|
||||
public function setComment($comment): Project
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment()
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $visible
|
||||
* @return Project
|
||||
*/
|
||||
public function setVisible($visible)
|
||||
public function setVisible(bool $visible): Project
|
||||
{
|
||||
$this->visible = $visible;
|
||||
|
||||
@@ -173,7 +157,7 @@ class Project
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getVisible()
|
||||
public function getVisible(): bool
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
@@ -182,7 +166,7 @@ class Project
|
||||
* @param float $budget
|
||||
* @return Project
|
||||
*/
|
||||
public function setBudget($budget)
|
||||
public function setBudget($budget): Project
|
||||
{
|
||||
$this->budget = $budget;
|
||||
|
||||
@@ -198,39 +182,17 @@ class Project
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet[] $timesheets
|
||||
* @return Project
|
||||
* @return Collection<Timesheet>
|
||||
*/
|
||||
public function setTimesheets($timesheets)
|
||||
{
|
||||
$this->timesheets = $timesheets;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Timesheet[]
|
||||
*/
|
||||
public function getTimesheets()
|
||||
public function getTimesheets(): Collection
|
||||
{
|
||||
return $this->timesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Activity[] $activities
|
||||
* @return Project
|
||||
* @return Collection<Activity>
|
||||
*/
|
||||
public function setActivities($activities)
|
||||
{
|
||||
$this->activities = $activities;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Activity[]
|
||||
*/
|
||||
public function getActivities()
|
||||
public function getActivities(): Collection
|
||||
{
|
||||
return $this->activities;
|
||||
}
|
||||
@@ -247,7 +209,7 @@ class Project
|
||||
* @param string $orderNumber
|
||||
* @return Project
|
||||
*/
|
||||
public function setOrderNumber($orderNumber)
|
||||
public function setOrderNumber($orderNumber): Project
|
||||
{
|
||||
$this->orderNumber = $orderNumber;
|
||||
|
||||
|
||||
@@ -55,36 +55,23 @@ class Tag
|
||||
$this->timesheets = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tagName
|
||||
* @return Tag
|
||||
*/
|
||||
public function setName($tagName)
|
||||
public function setName(string $tagName): Tag
|
||||
{
|
||||
$this->name = $tagName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $timesheet
|
||||
*/
|
||||
public function addTimesheet(Timesheet $timesheet)
|
||||
{
|
||||
if ($this->timesheets->contains($timesheet)) {
|
||||
@@ -95,9 +82,6 @@ class Tag
|
||||
$timesheet->addTag($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Timesheet $timesheet
|
||||
*/
|
||||
public function removeTimesheet(Timesheet $timesheet)
|
||||
{
|
||||
if (!$this->timesheets->contains($timesheet)) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -148,11 +149,11 @@ class Timesheet
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry id
|
||||
* Get entry id, returns null for new entities which were not persisted.
|
||||
*
|
||||
* @return int
|
||||
* @return int|null
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
@@ -178,10 +179,7 @@ class Timesheet
|
||||
$this->localized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getBegin()
|
||||
public function getBegin(): ?\DateTime
|
||||
{
|
||||
$this->localizeDates();
|
||||
|
||||
@@ -192,7 +190,7 @@ class Timesheet
|
||||
* @param \DateTime $begin
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setBegin(\DateTime $begin)
|
||||
public function setBegin(\DateTime $begin): Timesheet
|
||||
{
|
||||
$this->begin = $begin;
|
||||
$this->timezone = $begin->getTimezone()->getName();
|
||||
@@ -200,10 +198,7 @@ class Timesheet
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function getEnd()
|
||||
public function getEnd(): ?\DateTime
|
||||
{
|
||||
$this->localizeDates();
|
||||
|
||||
@@ -214,7 +209,7 @@ class Timesheet
|
||||
* @param \DateTime $end
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setEnd(?\DateTime $end)
|
||||
public function setEnd(?\DateTime $end): Timesheet
|
||||
{
|
||||
$this->end = $end;
|
||||
|
||||
@@ -232,7 +227,7 @@ class Timesheet
|
||||
* @param int $duration
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setDuration($duration)
|
||||
public function setDuration($duration): Timesheet
|
||||
{
|
||||
$this->duration = $duration;
|
||||
|
||||
@@ -253,17 +248,14 @@ class Timesheet
|
||||
* @param User $user
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
public function setUser(User $user): Timesheet
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
@@ -272,25 +264,19 @@ class Timesheet
|
||||
* @param Activity $activity
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setActivity($activity)
|
||||
public function setActivity($activity): Timesheet
|
||||
{
|
||||
$this->activity = $activity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Activity
|
||||
*/
|
||||
public function getActivity()
|
||||
public function getActivity(): ?Activity
|
||||
{
|
||||
return $this->activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project
|
||||
*/
|
||||
public function getProject()
|
||||
public function getProject(): ?Project
|
||||
{
|
||||
return $this->project;
|
||||
}
|
||||
@@ -299,7 +285,7 @@ class Timesheet
|
||||
* @param Project $project
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setProject(Project $project)
|
||||
public function setProject(Project $project): Timesheet
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
@@ -310,17 +296,14 @@ class Timesheet
|
||||
* @param string $description
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setDescription($description)
|
||||
public function setDescription($description): Timesheet
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription()
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
@@ -329,7 +312,7 @@ class Timesheet
|
||||
* @param float $rate
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setRate($rate)
|
||||
public function setRate($rate): Timesheet
|
||||
{
|
||||
$this->rate = $rate;
|
||||
|
||||
@@ -348,7 +331,7 @@ class Timesheet
|
||||
* @param Tag $tag
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function addTag(Tag $tag)
|
||||
public function addTag(Tag $tag): Timesheet
|
||||
{
|
||||
if ($this->tags->contains($tag)) {
|
||||
return $this;
|
||||
@@ -372,9 +355,9 @@ class Timesheet
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Tag[]|ArrayCollection
|
||||
* @return Collection<Tag>
|
||||
*/
|
||||
public function getTags()
|
||||
public function getTags(): Collection
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
@@ -404,7 +387,7 @@ class Timesheet
|
||||
* @param bool $exported
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setExported(bool $exported)
|
||||
public function setExported(bool $exported): Timesheet
|
||||
{
|
||||
$this->exported = $exported;
|
||||
|
||||
@@ -426,7 +409,7 @@ class Timesheet
|
||||
* @param string $timezone
|
||||
* @return Timesheet
|
||||
*/
|
||||
public function setTimezone(string $timezone)
|
||||
public function setTimezone(string $timezone): Timesheet
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
|
||||
|
||||
@@ -105,122 +105,77 @@ class User extends BaseUser implements UserInterface
|
||||
$this->preferences = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getRegisteredAt()
|
||||
public function getRegisteredAt(): ?\DateTime
|
||||
{
|
||||
return $this->registeredAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $registeredAt
|
||||
* @return $this
|
||||
*/
|
||||
public function setRegisteredAt(\DateTime $registeredAt)
|
||||
public function setRegisteredAt(\DateTime $registeredAt): User
|
||||
{
|
||||
$this->registeredAt = $registeredAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $alias
|
||||
* @return $this
|
||||
*/
|
||||
public function setAlias($alias)
|
||||
public function setAlias(?string $alias): User
|
||||
{
|
||||
$this->alias = $alias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAlias()
|
||||
public function getAlias(): ?string
|
||||
{
|
||||
return $this->alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @return $this
|
||||
*/
|
||||
public function setTitle($title)
|
||||
public function setTitle(?string $title): User
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAvatar()
|
||||
public function getAvatar(): ?string
|
||||
{
|
||||
return $this->avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $avatar
|
||||
* @return $this
|
||||
*/
|
||||
public function setAvatar($avatar)
|
||||
public function setAvatar(?string $avatar): User
|
||||
{
|
||||
$this->avatar = $avatar;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getApiToken()
|
||||
public function getApiToken(): ?string
|
||||
{
|
||||
return $this->apiToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $apiToken
|
||||
* @return User
|
||||
*/
|
||||
public function setApiToken($apiToken)
|
||||
public function setApiToken(?string $apiToken): User
|
||||
{
|
||||
$this->apiToken = $apiToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPlainApiToken(): ?string
|
||||
{
|
||||
return $this->plainApiToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $plainApiToken
|
||||
* @return User
|
||||
*/
|
||||
public function setPlainApiToken(string $plainApiToken)
|
||||
public function setPlainApiToken(?string $plainApiToken): User
|
||||
{
|
||||
$this->plainApiToken = $plainApiToken;
|
||||
|
||||
@@ -228,7 +183,7 @@ class User extends BaseUser implements UserInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserPreference[]|Collection
|
||||
* @return Collection<UserPreference>
|
||||
*/
|
||||
public function getPreferences(): Collection
|
||||
{
|
||||
@@ -236,10 +191,10 @@ class User extends BaseUser implements UserInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserPreference[]|Collection<UserPreference> $preferences
|
||||
* @param iterable<UserPreference> $preferences
|
||||
* @return User
|
||||
*/
|
||||
public function setPreferences($preferences)
|
||||
public function setPreferences(iterable $preferences): User
|
||||
{
|
||||
$this->preferences = new ArrayCollection();
|
||||
|
||||
@@ -252,10 +207,29 @@ class User extends BaseUser implements UserInterface
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return UserPreference|null
|
||||
* @param bool|int|string|null $value
|
||||
*/
|
||||
public function getPreference(string $name)
|
||||
public function setPreferenceValue(string $name, $value = null)
|
||||
{
|
||||
$pref = $this->getPreference($name);
|
||||
|
||||
if (null === $pref) {
|
||||
$pref = new UserPreference();
|
||||
$pref->setName($name);
|
||||
$this->addPreference($pref);
|
||||
}
|
||||
|
||||
$pref->setValue($value);
|
||||
}
|
||||
|
||||
public function getPreference(string $name): ?UserPreference
|
||||
{
|
||||
// this code will be triggered, if a currently logged-in user will be deleted and the refreshed from the session
|
||||
// via one of the UserProvider - e.g. see LdapUserProvider::refreshUser() which calls $user->getPreferenceValue()
|
||||
if (empty($this->preferences)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($this->preferences as $preference) {
|
||||
if ($preference->getName() == $name) {
|
||||
return $preference;
|
||||
@@ -268,7 +242,7 @@ class User extends BaseUser implements UserInterface
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLocale()
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->getPreferenceValue(UserPreference::LOCALE, User::DEFAULT_LANGUAGE);
|
||||
}
|
||||
@@ -292,7 +266,7 @@ class User extends BaseUser implements UserInterface
|
||||
* @param UserPreference $preference
|
||||
* @return User
|
||||
*/
|
||||
public function addPreference(UserPreference $preference)
|
||||
public function addPreference(UserPreference $preference): User
|
||||
{
|
||||
$this->preferences->add($preference);
|
||||
$preference->setUser($this);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Security\RoleService;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -19,14 +20,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
class UserRoleType extends AbstractType
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
* @var RoleService
|
||||
*/
|
||||
protected $roles = [];
|
||||
protected $roles;
|
||||
|
||||
/**
|
||||
* @param string[] $roles
|
||||
*/
|
||||
public function __construct(array $roles = [])
|
||||
public function __construct(RoleService $roles)
|
||||
{
|
||||
$this->roles = $roles;
|
||||
}
|
||||
@@ -37,13 +35,8 @@ class UserRoleType extends AbstractType
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$roles = [];
|
||||
foreach ($this->roles as $key => $value) {
|
||||
$roles[$key] = $key;
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $value2) {
|
||||
$roles[$value2] = $value2;
|
||||
}
|
||||
}
|
||||
foreach ($this->roles->getAvailableNames() as $name) {
|
||||
$roles[$name] = $name;
|
||||
}
|
||||
|
||||
$resolver->setDefaults([
|
||||
|
||||
@@ -17,9 +17,11 @@ use App\Export\RendererInterface as ExportRendererInterface;
|
||||
use App\Invoice\CalculatorInterface as InvoiceCalculator;
|
||||
use App\Invoice\NumberGeneratorInterface;
|
||||
use App\Invoice\RendererInterface as InvoiceRendererInterface;
|
||||
use App\Ldap\FormLoginLdapFactory;
|
||||
use App\Plugin\PluginInterface;
|
||||
use App\Timesheet\CalculatorInterface as TimesheetCalculator;
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityExtension;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@@ -58,6 +60,10 @@ class Kernel extends BaseKernel
|
||||
$container->registerForAutoconfiguration(NumberGeneratorInterface::class)->addTag(self::TAG_INVOICE_NUMBER_GENERATOR);
|
||||
$container->registerForAutoconfiguration(InvoiceCalculator::class)->addTag(self::TAG_INVOICE_CALCULATOR);
|
||||
$container->registerForAutoconfiguration(PluginInterface::class)->addTag(self::TAG_PLUGIN);
|
||||
|
||||
/** @var SecurityExtension $extension */
|
||||
$extension = $container->getExtension('security');
|
||||
$extension->addSecurityListenerFactory(new FormLoginLdapFactory());
|
||||
}
|
||||
|
||||
public function registerBundles()
|
||||
|
||||
72
src/Ldap/FormLoginLdapFactory.php
Normal file
72
src/Ldap/FormLoginLdapFactory.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class FormLoginLdapFactory implements SecurityFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProviderId, $defaultEntryPointId)
|
||||
{
|
||||
$authProviderId = $this->createAuthProvider($container, $id, $userProviderId);
|
||||
$listenerId = $this->createListener($container, $id, $config);
|
||||
|
||||
return [$authProviderId, $listenerId, $defaultEntryPointId];
|
||||
}
|
||||
|
||||
public function getPosition()
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'kimai_ldap';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
}
|
||||
|
||||
protected function createAuthProvider(ContainerBuilder $container, $id, $userProviderId)
|
||||
{
|
||||
$provider = 'kimai_ldap.security.authentication.provider';
|
||||
$providerId = $provider . '.' . $id;
|
||||
|
||||
$container
|
||||
->setDefinition($providerId, new ChildDefinition($provider))
|
||||
->replaceArgument(1, $id)
|
||||
->replaceArgument(2, new Reference($userProviderId))
|
||||
;
|
||||
|
||||
return $providerId;
|
||||
}
|
||||
|
||||
protected function createListener(ContainerBuilder $container, $id, $config)
|
||||
{
|
||||
$listenerId = 'security.authentication.listener.form';
|
||||
|
||||
$listener = new ChildDefinition($listenerId);
|
||||
$listener->replaceArgument(4, $id);
|
||||
$listener->replaceArgument(5, $config);
|
||||
|
||||
$listenerId .= '.' . $id;
|
||||
$container->setDefinition($listenerId, $listener);
|
||||
|
||||
return $listenerId;
|
||||
}
|
||||
}
|
||||
131
src/Ldap/LdapAuthenticationProvider.php
Normal file
131
src/Ldap/LdapAuthenticationProvider.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Configuration\LdapConfiguration;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class LdapAuthenticationProvider extends UserAuthenticationProvider
|
||||
{
|
||||
/**
|
||||
* @var UserProviderInterface
|
||||
*/
|
||||
private $userProvider;
|
||||
|
||||
/**
|
||||
* @var LdapManager
|
||||
*/
|
||||
private $ldapManager;
|
||||
/**
|
||||
* @var LdapConfiguration
|
||||
*/
|
||||
private $config;
|
||||
|
||||
public function __construct(UserCheckerInterface $userChecker, $providerKey, UserProviderInterface $userProvider, LdapManager $ldapManager, LdapConfiguration $config, $hideUserNotFoundExceptions = true)
|
||||
{
|
||||
parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
|
||||
|
||||
$this->ldapManager = $ldapManager;
|
||||
$this->config = $config;
|
||||
$this->userProvider = $userProvider;
|
||||
}
|
||||
|
||||
public function supports(TokenInterface $token)
|
||||
{
|
||||
if (!$this->config->isActivated()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::supports($token);
|
||||
}
|
||||
|
||||
protected function retrieveUser($username, UsernamePasswordToken $token)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
if ($user instanceof UserInterface) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
try {
|
||||
// this will always query the FOSUserBundle first...
|
||||
// only first-time logins from LDAP user (not yet existing in local user database)
|
||||
// will actually hit the LdapUserProvider
|
||||
$user = $this->userProvider->loadUserByUsername($username);
|
||||
|
||||
// do not update the user here from LDAP, as we don't know if the user can be authenticated
|
||||
} catch (UsernameNotFoundException $notFound) {
|
||||
throw $notFound;
|
||||
} catch (\Exception $repositoryProblem) {
|
||||
$e = new AuthenticationServiceException($repositoryProblem->getMessage(), (int) $repositoryProblem->getCode(), $repositoryProblem);
|
||||
$e->setToken($token);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* The updateUser() call should theoretically happen in retrieveUser() but that would require an additional
|
||||
* $this->ldapManager->bind($user, $token->getCredentials())
|
||||
* to check if the user is still valid.
|
||||
*
|
||||
* Symfony calls retrieveUser() before checkAuthentication()
|
||||
* and we should not used ldap->search() before ldap->bind()
|
||||
*
|
||||
* @param UserInterface $user
|
||||
* @param UsernamePasswordToken $token
|
||||
* @throws LdapDriverException
|
||||
*/
|
||||
protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
|
||||
{
|
||||
$currentUser = $token->getUser();
|
||||
$presentedPassword = $token->getCredentials();
|
||||
if ($currentUser instanceof UserInterface) {
|
||||
if ('' === $presentedPassword) {
|
||||
throw new BadCredentialsException(
|
||||
'The password in the token is empty. Check `erase_credentials` in your `security.yaml`'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->ldapManager->bind($currentUser, $presentedPassword)) {
|
||||
throw new BadCredentialsException('The credentials were changed from another session.');
|
||||
}
|
||||
} else {
|
||||
if ('' === $presentedPassword) {
|
||||
throw new BadCredentialsException('The presented password cannot be empty.');
|
||||
}
|
||||
|
||||
if (!$this->ldapManager->bind($user, $presentedPassword)) {
|
||||
throw new BadCredentialsException('The presented password is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($user instanceof User && null !== $user->getPreferenceValue('ldap.dn')) {
|
||||
try {
|
||||
$this->ldapManager->updateUser($user);
|
||||
} catch (LdapDriverException $ex) {
|
||||
throw new BadCredentialsException('Fetching user data/roles failed, probably DN is expired.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/Ldap/LdapDriver.php
Normal file
129
src/Ldap/LdapDriver.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Zend\Ldap\Exception\LdapException;
|
||||
use Zend\Ldap\Ldap;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class LdapDriver
|
||||
{
|
||||
/**
|
||||
* @var Ldap
|
||||
*/
|
||||
private $driver;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param Ldap $driver Initialized Zend::Ldap Object
|
||||
* @param LoggerInterface $logger optional logger for write debug messages
|
||||
*/
|
||||
public function __construct(Ldap $driver, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->driver = $driver;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseDn
|
||||
* @param string $filter
|
||||
* @param array $attributes
|
||||
* @return array
|
||||
* @throws LdapDriverException
|
||||
*/
|
||||
public function search(string $baseDn, string $filter, array $attributes = []): array
|
||||
{
|
||||
$attributes = array_unique(array_merge($attributes, ['+', '*']));
|
||||
|
||||
$this->logDebug('{action}({base_dn}, {filter}, {attributes})', [
|
||||
'action' => 'ldap_search',
|
||||
'base_dn' => $baseDn,
|
||||
'filter' => $filter,
|
||||
'attributes' => $attributes,
|
||||
]);
|
||||
|
||||
try {
|
||||
$this->driver->bind();
|
||||
$entries = $this->driver->searchEntries($filter, $baseDn, Ldap::SEARCH_SCOPE_SUB, $attributes);
|
||||
|
||||
// searchEntries don't return 'count' key as specified by php native function ldap_get_entries()
|
||||
$entries['count'] = count($entries);
|
||||
} catch (LdapException $exception) {
|
||||
$this->zendExceptionHandler($exception);
|
||||
|
||||
throw new LdapDriverException('An error occurred with the search operation.');
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
public function bind(UserInterface $user, string $password): bool
|
||||
{
|
||||
$bindDn = $user->getUsername();
|
||||
|
||||
try {
|
||||
$this->logDebug('{action}({bindDn}, ****)', [
|
||||
'action' => 'ldap_bind',
|
||||
'bindDn' => $bindDn,
|
||||
]);
|
||||
$bind = $this->driver->bind($bindDn, $password);
|
||||
|
||||
return $bind instanceof Ldap;
|
||||
} catch (LdapException $exception) {
|
||||
$this->zendExceptionHandler($exception, $password);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat a Zend Ldap Exception.
|
||||
*/
|
||||
protected function zendExceptionHandler(LdapException $exception, string $password = null): void
|
||||
{
|
||||
$sanitizedException = null !== $password ? new SanitizingException($exception, $password) : $exception;
|
||||
|
||||
switch ($exception->getCode()) {
|
||||
// Error level codes
|
||||
case LdapException::LDAP_SERVER_DOWN:
|
||||
if ($this->logger) {
|
||||
$this->logger->error('{exception}', ['exception' => $sanitizedException]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Other level codes
|
||||
default:
|
||||
$this->logDebug('{exception}', ['exception' => $sanitizedException]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug messages if the logger is set.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $context
|
||||
*/
|
||||
private function logDebug(string $message, array $context = []): void
|
||||
{
|
||||
if (null === $this->logger) {
|
||||
return;
|
||||
}
|
||||
$this->logger->debug($message, $context);
|
||||
}
|
||||
}
|
||||
18
src/Ldap/LdapDriverException.php
Normal file
18
src/Ldap/LdapDriverException.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
class LdapDriverException extends \Exception
|
||||
{
|
||||
public function __construct($message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
||||
165
src/Ldap/LdapManager.php
Normal file
165
src/Ldap/LdapManager.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Configuration\LdapConfiguration;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class LdapManager
|
||||
{
|
||||
/**
|
||||
* @var LdapConfiguration
|
||||
*/
|
||||
protected $config;
|
||||
/**
|
||||
* @var LdapDriver
|
||||
*/
|
||||
protected $driver;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params = [];
|
||||
/**
|
||||
* @var LdapUserHydrator
|
||||
*/
|
||||
protected $hydrator;
|
||||
|
||||
public function __construct(LdapDriver $driver, LdapUserHydrator $hydrator, LdapConfiguration $config)
|
||||
{
|
||||
$this->params = $config->getUserParameters();
|
||||
$this->config = $config;
|
||||
$this->driver = $driver;
|
||||
$this->hydrator = $hydrator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only executed for unknown local users.
|
||||
*
|
||||
* @param string $username
|
||||
* @return User|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function findUserByUsername(string $username): ?UserInterface
|
||||
{
|
||||
return $this->findUserBy([$this->params['usernameAttribute'] => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $criteria
|
||||
* @return User|null
|
||||
* @throws LdapDriverException
|
||||
*/
|
||||
public function findUserBy(array $criteria): ?UserInterface
|
||||
{
|
||||
$filter = $this->buildFilter($criteria);
|
||||
$entries = $this->driver->search($this->params['baseDn'], $filter);
|
||||
|
||||
if ($entries['count'] > 1) {
|
||||
throw new LdapDriverException('This search must only return a single user');
|
||||
}
|
||||
|
||||
if (0 === $entries['count']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// do not updateUser() here, as this would happen before bind()
|
||||
return $this->hydrator->hydrate($entries[0]);
|
||||
}
|
||||
|
||||
protected function buildFilter(array $criteria, string $condition = '&'): string
|
||||
{
|
||||
$filters = [];
|
||||
$filters[] = $this->params['filter'];
|
||||
foreach ($criteria as $key => $value) {
|
||||
$value = ldap_escape($value, '', LDAP_ESCAPE_FILTER);
|
||||
$filters[] = sprintf('(%s=%s)', $key, $value);
|
||||
}
|
||||
|
||||
return sprintf('(%s%s)', $condition, implode($filters));
|
||||
}
|
||||
|
||||
public function bind(UserInterface $user, string $password): bool
|
||||
{
|
||||
return $this->driver->bind($user, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method does all the heavy lifting:
|
||||
* - searching for latest 'dn'
|
||||
* - syncing user attributes
|
||||
* - syncing roles
|
||||
*
|
||||
* @param User $user
|
||||
* @throws LdapDriverException
|
||||
*/
|
||||
public function updateUser(User $user)
|
||||
{
|
||||
$baseDn = $user->getPreferenceValue('ldap.dn');
|
||||
$filter = '(objectClass=*)';
|
||||
|
||||
if (null === $baseDn) {
|
||||
throw new LdapDriverException('This account is not a registered LDAP user');
|
||||
}
|
||||
|
||||
// always look up the users current DN first, as the cached DN might have been renamed in LDAP
|
||||
$userFresh = $this->findUserByUsername($user->getUsername());
|
||||
if (null === $userFresh || null === ($baseDn = $userFresh->getPreferenceValue('ldap.dn'))) {
|
||||
throw new LdapDriverException(sprintf('Failed fetching user DN for %s', $user->getUsername()));
|
||||
}
|
||||
$user->setPreferenceValue('ldap.dn', $baseDn);
|
||||
|
||||
$entries = $this->driver->search($baseDn, $filter);
|
||||
|
||||
if ($entries['count'] > 1) {
|
||||
throw new LdapDriverException('This search must only return a single user');
|
||||
}
|
||||
|
||||
if (0 === $entries['count']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->hydrator->hydrateUser($user, $entries[0]);
|
||||
|
||||
$roleParameter = $this->config->getRoleParameters();
|
||||
if (null === $roleParameter['baseDn']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$param = $roleParameter['usernameAttribute'];
|
||||
if (!isset($entries[0][$param]) && $param !== 'dn') {
|
||||
$param = 'dn';
|
||||
}
|
||||
|
||||
$roleValue = $entries[0][$param];
|
||||
if (is_array($roleValue)) {
|
||||
$roleValue = $roleValue[0];
|
||||
}
|
||||
$roles = $this->getRoles($roleValue, $roleParameter);
|
||||
|
||||
if (!empty($roles)) {
|
||||
$this->hydrator->hydrateRoles($user, $roles);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getRoles(string $dn, array $roleParameter): array
|
||||
{
|
||||
$filter = $roleParameter['filter'] ?? '';
|
||||
|
||||
return $this->driver->search(
|
||||
$roleParameter['baseDn'],
|
||||
sprintf('(&%s(%s=%s))', $filter, $roleParameter['userDnAttribute'], $dn),
|
||||
[$roleParameter['nameAttribute']]
|
||||
);
|
||||
}
|
||||
}
|
||||
144
src/Ldap/LdapUserHydrator.php
Normal file
144
src/Ldap/LdapUserHydrator.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Configuration\LdapConfiguration;
|
||||
use App\Entity\User;
|
||||
use App\Security\RoleService;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class LdapUserHydrator
|
||||
{
|
||||
/**
|
||||
* @var LdapConfiguration
|
||||
*/
|
||||
private $config;
|
||||
/**
|
||||
* @var RoleService
|
||||
*/
|
||||
private $roles;
|
||||
|
||||
public function __construct(LdapConfiguration $config, RoleService $roles)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->roles = $roles;
|
||||
}
|
||||
|
||||
protected function createUser(): User
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEnabled(true);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function hydrate(array $ldapEntry): User
|
||||
{
|
||||
$user = $this->createUser();
|
||||
$this->hydrateUser($user, $ldapEntry);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function hydrateUser(User $user, array $ldapEntry)
|
||||
{
|
||||
$userParams = $this->config->getUserParameters();
|
||||
$attributeMap = $userParams['attributes'];
|
||||
$attributeMap = array_merge(
|
||||
[
|
||||
['ldap_attr' => $userParams['usernameAttribute'], 'user_method' => 'setUsername'],
|
||||
],
|
||||
$attributeMap
|
||||
);
|
||||
|
||||
$this->hydrateUserWithAttributesMap($user, $ldapEntry, $attributeMap);
|
||||
|
||||
if (null === $user->getEmail()) {
|
||||
$user->setEmail($user->getUsername());
|
||||
}
|
||||
|
||||
// prevent that users will define a password for the internal account
|
||||
$user->setPassword('');
|
||||
|
||||
$user->setPreferenceValue('ldap.dn', $ldapEntry['dn']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
* @param array $entries
|
||||
*/
|
||||
public function hydrateRoles(User $user, array $entries)
|
||||
{
|
||||
$roleParams = $this->config->getRoleParameters();
|
||||
$allowedRoles = $this->roles->getAvailableNames();
|
||||
$groupNameMapping = $roleParams['groups'];
|
||||
$roleNameAttr = $roleParams['nameAttribute'];
|
||||
|
||||
$roles = [];
|
||||
for ($i = 0; $i < $entries['count']; $i++) {
|
||||
$roleName = $entries[$i][$roleNameAttr][0];
|
||||
$mapped = false;
|
||||
foreach ($groupNameMapping as $attr) {
|
||||
if ($roleName === $attr['ldap_value']) {
|
||||
$roleName = $attr['role'];
|
||||
$mapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mapped) {
|
||||
$roleName = sprintf('ROLE_%s', self::slugify($roleName));
|
||||
}
|
||||
|
||||
if (!in_array($roleName, $allowedRoles)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$roles[] = $roleName;
|
||||
}
|
||||
|
||||
$user->setRoles($roles);
|
||||
}
|
||||
|
||||
private static function slugify(string $role): string
|
||||
{
|
||||
$role = preg_replace('/\W+/', '_', $role);
|
||||
$role = trim($role, '_');
|
||||
$role = strtoupper($role);
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
protected function hydrateUserWithAttributesMap(UserInterface $user, array $ldapUserAttributes, array $attributeMap)
|
||||
{
|
||||
/** @var array $attr */
|
||||
foreach ($attributeMap as $attr) {
|
||||
if (!array_key_exists($attr['ldap_attr'], $ldapUserAttributes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ldapValue = $ldapUserAttributes[$attr['ldap_attr']];
|
||||
|
||||
if (array_key_exists('count', $ldapValue)) {
|
||||
unset($ldapValue['count']);
|
||||
}
|
||||
|
||||
if (1 === count($ldapValue)) {
|
||||
$value = array_shift($ldapValue);
|
||||
} else {
|
||||
$value = $ldapValue;
|
||||
}
|
||||
|
||||
$user->{$attr['user_method']}($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
src/Ldap/LdapUserProvider.php
Normal file
120
src/Ldap/LdapUserProvider.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Configuration\LdapConfiguration;
|
||||
use App\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
/**
|
||||
* Overwritten to be able to deactivate LDAP via config switch.
|
||||
*
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class LdapUserProvider implements UserProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $activated = false;
|
||||
/**
|
||||
* @var LdapManager
|
||||
*/
|
||||
protected $ldapManager;
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct(LdapManager $ldapManager, LdapConfiguration $config, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->ldapManager = $ldapManager;
|
||||
$this->logger = $logger;
|
||||
$this->activated = $config->isActivated();
|
||||
}
|
||||
|
||||
public function loadUserByUsername($username)
|
||||
{
|
||||
// this method is called at least for unknown user, no matter what supportsClass() returns,
|
||||
// so we have to check if LDAP is activated here as well
|
||||
if (!$this->activated) {
|
||||
$ex = new UsernameNotFoundException(sprintf('LDAP is deactivated, user "%s" not searched', $username));
|
||||
$ex->setUsername($username);
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$user = $this->ldapManager->findUserByUsername($username);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->logInfo('User {username} {result} on LDAP', [
|
||||
'action' => 'loadUserByUsername',
|
||||
'username' => $username,
|
||||
'result' => 'not found',
|
||||
]);
|
||||
$ex = new UsernameNotFoundException(sprintf('User "%s" not found', $username));
|
||||
$ex->setUsername($username);
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$this->logInfo('User {username} {result} on LDAP', [
|
||||
'action' => 'loadUserByUsername',
|
||||
'username' => $username,
|
||||
'result' => 'found',
|
||||
]);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function refreshUser(UserInterface $user)
|
||||
{
|
||||
if (!($user instanceof User) || !$this->supportsClass(get_class($user))) {
|
||||
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
|
||||
}
|
||||
|
||||
if (null === $user->getPreferenceValue('ldap.dn')) {
|
||||
throw new UnsupportedUserException(sprintf('Account "%s" is not a registered LDAP user.', $user->getUsername()));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->ldapManager->updateUser($user);
|
||||
} catch (LdapDriverException $ex) {
|
||||
throw new UnsupportedUserException(sprintf('Failed to refresh user "%s", probably DN is expired.', $user->getUsername()));
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function supportsClass($class)
|
||||
{
|
||||
if (!$this->activated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $class === User::class || $class === 'App\Entity\User';
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message into the logger if this exists.
|
||||
*/
|
||||
private function logInfo(string $message, array $context = []): void
|
||||
{
|
||||
if (!$this->logger) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info($message, $context);
|
||||
}
|
||||
}
|
||||
40
src/Ldap/SanitizingException.php
Normal file
40
src/Ldap/SanitizingException.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
/**
|
||||
* Inspired by https://github.com/Maks3w/FR3DLdapBundle @ MIT License
|
||||
*/
|
||||
class SanitizingException extends \Exception
|
||||
{
|
||||
protected $actualException;
|
||||
protected $secret;
|
||||
|
||||
public function __construct(\Exception $actualException, $secret)
|
||||
{
|
||||
parent::__construct(
|
||||
$this->stripSecret($actualException->getMessage(), $secret),
|
||||
$actualException->getCode()
|
||||
);
|
||||
|
||||
$this->actualException = $actualException;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
protected function stripSecret(string $message, string $secret)
|
||||
{
|
||||
return str_replace($secret, '****', $message);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->stripSecret($this->actualException->__toString(), $this->secret);
|
||||
}
|
||||
}
|
||||
29
src/Ldap/ZendLdap.php
Normal file
29
src/Ldap/ZendLdap.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Configuration\LdapConfiguration;
|
||||
use Zend\Ldap\Ldap;
|
||||
|
||||
/**
|
||||
* Overwritten to prevent errors in case:
|
||||
* LDAP is deactivated and LDAP extension is not loaded
|
||||
*/
|
||||
class ZendLdap extends Ldap
|
||||
{
|
||||
public function __construct(LdapConfiguration $config)
|
||||
{
|
||||
if (!$config->isActivated()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::__construct($config->getConnectionParameters());
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ use App\Repository\Query\InvoiceQuery;
|
||||
class InvoiceModel
|
||||
{
|
||||
/**
|
||||
* @var Customer
|
||||
* @var Customer|null
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
@@ -95,36 +95,26 @@ class InvoiceModel
|
||||
* @param Timesheet[] $entries
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setEntries(array $entries)
|
||||
public function setEntries(array $entries): InvoiceModel
|
||||
{
|
||||
$this->entries = $entries;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InvoiceTemplate
|
||||
*/
|
||||
public function getTemplate(): ?InvoiceTemplate
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InvoiceTemplate $template
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setTemplate(InvoiceTemplate $template)
|
||||
public function setTemplate(InvoiceTemplate $template): InvoiceModel
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer
|
||||
*/
|
||||
public function getCustomer()
|
||||
public function getCustomer(): ?Customer
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
@@ -133,16 +123,13 @@ class InvoiceModel
|
||||
* @param Customer $customer
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setCustomer($customer)
|
||||
public function setCustomer($customer): InvoiceModel
|
||||
{
|
||||
$this->customer = $customer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function getDueDate(): ?\DateTime
|
||||
{
|
||||
if (null === $this->getTemplate()) {
|
||||
@@ -160,11 +147,7 @@ class InvoiceModel
|
||||
return $this->invoiceDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param NumberGeneratorInterface $generator
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setNumberGenerator(NumberGeneratorInterface $generator)
|
||||
public function setNumberGenerator(NumberGeneratorInterface $generator): InvoiceModel
|
||||
{
|
||||
$this->generator = $generator;
|
||||
$this->generator->setModel($this);
|
||||
@@ -172,19 +155,12 @@ class InvoiceModel
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NumberGeneratorInterface
|
||||
*/
|
||||
public function getNumberGenerator(): ?NumberGeneratorInterface
|
||||
{
|
||||
return $this->generator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CalculatorInterface $calculator
|
||||
* @return InvoiceModel
|
||||
*/
|
||||
public function setCalculator(CalculatorInterface $calculator)
|
||||
public function setCalculator(CalculatorInterface $calculator): InvoiceModel
|
||||
{
|
||||
$this->calculator = $calculator;
|
||||
$this->calculator->setModel($this);
|
||||
@@ -192,9 +168,6 @@ class InvoiceModel
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CalculatorInterface
|
||||
*/
|
||||
public function getCalculator(): ?CalculatorInterface
|
||||
{
|
||||
return $this->calculator;
|
||||
|
||||
@@ -33,7 +33,7 @@ class TimesheetStatistic
|
||||
*/
|
||||
protected $amountTotal = 0;
|
||||
/**
|
||||
* @var \DateTime
|
||||
* @var \DateTime|null
|
||||
*/
|
||||
protected $firstEntry;
|
||||
/**
|
||||
@@ -41,10 +41,7 @@ class TimesheetStatistic
|
||||
*/
|
||||
protected $recordsTotal = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDurationThisMonth()
|
||||
public function getDurationThisMonth(): int
|
||||
{
|
||||
return $this->durationThisMonth;
|
||||
}
|
||||
@@ -57,10 +54,7 @@ class TimesheetStatistic
|
||||
$this->durationThisMonth = (int) $durationThisMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAmountTotal()
|
||||
public function getAmountTotal(): int
|
||||
{
|
||||
return $this->amountTotal;
|
||||
}
|
||||
@@ -73,10 +67,7 @@ class TimesheetStatistic
|
||||
$this->amountTotal = (int) $amountTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDurationTotal()
|
||||
public function getDurationTotal(): int
|
||||
{
|
||||
return $this->durationTotal;
|
||||
}
|
||||
@@ -89,10 +80,7 @@ class TimesheetStatistic
|
||||
$this->durationTotal = (int) $durationTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAmountThisMonth()
|
||||
public function getAmountThisMonth(): int
|
||||
{
|
||||
return $this->amountThisMonth;
|
||||
}
|
||||
@@ -105,10 +93,7 @@ class TimesheetStatistic
|
||||
$this->amountThisMonth = (int) $amountThisMonth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getFirstEntry()
|
||||
public function getFirstEntry(): ?\DateTime
|
||||
{
|
||||
return $this->firstEntry;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use App\Entity\Project;
|
||||
class ActivityQuery extends ProjectQuery
|
||||
{
|
||||
/**
|
||||
* @var Project|int
|
||||
* @var Project|int|null
|
||||
*/
|
||||
protected $project;
|
||||
/**
|
||||
@@ -37,11 +37,7 @@ class ActivityQuery extends ProjectQuery
|
||||
return $this->orderGlobalsFirst;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $orderGlobalsFirst
|
||||
* @return ActivityQuery
|
||||
*/
|
||||
public function setOrderGlobalsFirst(bool $orderGlobalsFirst)
|
||||
public function setOrderGlobalsFirst(bool $orderGlobalsFirst): ActivityQuery
|
||||
{
|
||||
$this->orderGlobalsFirst = $orderGlobalsFirst;
|
||||
|
||||
@@ -60,15 +56,15 @@ class ActivityQuery extends ProjectQuery
|
||||
* @param bool $globalsOnly
|
||||
* @return ActivityQuery
|
||||
*/
|
||||
public function setGlobalsOnly($globalsOnly)
|
||||
public function setGlobalsOnly($globalsOnly): ActivityQuery
|
||||
{
|
||||
$this->globalsOnly = $globalsOnly;
|
||||
$this->globalsOnly = (bool) $globalsOnly;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project|int
|
||||
* @return Project|int|null
|
||||
*/
|
||||
public function getProject()
|
||||
{
|
||||
@@ -76,10 +72,10 @@ class ActivityQuery extends ProjectQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project|int $project
|
||||
* @return $this
|
||||
* @param Project|int|null $project
|
||||
* @return ActivityQuery
|
||||
*/
|
||||
public function setProject($project = null)
|
||||
public function setProject($project = null): ActivityQuery
|
||||
{
|
||||
$this->project = $project;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class BaseQuery
|
||||
public const RESULT_TYPE_QUERYBUILDER = 'QueryBuilder';
|
||||
|
||||
/**
|
||||
* @var object
|
||||
* @var object|null
|
||||
*/
|
||||
protected $hiddenEntity;
|
||||
/**
|
||||
@@ -158,7 +158,7 @@ class BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @return object|null
|
||||
*/
|
||||
public function getHiddenEntity()
|
||||
{
|
||||
@@ -166,7 +166,7 @@ class BaseQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object|string $hiddenEntity
|
||||
* @param object|string|null $hiddenEntity
|
||||
* @return BaseQuery
|
||||
*/
|
||||
public function setHiddenEntity($hiddenEntity)
|
||||
|
||||
@@ -17,7 +17,7 @@ use App\Entity\Customer;
|
||||
class ProjectQuery extends VisibilityQuery
|
||||
{
|
||||
/**
|
||||
* @var Customer|int
|
||||
* @var Customer|int|null
|
||||
*/
|
||||
protected $customer;
|
||||
|
||||
@@ -46,7 +46,7 @@ class ProjectQuery extends VisibilityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Customer|int
|
||||
* @return Customer|int|null
|
||||
*/
|
||||
public function getCustomer()
|
||||
{
|
||||
@@ -54,7 +54,7 @@ class ProjectQuery extends VisibilityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Customer|int $customer
|
||||
* @param Customer|int|null $customer
|
||||
* @return $this
|
||||
*/
|
||||
public function setCustomer($customer = null)
|
||||
|
||||
@@ -36,11 +36,11 @@ class TimesheetQuery extends ActivityQuery
|
||||
*/
|
||||
protected $orderBy = 'begin';
|
||||
/**
|
||||
* @var User
|
||||
* @var User|null
|
||||
*/
|
||||
protected $user;
|
||||
/**
|
||||
* @var Activity
|
||||
* @var Activity|null
|
||||
*/
|
||||
protected $activity;
|
||||
/**
|
||||
@@ -66,7 +66,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
* @return User|null
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
@@ -74,7 +74,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|int $user
|
||||
* @param User|int|null $user
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setUser($user = null)
|
||||
@@ -87,7 +87,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
/**
|
||||
* Activity overwrites: setProject() and setCustomer()
|
||||
*
|
||||
* @return Activity
|
||||
* @return Activity|null
|
||||
*/
|
||||
public function getActivity()
|
||||
{
|
||||
@@ -95,7 +95,7 @@ class TimesheetQuery extends ActivityQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Activity|int $activity
|
||||
* @param Activity|int|null $activity
|
||||
* @return TimesheetQuery
|
||||
*/
|
||||
public function setActivity($activity = null)
|
||||
|
||||
@@ -15,12 +15,12 @@ namespace App\Repository\Query;
|
||||
class UserQuery extends VisibilityQuery
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
protected $role;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRole()
|
||||
{
|
||||
|
||||
@@ -274,6 +274,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
->join('t.activity', 'a')
|
||||
->join('t.project', 'p')
|
||||
->join('p.customer', 'c')
|
||||
->leftJoin('t.tags', 'tags')
|
||||
->where($qb->expr()->isNotNull('t.begin'))
|
||||
->andWhere($qb->expr()->isNull('t.end'))
|
||||
->orderBy('t.begin', 'DESC');
|
||||
@@ -438,11 +439,12 @@ class TimesheetRepository extends AbstractRepository
|
||||
$ids = array_column($results, 'maxid');
|
||||
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->select('t', 'a', 'p', 'c')
|
||||
$qb->select('t', 'a', 'p', 'c', 'tags')
|
||||
->from(Timesheet::class, 't')
|
||||
->join('t.activity', 'a')
|
||||
->join('t.project', 'p')
|
||||
->join('p.customer', 'c')
|
||||
->leftJoin('t.tags', 'tags')
|
||||
->andWhere($qb->expr()->in('t.id', $ids))
|
||||
->orderBy('t.end', 'DESC')
|
||||
;
|
||||
|
||||
@@ -20,7 +20,6 @@ class AclDecisionManager
|
||||
protected $decisionManager;
|
||||
|
||||
/**
|
||||
* AbstractVoter constructor.
|
||||
* @param AccessDecisionManagerInterface $decisionManager
|
||||
*/
|
||||
public function __construct(AccessDecisionManagerInterface $decisionManager)
|
||||
@@ -40,22 +39,4 @@ class AclDecisionManager
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TokenInterface $token
|
||||
* @param string|array $roles
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRole(TokenInterface $token, $roles)
|
||||
{
|
||||
if (!is_array($roles)) {
|
||||
$roles = [$roles];
|
||||
}
|
||||
|
||||
if ($this->decisionManager->decide($token, $roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +49,6 @@ class RolePermissionManager
|
||||
return array_keys($this->permissions);
|
||||
}
|
||||
|
||||
public function roleHasPermission(string $role): bool
|
||||
{
|
||||
return isset($this->permissions[$role]);
|
||||
}
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->knownPermissions;
|
||||
|
||||
38
src/Security/RoleService.php
Normal file
38
src/Security/RoleService.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Security;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $roles;
|
||||
|
||||
public function __construct(array $roles)
|
||||
{
|
||||
$this->roles = $roles;
|
||||
}
|
||||
|
||||
public function getAvailableNames(): array
|
||||
{
|
||||
$roles = [];
|
||||
foreach ($this->roles as $key => $value) {
|
||||
$roles[] = $key;
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $name) {
|
||||
$roles[] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($roles));
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace App\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Exception\AccountStatusException;
|
||||
use Symfony\Component\Security\Core\Exception\LockedException;
|
||||
use Symfony\Component\Security\Core\Exception\DisabledException;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
@@ -26,6 +26,15 @@ class UserChecker implements UserCheckerInterface
|
||||
*/
|
||||
public function checkPreAuth(UserInterface $user)
|
||||
{
|
||||
if (!($user instanceof User)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user->isEnabled()) {
|
||||
$ex = new DisabledException('User account is disabled.');
|
||||
$ex->setUser($user);
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,13 +43,14 @@ class UserChecker implements UserCheckerInterface
|
||||
*/
|
||||
public function checkPostAuth(UserInterface $user)
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
if (!($user instanceof User)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// user account is not enabled, the user may be notified
|
||||
if (!$user->isEnabled()) {
|
||||
throw new LockedException();
|
||||
$ex = new DisabledException('User account is disabled.');
|
||||
$ex->setUser($user);
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,16 +48,6 @@ abstract class AbstractVoter extends Voter
|
||||
return $this->decisionManager->isFullyAuthenticated($token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @param TokenInterface $token
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasRole($role, TokenInterface $token)
|
||||
{
|
||||
return $this->decisionManager->hasRole($token, [$role]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @param string $permission
|
||||
|
||||
@@ -83,7 +83,6 @@ class UserVoter extends AbstractVoter
|
||||
// used in templates and ProfileController
|
||||
case self::VIEW:
|
||||
case self::EDIT:
|
||||
case self::PASSWORD:
|
||||
case self::PREFERENCES:
|
||||
// always allow the user to edit these own settings
|
||||
if ($subject->getId() === $user->getId()) {
|
||||
@@ -91,6 +90,7 @@ class UserVoter extends AbstractVoter
|
||||
}
|
||||
// no break on purpose
|
||||
|
||||
case self::PASSWORD:
|
||||
case self::API_TOKEN:
|
||||
case self::ROLES:
|
||||
case self::HOURLY_RATE:
|
||||
|
||||
Reference in New Issue
Block a user