Issues (84)

src/MemberKeyProvider.php (3 issues)

1
<?php
2
3
namespace LeKoala\Encrypt;
4
5
use SilverStripe\Security\Security;
6
use ParagonIE\CipherSweet\Contract\KeyProviderInterface;
7
use ParagonIE\CipherSweet\Exception\CipherSweetException;
8
use ParagonIE\CipherSweet\KeyProvider\MultiTenantProvider;
9
10
/**
11
 * This class provides a multi tenant key provider
12
 * Each user gets its own key to encrypt its data
13
 *
14
 * - getTenant() selects a KeyProvider based on a given tenant.
15
 * - getTenantFromRow() gets the tenant ID (array key) based on the data stored in an encrypted row.
16
 * - injectTenantMetadata() injects some breadcrumb for getTenantFromRow() to use to select the appropriate key.
17
 *
18
 * These methods were designed to be generalizable:
19
 * If you implement AWS KMS support, for example, you'd probably store an encrypted data key with injectTenantMetadata()
20
 * and then ask KMS to decrypt it in getTenantFromRow() (unless it's cached).
21
 */
22
class MemberKeyProvider extends MultiTenantProvider
23
{
24
    protected ?int $forcedTenant = null;
25
26
    /**
27
     * MultiTenantProvider constructor.
28
     *
29
     * @param array<array-key,KeyProviderInterface> $keyProviders
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key,KeyProviderInterface> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key,KeyProviderInterface>.
Loading history...
30
     * @param array-key|null $active
31
     */
32
    public function __construct(array $keyProviders, string|int|null $active = null)
33
    {
34
        if ($active === null && Security::getCurrentUser()) {
35
            $active = Security::getCurrentUser()->ID;
36
        }
37
        parent::__construct($keyProviders, $active);
38
    }
39
40
    /**
41
     * @param array-key $name
0 ignored issues
show
Documentation Bug introduced by
The doc comment array-key at position 0 could not be parsed: Unknown type name 'array-key' at position 0 in array-key.
Loading history...
42
     * @return KeyProviderInterface
43
     * @throws CipherSweetException
44
     * @psalm-suppress PossiblyNullArrayOffset
45
     */
46
    public function getTenant(string|int $name): KeyProviderInterface
47
    {
48
        if (!array_key_exists($name, $this->tenants)) {
49
            throw new CipherSweetException("Tenant '{$name}' does not exist");
50
        }
51
        return $this->tenants[$this->active];
52
    }
53
54
    /**
55
     * @return KeyProviderInterface
56
     * @throws CipherSweetException
57
     */
58
    public function getActiveTenant(): KeyProviderInterface
59
    {
60
        if ($this->forcedTenant) {
61
            return $this->tenants[$this->forcedTenant];
62
        }
63
        $this->active = Security::getCurrentUser()->ID ?? $this->active;
64
        if (is_null($this->active)) {
65
            throw new CipherSweetException('Active tenant not set');
66
        }
67
        if (!array_key_exists($this->active, $this->tenants)) {
68
            throw new CipherSweetException("Tenant '{$this->active}' does not exist");
69
        }
70
        return $this->tenants[$this->active];
71
    }
72
73
    /**
74
     * @param array-key $index
0 ignored issues
show
Documentation Bug introduced by
The doc comment array-key at position 0 could not be parsed: Unknown type name 'array-key' at position 0 in array-key.
Loading history...
75
     * @return static
76
     */
77
    public function setActiveTenant(string|int $index): static
78
    {
79
        if (!$index && Security::getCurrentUser()) {
80
            $index = Security::getCurrentUser();
81
        }
82
        $this->active = $index;
83
        return $this;
84
    }
85
86
    public function getForcedTenant(): ?int
87
    {
88
        return $this->forcedTenant;
89
    }
90
91
    public function setForcedTenant(?int $index): self
92
    {
93
        $this->forcedTenant = $index;
94
        return $this;
95
    }
96
97
    /**
98
     * Given a row of data, determine which tenant should be selected.
99
     *
100
     * This is not super useful since we mostly go through the ORM
101
     *
102
     * @param array<string,mixed> $row
103
     * @param string $tableName
104
     * @return string|int
105
     *
106
     * @throws CipherSweetException
107
     */
108
    public function getTenantFromRow(array $row, string $tableName): string|int
109
    {
110
        // Expect member bound encryption to have a Member relation
111
        if (isset($row['MemberID'])) {
112
            return $row['MemberID'];
113
        }
114
        return $this->active ?? 0;
115
    }
116
117
    /**
118
     * This is called when you encrypt a row, extra fields can be added
119
     * It's not really used in our case because we encrypt each fields
120
     * one by one anyway
121
     *
122
     * @param array<string,mixed> $row
123
     * @param string $tableName
124
     * @return array<string,mixed>
125
     */
126
    public function injectTenantMetadata(array $row, string $tableName): array
127
    {
128
        // If our class uses encryption per user, inject member id
129
        $row['MemberID'] = $this->active;
130
        return $row;
131
    }
132
}
133