Passed
Pull Request — master (#105)
by Robbie
01:55
created

SessionStore::getMethod()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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