Failed Conditions
Push — JWKSet ( 3580c0...03e3f0 )
by Florent
02:43
created

BaseJWKSet::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose\Object;
13
14
use Assert\Assertion;
15
16
/**
17
 * Class BaseJWKSet
18
 */
19
trait BaseJWKSet
20
{
21
    /**
22
     * @var int
23
     */
24
    private $position = 0;
25
26
    /**
27
     * @param int $index
28
     *
29
     * @return bool
30
     */
31
    public function hasKey($index)
32
    {
33
        return array_key_exists($index, $this->getKeys());
34
    }
35
36
    /**
37
     * @param int $index
38
     *
39
     * @return \Jose\Object\JWKInterface
40
     */
41
    public function getKey($index)
42
    {
43
        Assertion::integer($index, 'The index must be a positive integer.');
44
        Assertion::greaterOrEqualThan($index, 0, 'The index must be a positive integer.');
45
        Assertion::true($this->hasKey($index), 'Undefined index.');
46
47
        return $this->getKeys()[$index];
48
    }
49
50
    /**
51
     * @return \Jose\Object\JWKInterface[]
52
     */
53
    abstract public function getKeys();
54
55
    /**
56
     * @param \Jose\Object\JWKInterface $key
57
     */
58
    abstract public function addKey(JWKInterface $key);
59
60
    /**
61
     * @param int $index
62
     */
63
    abstract public function removeKey($index);
64
65
    /**
66
     * @return array
67
     */
68
    public function jsonSerialize()
69
    {
70
        return ['keys' => array_values($this->getKeys())];
71
    }
72
73
    /**
74
     * @param int $mode
75
     *
76
     * @return int
77
     */
78
    public function count($mode = COUNT_NORMAL)
79
    {
80
        return count($this->getKeys(), $mode);
81
    }
82
83
    /**
84
     * @return \Jose\Object\JWKInterface|null
85
     */
86
    public function current()
87
    {
88
        return $this->hasKey($this->position) ? $this->getKey($this->position) : null;
89
    }
90
91
    /**
92
     * @return int
93
     */
94
    public function key()
95
    {
96
        return $this->position;
97
    }
98
99
    public function next()
100
    {
101
        $this->position++;
102
    }
103
104
    public function rewind()
105
    {
106
        $this->position = 0;
107
    }
108
109
    /**
110
     * @return bool
111
     */
112
    public function valid()
113
    {
114
        return $this->current() instanceof JWKInterface;
115
    }
116
117
    /**
118
     * @return int
119
     */
120
    public function countKeys()
121
    {
122
        return count($this->getKeys());
123
    }
124
125
    /**
126
     * @param mixed $offset
127
     *
128
     * @return bool
129
     */
130
    public function offsetExists($offset)
131
    {
132
        return $this->hasKey($offset);
133
    }
134
135
    /**
136
     * @param mixed $offset
137
     *
138
     * @return \Jose\Object\JWKInterface
139
     */
140
    public function offsetGet($offset)
141
    {
142
        return $this->getKey($offset);
143
    }
144
145
    /**
146
     * @param mixed $offset
147
     * @param mixed $value
148
     */
149
    public function offsetSet($offset, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $offset is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
150
    {
151
        $this->addKey($value);
152
    }
153
154
    /**
155
     * @param int $offset
156
     */
157
    public function offsetUnset($offset)
158
    {
159
        $this->removeKey($offset);
160
    }
161
162
    /**
163
     * @param string      $type
164
     * @param null|string $algorithm
165
     * @param array $restrictions
166
     *
167
     * @return null|\Jose\Object\JWKInterface
168
     */
169
    public function selectKey($type, $algorithm = null, array $restrictions = [])
170
    {
171
        Assertion::inArray($type, ['enc', 'sig']);
172
        Assertion::nullOrString($algorithm);
173
174
        $result = [];
175
        foreach ($this->getKeys() as $key) {
176
            $ind = 0;
177
178
            // Check usage
179
            $can_use = $this->canKeyBeUsedFor($type, $key);
180
            if (false === $can_use) {
181
                continue;
182
            }
183
            $ind += $can_use;
184
185
            // Check algorithm
186
            $alg = $this->canKeyBeUsedWithAlgorithm($algorithm, $key);
187
            if (false === $alg) {
188
                continue;
189
            }
190
            $ind += $alg;
191
192
            // Validate restrictions
193
            if (false === $this->doesKeySatisfyRestrictions($restrictions, $key)) {
194
                continue;
195
            }
196
197
            // Add to the list with trust indicator
198
            $result[] = ['key' => $key, 'ind' => $ind];
199
        }
200
201
        //Return null if no key
202
        if (empty($result)) {
203
            return;
204
        }
205
206
        //Sort by trust indicator
207
        usort($result, [$this, 'sortKeys']);
208
        //Return the highest trust indicator (first key)
209
        return $result[0]['key'];
210
    }
211
212
    /**
213
     * @param string                    $type
214
     * @param \Jose\Object\JWKInterface $key
215
     *
216
     * @return bool|int
217
     */
218
    private function canKeyBeUsedFor($type, JWKInterface $key)
219
    {
220
        if ($key->has('use')) {
221
            return $type === $key->get('use') ? 1 : false;
222
        }
223
        if ($key->has('key_ops')) {
224
            return $type === self::convertKeyOpsToKeyUse($key->get('use')) ? 1 : false;
225
        }
226
227
        return 0;
228
    }
229
230
    /**
231
     * @param null|string               $algorithm
232
     * @param \Jose\Object\JWKInterface $key
233
     *
234
     * @return bool|int
235
     */
236
    private function canKeyBeUsedWithAlgorithm($algorithm, JWKInterface $key)
237
    {
238
        if (null === $algorithm) {
239
            return 0;
240
        }
241
        if ($key->has('alg')) {
242
            return $algorithm === $key->get('alg') ? 1 : false;
243
        }
244
245
        return 0;
246
    }
247
248
    /**
249
     * @param array                     $restrictions
250
     * @param \Jose\Object\JWKInterface $key
251
     *
252
     * @return bool
253
     */
254
    private function doesKeySatisfyRestrictions(array $restrictions, JWKInterface $key)
255
    {
256
        foreach ($restrictions as $k => $v) {
257
            if (!$key->has($k) || $v !== $key->get($k)) {
258
                return false;
259
            }
260
        }
261
262
        return true;
263
    }
264
265
    /**
266
     * @param string $key_ops
267
     *
268
     * @return string
269
     */
270
    private static function convertKeyOpsToKeyUse($key_ops)
271
    {
272
        switch ($key_ops) {
273
            case 'verify':
274
            case 'sign':
275
                return 'sig';
276
            case 'encrypt':
277
            case 'decrypt':
278
            case 'wrapKey':
279
            case 'unwrapKey':
280
                return 'enc';
281
            default:
282
                throw new \InvalidArgumentException(sprintf('Unsupported key operation value "%s"', $key_ops));
283
        }
284
    }
285
286
    /**
287
     * @param array $a
288
     * @param array $b
289
     *
290
     * @return int
291
     */
292
    public function sortKeys($a, $b)
293
    {
294
        if ($a['ind'] === $b['ind']) {
295
            return 0;
296
        }
297
298
        return ($a['ind'] > $b['ind']) ? -1 : 1;
299
    }
300
}
301