Completed
Push — master ( 1bb428...fe8713 )
by Tobias
8s
created

Connection::getAPIVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
FreeIPA library for PHP
4
Copyright (C) 2015  Tobias Sette <[email protected]>
5
6
This program is free software: you can redistribute it and/or modify
7
it under the terms of the GNU Lesser General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
This program is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
GNU Lesser General Public License for more details.
15
16
You should have received a copy of the GNU Lesser General Public License
17
along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
*/
19
20
/**
21
 * Classes for access to FreeIPA API
22
 * @sice GIT 0.1.0
23
 */
24
namespace FreeIPA\APIAccess;
25
26
/**
27
 * Singleton class for connection with the freeIPA server
28
 *
29
 * Note there is a problem doing in PHP similar to "--negotiate -u :" in
30
 * cURL cli. It was made a workaround that can be found using the case sensitive
31
 * string "workaround_for_auth" in this file
32
 *
33
 * @author Tobias Sette <[email protected]>
34
 * @copyright Copyright (c) 2015 Tobias Sette <[email protected]>
35
 * @license LGPLv3
36
 * @package php-freeipa
37
 * @since GIT: 0.1.0
38
 * @version GIT 0.2.0
39
 */
40
class Connection
41
{
42
    /**
43
     * Sotre only one instance of this class
44
     * @var instance
45
     * @access private
46
     * @since GIT: 0.1.0
47
     */
48
    private static $_instance;
49
50
    /**
51
     * API version that will be defined in the request
52
     * The freeIPA returns VersionError if diferent version of server version is sent
53
     * and a warning if the version is not sent
54
     *
55
     * @var string|null api_version API version that will be sent in each requisition
56
     * @access private
57
     * @since GIT: 0.1.0
58
     */
59
    protected $api_version = null;
60
61
    /**
62
     * @var mixed cURL handler
63
     * @access public
64
     * @since GIT: 0.1.0
65
     */
66
    protected $curl_handler = null;
67
68
    /**
69
     * @var bool curl_initiated if cURL was initiated or not
70
     * @access protected
71
     * @since GIT: 0.1.0
72
     */
73
    protected $curl_initiated = false;
74
75
    /**
76
     * @var bool curl_debug if cURL will be initiated with debug or not
77
     * @access private
78
     * @since GIT: 0.1.0
79
     */
80
    protected $curl_debug = false;
81
82
    /**
83
     * @var string|null curl_response Stores response/return of cURL
84
     * @access protected
85
     * @since GIT: 0.1.0
86
     */
87
    protected $curl_response = null;
88
89
    /**
90
     * @var int curl_timeout Timeout for cURL connection
91
     * @access public
92
     * @sice GIT 0.1.0
93
     */
94
    protected $curl_timeout = 10;
95
96
    /**
97
     * @var string|null cookie_file Full path for file that will stores cookie
98
     * @access private
99
     * @sice GIT 0.1.0
100
     */
101
    protected $cookie_file = null;
102
103
    /**
104
     * @var string|null cookie_string String that contains cookie for use in cURL. workaround_for_auth
105
     * @access private
106
     * @sice GIT 0.1.0
107
     */
108
    protected $cookie_string = null;
109
110
    /**
111
     * @var string|null certificate_file Full path of certificate file for use in connections with the server
112
     * @access public
113
     * @sice GIT 0.1.0
114
     */
115
    protected $certificate_file  = null;
116
117
    /**
118
     * @var array curl_http_header HTTP header that will be used with cURL
119
     * @access public
120
     * @sice GIT 0.1.0
121
     */
122
    protected $curl_http_header = array();
123
124
    /**
125
     * @var string|null ipa_server IP address or hostname of freeIPA server
126
     * @access protected
127
     * @sice GIT 0.1.0
128
     */
129
    protected $ipa_server = null;
130
131
    /**
132
     * @var string|null jsonrpc_url URL where the server accept json RPC connections
133
     * @access protected
134
     * @sice GIT 0.1.0
135
     */
136
    protected $jsonrpc_url = null;
137
138
    /**
139
     * @var string|null jsonrpc_login_url URL where the server accept loggin connections
140
     * @access protected
141
     * @sice GIT 0.1.0
142
     */
143
    protected $jsonrpc_login_url = null;
144
145
    /**
146
     * @var bool user_logged If user made login or not
147
     * @access protected
148
     * @sice GIT 0.1.0
149
     */
150
    protected $user_logged  = false;
151
    
152
    /**
153
     * @var string|null $json_request String that contains the last json request what will be (or was) sent to the server
154
     * @access protected
155
     * @since GIT: 0.1.0
156
     */
157
    protected $json_request = null;
158
159
    /**
160
     * @var string|null $json_response String that contains the last json response from server
161
     * @access protected
162
     * @since GIT: 0.1.0
163
     */
164
    protected $json_response = null;
165
    
166
    /**
167
     * @var array Stores information about a previous authentication
168
     * @see authenticate()
169
     * @see getAuthenticationInfo()
170
     * @since GIT: 0.2.0
171
     */
172
    protected $authentication_info = array();
173
174
175
    /**
176
     * Executa ações necessárias ao início do uso de uma instância desta classe.
177
     * Por favor, note que o servidor e certificado não são obrigatórios ao instanciar a classe,
178
     * mas são obrigatórios em diversos métodos.
179
     *
180
     * <code>
181
     * $ipa = FreeIPA\APIAccess\Connection::getInstance('192.168.0.5', '/tmp/certificate.crt');
182
     * $ipa2 = FreeIPA\APIAccess\Connection::getInstance();
183
     * $ipa2->setIPAServer('192.168.0.5');
184
     * $ipa2->setCertificateFile('/tmp/certificado.crt');
185
     * </code>
186
     *
187
     * @param string|null $server address (IP or hostname) of server
188
     * @param string|null $certificate full path of server certificate
189
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
190
     * @sice GIT 0.1.0
191
     * @see getInstance()
192
     * @see setIPAServer()
193
     * @see setCertificateFile()
194
     * @throws \Exception caso o módulo não esteja instalado curl
195
     * @throws \Exception caso o método setIPAServer() retorne false
196
     * @throws \Exception caso o método setCertificateFile() retorne false
197
     */
198
    private function __construct($server = null, $certificate = null) {
199
        if (! function_exists('curl_init')) {
200
            throw new \Exception('Unable to find cURL');
201
        }
202
        
203
        if ( ! empty($server) && ! $this->setIPAServer($server) ) {
204
            throw new \Exception("Error while validating the server");
205
        }
206
        else if ( ! empty($certificate) && ! $this->setCertificateFile($certificate) ) {
207
            throw new \Exception("Error while validating the certificate");
208
        }
209
        
210
        $this->cookie_file = tempnam(sys_get_temp_dir(), 'php_freeipa_api');
211
    }
212
    
213
    /**
214
     * This a Singleton class
215
     * 
216
     * @since GIT: 0.1.0
217
     */
218
    private function __clone()
219
    {
220
        // nothing
221
    }
222
    
223
    /**
224
     * This is a Singlton class
225
     * 
226
     * @since GIT: 0.1.0
227
     */
228
    private function __wakeup()
229
    {
230
        // nothing
231
    }
232
233
    /**
234
     * To finalize a instance of this class
235
     *
236
     * @param void
237
     * @return void
238
     * @sice GIT 0.1.0
239
     */
240
    public function __destruct()
241
    {
242
        $this->endCurl();
243
        unlink($this->cookie_file);
244
    }
245
    
246
    /**
247
     * 
248
     * @param type $server address (IP or hostname) of server
249
     * @param type $certificate full path of server certificate
250
     * @param type $force_new if true, a new instance is returned (breaking the Singleton)
251
     * @return type
252
     * @return instance of this class
253
     * @since GIT: 0.1.0
254
     * @version GIT: 0.1.0
255
     */
256
    public static function getInstance($server = null, $certificate = null, $force_new = false)
257
    {
258
        switch ($force_new) {
259
            case false:
260
                if (! isset(self::$_instance)) {
261
                    self::$_instance = new self($server, $certificate);
0 ignored issues
show
Documentation Bug introduced by
It seems like new self($server, $certificate) of type object<FreeIPA\APIAccess\Connection> is incompatible with the declared type object<FreeIPA\APIAccess\instance> of property $_instance.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
262
                }
263
                $r = self::$_instance;
264
                break;
265
            case true:
266
                $r = new self($server, $certificate);
267
                break;
268
        }
269
        return($r);
0 ignored issues
show
Bug introduced by
The variable $r does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
270
	}
271
272
    /**
273
     * Define a version that will be used in json sent to the server. The server will refuse
274
     * requests from API that are greater than him
275
     *
276
     * @param string
277
     * @return void
278
     * @sice GIT 0.1.0
279
     * @version GIT: 0.1.0
280
     * @see getAPIVersion()
281
     */
282
    private function setAPIVersion($version)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
283
    {
284
        $this->api_version = $version;
285
    }
286
287
    /**
288
     * Get the API version that is being used in this class
289
     *
290
     * @param void
291
     * @return string
292
     * @sice GIT 0.1.0
293
     * @version GIT: 0.2.0
294
     * @see setAPIVersion()
295
     */
296
    private function getAPIVersion()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
297
    {
298
        return $this->api_version;
299
    }
300
301
    /**
302
     * Define the server address (IP or hostname)
303
     *
304
     * @param string $host endereço (IP ou hostname) do servidor
305
     * @return bool
306
     * @sice GIT 0.1.0
307
     * @version GIT: 0.1.0
308
     * @see __construct()
309
     * @see getIPAServer()
310
     */
311
    public function setIPAServer($host = null)
312
    {
313
        if (empty($host) || is_null($host) || !is_string($host)) {
314
            return false;
315
        }
316
        $this->ipa_server = $host;
317
        $this->jsonrpc_url = 'https://' . $host . '/ipa/session/json';
318
        $this->jsonrpc_login_url = 'https://' . $host . '/ipa/session/login_password';
319
        return true;
320
    }
321
322
    /**
323
     * Get the server address (IP or hostname)
324
     *
325
     * @param void
326
     * @return string|bool
327
     * @sice GIT 0.1.0
328
     * @version GIT: 0.1.0
329
     * @see setIPAServer()
330
     */
331
    public function getIPAServer()
332
    {
333
        return $this->ipa_server;
334
    }
335
336
    /**
337
     * Define the full path of certificate file
338
     *
339
     * @param string $file full path of certificate file
340
     * @return bool false if the file is not stated nor string. True in success
341
     * @sice GIT 0.1.0
342
     * @version GIT: 0.1.0
343
     * @see __construct()
344
     * @see getCertificateFile()
345
     * @throws \Exception if the file does not exist or can't be read
346
     */
347
    public function setCertificateFile($file)
348
    {
349
        if (empty($file) || is_null($file) || !is_string($file)) {
350
            return false;
351
        } else if (!file_exists($file)) {
352
            throw new \Exception("Certificate file doesn't exists");
353
        } else if (!is_readable($file)) {
354
            throw new \Exception("Certificate file can't be read");
355
        }
356
        $this->certificate_file = $file;
357
        return true;
358
    }
359
360
    /**
361
     * Get the full path of certificate file
362
     *
363
     * @return string|bool
364
     * @sice GIT 0.1.0
365
     * @version GIT: 0.1.0
366
     * @see setCertificateFile()
367
     */
368
    public function getCertificateFile()
369
    {
370
        return($this->certificate_file);
371
    }
372
373
    /**
374
     * Define the string returned by cURL
375
     *
376
     * @param string $string string returned by cURL
377
     * @return void
378
     * @sice GIT 0.1.0
379
     * @version GIT: 0.1.0
380
     * @see getCurlReturn()
381
     */
382
    public function setCurlReturn($string = null)
383
    {
384
        $this->curl_response = $string;
385
    }
386
387
    /**
388
     * Get the string returned by cURL
389
     *
390
     * @param void
391
     * @return string|bool
392
     * @sice GIT 0.1.0
393
     * @version GIT: 0.1.0
394
     * @see setCurlReturn()
395
     */
396
    public function getCurlReturn()
397
    {
398
        return $this->curl_response;
399
    }
400
401
    /**
402
     * Get the string of last json request (or the one that will be made) to the server
403
     *
404
     * @param void
405
     * @return string|null
406
     * @see buildJsonRequest()
407
     * @since GIT: 0.1.0
408
     * @version GIT: 0.1.0
409
     */
410
    public function getJsonRequest()
411
    {
412
        return $this->json_request;
413
    }
414
415
    /**
416
     * Get the string of last json return of freeIPA server
417
     *
418
     * @param void
419
     * @return string|null
420
     * @since GIT: 0.1.0
421
     * @version GIT: 0.1.0
422
     */
423
    public function getJsonResponse()
424
    {
425
        return $this->json_response;
426
    }
427
428
    /**
429
     * Get the cURL handler with options already defined
430
     *
431
     * @param bool $force force cURL to be initiated again
432
     * @return mixed cURL handler
433
     * @sice GIT 0.1.0
434
     * @version GIT: 0.1.0
435
     * @see endCurl()
436
     */
437
    public function startCurl($force = false)
438
    {
439
        if (false === $this->curl_initiated || true === $force) {
440
            $this->endCurl(); // for precaution
441
            $this->curl_handler = curl_init();
442
443
            $curl_options = array(
444
                // nome do arquivo do cookie
445
                CURLOPT_COOKIEFILE => $this->cookie_file,
446
                // The name of a file to save all internal cookies to when the handle is closed, e.g. after a call to curl_close.
447
                //CURLOPT_COOKIEJAR => $cookie_file,
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
448
                // Verify the certificate
449
                CURLOPT_SSL_VERIFYPEER => true,
450
                // http://php.net/manual/en/function.curl-setopt.php
451
                CURLOPT_SSL_VERIFYHOST => 2,
452
                //
453
                CURLOPT_CAINFO => $this->certificate_file,
454
                //
455
                CURLOPT_POST => true,
456
                //
457
                CURLOPT_FOLLOWLOCATION => true,
458
                /*
459
                 * Return the value of curl_exec() as string insted of print to screen
460
                 * IMPORTANT: the returned value by the function curl_exec() changes according to
461
                 * this parameter and PHP does not give an method to obtain this parameter's value,
462
                 * so understand that the code will assume that this option is always true, except
463
                 * where explicitly defined the opossite.
464
                 */
465
                CURLOPT_RETURNTRANSFER => true,
466
                // The maximum number of seconds to allow cURL functions to execute.
467
                CURLOPT_TIMEOUT => $this->curl_timeout,
468
            );
469
470
            // workaround_for_auth
471
            if ($this->cookie_string) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cookie_string of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
472
                $curl_options = array_merge($curl_options, array(CURLOPT_COOKIE => $this->cookie_string));
473
            }
474
475
            return $this->curl_initiated = curl_setopt_array($this->curl_handler, $curl_options);
476
        }
477
478
        return $this->curl_initiated;
479
    }
