1<?php
2
3/**
4 * This file is part of the FreeDSx SASL package.
5 *
6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace FreeDSx\Sasl;
13
14use FreeDSx\Sasl\Exception\SaslException;
15use FreeDSx\Sasl\Mechanism\AnonymousMechanism;
16use FreeDSx\Sasl\Mechanism\CramMD5Mechanism;
17use FreeDSx\Sasl\Mechanism\DigestMD5Mechanism;
18use FreeDSx\Sasl\Mechanism\MechanismInterface;
19use FreeDSx\Sasl\Mechanism\PlainMechanism;
20
21/**
22 * The main SASL class.
23 *
24 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
25 */
26class Sasl
27{
28    /**
29     * @var MechanismInterface[]
30     */
31    protected $mechanisms = [];
32
33    protected $options = [
34        'supported' => []
35    ];
36
37    public function __construct(array $options = [])
38    {
39        $this->options = $options + $this->options;
40        $this->initMechs();
41    }
42
43    /**
44     * Get a mechanism object by its name.
45     */
46    public function get(string $mechanism): MechanismInterface
47    {
48        $mech = $this->mechanisms[$mechanism] ?? null;
49
50        if ($mech === null) {
51            throw new SaslException('The mechanism "%s" is not supported.');
52        }
53
54        return $mech;
55    }
56
57    /**
58     * Whether or not the mechanism is supported.
59     */
60    public function supports(string $mechanism): bool
61    {
62        return isset($this->mechanisms()[$mechanism]);
63    }
64
65    /**
66     * Add a mechanism object.
67     *
68     * @return Sasl
69     */
70    public function add(MechanismInterface $mechanism): self
71    {
72        $this->mechanisms[$mechanism->getName()] = $mechanism;
73
74        return $this;
75    }
76
77    /**
78     * Remove a mechanism by its name.
79     *
80     * @return Sasl
81     */
82    public function remove(string $mechanism): self
83    {
84        if (isset($this->mechanisms[$mechanism])) {
85            unset($this->mechanisms[$mechanism]);
86        }
87
88        return $this;
89    }
90
91    /**
92     * Given an array of mechanism names, and optional options, select the best supported mechanism available.
93     *
94     * @param string[] $choices array of mechanisms by their name
95     * @param array $options array of options (ie. ['use_integrity' => true])
96     * @return MechanismInterface the mechanism selected.
97     * @throws SaslException if no supported mechanism could be found.
98     */
99    public function select(array $choices = [], array $options = []): MechanismInterface
100    {
101        $selector = new MechanismSelector($this->mechanisms());
102
103        return $selector->select($choices, $options);
104    }
105
106    /**
107     * @return MechanismInterface[]
108     */
109    public function mechanisms(): array
110    {
111        return $this->mechanisms;
112    }
113
114    protected function initMechs(): void
115    {
116        $this->mechanisms = [
117            DigestMD5Mechanism::NAME => new DigestMD5Mechanism(),
118            CramMD5Mechanism::NAME => new CramMD5Mechanism(),
119            PlainMechanism::NAME => new PlainMechanism(),
120            AnonymousMechanism::NAME => new AnonymousMechanism(),
121        ];
122
123        if (is_array($this->options['supported']) && !empty($this->options['supported'])) {
124            foreach (array_keys($this->mechanisms) as $mechName) {
125                if (!in_array($mechName, $this->options['supported'], true)) {
126                    unset($this->mechanisms[$mechName]);
127                }
128            }
129        }
130    }
131}
132