Passed
Push — master ( 74b590...6f1b8a )
by Dmitry
01:56
created

General::validate_request()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 2
eloc 5
c 2
b 0
f 1
nc 1
nop 1
dl 0
loc 10
rs 9.4285
1
<?php
2
3
/**
4
 *      General class for all request types
5
 *
6
 *      @package php_EasyPay
7
 *      @version 1.1
8
 *      @author Dmitry Shovchko <[email protected]>
9
 *
10
 */
11
12
namespace EasyPay\Provider31\Request;
13
14
use EasyPay\Log as Log;
15
use EasyPay\Exception;
16
use EasyPay\Key as Key;
17
18
class General
19
{
20
    /**
21
     *      @var string raw request
22
     */
23
    protected $raw_request;
24
25
    /**
26
     *      @var string 'DateTime' node
27
     */
28
    protected $DateTime;
29
30
    /**
31
     *      @var string 'Sign' node
32
     */
33
    protected $Sign;
34
35
    /**
36
     *      @var string 'Operation' type
37
     */
38
    protected $Operation;
39
40
    /**
41
     *      @var string 'ServiceId' node
42
     */
43
    protected $ServiceId;
44
45
    /**
46
     *      @var array list of possible operations
47
     */
48
    protected $operations = array('Check','Payment','Confirm','Cancel');
49
50
    /**
51
     *      General constructor
52
     *
53
     *      @param string $raw Raw request data
54
     */
55
    public function __construct($raw)
56
    {
57
        $this->raw_request = strval($raw);
58
59
        $this->parse_request_data();
60
    }
61
62
    /**
63
     *      Get DateTime
64
     *
65
     *      @return string
66
     */
67
    public function DateTime()
68
    {
69
        return $this->DateTime;
70
    }
71
72
    /**
73
     *      Get Sign
74
     *
75
     *      @return string
76
     */
77
    public function Sign()
78
    {
79
        return $this->Sign;
80
    }
81
82
    /**
83
     *      Get Operation type
84
     *
85
     *      @return string
86
     */
87
    public function Operation()
88
    {
89
        return $this->Operation;
90
    }
91
92
    /**
93
     *      Get ServiceId
94
     *
95
     *      @return string
96
     */
97
    public function ServiceId()
98
    {
99
        return $this->ServiceId;
100
    }
101
102
    /**
103
     *      Parse xml-request, which was previously "extracted" from the body of the http request
104
     *
105
     *      @throws Exception\Structure
106
     */
107
    protected function parse_request_data()
108
    {
109
        if (empty($this->raw_request))
110
        {
111
            throw new Exception\Structure('An empty xml request', -50);
112
        }
113
114
        libxml_use_internal_errors(true);
115
        $doc = new \DOMDocument();
116
        if ( ! $doc->loadXML($this->raw_request))
117
        {
118
            foreach(libxml_get_errors() as $e){
119
                Log::instance()->error($e->message);
120
            }
121
            throw new Exception\Structure('The wrong XML is received', -51);
122
        }
123
124
        // process <Request> group
125
        $r = $this->getNodes($doc, 'Request');
126
127
        if (count($r) < 1)
128
        {
129
            throw new Exception\Structure('The xml-query does not contain any element Request!', -52);
130
        }
131
132
        foreach ($r[0]->childNodes as $child)
133
        {
134
            if ($child->nodeName == 'DateTime')
135
            {
136
                $this->parse_request_node($child, 'DateTime');
137
            }
138
            elseif ($child->nodeName == 'Sign')
139
            {
140
                $this->parse_request_node($child, 'Sign');
141
            }
142
            elseif (in_array($child->nodeName, $this->operations))
143
            {
144
                if ( ! isset($this->Operation))
145
                {
146
                    $this->Operation = $child->nodeName;
147
                }
148
                else
149
                {
150
                    throw new Exception\Structure('There is more than one Operation type element in the xml-query!', -53);
151
                }
152
            }
153
        }
154
155
        if ( ! isset($this->Operation))
156
        {
157
            throw new Exception\Structure('There is no Operation type element in the xml request!', -55);
158
        }
159
160
        // process <Operation> group
161
        $r = $this->getNodes($doc, $this->Operation);
162
163
        foreach ($r[0]->childNodes as $child)
164
        {
165
            if ($child->nodeName == 'ServiceId')
166
            {
167
                $this->parse_request_node($child, 'ServiceId');
168
            }
169
        }
170
    }
171
172
    /**
173
     *      Parse node of request
174
     *
175
     *      @param \DOMNode $n
176
     *      @param string $name
177
     *
178
     *      @throws Exception\Structure
179
     */
180
    protected function parse_request_node($n, $name)
181
    {
182
        if ( ! isset($this->$name))
183
        {
184
            $this->$name = $n->nodeValue;
185
        }
186
        else
187
        {
188
            throw new Exception\Structure('There is more than one '.$name.' element in the xml-query!', -56);
189
        }
190
    }
191
192
    /**
193
     *      "Rough" validation of the received xml request
194
     *
195
     *      @param array $options
196
     *      @throws Exception\Data
197
     *      @throws Exception\Structure
198
     */
199
    public function validate_request($options)
200
    {
201
        $this->validate_element('DateTime');
202
        $this->validate_element('Sign');
203
        $this->validate_element('ServiceId');
204
205
        // compare received value ServiceId with option ServiceId
206
        if (intval($options['ServiceId']) != intval($this->ServiceId))
207
        {
208
            throw new Exception\Data('This request is not for our ServiceId!', -58);
209
        }
210
    }
211
212
    public function validate_element($name)
213
    {
214
        if ( ! isset($this->$name))
215
        {
216
            throw new Exception\Structure('There is no '.$name.' element in the xml request!', -57);
217
        }
218
    }
219
220
    /**
221
     *      Verify signature of request
222
     *
223
     *      @param array $options
224
     *      @throws Exception\Runtime
225
     *      @throws Exception\Sign
226
     */
227
    public function verify_sign($options)
228
    {
229
        if (!isset($options['UseSign']) || ($options['UseSign'] === false))
230
        {
231
            return null;
232
        }
233
        if ( ! isset($options['EasySoftPKey']))
234
        {
235
            throw new Exception\Runtime('The parameter EasySoftPKey is not set!', -94);
236
        }
237
        $pkeyid = (new Key())->get($options['EasySoftPKey'], 'public');
238
239
        $pub_key = openssl_pkey_get_public($pkeyid);
240
        if ($pub_key === FALSE)
241
        {
242
            throw new Exception\Runtime('Can not extract the public key from certificate!', -97);
243
        }
244
        $bin_sign = pack("H*", $this->Sign);
245
        $xml = str_replace($this->Sign, '', $this->raw_request);
246
        $check = openssl_verify($xml, $bin_sign, $pub_key);
247
        if ($check == -1)
248
        {
249
            throw new Exception\Sign('Error verify signature of request!', -96);
250
        }
251
        elseif ($check == 0)
252
        {
253
            throw new Exception\Sign('Signature of request is incorrect!', -95);
254
        }
255
    }
256
257
    /**
258
     *      Selects nodes by name
259
     *
260
     *      @param \DOMDocument $dom
261
     *      @param string $name
262
     *      @param array $ret
263
     *
264
     *      @return array nodes with the name
265
     */
266
    protected function getNodes($dom, $name, $ret=array())
267
    {
268
        foreach($dom->childNodes as $child)
269
        {
270
            if ($child->nodeName == $name)
271
            {
272
                array_push($ret, $child);
273
            }
274
            else
275
            {
276
                if (count($child->childNodes) > 0)
277
                {
278
                    $ret = $this->getNodes($child, $name, $ret);
279
                }
280
            }
281
        }
282
283
        return $ret;
284
    }
285
286
}
287