480
481
    /**
482
     * Close the cURL handler
483
     *
484
     * @param void
485
     * @return void
486
     * @sice GIT 0.1.0
487
     * @version GIT: 0.1.0
488
     * @see startCurl()
489
     */
490
    public function endCurl()
491
    {
492
        // @ suppress error. In the beginning the curl_handler is null
493
        @curl_close($this->curl_handler);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
494
    }
495
496
    /**
497
     * Aid in cURL debug. Must be used in the place of startCurl()
498
     *
499
     * @param void
500
     * @return Manipulador (handler) para o cURL
501
     * @sice GIT 0.1.0
502
     * @version GIT: 0.1.0
503
     * @see startCurl()
504
     * @todo need improvements
505
     */
506
//    public function debugCurl()
507
//    {
508
//        $this->startCurl();
509
//        print PHP_EOL . '<br/>Debug do curl ativado<br/>' . PHP_EOL;
510
//        $curl_options = array(
511
//            // Verbosity
512
//            CURLOPT_VERBOSE => true,
513
//            // Include header in the response
514
//            CURLOPT_HEADER => true,
515
//            // true to output SSL certification information to STDERR on secure transfers.
516
//            CURLOPT_CERTINFO => true,
517
//            // 
518
//            CURLINFO_HEADER_OUT => true,
519
//        );
520
//        $this->curl_debug = true;
521
//        return curl_setopt_array($this->curl_handler, $curl_options);
522
//    }
523
524
    /**
525
     * If a previous use of cURL has generated an error
526
     *
527
     * @param void
528
     * @return bool
529
     * @sice GIT 0.1.0
530
     * @version GIT: 0.1.0
531
     */
