Passed
Pull Request — master (#102)
by Guy
02:13
created

SessionStore::load()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php declare(strict_types=1);
2
3
namespace SilverStripe\MFA\Store;
4
5
use Serializable;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\MFA\Exception\InvalidMethodException;
8
use SilverStripe\MFA\Extension\MemberExtension;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\Security\Member;
11
12
/**
13
 * This class provides an interface to store data in session during an MFA process. This is implemented as a measure to
14
 * prevent bleeding state between individual MFA auth types
15
 *
16
 * @package SilverStripe\MFA
17
 */
18
class SessionStore implements StoreInterface, Serializable
19
{
20
    const SESSION_KEY = 'MFASessionStore';
21
22
    /**
23
     * The member that is currently going through the MFA process
24
     *
25
     * @var Member
26
     */
27
    protected $member;
28
29
    /**
30
     * A string representing the current authentication method that is underway
31
     *
32
     * @var string
33
     */
34
    protected $method;
35
36
    /**
37
     * Any state that the current authentication method needs to retain while it is underway
38
     *
39
     * @var array
40
     */
41
    protected $state = [];
42
43
    /**
44
     * The URL segment identifiers of methods that have been verified in this session
45
     *
46
     * @var string[]
47
     */
48
    protected $verifiedMethods = [];
49
50
    /**
51
     * Attempt to create a store from the given request getting any existing state from the session of the request
52
     *
53
     * {@inheritdoc}
54
     */
55
    public function __construct(Member $member)
56
    {
57
        $this->setMember($member);
58
    }
59
60
    /**
61
     * @return Member&MemberExtension|null
62
     */
63
    public function getMember(): ?Member
64
    {
65
        return $this->member;
66
    }
67
68
    /**
69
     * @param Member $member
70
     * @return $this
71
     */
72
    public function setMember(Member $member): StoreInterface
73
    {
74
        // Early return if there's no change
75
        if ($this->member && $this->member->ID === $member->ID) {
76
            return $this;
77
        }
78
79
        // If the member has changed we should null out the method that's underway and the state of it
80
        $this->resetMethod();
81
82
        $this->member = $member;
83
84
        // When the member changes the list of verified methods should reset
85
        $this->verifiedMethods = [];
86
87
        return $this;
88
    }
89
90
    /**
91
     * @return string|null
92
     */
93
    public function getMethod(): ?string
94
    {
95
        return $this->method;
96
    }
97
98
    /**
99
     * @param string $method
100
     * @return $this
101
     */
102
    public function setMethod($method): StoreInterface
103
    {
104
        if (in_array($method, $this->verifiedMethods)) {
105
            throw new InvalidMethodException('You cannot verify with a method you have already verified');
106
        }
107
108
        $this->method = $method;
109
110
        return $this;
111
    }
112
113
    public function getState(): array
114
    {
115
        return $this->state;
116
    }
117
118
    public function setState(array $state): StoreInterface
119
    {
120
        $this->state = $state;
121
122
        return $this;
123
    }
124
125
    public function addVerifiedMethod(string $method): StoreInterface
126
    {
127
        if (!in_array($method, $this->verifiedMethods)) {
128
            $this->verifiedMethods[] = $method;
129
        }
130
131
        return $this;
132
    }
133
134
    public function getVerifiedMethods(): array
135
    {
136
        return $this->verifiedMethods;
137
    }
138
139
    /**
140
     * Save this store into the session of the given request
141
     *
142
     * {@inheritdoc}
143
     */
144
    public function save(HTTPRequest $request): StoreInterface
145
    {
146
        $request->getSession()->set(static::SESSION_KEY, $this);
147
148
        return $this;
149
    }
150
151
    /**
152
     * Load a StoreInterface from the given request and return it if it exists
153
     *
154
     * @param HTTPRequest $request
155
     * @return StoreInterface|null
156
     */
157
    public static function load(HTTPRequest $request): ?StoreInterface
158
    {
159
        $store = $request->getSession()->get(static::SESSION_KEY);
160
        return $store instanceof self ? $store : null;
161
    }
162
163
    /**
164
     * Clear any stored values for the given request
165
     *
166
     * {@inheritdoc}
167
     */
168
    public static function clear(HTTPRequest $request): void
169
    {
170
        $request->getSession()->clear(static::SESSION_KEY);
171
    }
172
173
    /**
174
     * "Reset" the method currently in progress by clearing the identifier and state
175
     *
176
     * @return StoreInterface
177
     */
178
    protected function resetMethod(): StoreInterface
179
    {
180
        $this->setMethod(null)->setState([]);
181
182
        return $this;
183
    }
184
185
    public function serialize(): string
186
    {
187
        $stuff = json_encode([
188
            'member' => $this->getMember() ? $this->getMember()->ID : null,
189
            'method' => $this->getMethod(),
190
            'state' => $this->getState(),
191
            'verifiedMethods' => $this->getVerifiedMethods(),
192
        ]);
193
194
        return $stuff;
195
    }
196
197
    public function unserialize($serialized): void
198
    {
199
        $state = json_decode($serialized, true);
200
201
        if (is_array($state) && $state['member']) {
202
            /** @var Member $member */
203
            $member = DataObject::get_by_id(Member::class, $state['member']);
204
205
            $this->setMember($member);
206
            $this->setMethod($state['method']);
207
            $this->setState($state['state']);
208
209
            foreach ($state['verifiedMethods'] as $method) {
210
                $this->addVerifiedMethod($method);
211
            }
212
        }
213
    }
214
}
215