Passed
Push — master ( dd3535...a17294 )
by Stefan
03:56
created

API::uglify()   C

Complexity

Conditions 10
Paths 13

Size

Total Lines 47
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 47
rs 5.1578
c 0
b 0
f 0
cc 10
eloc 36
nc 13
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * ******************************************************************************
5
 * Copyright 2011-2018 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 
6
 * and GN4-2 consortia
7
 *
8
 * License: see the web/copyright.php file in the file structure
9
 * ******************************************************************************
10
 */
11
12
namespace web\lib\admin;
13
14
use Exception;
15
16
require_once(dirname(dirname(dirname(dirname(__FILE__)))) . "/config/_config.php");
17
18
class API {
19
20
    const ERROR_API_DISABLED = 1;
21
    const ERROR_NO_APIKEY = 2;
22
    const ERROR_INVALID_APIKEY = 3;
23
    const ERROR_MISSING_PARAMETER = 4;
24
    const ERROR_INVALID_PARAMETER = 5;
25
    const ERROR_NO_ACTION = 6;
26
    const ERROR_INVALID_ACTION = 7;
27
    const ERROR_MALFORMED_REQUEST = 8;
28
    const ERROR_INTERNAL_ERROR = 9;
29
    const ACTION_NEWINST_BY_REF = "NEWINST-BY-REF";
30
    const ACTION_NEWINST = "NEWINST";
31
    const ACTION_DELINST = "DELINST";
32
    const ACTION_ADMIN_LIST = "ADMIN-LIST";
33
    const ACTION_ADMIN_ADD = "ADMIN-ADD";
34
    const ACTION_ADMIN_DEL = "ADMIN-DEL";
35
    const ACTION_STATISTICS_INST = "STATISTICS-INST";
36
    const ACTION_STATISTICS_FED = "STATISTICS-FED";
37
    const ACTION_NEWPROF_RADIUS = "NEWPROF-RADIUS";
38
    const ACTION_NEWPROF_SB = "NEWPROF-SB";
39
    const ACTION_ENDUSER_NEW = "ENDUSER-NEW";
40
    const ACTION_ENDUSER_DEACTIVATE = "ENDUSER-DEACTIVATE";
41
    const ACTION_ENDUSER_LIST = "ENDUSER-LIST";
42
    const ACTION_TOKEN_NEW = "TOKEN-NEW";
43
    const ACTION_TOKEN_REVOKE = "TOKEN-REVOKE";
44
    const ACTION_TOKEN_LIST = "TOKEN-LIST";
45
    const ACTION_CERT_LIST = "CERT-LIST";
46
    const ACTION_CERT_REVOKE = "CERT-REVOKE";
47
    const AUXATTRIB_ADMINID = "ATTRIB-ADMINID";
48
    const AUXATTRIB_ADMINEMAIL = "ATTRIB-ADMINEMAIL";
49
    const AUXATTRIB_EXTERNALID = "ATTRIB-EXTERNALID";
50
    const AUXATTRIB_CAT_INST_ID = "ATTRIB-CAT-INSTID";
51
    const AUXATTRIB_CAT_PROFILE_ID = "ATTRIB-CAT-PROFILEID";
52
    const AUXATTRIB_PROFILE_REALM = 'ATTRIB-PROFILE-REALM';
53
    const AUXATTRIB_PROFILE_OUTERVALUE = 'ATTRIB-PROFILE-OUTERVALUE';
54
    const AUXATTRIB_PROFILE_TESTUSER = 'ATTRIB-PROFILE-TESTUSER';
55
    const AUXATTRIB_PROFILE_INPUT_HINT = 'ATTRIB-PROFILE-HINTREALM';
56
    const AUXATTRIB_PROFILE_INPUT_VERIFY = 'ATTRIB-PROFILE-VERIFYREALM';
57
    const AUXATTRIB_PROFILE_EAPTYPE = "ATTRIB-PROFILE-EAPTYPE";
58
59
    /*
60
     * ACTIONS consists of a list of keywords, and associated REQuired and OPTional parameters
61
     * 
62
     */
63
    const ACTIONS = [
64
        # inst-level actions
65
        API::ACTION_NEWINST_BY_REF => [
66
            "REQ" => [API::AUXATTRIB_EXTERNALID,],
67
            "OPT" => [
68
                'general:geo_coordinates',
69
                'general:logo_file',
70
                'media:SSID',
71
                'media:SSID_with_legacy',
72
                'media:wired',
73
                'media:remove_SSID',
74
                'media:consortium_OI',
75
                'media:force_proxy',
76
                'support:email',
77
                'support:info_file',
78
                'support:phone',
79
                'support:url'
80
            ],
81
        ],
82
        API::ACTION_NEWINST => [
83
            "REQ" => [],
84
            "OPT" => [
85
                'general:instname',
86
                'general:geo_coordinates',
87
                'general:logo_file',
88
                'media:SSID',
89
                'media:SSID_with_legacy',
90
                'media:wired',
91
                'media:remove_SSID',
92
                'media:consortium_OI',
93
                'media:force_proxy',
94
                'support:email',
95
                'support:info_file',
96
                'support:phone',
97
                'support:url'
98
            ],
99
        ],
100
        API::ACTION_DELINST => [
101
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
102
            "OPT" => []
103
        ],
104
        # inst administrator management
105
        API::ACTION_ADMIN_LIST => [
106
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
107
            "OPT" => []
108
        ],
109
        API::ACTION_ADMIN_ADD => [
110
            "REQ" => [
111
                API::AUXATTRIB_ADMINID,
112
                API::AUXATTRIB_CAT_INST_ID
113
            ],
114
            "OPT" => [API::AUXATTRIB_ADMINEMAIL]
115
        ],
116
        API::ACTION_ADMIN_DEL => [
117
            "REQ" => [
118
                API::AUXATTRIB_ADMINID,
119
                API::AUXATTRIB_CAT_INST_ID
120
            ],
121
            "OPT" => []
122
        ],
123
        # statistics
124
        API::ACTION_STATISTICS_INST => [
125
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
126
            "OPT" => []
127
        ],
128
        API::ACTION_STATISTICS_FED => [
129
            "REQ" => [],
130
            "OPT" => []
131
        ],
132
        # RADIUS profile actions
133
        API::ACTION_NEWPROF_RADIUS => [
134
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
135
            "OPT" => [
136
                'eap:ca_file',
137
                'eap:server_name',
138
                'media:SSID',
139
                'media:SSID_with_legacy',
140
                'media:wired',
141
                'media:remove_SSID',
142
                'media:consortium_OI',
143
                'media:force_proxy',
144
                'profile:name',
145
                'profile:customsuffix',
146
                'profile:description',
147
                'profile:production',
148
                'support:email',
149
                'support:info_file',
150
                'support:phone',
151
                'support:url',
152
                API::AUXATTRIB_PROFILE_INPUT_HINT,
153
                API::AUXATTRIB_PROFILE_INPUT_VERIFY,
154
                API::AUXATTRIB_PROFILE_OUTERVALUE,
155
                API::AUXATTRIB_PROFILE_REALM,
156
                API::AUXATTRIB_PROFILE_TESTUSER,
157
                API::AUXATTRIB_PROFILE_EAPTYPE,
158
            ]
159
        ],
160
        # Silverbullet profile actions
161
        API::ACTION_NEWPROF_SB => [
162
            "REQ" => [],
163
            "OPT" => []
164
        ],
165
        API::ACTION_ENDUSER_NEW => [
166
            "REQ" => [],
167
            "OPT" => []
168
        ],
169
        API::ACTION_ENDUSER_DEACTIVATE => [
170
            "REQ" => [],
171
            "OPT" => []
172
        ],
173
        API::ACTION_ENDUSER_LIST => [
174
            "REQ" => [],
175
            "OPT" => []
176
        ],
177
        API::ACTION_TOKEN_NEW => [
178
            "REQ" => [],
179
            "OPT" => []
180
        ],
181
        API::ACTION_TOKEN_REVOKE => [
182
            "REQ" => [],
183
            "OPT" => []
184
        ],
185
        API::ACTION_TOKEN_LIST => [
186
            "REQ" => [],
187
            "OPT" => []
188
        ],
189
        API::ACTION_CERT_LIST => [
190
            "REQ" => [],
191
            "OPT" => []
192
        ],
193
        API::ACTION_CERT_REVOKE => [
194
            "REQ" => [],
195
            "OPT" => []
196
        ],
197
    ];
198
199
    /**
200
     *
201
     * @var \web\lib\common\InputValidation
202
     */
203
    private $validator;
204
205
    public function __construct() {
206
        $this->validator = new \web\lib\common\InputValidation();
207
    }
208
209
    /**
210
     * Only leave attributes in the request which are related to the ACTION.
211
     * Also sanitise by enforcing LANG attribute in multi-lang attributes.
212
     * 
213
     * @param array $inputJson the incoming JSON request
214
     * @param \core\Federation $fedObject the federation the user is acting within
215
     * @return array the scrubbed attributes
216
     */
217
    public function scrub($inputJson, $fedObject) {
218
        $optionInstance = \core\Options::instance();
219
        $parameters = [];
220
        $allPossibleAttribs = array_merge(API::ACTIONS[$inputJson['ACTION']]['REQ'], API::ACTIONS[$inputJson['ACTION']]['OPT']);
221
        // some actions don't need parameters. Don't get excited when there aren't any.
222
        if (!isset($inputJson['PARAMETERS'])) {
223
            $inputJson['PARAMETERS'] = [];
224
        }
225
        foreach ($inputJson['PARAMETERS'] as $number => $oneIncomingParam) {
226
            // index has to be an integer
227
            if (!is_int($number)) {
228
                continue;
229
            }
230
            // do we actually have a value?
231
            if (!array_key_exists("VALUE", $oneIncomingParam)) {
232
                continue;
233
            }
234
            // is this multi-lingual, and not an AUX attrib? Then check for presence of LANG and CONTENT before considering to add
235
            if (!preg_match("/^ATTRIB-/", $oneIncomingParam['NAME'])) {
236
                $optionProperties = $optionInstance->optionType($oneIncomingParam['NAME']);
237
                if ($optionProperties["flag"] == "ML" && !array_key_exists("LANG", $oneIncomingParam)) {
238
                    continue;
239
                }
240
            } else { // sanitise the AUX attr 
241
                switch ($oneIncomingParam['NAME']) {
242
                    case API::AUXATTRIB_CAT_INST_ID:
243
                        try {
244
                            $inst = $this->validator->IdP($oneIncomingParam['VALUE']);
245
                        } catch (Exception $e) {
246
                            continue;
247
                        }
248
                        if (strtoupper($inst->federation) != strtoupper($fedObject->tld)) {
249
                            // IdP in different fed, scrub it.
250
                            continue;
251
                        }
252
                        break;
253
                    case API::AUXATTRIB_ADMINEMAIL:
254
                        if ($this->validator->email($oneIncomingParam['VALUE']) === FALSE) {
255
                            continue;
256
                        }
257
                        break;
258
                    case API::AUXATTRIB_ADMINID:
259
                        try {
260
                            $oneIncomingParam['VALUE'] = $this->validator->string($oneIncomingParam['VALUE']);
261
                        } catch (Exception $e) {
262
                            continue;
263
                        }
264
                        break;
265
                    default:
266
                        continue;
267
                }
268
            }
269
            if (in_array($oneIncomingParam['NAME'], $allPossibleAttribs)) {
270
                $parameters[$number] = $oneIncomingParam;
271
            }
272
        }
273
        return $parameters;
274
    }
275
276
    /**
277
     * extracts the first occurence of a given parameter name from the set of inputs
278
     * 
279
     * @param array $inputs incoming set of arrays
280
     * @param string $expected attribute that is to be extracted
281
     * @return mixed the value, or FALSE if none was found
282
     */
283
    public function firstParameterInstance($inputs, $expected) {
284
        foreach ($inputs as $attrib) {
285
            if ($attrib['NAME'] == $expected) {
286
                return $attrib['VALUE'];
287
            }
288
        }
289
        return FALSE;
290
    }
291
292
    /**
293
     * we are coercing the submitted JSON-style parameters into the same format
294
     * we use for the HTML POST user-interactively.
295
     * That's ugly, hence the function name.
296
     * 
297
     * @param array $parameters
298
     */
299
    public function uglify($parameters) {
300
        $coercedInline = [];
301
        $coercedFile = [];
302
        $optionObject = \core\Options::instance();
303
        $cat = new \core\CAT();
304
        $dir = $cat->createTemporaryDirectory('test');
305
        foreach ($parameters as $number => $oneAttrib) {
306
            if (preg_match("/^ATTRIB-/", $oneAttrib['NAME'])) {
307
                continue;
308
            }
309
            $optionInfo = $optionObject->optionType($oneAttrib['NAME']);
310
            $basename = "S$number";
311
            $extension = "";
312
            switch ($optionInfo['type']) {
313
314
                case \core\Options::TYPECODE_COORDINATES:
315
                    $extension = \core\Options::TYPECODE_TEXT;
316
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
317
                    $coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
318
                    break;
319
                case \core\Options::TYPECODE_TEXT:
320
                // fall-through: they all get the same treatment
321
                case \core\Options::TYPECODE_BOOLEAN:
322
                // fall-through: they all get the same treatment
323
                case \core\Options::TYPECODE_STRING:
324
                // fall-through: they all get the same treatment
325
                case \core\Options::TYPECODE_INTEGER:
326
                    $extension = $optionInfo['type'];
327
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
328
                    $coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
329
                    if ($optionInfo['flag'] == "ML") {
330
                        $coercedInline["value"][$basename . "-lang"] = $oneAttrib['LANG'];
331
                    }
332
                    break;
333
                case \core\Options::TYPECODE_FILE:
334
                    // binary data is expected in base64 encoding. This is true
335
                    // also for PEM files!
336
                    $extension = $optionInfo['type'];
337
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
338
                    file_put_contents($dir['dir'] . "/" . $basename . "-" . $extension, base64_decode($oneAttrib['VALUE']));
339
                    $coercedFile["value"]['tmp_name'][$basename . "-" . $extension] = $dir['dir'] . "/" . $basename . "-" . $extension;
340
                    break;
341
                default:
342
                    throw new Exception("We don't seem to know this type code!");
343
            }
344
        }
345
        return ["POST" => $coercedInline, "FILES" => $coercedFile];
346
    }
347
348
    public function returnError($code, $description) {
349
        echo json_encode(["result" => "ERROR", "details" => ["errorcode" => $code, "description" => $description]], JSON_PRETTY_PRINT);
350
        exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
351
    }
352
353
    public function returnSuccess($details) {
354
        echo json_encode(["result" => "SUCCESS", "details" => $details], JSON_PRETTY_PRINT);
355
        exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
356
    }
357
358
}
359