532
    public function curlHaveError()
533
    {
534
        return ( curl_errno($this->curl_handler) ) ? true : false;
535
    }
536
537
    /**
538
     * Return an array that contains the message and error number of last cURL error
539
     *
540
     * @param void
541
     * @return array
542
     * @sice GIT 0.1.0
543
     * @version GIT: 0.1.0
544
     * @link http://curl.haxx.se/libcurl/c/libcurl-errors.html
545
     */
546
    public function getCurlError()
547
    {
548
        return array(
549
            curl_error($this->curl_handler),
550
            curl_errno($this->curl_handler),
551
        );
552
    }
553
554
    /**
555
     * Return an array that contains cURL information
556
     *
557
     * @param void
558
     * @return array
559
     * @sice GIT 0.1.0
560
     * @link http://php.net/manual/en/function.curl-getinfo.php
561
     */
562
    public function getCurlInfo()
563
    {
564
        return curl_getinfo($this->curl_handler);
565
    }
566
567
    /**
568
     * Execute a statement with cURL handler.
569
     * In error, use the method getCurlError to obtain more information e
570
     * getCurlReturn to obtain the cURL response
571
     *
572
     * @param void
573
     * @return string|bool return false in error or HTTP response code
574
     * @sice GIT 0.1.0
575
     * @version GIT: 0.1.0
576
     * @see getCurlError()
577
     * @see getCurlReturn()
578
     * @link http://php.net/manual/en/function.curl-exec.php
579
     * @TODO without certificate the $http_code é 0 e nenhum output é gerado
580
     */
