Test Failed
Push — master ( 5d347f...d6f93a )
by Stefan
07:03
created

DeploymentManaged::initialise()   B

Complexity

Conditions 8
Paths 24

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 33
rs 8.4444
c 0
b 0
f 0
cc 8
nc 24
nop 0
1
<?php
2
3
/*
4
 * *****************************************************************************
5
 * Contributions to this work were made on behalf of the GÉANT project, a 
6
 * project that has received funding from the European Union’s Framework 
7
 * Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
8
 * Horizon 2020 research and innovation programme under Grant Agreements No. 
9
 * 691567 (GN4-1) and No. 731122 (GN4-2).
10
 * On behalf of the aforementioned projects, GEANT Association is the sole owner
11
 * of the copyright in all material which was developed by a member of the GÉANT
12
 * project. GÉANT Vereniging (Association) is registered with the Chamber of 
13
 * Commerce in Amsterdam with registration number 40535155 and operates in the 
14
 * UK as a branch of GÉANT Vereniging.
15
 * 
16
 * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands. 
17
 * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
18
 *
19
 * License: see the web/copyright.inc.php file in the file structure or
20
 *          <base_url>/copyright.php after deploying the software
21
 */
22
23
/**
24
 * This file contains the AbstractProfile class. It contains common methods for
25
 * both RADIUS/EAP profiles and SilverBullet profiles
26
 *
27
 * @author Stefan Winter <[email protected]>
28
 * @author Tomasz Wolniewicz <[email protected]>
29
 *
30
 * @package Developer
31
 *
32
 */
33
34
namespace core;
35
36
use \Exception;
37
38
/**
39
 * This class represents an EAP Profile.
40
 * Profiles can inherit attributes from their IdP, if the IdP has some. Otherwise,
41
 * one can set attribute in the Profile directly. If there is a conflict between
42
 * IdP-wide and Profile-wide attributes, the more specific ones (i.e. Profile) win.
43
 * 
44
 * @author Stefan Winter <[email protected]>
45
 * @author Tomasz Wolniewicz <[email protected]>
46
 *
47
 * @license see LICENSE file in root directory
48
 *
49
 * @package Developer
50
 */
51
class DeploymentManaged extends AbstractDeployment {
52
53
    const MAX_CLIENTS_PER_SERVER = 1000;
54
55
    const PRODUCTNAME = "Managed SP";
56
    /**
57
     * the RADIUS port for this SP instance
58
     * 
59
     * @var int
60
     */
61
    public $port;
62
63
    /**
64
     * the shared secret for this SP instance
65
     * 
66
     * @var string
67
     */
68
    public $secret;
69
70
    /**
71
     * the IPv4 address of the RADIUS server for this SP instance (can be NULL)
72
     * 
73
     * @var string
74
     */
75
    public $host4;
76
    
77
    /**
78
     * the IPv6 address of the RADIUS server for this SP instance (can be NULL)
79
     * 
80
     * @var string
81
     */
82
    public $host6;
83
    /**
84
     * the RADIUS server instance for this SP instance
85
     * 
86
     * @var string
87
     */
88
    public $radius_instance;
89
90
    /**
91
     * Class constructor for existing deployments (use 
92
     * IdP::newDeployment() to actually create one). Retrieves all 
93
     * attributes from the DB and stores them in the priv_ arrays.
94
     * 
95
     * @param IdP $idpObject       optionally, the institution to which this Profile belongs. Saves the construction of the IdP instance. If omitted, an extra query and instantiation is executed to find out.
0 ignored issues
show
Coding Style introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
96
     * @param string|int $deploymentIdRaw identifier of the deployment in the DB
97
     */
98
    public function __construct($idpObject, $deploymentIdRaw) {
99
        parent::__construct($idpObject, $deploymentIdRaw); // we now have access to our INST database handle and logging
100
        $this->type = AbstractDeployment::DEPLOYMENTTYPE_MANAGED;
101
        if (!is_numeric($deploymentIdRaw)) {
102
            throw new Exception("Managed SP instances have to have a numeric identifier");
103
        }
104
        $propertyQuery = "SELECT status,port,secret,radius_instance FROM deployment WHERE deployment_id = ?";
105
        $queryExec = $this->databaseHandle->exec($propertyQuery, "i", $deploymentIdRaw);
106
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $queryExec)) {
107
            if ($iterator->secret == NULL && $iterator->radius_instance == NULL) {
108
                // we are instantiated for the first time, initialise us
109
                $details = $this->initialise();
110
                $this->port = $details["port"];
111
                $this->secret = $details["secret"];
112
                $this->radius_instance = $details["radius_instance"];
113
                $this->status = AbstractDeployment::INACTIVE;
114
            } else {
115
                $this->port = $iterator->port;
116
                $this->secret = $iterator->secret;
117
                $this->radius_instance = $iterator->radius_instance;
118
                $this->status = $iterator->status;
119
            }
120
        }
