Passed
Push — master ( 9b05f5...390067 )
by Stefan
03:53
created

API::uglify()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 44
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 4.909
c 0
b 0
f 0
cc 9
eloc 34
nc 12
nop 1
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 ACTION_NEWINST_BY_REF = "NEWINST-BY-REF";
29
    const ACTION_NEWINST = "NEWINST";
30
    const ACTION_DELINST = "DELINST";
31
    const ACTION_ADMIN_LIST = "ADMIN-LIST";
32
    const ACTION_ADMIN_ADD = "ADMIN-ADD";
33
    const ACTION_ADMIN_DEL = "ADMIN-DEL";
34
    const ACTION_STATISTICS_INST = "STATISTICS-INST";
35
    const ACTION_STATISTICS_FED = "STATISTICS-FED";
36
    const ACTION_NEWPROF_RADIUS = "NEWPROF-RADIUS";
37
    const ACTION_NEWPROF_SB = "NEWPROF-SB";
38
    const ACTION_ENDUSER_NEW = "ENDUSER-NEW";
39
    const ACTION_ENDUSER_DEACTIVATE = "ENDUSER-DEACTIVATE";
40
    const ACTION_ENDUSER_LIST = "ENDUSER-LIST";
41
    const ACTION_TOKEN_NEW = "TOKEN-NEW";
42
    const ACTION_TOKEN_REVOKE = "TOKEN-REVOKE";
43
    const ACTION_TOKEN_LIST = "TOKEN-LIST";
44
    const ACTION_CERT_LIST = "CERT-LIST";
45
    const ACTION_CERT_REVOKE = "CERT-REVOKE";
46
    const AUXATTRIB_ADMINID = "ATTRIB-ADMINID";
47
    const AUXATTRIB_ADMINEMAIL = "ATTRIB-ADMINEMAIL";
48
    const AUXATTRIB_EXTERNALID = "ATTRIB-EXTERNALID";
49
    const AUXATTRIB_CAT_INST_ID = "ATTRIB-CAT-INSTID";
50
51
    /*
52
     * ACTIONS consists of a list of keywords, and associated REQuired and OPTional parameters
53
     * 
54
     */
55
    const ACTIONS = [
56
        # inst-level actions
57
        API::ACTION_NEWINST_BY_REF => [
58
            "REQ" => [API::AUXATTRIB_EXTERNALID,],
59
            "OPT" => ['general:geo_coordinates', 'general:logo_file', 'media:SSID', 'media:SSID_with_legacy', 'media:wired', 'media:remove_SSID', 'media:consortium_OI', 'media:force_proxy', 'support:email', 'support:info_file', 'support:phone', 'support:url'],
60
        ],
61
        API::ACTION_NEWINST => [
62
            "REQ" => [],
63
            "OPT" => ['general:instname', 'general:geo_coordinates', 'general:logo_file', 'media:SSID', 'media:SSID_with_legacy', 'media:wired', 'media:remove_SSID', 'media:consortium_OI', 'media:force_proxy', 'support:email', 'support:info_file', 'support:phone', 'support:url'],
64
        ],
65
        API::ACTION_DELINST => [
66
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
67
            "OPT" => []
68
        ],
69
        # inst administrator management
70
        API::ACTION_ADMIN_LIST => [
71
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
72
            "OPT" => []
73
        ],
74
        API::ACTION_ADMIN_ADD => [
75
            "REQ" => [API::AUXATTRIB_ADMINID, API::AUXATTRIB_CAT_INST_ID],
76
            "OPT" => [API::AUXATTRIB_ADMINEMAIL]
77
        ],
78
        API::ACTION_ADMIN_DEL => [
79
            "REQ" => [API::AUXATTRIB_ADMINID, API::AUXATTRIB_CAT_INST_ID],
80
            "OPT" => []
81
        ],
82
        # statistics
83
        API::ACTION_STATISTICS_INST => [
84
            "REQ" => [API::AUXATTRIB_CAT_INST_ID],
85
            "OPT" => []
86
        ],
87
        API::ACTION_STATISTICS_FED => [
88
            "REQ" => [],
89
            "OPT" => []
90
        ],
91
        # RADIUS profile actions
92
        API::ACTION_NEWPROF_RADIUS => [
93
            "REQ" => [],
94
            "OPT" => []
95
        ],
96
        # Silverbullet profile actions
97
        API::ACTION_NEWPROF_SB => [
98
            "REQ" => [],
99
            "OPT" => []
100
        ],
101
        API::ACTION_ENDUSER_NEW => [
102
            "REQ" => [],
103
            "OPT" => []
104
        ],
105
        API::ACTION_ENDUSER_DEACTIVATE => [
106
            "REQ" => [],
107
            "OPT" => []
108
        ],
109
        API::ACTION_ENDUSER_LIST => [
110
            "REQ" => [],
111
            "OPT" => []
112
        ],
113
        API::ACTION_TOKEN_NEW => [
114
            "REQ" => [],
115
            "OPT" => []
116
        ],
117
        API::ACTION_TOKEN_REVOKE => [
118
            "REQ" => [],
119
            "OPT" => []
120
        ],
121
        API::ACTION_TOKEN_LIST => [
122
            "REQ" => [],
123
            "OPT" => []
124
        ],
125
        API::ACTION_CERT_LIST => [
126
            "REQ" => [],
127
            "OPT" => []
128
        ],
129
        API::ACTION_CERT_REVOKE => [
130
            "REQ" => [],
131
            "OPT" => []
132
        ],
133
    ];