581
    public function curlExec()
582
    {
583
        $this->setCurlReturn(curl_exec($this->curl_handler));
584
        $http_code = curl_getinfo($this->curl_handler, CURLINFO_HTTP_CODE);
585
        // Pelo que entendi da documentação http://php.net/manual/en/function.curl-exec.php
586
        // e da prática o curl_exec retornará false somente se der algo errado na conexao. O webservice
587
        // do IPA retorna um html com o erro, então não vem o true, vem string.
588
        //
589
        // when CURLOPT_RETURNTRANSFER is false
590
        // if ( '1' == $curl_response_exec ) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
591
        //     return true;
592
        // }
593
        return (($this->curlHaveError()) ? false : $http_code);
594
    }
595
    
596
    /**
597
     * Try to authenticate the user and password in the server through URL
598
     * defined in $jsonrpc_login_url
599
     *
600
     * @param string $user
601
     * @param string $password
602
     * @return bool
603
     * @sice GIT 0.1.0
604
     * @version GIT: 0.2.0
605
     * @throws \Exception if cURL has error
606
     * @throws \Exception if $this->ipa_server is invalid
607
     * @throws \Exception if $this->certificate_file is invalid
608
     * @throws \Exception if unable to find the session cookie. workaround_for_auth
609
     * @see docs/return_samples/authentication.txt
610
     * @TODO this method contains a workaround_for_auth
611
     */
