1<?php
2
3/**
4 * Class for representing a SAML 2 error.
5 *
6 * @package SimpleSAMLphp
7 */
8class sspmod_saml_Error extends SimpleSAML_Error_Exception
9{
10    /**
11     * The top-level status code.
12     *
13     * @var string
14     */
15    private $status;
16
17    /**
18     * The second-level status code, or NULL if no second-level status code is defined.
19     *
20     * @var string|null
21     */
22    private $subStatus;
23
24    /**
25     * The status message, or NULL if no status message is defined.
26     *
27     * @var string|null
28     */
29    private $statusMessage;
30
31
32    /**
33     * Create a SAML 2 error.
34     *
35     * @param string $status  The top-level status code.
36     * @param string|null $subStatus  The second-level status code. Can be NULL, in which case there is no second-level status code.
37     * @param string|null $statusMessage  The status message. Can be NULL, in which case there is no status message.
38     * @param Exception|null $cause  The cause of this exception. Can be NULL.
39     */
40    public function __construct($status, $subStatus = null, $statusMessage = null, Exception $cause = null)
41    {
42        assert(is_string($status));
43        assert($subStatus === null || is_string($subStatus));
44        assert($statusMessage === null || is_string($statusMessage));
45
46        $st = self::shortStatus($status);
47        if ($subStatus !== null) {
48            $st .= '/' . self::shortStatus($subStatus);
49        }
50        if ($statusMessage !== null) {
51            $st .= ': ' . $statusMessage;
52        }
53        parent::__construct($st, 0, $cause);
54
55        $this->status = $status;
56        $this->subStatus = $subStatus;
57        $this->statusMessage = $statusMessage;
58    }
59
60
61    /**
62     * Get the top-level status code.
63     *
64     * @return string  The top-level status code.
65     */
66    public function getStatus()
67    {
68        return $this->status;
69    }
70
71
72    /**
73     * Get the second-level status code.
74     *
75     * @return string|null  The second-level status code or NULL if no second-level status code is present.
76     */
77    public function getSubStatus()
78    {
79        return $this->subStatus;
80    }
81
82
83    /**
84     * Get the status message.
85     *
86     * @return string|null  The status message or NULL if no status message is present.
87     */
88    public function getStatusMessage()
89    {
90        return $this->statusMessage;
91    }
92
93
94    /**
95     * Create a SAML2 error from an exception.
96     *
97     * This function attempts to create a SAML2 error with the appropriate
98     * status codes from an arbitrary exception.
99     *
100     * @param Exception $exception  The original exception.
101     * @return sspmod_saml_Error  The new exception.
102     */
103    public static function fromException(Exception $exception)
104    {
105        if ($exception instanceof sspmod_saml_Error) {
106            // Return the original exception unchanged
107            return $exception;
108
109        // TODO: remove this branch in 2.0
110        } elseif ($exception instanceof SimpleSAML_Error_NoPassive) {
111            $e = new self(
112                \SAML2\Constants::STATUS_RESPONDER,
113                \SAML2\Constants::STATUS_NO_PASSIVE,
114                $exception->getMessage(),
115                $exception
116                );
117        // TODO: remove this branch in 2.0
118        } elseif ($exception instanceof SimpleSAML_Error_ProxyCountExceeded) {
119            $e = new self(
120                \SAML2\Constants::STATUS_RESPONDER,
121                \SAML2\Constants::STATUS_PROXY_COUNT_EXCEEDED,
122                $exception->getMessage(),
123                $exception
124            );
125        } else {
126            $e = new self(
127                \SAML2\Constants::STATUS_RESPONDER,
128                null,
129                get_class($exception) . ': ' . $exception->getMessage(),
130                $exception
131                );
132        }
133
134        return $e;
135    }
136
137
138    /**
139     * Create a normal exception from a SAML2 error.
140     *
141     * This function attempts to reverse the operation of the fromException() function.
142     * If it is unable to create a more specific exception, it will return the current
143     * object.
144     *
145     * @see sspmod_saml_Error::fromException()
146     *
147     * @return SimpleSAML_Error_Exception  An exception representing this error.
148     */
149    public function toException()
150    {
151        $e = null;
152
153        switch ($this->status) {
154            case \SAML2\Constants::STATUS_RESPONDER:
155                switch ($this->subStatus) {
156                    case \SAML2\Constants::STATUS_NO_PASSIVE:
157                        $e = new SimpleSAML\Module\saml\Error\NoPassive(
158                            \SAML2\Constants::STATUS_RESPONDER,
159                            $this->statusMessage
160                        );
161                        break;
162                }
163            break;
164        }
165
166        if ($e === null) {
167            return $this;
168        }
169
170        return $e;
171    }
172
173
174    /**
175     * Create a short version of the status code.
176     *
177     * Remove the 'urn:oasis:names:tc:SAML:2.0:status:'-prefix of status codes
178     * if it is present.
179     *
180     * @param string $status  The status code.
181     * @return string  A shorter version of the status code.
182     */
183    private static function shortStatus($status)
184    {
185        assert(is_string($status));
186
187        $t = 'urn:oasis:names:tc:SAML:2.0:status:';
188        if (substr($status, 0, strlen($t)) === $t) {
189            return substr($status, strlen($t));
190        }
191
192        return $status;
193    }
194}
195