134
135
    /**
136
     *
137
     * @var \web\lib\common\InputValidation
138
     */
139
    private $validator;
140
141
    public function __construct() {
142
        $this->validator = new \web\lib\common\InputValidation();
143
    }
144
145
    /**
146
     * Only leave attributes in the request which are related to the ACTION.
147
     * Also sanitise by enforcing LANG attribute in multi-lang attributes.
148
     * 
149
     * @param array $inputJson the incoming JSON request
150
     * @param \core\Federation $fedObject the federation the user is acting within
151
     * @return array the scrubbed attributes
152
     */
153
    public function scrub($inputJson, $fedObject) {
154
        $optionInstance = \core\Options::instance();
155
        $parameters = [];
156
        $allPossibleAttribs = array_merge(API::ACTIONS[$inputJson['ACTION']]['REQ'], API::ACTIONS[$inputJson['ACTION']]['OPT']);
157
        // some actions don't need parameters. Don't get excited when there aren't any.
158
        if (!isset($inputJson['PARAMETERS'])) {
159
            $inputJson['PARAMETERS'] = [];
160
        }
161
        foreach ($inputJson['PARAMETERS'] as $number => $oneIncomingParam) {
162
            // index has to be an integer
163
            if (!is_int($number)) {
164
                continue;
165
            }
166
            // do we actually have a value?
167
            if (!array_key_exists("VALUE", $oneIncomingParam)) {
168
                continue;
169
            }
170
            // is this multi-lingual, and not an AUX attrib? Then check for presence of LANG and CONTENT before considering to add
171
            if (!preg_match("/^ATTRIB-/", $oneIncomingParam['NAME'])) {
172
                $optionProperties = $optionInstance->optionType($oneIncomingParam['NAME']);
173
                if ($optionProperties["flag"] == "ML" && !array_key_exists("LANG", $oneIncomingParam)) {
174
                    continue;
175
                }
176
            } else { // sanitise the AUX attr 
177
                switch ($oneIncomingParam['NAME']) {
178
                    case API::AUXATTRIB_CAT_INST_ID:
179
                        try {
180
                            $inst = $this->validator->IdP($oneIncomingParam['VALUE']);
181
                        } catch (Exception $e) {
182
                            continue;
183
                        }
184
                        if (strtoupper($inst->federation) != strtoupper($fedObject->tld)) {
185
                            // IdP in different fed, scrub it.
186
                            continue;
187
                        }
188
                        break;
189
                    case API::AUXATTRIB_ADMINEMAIL:
190
                        if ($this->validator->email($oneIncomingParam['VALUE']) === FALSE) {
191
                            continue;
192
                        }
193
                        break;
194
                    case API::AUXATTRIB_ADMINID:
195
                        try {
196
                            $oneIncomingParam['VALUE'] = $this->validator->string($oneIncomingParam['VALUE']);
197
                        } catch (Exception $e) {
198
                            continue;
199
                        }
200
                        break;
201
                    default:
202
                        continue;
203
                }
204
            }
205
            if (in_array($oneIncomingParam['NAME'], $allPossibleAttribs)) {
206
                $parameters[$number] = $oneIncomingParam;
207
            }
208
        }
209
        return $parameters;
210
    }