612
    public function authenticate($user = null, $password = null)
613
    {
614
        if ($this->userLogged()) {
615
            return true;
616
        }
617
        
618
        if (!$user || !$password) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $user of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
Bug Best Practice introduced by
The expression $password of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
619
            return false;
620
        }
621
        
622
        if (! $this->getIPAServer()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getIPAServer() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
623
            throw new \Exception("Error while validating the server");
624
        }
625
        if (! $this->getCertificateFile()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getCertificateFile() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
626
            throw new \Exception("Error while validating the certificate");
627
        }
628
629
        $auth_info = array(
630
            'authenticate' => false,
631
            'reason' => '',
632
            'message' => '',
633
            'http_code' => null,
634
        );
635
        $this->startCurl();
636
        $this->curl_http_header = array(
637
            'Content-type: application/x-www-form-urlencoded',
638
            'Accept: */*',
639
        );
640
641
        if (empty($user) || empty($password)) {
642
            $auth_info['authenticate'] = false;
643
            $auth_info['message'] = 'User/password is empty';
644
            return $auth_info;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $auth_info; (array<string,false|string|null>) is incompatible with the return type documented by FreeIPA\APIAccess\Connection::authenticate of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
645
        }
646
647
        //$user = urlencode($user);
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
648
        //$password = urlencode($password);
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
649
650
        curl_setopt($this->curl_handler, CURLOPT_HTTPHEADER, $this->curl_http_header);
