MemberKeyProvider::getActiveTenant()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 13
rs 10
cc 4
nc 4
nop 0
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
    /**
25
     * @var int
26
     */
27
    protected $forcedTenant;
28
29
    /**
30
     * MultiTenantProvider constructor.
31
     *
32
     * @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...
33
     * @param array-key|null $active
34
     */
35
    public function __construct(array $keyProviders, string|int|null $active = null)
36
    {
37
        if ($active === null && Security::getCurrentUser()) {
38
            $active = Security::getCurrentUser()->ID;
39
        }
40
        parent::__construct($keyProviders, $active);
41
    }
42
43
    /**
44
     * @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...
45
     * @return KeyProviderInterface
46
     * @throws CipherSweetException
47
     * @psalm-suppress PossiblyNullArrayOffset
48
     */
49
    public function getTenant(string|int $name): KeyProviderInterface
50
    {
51
        if (!array_key_exists($name, $this->tenants)) {
52
            throw new CipherSweetException("Tenant '{$name}' does not exist");
53
        }
54
        return $this->tenants[$this->active];
55
    }
56
57
    /**
58
     * @return KeyProviderInterface
59
     * @throws CipherSweetException
60
     */
61
    public function getActiveTenant(): KeyProviderInterface
62
    {
63
        if ($this->forcedTenant) {
64
            return $this->tenants[$this->forcedTenant];
65
        }
66
        $this->active = Security::getCurrentUser()->ID ?? $this->active;
67
        if (is_null($this->active)) {
68
            throw new CipherSweetException('Active tenant not set');
69
        }
70
        if (!array_key_exists($this->active, $this->tenants)) {
71
            throw new CipherSweetException("Tenant '{$this->active}' does not exist");
72
        }
73
        return $this->tenants[$this->active];
74
    }
75
76
    /**
77
     * @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...
78
     * @return static
79
     */
80
    public function setActiveTenant(string|int $index): static
81
    {
82
        if (!$index && Security::getCurrentUser()) {
83
            $index = Security::getCurrentUser();
84
        }
85
        $this->active = $index;
86
        return $this;
87
    }
88
89
    /**
90
     * @return int
91
     */
92
    public function getForcedTenant()
93
    {
94
        return $this->forcedTenant;
95
    }
96
97
    /**
98
     * @param int $index
99
     * @return self
100
     */
101
    public function setForcedTenant($index)
102
    {
103
        $this->forcedTenant = $index;
104
        return $this;
105
    }
106
107
    /**
108
     * Given a row of data, determine which tenant should be selected.
109
     *
110
     * This is not super useful since we mostly go through the ORM
111
     *
112
     * @param array<string,mixed> $row
113
     * @param string $tableName
114
     * @return string|int
115
     *
116
     * @throws CipherSweetException
117
     */
118
    public function getTenantFromRow(array $row, string $tableName): string|int
119
    {
120
        // Expect member bound encryption to have a Member relation
121
        if (isset($row['MemberID'])) {
122
            return $row['MemberID'];
123
        }
124
        return $this->active ?? 0;
125
    }
126
127
    /**
128
     * This is called when you encrypt a row, extra fields can be added
129
     * It's not really used in our case because we encrypt each fields
130
     * one by one anyway
131
     *
132
     * @param array<string,mixed> $row
133
     * @param string $tableName
134
     * @return array<string,mixed>
135
     */
136
    public function injectTenantMetadata(array $row, string $tableName): array
137
    {
138
        // If our class uses encryption per user, inject member id
139
        $row['MemberID'] = $this->active;
140
        return $row;
141
    }
142
}
143