211
212
    public function firstParameterInstance($inputs, $expected) {
213
        foreach ($inputs as $attrib) {
214
            if ($attrib['NAME'] == $expected) {
215
                return $attrib['VALUE'];
216
            }
217
        }
218
        return FALSE;
219
    }
220
221
    /**
222
     * we are coercing the submitted JSON-style parameters into the same format
223
     * we use for the HTML POST user-interactively.
224
     * That's ugly, hence the function name.
225
     * 
226
     * @param array $parameters
227
     */
228
    public function uglify($parameters) {
229
        $coercedInline = [];
230
        $coercedFile = [];
231
        $optionObject = \core\Options::instance();
232
        $cat = new \core\CAT();
233
        $dir = $cat->createTemporaryDirectory('test');
234
        foreach ($parameters as $number => $oneAttrib) {
235
            $optionInfo = $optionObject->optionType($oneAttrib['NAME']);
236
            $basename = "S$number";
237
            $extension = "";
238
            switch ($optionInfo['type']) {
239
240
                case \core\Options::TYPECODE_COORDINATES:
241
                    $extension = \core\Options::TYPECODE_TEXT;
242
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
243
                    $coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
244
                    break;
245
                case \core\Options::TYPECODE_TEXT:
246
                // fall-through: they all get the same treatment
247
                case \core\Options::TYPECODE_BOOLEAN:
248
                // fall-through: they all get the same treatment
249
                case \core\Options::TYPECODE_STRING:
250
                // fall-through: they all get the same treatment
251
                case \core\Options::TYPECODE_INTEGER:
252
                    $extension = $optionInfo['type'];
253
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
254
                    $coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
255
                    if ($optionInfo['flag'] == "ML") {
256
                        $coercedInline["value"][$basename . "-lang"] = $oneAttrib['LANG'];
257
                    }
258
                    break;
259
                case \core\Options::TYPECODE_FILE:
260
                    // binary data is expected in base64 encoding. This is true
261
                    // also for PEM files!
262
                    $extension = $optionInfo['type'];
263
                    $coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
264
                    file_put_contents($dir['dir'] . "/" . $basename . "-" . $extension, base64_decode($oneAttrib['VALUE']));
265
                    $coercedFile["value"]['tmp_name'][$basename . "-" . $extension] = $dir['dir'] . "/" . $basename . "-" . $extension;
266
                    break;
267
                default:
268
                    throw new Exception("We don't seem to know this type code!");
269
            }
270
        }
271
        return ["POST" => $coercedInline, "FILES" => $coercedFile];
272
    }
273
274
    public function return_error($code, $description) {
275
        echo json_encode(["result" => "ERROR", "details" => ["errorcode" => $code, "description" => $description]], JSON_PRETTY_PRINT);
276
        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...
277
    }
278
279
    public function return_success($details) {
280
        echo json_encode(["result" => "SUCCESS", "details" => $details], JSON_PRETTY_PRINT);
281
        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...
282
    }
283
284
}
285