651
        curl_setopt($this->curl_handler, CURLOPT_URL,        $this->jsonrpc_login_url);
652
        curl_setopt($this->curl_handler, CURLOPT_POSTFIELDS, "user=$user&password=$password");
653
        // I need header for get the value for X-IPA-Rejection-Reason field
654
        // and as workaround_for_auth
655
        if (! $this->curl_debug) {
656
            curl_setopt($this->curl_handler, CURLOPT_HEADER, true);
657
        }
658
659
        $auth_info['http_code'] = $this->curlExec();
660
661
        if ($this->curlHaveError()) {
662
            $e = $this->getCurlError();
663
            throw new \Exception($e[0], $e[1]);
664
        }
665
666
        // #TODO Maybe this field be returned only in 401 errors
667
        // Example of this field on header: X-IPA-Rejection-Reason: invalid-password
668
        preg_match('/X-IPA-Rejection-Reason: ([^\n]*)/i', $this->getCurlReturn(), $search_reject_reason);
669
        $auth_info['reason'] = (empty($search_reject_reason[1])) ? false : trim($search_reject_reason[1]);
670
671
        // I need header for get the value for X-IPA-Rejection-Reason field
672
        // and as workaround_for_auth
673
        if (!$this->curl_debug) {
674
            curl_setopt($this->curl_handler, CURLOPT_HEADER, false);
675
        }
676
677
        // #TODO Maybe this field be returned only in 401 errors
678
        // I see that in 401 error freeIPA server returns a html that contains  a error message
679
        $online_return = str_replace(array("\n", "\r", "\r\n"), ' ', $this->curl_response);
680
        preg_match('#<p>(.*?)</p>#', $online_return, $search_description_in_ipa);
681
        if (empty($search_description_in_ipa[1])) {
682
            $ipa_error_description = null;
683
        } else {
684
            $ipa_error_description = str_replace(array('<strong>', '</strong>'), '', $search_description_in_ipa[1]);
685
            $ipa_error_description = trim($ipa_error_description);
686
        }
687
688
        if ('401' == $auth_info['http_code']) {
689
            $auth_info['authenticate'] = false;
690
            // É melhor não exibir todas as mensagens diretamente ao usuário, para ficar mais amigável.
691
            // O $auth_info['reason'] invalid-password vem em mais de um caso (quando o usuario é bloqueado e ou usuario/senha está incorreto)
692
            if ('kinit: Preauthentication failed while getting initial credentials' == $ipa_error_description) {
693
                $auth_info['message'] .= 'User or password are wrong. ';
694
            } else if (preg_match("/Client (.*?) not found in Kerberos database while getting initial credentials/i", $ipa_error_description)) {
695
                $auth_info['message'] .= 'Unable to find user in the server. ';
696
            } else {
697
                $auth_info['message'] .= 'Generic error in authentication. ';
698
                if (! empty($ipa_error_description)) {
699
                    $auth_info['message'] .= "The server returned \"" . $ipa_error_description . "\". ";
700
                }
701
            }
702
        } else if ('200' != $auth_info['http_code']) {
703
            $auth_info['authenticate'] = false;
704
            $auth_info['message'] = "The response returned the HTTP code \"" . $auth_info['http_code'] . "\" that is not acceptable. ";
705
            if (!empty($ipa_error_description)) {
706
                $auth_info['message'] .= "The server returned \"" . $ipa_error_description . "\". ";
707
            }
708
        } else {
709
            $auth_info['authenticate'] = true;
710
            $auth_info['message'] = 'User has successfully authenticated. ';
711
            // workaround_for_auth. Obtenho a string do cookie manualmente.
712
            preg_match("/Set-Cookie: ([^\n]*)/", $this->getCurlReturn(), $found);
713
            if (empty($found[1])) {
714
                throw new \Exception('Erro for locate the session cookie');
715
            }
716
            // example of $found[1]:
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
717
            //ipa_session=2dd6a6e7ae5c0c388be3de7e50b454e9; Domain=fedora.ipatest.com; Path=/ipa; Expires=Sat, 06 Jun 2015 20:14:50 GMT; Secure; HttpOnly
718
            $this->cookie_string = trim($found[1]);
719
            curl_setopt($this->curl_handler, CURLOPT_COOKIE, $this->cookie_string);
720
        }