121
        $serverdetails = $this->databaseHandle->exec("SELECT radius_ip4, radius_ip6 FROM managed_sp_servers WHERE server_id = '$this->radius_instance'");
122
        while ($iterator2 = mysqli_fetch_object($serverdetails)) {
0 ignored issues
show
Bug introduced by
It seems like $serverdetails can also be of type true; however, parameter $result of mysqli_fetch_object() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        while ($iterator2 = mysqli_fetch_object(/** @scrutinizer ignore-type */ $serverdetails)) {
Loading history...
123
            $this->host4 = $iterator2->radius_ip4;
124
            $this->host6 = $iterator2->radius_ip6;
125
        }
126
    }
127
128
    /**
129
     * initialises a new SP
130
     * 
131
     * @return array details of the SP as generated during initialisation
132
     * @throws Exception
133
     */
134
    private function initialise() {
135
        // find a server near us (list of all servers, ordered by distance)
136
        $servers = $this->databaseHandle->exec("SELECT server_id, location_lon, location_lat FROM managed_sp_servers");
137
        $ourserver = [];
138
        // TODO: for ease of prototyping, no particular order - add location-based selection later
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
139
        while ($iterator = mysqli_fetch_object($servers)) {
0 ignored issues
show
Bug introduced by
It seems like $servers can also be of type true; however, parameter $result of mysqli_fetch_object() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
        while ($iterator = mysqli_fetch_object(/** @scrutinizer ignore-type */ $servers)) {
Loading history...
140
            $clientCount = $this->databaseHandle->exec("SELECT count(port) AS tenants FROM deployment WHERE radius_instance = '$iterator->server_id'");
141
            while ($iterator2 = mysqli_fetch_object($clientCount)) {
142
                $clients = $iterator2->tenants;
143
                if ($clients < DeploymentManaged::MAX_CLIENTS_PER_SERVER) {
144
                    $ourserver[] = $iterator->server_id;
145
                }
146
                if ($clients > DeploymentManaged::MAX_CLIENTS_PER_SERVER * 0.9) {
147
                    $this->loggerInstance->debug(1, "A RADIUS server for Managed SP (" . $iterator->server_id . ") is serving at more than 90% capacity!");
148
                }
149
            }
150
        }
151
        if (count($ourserver) == 0) {
152
            throw new Exception("No available server found for new SP!");
153
        }
154
        // now, find an unused port in our preferred server
155
        $foundFreePort = 0;
156
        while ($foundFreePort == 0) {
157
            $portCandidate = random_int(1025, 65535);
158
            $check = $this->databaseHandle->exec("SELECT port FROM deployment WHERE radius_instance = '" . $ourserver[0] . "' AND port = $portCandidate");
159
            if (mysqli_num_rows($check) == 0) {
0 ignored issues
show
Bug introduced by
It seems like $check can also be of type true; however, parameter $result of mysqli_num_rows() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
            if (mysqli_num_rows(/** @scrutinizer ignore-type */ $check) == 0) {
Loading history...
160
                $foundFreePort = $portCandidate;
161
            }
162
        };
163
        // and make up a shared secret that is halfways readable
164
        $futureSecret = $this->randomString(16, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
165
        $this->databaseHandle->exec("UPDATE deployment SET radius_instance = '".$ourserver[0]."', port = $foundFreePort, secret = '$futureSecret' WHERE deployment_id = $this->identifier");
166
        return ["port" => $foundFreePort, "secret" => $futureSecret, "radius_instance" => $ourserver[0]];
167
    }
168
169
    /**
170
     * update the last_changed timestamp for this deployment
171
     * 
172
     * @return void
173
     */
174
    public function updateFreshness() {
175
        $this->databaseHandle->exec("UPDATE deployment SET last_change = CURRENT_TIMESTAMP WHERE deployment_id = $this->identifier");
176
    }
177
178
    /**
179
     * gets the last-modified timestamp (useful for caching "dirty" check)
180
     * 
181
     * @return string the date in string form, as returned by SQL
182
     */
183
    public function getFreshness() {
184
        $execLastChange = $this->databaseHandle->exec("SELECT last_change FROM deployment WHERE deployment_id = $this->identifier");
185
        // SELECT always returns a resource, never a boolean
186
        if ($freshnessQuery = mysqli_fetch_object(/** @scrutinizer ignore-type */ $execLastChange)) {
187
            return $freshnessQuery->last_change;
188
        }
189
    }
190
191
    /**
192
     * Deletes the deployment from database
193
     * 
194
     * @return void
195
     */
196
    public function destroy() {
197
        $this->databaseHandle->exec("DELETE FROM deployment_option WHERE deployment_id = $this->identifier");
198
        $this->databaseHandle->exec("DELETE FROM deployment WHERE deployment_id = $this->identifier");
199
    }
200
201
}
202