Passed
Push — master ( 807996...333b05 )
by Tim
02:08
created

Cookie::verify()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 16
rs 9.9332
1
<?php
2
3
namespace SimpleSAML\Module\consent\Consent\Store;
4
5
use Webmozart\Assert\Assert;
6
7
/**
8
 * Cookie storage for consent
9
 *
10
 * This class implements a consent store which stores the consent information in cookies on the users computer.
11
 *
12
 * Example - Consent module with cookie store:
13
 *
14
 * <code>
15
 * 'authproc' => array(
16
 *   array(
17
 *     'consent:Consent',
18
 *     'store' => 'consent:Cookie',
19
 *     ),
20
 *   ),
21
 * </code>
22
 *
23
 * @author  Olav Morken <[email protected]>
24
 * @package SimpleSAMLphp
25
 */
26
27
class Cookie extends \SimpleSAML\Module\consent\Store
28
{
29
    /**
30
     * Check for consent.
31
     *
32
     * This function checks whether a given user has authorized the release of the attributes identified by
33
     * $attributeSet from $source to $destination.
34
     *
35
     * @param string $userId        The hash identifying the user at an IdP.
36
     * @param string $destinationId A string which identifies the destination.
37
     * @param string $attributeSet  A hash which identifies the attributes.
38
     *
39
     * @return bool True if the user has given consent earlier, false if not (or on error).
40
     */
41
    public function hasConsent(string $userId, string $destinationId, string $attributeSet)
42
    {
43
        $cookieName = self::getCookieName($userId, $destinationId);
44
45
        $data = $userId.':'.$attributeSet.':'.$destinationId;
46
47
        \SimpleSAML\Logger::debug('Consent cookie - Get ['.$data.']');
48
49
        if (!array_key_exists($cookieName, $_COOKIE)) {
50
            \SimpleSAML\Logger::debug(
51
                'Consent cookie - no cookie with name \''.$cookieName.'\'.'
52
            );
53
            return false;
54
        }
55
        if (!is_string($_COOKIE[$cookieName])) {
56
            \SimpleSAML\Logger::warning(
57
                'Value of consent cookie wasn\'t a string. Was: '.
58
                var_export($_COOKIE[$cookieName], true)
59
            );
60
            return false;
61
        }
62
63
        $data = self::sign($data);
64
65
        if ($_COOKIE[$cookieName] !== $data) {
66
            \SimpleSAML\Logger::info(
67
                'Attribute set changed from the last time consent was given.'
68
            );
69
            return false;
70
        }
71
72
        \SimpleSAML\Logger::debug(
73
            'Consent cookie - found cookie with correct name and value.'
74
        );
75
76
        return true;
77
    }
78
79
80
    /**
81
     * Save consent.
82
     *
83
     * Called when the user asks for the consent to be saved. If consent information for the given user and destination
84
     * already exists, it should be overwritten.
85
     *
86
     * @param string $userId        The hash identifying the user at an IdP.
87
     * @param string $destinationId A string which identifies the destination.
88
     * @param string $attributeSet  A hash which identifies the attributes.
89
     *
90
     * @return bool
91
     */
92
    public function saveConsent(string $userId, string $destinationId, string $attributeSet)
93
    {
94
        $name = self::getCookieName($userId, $destinationId);
95
        $value = $userId.':'.$attributeSet.':'.$destinationId;
96
97
        \SimpleSAML\Logger::debug('Consent cookie - Set ['.$value.']');
98
99
        $value = self::sign($value);
100
        return $this->setConsentCookie($name, $value);
101
    }
102
103
104
    /**
105
     * Delete consent.
106
     *
107
     * Called when a user revokes consent for a given destination.
108
     *
109
     * @param string $userId        The hash identifying the user at an IdP.
110
     * @param string $destinationId A string which identifies the destination.
111
     *
112
     * @return void
113
     */
114
    public function deleteConsent(string $userId, string $destinationId)
115
    {
116
        $name = self::getCookieName($userId, $destinationId);
117
        $this->setConsentCookie($name, null);
118
    }
119
120
121
    /**
122
     * Delete consent.
123
     *
124
     * @param string $userId The hash identifying the user at an IdP.
125
     *
126
     * @return void This method does not return.
127
     *
128
     * @throws \Exception This method always throws an exception indicating that it is not possible to delete all given
129
     * consents with this handler.
130
     */
131
    public function deleteAllConsents(string $userId)
132
    {
133
        throw new \Exception(
134
            'The cookie consent handler does not support delete of all consents...'
135
        );
136
    }
137
138
139
    /**
140
     * Retrieve consents.
141
     *
142
     * This function should return a list of consents the user has saved.
143
     *
144
     * @param string $userId The hash identifying the user at an IdP.
145
     *
146
     * @return array Array of all destination ids the user has given consent for.
147
     */
148
    public function getConsents(string $userId)
149
    {
150
        $ret = [];
151
152
        $cookieNameStart = '\SimpleSAML\Module\consent:';
153
        $cookieNameStartLen = strlen($cookieNameStart);
154
        foreach ($_COOKIE as $name => $value) {
155
            if (substr($name, 0, $cookieNameStartLen) !== $cookieNameStart) {
156
                continue;
157
            }
158
159
            $value = self::verify($value);
160
            if ($value === false) {
161
                continue;
162
            }
163
164
            $tmp = explode(':', $value, 3);
165
            if (count($tmp) !== 3) {
166
                \SimpleSAML\Logger::warning(
167
                    'Consent cookie with invalid value: '.$value
168
                );
169
                continue;
170
            }
171
172
            if ($userId !== $tmp[0]) {
173
                // Wrong user
174
                continue;
175
            }
176
177
            $destination = $tmp[2];
178
            $ret[] = $destination;
179
        }
180
181
        return $ret;
182
    }
183
184
185
    /**
186
     * Calculate a signature of some data.
187
     *
188
     * This function calculates a signature of the data.
189
     *
190
     * @param string $data The data which should be signed.
191
     *
192
     * @return string The signed data.
193
     */
194
    private static function sign(string $data)
195
    {
196
        $secretSalt = \SimpleSAML\Utils\Config::getSecretSalt();
197
198
        return sha1($secretSalt.$data.$secretSalt).':'.$data;
199
    }
200
201
202
    /**
203
     * Verify signed data.
204
     *
205
     * This function verifies signed data.
206
     *
207
     * @param string $signedData The data which is signed.
208
     *
209
     * @return string|false The data, or false if the signature is invalid.
210
     */
211
    private static function verify(string $signedData)
212
    {
213
        $data = explode(':', $signedData, 2);
214
        if (count($data) !== 2) {
215
            \SimpleSAML\Logger::warning('Consent cookie: Missing signature.');
216
            return false;
217
        }
218
        $data = $data[1];
219
220
        $newSignedData = self::sign($data);
221
        if ($newSignedData !== $signedData) {
222
            \SimpleSAML\Logger::warning('Consent cookie: Invalid signature.');
223
            return false;
224
        }
225
226
        return $data;
227
    }
228
229
230
    /**
231
     * Get cookie name.
232
     *
233
     * This function gets the cookie name for the given user & destination.
234
     *
235
     * @param string $userId        The hash identifying the user at an IdP.
236
     * @param string $destinationId A string which identifies the destination.
237
     *
238
     * @return string The cookie name
239
     */
240
    private static function getCookieName(string $userId, string $destinationId)
241
    {
242
        return '\SimpleSAML\Module\consent:'.sha1($userId.':'.$destinationId);
243
    }
244
245
246
    /**
247
     * Helper function for setting a cookie.
248
     *
249
     * @param string      $name  Name of the cookie.
250
     * @param string|null $value Value of the cookie. Set this to null to delete the cookie.
251
     *
252
     * @return bool
253
     */
254
    private function setConsentCookie(string $name, $value)
255
    {
256
        $globalConfig = \SimpleSAML\Configuration::getInstance();
257
        $params = [
258
            'lifetime' => 7776000, // (90*24*60*60)
259
            'path' => ($globalConfig->getBasePath()),
260
            'httponly' => true,
261
            'secure' => \SimpleSAML\Utils\HTTP::isHTTPS(),
262
        ];
263
264
        try {
265
            \SimpleSAML\Utils\HTTP::setCookie($name, $value, $params, false);
266
            return true;
267
        } catch (\SimpleSAML\Error\CannotSetCookie $e) {
268
            return false;
269
        }
270
    }
271
}
272