721
722
        $this->user_logged = $auth_info['authenticate'];
723
        $this->authentication_info = $auth_info;
724
        return($this->user_logged);
725
    }
726
    
727
    /**
728
     * Get information about a previous authentication through
729
     * authenticate() method
730
     * 
731
     * $return
732
     *  ['authenticate'] bool if user is authenticated
733
     *  ['reason'] string the reason of the last action
734
     *  ['message'] string with the message generated for the last action
735
     *  ['http_code'] HTTP code for the response
736
     * 
737
     * @return array $return see description above
738
     * @since GIT: 0.2.0
739
     * @version GIT: 0.2.0
740
     */
741
    public function getAuthenticationInfo()
742
    {
743
        return($this->authentication_info);
744
    }
745
746
    /**
747
     * Retorna bool que diz se o usuário está logado ou não
748
     *
749
     * @param void
750
     * @return bool
751
     * @sice GIT 0.1.0
752
     * @version GIT: 0.1.0
753
     */
754
    public function userLogged()
755
    {
756
        return $this->user_logged;
757
    }
758
759
    /**
760
     * Checks if a variable is a associative array
761
     * 
762
     * @param array $var
763
     * @param bool $force if true array must be associative. If false, must be associative only if not empty
764
     * @return bool
765
     * @link http://php.net/manual/en/function.is-array.php#89332
766
     * @since GIT: 0.1.0
767
     * @version GIT: 0.1.0
768
     */
769
    public function isAssociativeArray($var, $force = true)
770
    {
771
        if (!is_array($var)) {
772
            return false;
773
        }
774
775
        if (!empty($var) || $force) {
776
            return array_diff_key($var, array_keys(array_keys($var)));
777
        }
778
779
        return true;
780
    }
781
782
    /**
783
     * Returns a json string in the format required by FreeIPA
784
     *
785
     * @param string $method required parameter that defines the method that will be executed in the server
786
     * @param array $args arguments for the method
787
     * @param array $options options for the method
788
     * @return string|bool returns false if there is error in passed parameters
789
     * @sice GIT 0.1.0
790
     * @version GIT: 0.1.0
791
     * @link http://php.net/manual/en/function.json-encode.php
792
     */
793
    public function buildJsonRequest($method = null, $args = array(), $options = array())
794
    {
795
        if (!$method || !is_array($args) || !$this->isAssociativeArray($options, false)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $method of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
796
            return false;
797
        }
798
799
        $default_args = array();
800
        $final_args = array_merge($default_args, $args);
801
802
        $default_options = array();
803
        // freeIPA returns VersionError if a different version of the server is sent
804
        if ($this->api_version) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->api_version of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
805
            $default_options['version'] = $this->api_version;
806
        }
807
        $final_options = array_merge($default_options, $options);
808
809
        // in ping the options are {} even if empty. The PHP send empty array as []
810
        // One possible solution is to use the JSON_FORCE_OBJECT parameter only with
811
        // the convert options and merge the result in the return, but doing that
812
        // the PHP delimits the {}
813
        if ('ping' == strtolower($method) && empty($final_options)) {
814
            return $this->json_request = '{ "id": 0, "method": "ping", "params": [ [],{} ] }';
815
        }
816
817
        $return = array(
818
            'id' => 0,
819
            'method' => $method,
820
            'params' => array($final_args, $final_options),
821
        );
822
823
        $this->json_request = json_encode($return, JSON_PRETTY_PRINT);
824
        return $this->json_request;
825
    }
826
827
    /**
828
     * Sends requests for the freeIPA server using the previous established session
829
     * and stores the return in $this->json_response
830
     * With this method is possible to make requests for any freeIPA API method
831
     *
832
     * @param string $method required parameter that defines the method that will be executed in the server
833
     * @param array $params arguments for the method
834
     * @param array $options options for the method
835
     * @param bool $exceptionInError if true, will lauch \Exception if error field in response comes filled
836
     * @return array with response object (comes of json_decode()) and http code of response
837
     * @sice GIT 0.1.0
838
     * @version GIT: 0.1.0
839
     * @throws \Exception if user is not logged in
840
     * @throws \Exception if has error while create request
841
     * @throws \Exception if has error while define cURL options or make a request
842
     * @throws \Exception if a http code of response is not 200
843
     * @throws \Exception if json response is empty
844
     * @throws \Exception (if $exceptionInError is true) with description and number of error if json returns error
845
     * @see userLogged()
846
     * @see buildJsonRequest()
847
     * @see $json_response
848
     * @see ../../docs/return_samples/invalid_json_request.txt
849
     * @link http://php.net/manual/en/function.json-decode.php
850
     */
851
    public function buildRequest($method = null, $params = array(), $options = array(), $exceptionInError = true)
852
    {
853
        if (!$this->userLogged()) {
854
            throw new \Exception('User is not logged in');
855
        }
856
857
        $json = $this->buildJsonRequest($method, $params, $options);
858
        if (false === $json) {
859
            throw new \Exception('Error while create json request');
860
        }
861
862
        $curl_options = array(
863
            CURLOPT_URL => $this->jsonrpc_url,
864
            CURLOPT_POSTFIELDS => $json,
865
            CURLOPT_HTTPHEADER => array(
866
                'referer:https://' . $this->ipa_server . '/ipa/ui/index.html',
867
                'Content-Type:application/json',
868
                'Accept:applicaton/json',
869
                'Content-Length: ' . strlen($json),
870
            ),
871
        );
872
873
        $define_options = curl_setopt_array($this->curl_handler, $curl_options);
874
        if (false === $define_options) {
875
            throw new \Exception('Error while define cURL options');
876
        }
877
878
        $response_http_code = $this->curlExec();
879
        $json_retorno = $this->json_response = $this->getCurlReturn();
880
        $object_json_returned = json_decode($json_retorno);
881
        if ($this->curlHaveError()) {
882
            throw new \Exception('Error in cURL request');
883
        }
884
        if (!$response_http_code || '200' != $response_http_code) {
885
            throw new \Exception("The value \"$response_http_code\" is not a valid response code");
886
        }
887
        if (empty($json_retorno) || empty($object_json_returned)) {
888
            #TODO criar exceção para passar os dados do erro ao inves do json puro. Vide arquivo exemplos_retornos.txt
889
            throw new \Exception("Erro in json return. Value is ${json_retorno}");
890
        }
891
        if ($exceptionInError && !empty($object_json_returned->error)) {
892
            throw new \Exception("Error in request. Details: " . $object_json_returned->error->message, $object_json_returned->error->code);
893
        }
894
895
        return array($object_json_returned, $response_http_code);
896
    }
897
    
898
    /**
899
     * Makes a ping in FreeIPA server through of api
900
     *
901
     * @param bool if $return_string is true, will return the summary field of json response
902
     * @return string|bool true if success or string if $return_string is true
903
     * @sice GIT 0.1.0
904
     * @version GIT: 0.1.0
905
     * @see ../../docs/return_samples/ping.txt
906
     */
907
    public function ping($return_string = false)
908
    {
909
        $ret = $this->buildRequest('ping'); // receives json and http response code
910
        $json = $ret[0];
911
        if (!empty($json->error) || empty($json->result) || empty($json->result->summary) || !is_string($json->result->summary)) {
912
            return false;
913
        }
914
        if ($return_string) {
915
            return $json->result->summary;
916
        } else {
917
            return true;
918
        }
919
    }
920
921
}
922