• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 19162851045

07 Nov 2025 08:38AM UTC coverage: 76.906% (-0.01%) from 76.92%
19162851045

Pull #11333

github

web-flow
Merge ee922ebb7 into 271e9617c
Pull Request #11333: Grant access to MapStore resources by IP #972

32064 of 49813 branches covered (64.37%)

74 of 104 new or added lines in 4 files covered. (71.15%)

199 existing lines in 11 files now uncovered.

39872 of 51845 relevant lines covered (76.91%)

37.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.57
/web/client/api/GeoStoreDAO.js
1
/**
2
 * Copyright 2015-2016, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
*/
8
import { castArray, findIndex, get, has, isArray, merge, omit, pick } from 'lodash';
9

10
import uuidv1 from 'uuid/v1';
11
import xml2js from 'xml2js';
12
const xmlBuilder = new xml2js.Builder();
1✔
13

14
import axios from '../libs/ajax';
15
import ConfigUtils from '../utils/ConfigUtils';
16
import { registerErrorParser } from '../utils/LocaleUtils';
17
import { encodeUTF8 } from '../utils/EncodeUtils';
18

19

20
const generateMetadata = (name = "", description = "", advertised = true) =>
1✔
21
    "<description><![CDATA[" + description + "]]></description>"
21✔
22
    + "<metadata></metadata>"
23
    + "<name><![CDATA[" + (name) + "]]></name>"
24
    + "<advertised>" + advertised + "</advertised>";
25
const createAttributeList = (metadata = {}) => {
1✔
26
    const attributes = metadata.attributes || omit(metadata, ["name", "description", "id", "advertised"]);
21✔
27

28
    const xmlAttrs = Object.keys(attributes).map((key) => {
21✔
29
        return "<attribute><name>" + key + "</name><value><![CDATA[" + attributes[key] + "]]></value><type>STRING</type></attribute>";
9✔
30
    });
31
    let attributesSection = "";
21✔
32
    if (xmlAttrs.length > 0) {
21✔
33
        attributesSection = "<Attributes>" + xmlAttrs.join("") + "</Attributes>";
6✔
34
    }
35
    return attributesSection;
21✔
36
};
37
let parseOptions = (opts) => opts;
97✔
38

39
let parseAdminGroups = (groupsObj) => {
1✔
40
    if (!groupsObj || !groupsObj.UserGroupList || !groupsObj.UserGroupList.UserGroup) return [];
2!
41

42
    const pickFromObj = (obj) => pick(obj, ["id", "groupName", "description"]);
3✔
43
    if (isArray(groupsObj.UserGroupList.UserGroup)) {
2✔
44
        return groupsObj.UserGroupList.UserGroup.filter(obj => !!obj.id).map(pickFromObj);
2✔
45
    }
46
    return [pickFromObj(groupsObj.UserGroupList.UserGroup)];
1✔
47
};
48

49
let parseUserGroups = (groupsObj) => {
1✔
50
    if (!groupsObj || !groupsObj.User || !groupsObj.User.groups || !groupsObj.User.groups.group || !isArray(groupsObj.User.groups.group)) {
×
51
        if (has(groupsObj.User.groups.group, "id", "groupName")) {
×
52
            return [groupsObj.User.groups.group];
×
53
        }
54
        return [];
×
55
    }
56
    return groupsObj.User.groups.group.filter(obj => !!obj.id).map((obj) => pick(obj, ["id", "groupName", "description"]));
×
57
};
58

59
const boolToString = (b) => b ? "true" : "false";
12✔
60

61
const errorParser = {
1✔
62
    /**
63
     * Returns localized message for geostore map errors
64
     * @param  {object} e error object
65
     * @return {object} {title, message}
66
     */
67
    mapsError: e => {
68
        if (e.status === 403 || e.status === 404 || e.status === 409 || e.status === 500) {
4✔
69
            return {
2✔
70
                title: 'map.mapError.errorTitle',
71
                message: 'map.mapError.error' + e.status
72
            };
73
        }
74
        return {
2✔
75
            title: 'map.mapError.errorTitle',
76
            message: 'map.mapError.errorDefault'
77
        };
78
    }
79
};
80

81
registerErrorParser('geostore', {...errorParser});
1✔
82

83
/**
84
 * API for local config
85
 */
86
const Api = {
1✔
87
    createAttributeList,
88
    generateMetadata,
89
    authProviderName: "geostore",
90
    /**
91
     * add the geostore base url, default is /mapstore/rest/geostore/
92
     * @param {object} options axios options
93
     * @return {object} options with baseURL
94
     */
95
    addBaseUrl: function(options) {
96
        return Object.assign({}, options, {baseURL: options && options.baseURL || ConfigUtils.getDefaults().geoStoreUrl});
138✔
97
    },
98
    getData: function(id, options) {
99
        const url = "data/" + id;
18✔
100
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
18✔
101
            return response.data;
16✔
102
        });
103
    },
104
    getResource: function(resourceId, options) {
105
        return axios.get(
×
106
            "resources/resource/" + resourceId,
107
            this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
×
108
    },
109
    getResourceIdByName: function(category, name, options) {
110
        return axios.get(
11✔
111
            "misc/category/name/" + category + "/resource/name/" + name,
112
            this.addBaseUrl(parseOptions(options))).then(response => get(response, 'data.Resource.id'));
7✔
113
    },
114
    getResourceDataByName: function(category, name, options) {
115
        return axios.get(
2✔
116
            "misc/category/name/" + category + "/resource/name/" + name + "/data",
117
            this.addBaseUrl(parseOptions(options))).then(response => get(response, 'data'));
2✔
118
    },
119
    getShortResource: function(resourceId, options) {
120
        return axios.get(
9✔
121
            "extjs/resource/" + resourceId,
122
            this.addBaseUrl(parseOptions(options))).then(function(response) { return response.data; });
9✔
123
    },
124
    getResourcesByCategory: function(category, query, options) {
125
        const q = query || "*";
5✔
126
        const url = "extjs/search/category/" + category + "/*" + q + "*/thumbnail,details,featured"; // comma-separated list of wanted attributes
5✔
127
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
5✔
128
    },
129
    createCategory: function(category) {
130
        return axios.post(
1✔
131
            "categories",
132
            `<Category><name>${category}</name></Category>`,
133
            this.addBaseUrl({
134
                headers: {
135
                    'Content-Type': "application/xml"
136
                }
137
            })
138
        ).then(response => response.data);
1✔
139
    },
140
    getUserDetailsBasic: function(username, password, options) {
141
        const url = "users/user/details";
×
142
        return axios.get(url, this.addBaseUrl(merge({
×
143
            auth: {
144
                username: username,
145
                password: password
146
            },
147
            params: {
148
                includeattributes: true
149
            }
150
        }, options))).then(function(response) {
151
            return response.data;
×
152
        });
153
    },
154
    /**
155
     * Gets the user details using the given access token.
156
     * Can be used to finalize access with openID after redirect, using the token passed by the service to retrieve the
157
     * remaining information.
158
     * @param {object} params contains access_token to pass in the bearer header
159
     * @returns {object} user details
160
     */
161
    getUserDetails: function({access_token: accessToken}) {
162
        const url = "users/user/details";
1✔
163
        return axios.get(url, {
1✔
164
            baseURL: "rest/geostore",
165
            headers: {
166
                Authorization: `Bearer ${accessToken}`
167
            },
168
            params: {
169
                includeattributes: true
170
            }
171
        }).then(function(response) {
172
            return response.data;
1✔
173
        });
174
    },
175
    /**
176
     * Gets the tokens for a given identifier, created during openID login.
177
     * @param {string} provider the provider name (e.g. keycloak)
178
     * @param {string} identifier the identifier of the token
179
     * @returns {object}
180
     */
181
    getTokens: function(provider, identifier) {
182
        return axios.get(
1✔
183
            `openid/${provider}/tokens`,
184
            this.addBaseUrl({params: {identifier}})
185
        ).then(response => response.data?.sessionToken);
1✔
186
    },
187
    login: function(username, password, options) {
188
        const url = "session/login";
3✔
189
        let authData;
190
        return axios.post(url, null, this.addBaseUrl(merge((username && password) ? {
3✔
191
            auth: {
192
                username: encodeUTF8(username),
193
                password: password // password is already encoded by axios
194
            }
195
        } : {}, options))).then((response) => {
196
            authData = response.data?.sessionToken ?? response.data;
3✔
197
            return axios.get("users/user/details", this.addBaseUrl(merge({
3✔
198
                headers: {
199
                    'Authorization': 'Bearer ' + authData?.access_token
200
                },
201
                params: {
202
                    includeattributes: true
203
                }
204
            }, options)));
205
        }).then((response) => {
206
            return { ...response.data, ...authData};
3✔
207
        });
208
    },
209
    logout: function() {
210
        const url = "session/logout";
×
211
        return axios.delete(url, this.addBaseUrl({}));
×
212
    },
213
    changePassword: function(user, newPassword, options) {
214
        return axios.put(
×
215
            "users/user/" + user.id, {
216
                User: {
217
                    newPassword
218
                }
219
            },
220
            this.addBaseUrl(merge({
221
                headers: {
222
                    'Content-Type': "application/json"
223
                }
224
            }, options)));
225
    },
226
    updateResourceAttribute: function(resourceId, name, value, type, options) {
227
        return axios.put(
1✔
228
            "resources/resource/" + resourceId + "/attributes/", {
229
                "restAttribute": {
230
                    name,
231
                    value
232
                }
233
            },
234
            this.addBaseUrl(merge({
235
                headers: {
236
                    'Content-Type': "application/json"
237
                }
238
            }, options)));
239
    },
240
    getResourceAttribute: function(resourceId, name, options = {}) {
×
241
        return axios.get(
×
242
            "resources/resource/" + resourceId + "/attributes/" + name,
243
            this.addBaseUrl(merge({
244
                headers: {
245
                    'Content-Type': "application/xml"
246
                }
247
            }, options)));
248
    },
249
    getResourceAttributes: function(resourceId, options = {}) {
13✔
250
        return axios.get(
13✔
251
            "resources/resource/" + resourceId + "/attributes",
252
            this.addBaseUrl({
253
                headers: {
254
                    'Accept': "application/json"
255
                },
256
                ...options
257
            })).then(({ data } = {}) => data)
12!
258
            .then(data => castArray(get(data, "AttributeList.Attribute") || []))
12✔
259
            .then(attributes => attributes || []);
12!
260
    },
261
    /**
262
     * same of getPermissions but clean data properly and returns only the array of rules.
263
     */
264
    getResourcePermissions: function(resourceId, options, withSelector = true) {
6✔
265
        return Api.getPermissions(resourceId, options)
6✔
266
            .then(rl => castArray( withSelector ? get(rl, 'SecurityRuleList.SecurityRule') : rl))
6!
267
            .then(rules => (rules && rules[0] && rules[0] !== "") ? rules : []);
6✔
268
    },
269
    putResourceMetadata: function(resourceId, newName, newDescription, advertised, options) {
270
        return axios.put(
×
271
            "resources/resource/" + resourceId,
272
            "<Resource>" + generateMetadata(newName, newDescription, advertised) + "</Resource>",
273
            this.addBaseUrl(merge({
274
                headers: {
275
                    'Content-Type': "application/xml"
276
                }
277
            }, options)));
278
    },
279
    putResourceMetadataAndAttributes: function(resourceId, metadata, options) {
280
        return axios.put(
9✔
281
            "resources/resource/" + resourceId,
282
            "<Resource>" + generateMetadata(metadata.name, metadata.description, metadata.advertised) + createAttributeList(metadata) + "</Resource>",
283
            this.addBaseUrl(merge({
284
                headers: {
285
                    'Content-Type': "application/xml"
286
                }
287
            }, options)));
288
    },
289
    putResource: function(resourceId, content, options) {
290
        return axios.put(
5✔
291
            "data/" + resourceId,
292
            content,
293
            this.addBaseUrl(merge({
294
                headers: {
295
                    'Content-Type': typeof content === 'string' ? "text/plain; charset=utf-8" : 'application/json; charset=utf-8'
5✔
296
                }
297
            }, options)));
298
    },
299
    writeSecurityRules: function(SecurityRuleList = {}) {
×
300
        return "<SecurityRuleList>" +
8✔
301
        (castArray(SecurityRuleList.SecurityRule) || []).flatMap( rule => {
8!
302
            if (rule.canRead || rule.canWrite) {
6!
303
                if (rule.user) {
6✔
304
                    return "<SecurityRule>"
4✔
305
                        + "<canRead>" + boolToString(rule.canRead || rule.canWrite) + "</canRead>"
4!
306
                        + "<canWrite>" + boolToString(rule.canWrite) + "</canWrite>"
307
                        + "<user><id>" + (rule.user.id || "") + "</id><name>" + (rule.user.name || "") + "</name></user>"
10!
308
                        + "</SecurityRule>";
309
                } else if (rule.group) {
2!
310
                    return "<SecurityRule>"
2✔
311
                        + "<canRead>" + boolToString(rule.canRead || rule.canWrite) + "</canRead>"
2!
312
                        + "<canWrite>" + boolToString(rule.canWrite) + "</canWrite>"
313
                        + "<group><id>" + (rule.group.id || "") + "</id><groupName>" + (rule.group.groupName || "") + "</groupName></group>"
4!
314
                        + "</SecurityRule>";
NEW
315
                } else if (rule.ipRanges) {
×
316
                    // Create a separate SecurityRule for each IP range
NEW
317
                    const ipRangesArray = castArray(rule.ipRanges.ipRange);
×
NEW
318
                    return ipRangesArray.map(ipRange =>
×
NEW
319
                        "<SecurityRule>"
×
320
                        + "<canRead>" + boolToString(rule.canRead || rule.canWrite) + "</canRead>"
×
321
                        + "<canWrite>" + boolToString(rule.canWrite) + "</canWrite>"
322
                        + "<ipRanges><ipRange><id>" + (ipRange.id) + "</id></ipRange></ipRanges>"
323
                        + "</SecurityRule>"
324
                    );
325
                }
326
                // NOTE: if rule has no group, user, or ipRanges, it is skipped
327
                // NOTE: if rule is "no read and no write", it is skipped
328
            }
329
            return "";
×
330
        }).join('') + "</SecurityRuleList>";
331
    },
332
    updateResourcePermissions: function(resourceId, securityRules) {
333
        const payload = Api.writeSecurityRules(securityRules.SecurityRuleList);
7✔
334
        return axios.post(
7✔
335
            "resources/resource/" + resourceId + "/permissions",
336
            payload,
337
            this.addBaseUrl({
338
                headers: {
339
                    'Content-Type': "application/xml"
340
                }
341
            }));
342
    },
343
    createResource: function(metadata, data, category, options) {
344
        const name = metadata.name;
10✔
345
        const description = metadata.description || "";
10✔
346
        // filter attributes from the metadata object excluding the default ones
347
        const attributesSection = createAttributeList(metadata);
10✔
348
        return axios.post(
10✔
349
            "resources/",
350
            "<Resource>" + generateMetadata(name, description, metadata.advertised) + "<category><name>" + (category || "") + "</name></category>" +
11✔
351
                attributesSection +
352
                "<store><data><![CDATA[" + (
353
                data
20✔
354
                        && (
355
                            (typeof data === 'object')
9✔
356
                                ? JSON.stringify(data)
357
                                : data)
358
                        || "") + "]]></data></store></Resource>",
359
            this.addBaseUrl(merge({
360
                headers: {
361
                    'Content-Type': "application/xml"
362
                }
363
            }, options)));
364
    },
365
    deleteResource: function(resourceId, options) {
366
        return axios.delete(
3✔
367
            "resources/resource/" + resourceId,
368
            this.addBaseUrl(merge({
369
            }, options)));
370
    },
371
    getUserGroups: function(options) {
372
        const url = "usergroups/";
×
373
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
×
374
            return response.data;
×
375
        });
376
    },
377
    getPermissions: function(mapId, options) {
378
        const url = "resources/resource/" + mapId + "/permissions";
6✔
379
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
6✔
380
    },
381
    getAvailableGroups: function(user) {
382
        if (user && user.role === "ADMIN") {
3✔
383
            return axios.get(
2✔
384
                "usergroups/?all=true&users=false",
385
                this.addBaseUrl({
386
                    headers: {
387
                        'Accept': "application/json"
388
                    }
389
                })).then(function(response) {
390
                return parseAdminGroups(response.data);
2✔
391
            });
392
        }
393
        return axios.get(
1✔
394
            "users/user/details",
395
            this.addBaseUrl({
396
                headers: {
397
                    'Accept': "application/json"
398
                }
399
            })).then(function(response) {
400
            return parseUserGroups(response.data);
×
401
        });
402
    },
403
    getUsers: function(textSearch, options = {}) {
×
404
        const url = "extjs/search/users" + (textSearch ? "/" + textSearch : "");
2!
405
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
406
    },
407
    getUser: function(id, options = {params: {includeattributes: true}}) {
×
408
        const url = "users/user/" + id;
3✔
409
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
3✔
410
    },
411
    updateUser: function(id, user, options) {
412
        const url = "users/user/" + id;
2✔
413
        const postUser = Object.assign({}, user);
2✔
414
        if (postUser.newPassword === "") {
2!
415
            delete postUser.newPassword;
×
416
        }
417
        return axios.put(url, {User: postUser}, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
418
    },
419
    createUser: function(user, options) {
420
        const url = "users/";
2✔
421

422
        return axios.post(url, {User: Api.utils.initUser(user)}, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
423
    },
424
    deleteUser: function(id, options = {}) {
1✔
425
        const url = "users/user/" + id;
1✔
426
        return axios.delete(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
1✔
427
    },
428
    getGroups: function(textSearch, options = {}) {
×
429
        const url = "extjs/search/groups" + (textSearch ? "/" + textSearch : "");
×
430
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
×
431
    },
432
    getGroup: function(id, options = {}) {
×
433
        const url = "usergroups/group/" + id;
2✔
434
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
2✔
435
            const groupLoaded = response.data.UserGroup;
1✔
436
            const users = groupLoaded?.restUsers?.User;
1✔
437
            const attributes = groupLoaded?.attributes;
1✔
438
            return {
1✔
439
                ...groupLoaded,
440
                users: users ? castArray(users) : undefined,
1!
441
                attributes: attributes ? castArray(attributes) : undefined
1!
442
            };
443
        });
444
    },
445
    createGroup: function(group, options) {
446
        const url = "usergroups/";
2✔
447
        let groupId;
448
        return axios.post(url, {UserGroup: {...group}}, this.addBaseUrl(parseOptions(options)))
2✔
449
            .then(function(response) {
450
                groupId = response.data;
2✔
451
                return Api.updateGroupMembers({...group, id: groupId}, options);
2✔
452
            }).then(() => groupId);
2✔
453
    },
454
    updateGroup: function(group, options) {
455
        const id = group?.id;
3✔
456
        const url = `usergroups/group/${id}`;
3✔
457
        return axios.put(url, {UserGroup: {...group}}, this.addBaseUrl(parseOptions(options)))
3✔
458
            .then(function() {
459
                return Api.updateGroupMembers(group, options);
3✔
460
            })
461
            .then(() => id);
3✔
462
    },
463
    updateGroupMembers: function(group, options) {
464
        // No GeoStore API to update group name and description. only update new users
465
        if (group.newUsers) {
5✔
466
            let restUsers = group.users || group.restUsers && group.restUsers.User || [];
3!
467
            restUsers = Array.isArray(restUsers) ? restUsers : [restUsers];
3!
468
            // old users not present in the new users list
469
            let toRemove = restUsers.filter( (user) => findIndex(group.newUsers, u => u.id === user.id) < 0);
3✔
470
            // new users not present in the old users list
471
            let toAdd = group.newUsers.filter( (user) => findIndex(restUsers, u => u.id === user.id) < 0);
3✔
472

473
            // create callbacks
474
            let removeCallbacks = toRemove.map( (user) => () => this.removeUserFromGroup(user.id, group.id, options) );
3✔
475
            let addCallbacks = toAdd.map( (user) => () => this.addUserToGroup(user.id, group.id), options );
3✔
476
            let requests = [...removeCallbacks.map( call => call.call(this)), ...addCallbacks.map(call => call())];
3✔
477
            return axios.all(requests).then(() => {
3✔
478
                return {
3✔
479
                    ...group,
480
                    newUsers: null,
481
                    restUsers: { User: group.newUsers},
482
                    users: group.newUsers
483
                };
484
            });
485
        }
486
        return new Promise( (resolve) => {
2✔
487
            resolve({
2✔
488
                ...group
489
            });
490
        });
491
    },
492
    deleteGroup: function(id, options = {}) {
1✔
493
        const url = "usergroups/group/" + id;
1✔
494
        return axios.delete(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
1✔
495
    },
496
    addUserToGroup(userId, groupId, options = {}) {
3✔
497
        const url = "/usergroups/group/" + userId + "/" + groupId + "/";
3✔
498
        return axios.post(url, null, this.addBaseUrl(parseOptions(options)));
3✔
499
    },
500
    removeUserFromGroup(userId, groupId, options = {}) {
1✔
501
        const url = "/usergroups/group/" + userId + "/" + groupId + "/";
1✔
502
        return axios.delete(url, this.addBaseUrl(parseOptions(options)));
1✔
503
    },
504
    verifySession: function(options) {
505
        const url = "users/user/details";
×
506
        return axios.get(url, this.addBaseUrl(merge({
×
507
            params: {
508
                includeattributes: true
509
            }
510
        }, options))).then(function(response) {
511
            return response.data;
×
512
        });
513
    },
514
    refreshToken: function(accessToken, refreshToken, options) {
515
        const url = "session/refreshToken";
1✔
516
        return axios.post(url, {
1✔
517
            sessionToken: {
518
                access_token: accessToken,
519
                refresh_token: refreshToken
520
            }
521
        }, this.addBaseUrl(parseOptions(options))).then(function(response) {
522
            return response.data?.sessionToken ?? response.data;
1!
523
        });
524
    },
525
    /**
526
     * send a request to /extjs/search/list
527
     * @param  {object} filters
528
     * @param  {object} options additional axios options
529
     * @return {object}
530
     * @example
531
     *
532
     *  const filters = {
533
     *      AND: {
534
     *          ATTRIBUTE: [
535
     *              {
536
     *                  name: ['featured'],
537
     *                  operator: ['EQUAL_TO'],
538
     *                  type: ['STRING'],
539
     *                  value: [true]
540
     *              }
541
     *          ]
542
     *      }
543
     *  }
544
     *
545
     *  searchListByAttributes(filters)
546
     *      .then(results => results)
547
     *      .catch(error => error);
548
     *
549
     */
550
    searchListByAttributes: (filter, options, url = "/extjs/search/list") => {
11✔
551
        const xmlFilter = xmlBuilder.buildObject(filter);
12✔
552
        return axios.post(
12✔
553
            url,
554
            xmlFilter,
555
            Api.addBaseUrl({
556
                ...parseOptions(options),
557
                headers: {
558
                    "Content-Type": "application/xml",
559
                    "Accept": "application/json"
560
                }
561
            })
562
        )
563
            .then(response => response.data);
12✔
564
    },
565
    utils: {
566
        /**
567
         * initialize User with newPassword and UUID
568
         * @param  {object} user The user object
569
         * @return {object}      The user object adapted for creation (newPassword, UUID)
570
         */
571
        initUser: (user) => {
572
            const postUser = Object.assign({}, user);
4✔
573
            if (postUser.newPassword) {
4✔
574
                postUser.password = postUser.newPassword;
2✔
575
            }
576
            // uuid is time-based
577
            const uuidAttr = {
4✔
578
                name: "UUID", value: uuidv1()
579
            };
580
            postUser.attribute = postUser.attribute && postUser.attribute.length > 0 ? [...postUser.attribute, uuidAttr] : [uuidAttr];
4✔
581
            return postUser;
4✔
582
        }
583
    },
584
    errorParser,
585
    /**
586
     * get the available tags
587
     * @param {string} textSearch search text query
588
     * @param {object} options additional axios options
589
     */
590
    getTags: (textSearch, options = {}) => {
1✔
591
        const url = '/resources/tag';
1✔
592
        return axios.get(url, Api.addBaseUrl(parseOptions({
1✔
593
            ...options,
594
            params: {
595
                ...options?.params,
596
                ...(textSearch && { nameLike: textSearch })
2✔
597
            }
598
        }))).then((response) => response.data);
1✔
599
    },
600
    /**
601
     * update/create a tag
602
     * @param {object} tag a tag object { id, name, description, color } (it will create a new tag if id is undefined)
603
     * @param {object} options additional axios options
604
     */
605
    updateTag: (tag = {}, options = {}) => {
2!
606
        const url = `/resources/tag${tag.id ? `/${tag.id}` : ''}`;
2✔
607
        return axios[tag.id ? 'put' : 'post'](
2✔
608
            url,
609
            [
610
                '<Tag>',
611
                `<name><![CDATA[${tag.name}]]></name>`,
612
                `<description><![CDATA[${tag.description}]]></description>`,
613
                `<color>${tag.color}</color>`,
614
                '</Tag>'
615
            ].join(''),
616
            Api.addBaseUrl(
617
                parseOptions({
618
                    ...options,
619
                    headers: {
620
                        'Content-Type': "application/xml"
621
                    }
622
                })
623
            )).then((response) => response.data);
2✔
624
    },
625
    /**
626
     * get the available tags
627
     * @param {string} tagId tag identifier
628
     * @param {object} options additional axios options
629
     */
630
    deleteTag: (tagId, options = {}) => {
1✔
631
        const url = `/resources/tag/${tagId}`;
1✔
632
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
633
    },
634
    /**
635
     * link a tag to a resource
636
     * @param {string} tagId tag identifier
637
     * @param {string} resourceId resource identifier
638
     * @param {object} options additional axios options
639
     */
640
    linkTagToResource: (tagId, resourceId, options) => {
641
        const url = `/resources/tag/${tagId}/resource/${resourceId}`;
1✔
642
        return axios.post(url, undefined, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
643
    },
644
    /**
645
     * unlink a tag from a resource
646
     * @param {string} tagId tag identifier
647
     * @param {string} resourceId resource identifier
648
     * @param {object} options additional axios options
649
     */
650
    unlinkTagFromResource: (tagId, resourceId, options) => {
651
        const url = `/resources/tag/${tagId}/resource/${resourceId}`;
1✔
652
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
653
    },
654
    /**
655
     * add a resource to user favorites
656
     * @param  {string} userId user identifier
657
     * @param  {string} resourceId resource identifier
658
     * @param  {object} options additional axios options
659
     */
660
    addFavoriteResource: (userId, resourceId, options) => {
661
        const url = `/users/user/${userId}/favorite/${resourceId}`;
3✔
662
        return axios.post(url, undefined, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
3✔
663
    },
664
    /**
665
     * remove a resource from user favorites
666
     * @param  {string} userId user identifier
667
     * @param  {string} resourceId resource identifier
668
     * @param  {object} options additional axios options
669
     */
670
    removeFavoriteResource: (userId, resourceId, options) => {
671
        const url = `/users/user/${userId}/favorite/${resourceId}`;
2✔
672
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
2✔
673
    },
674
    getIPRanges: function(options = {}) {
×
NEW
675
        const url = "ipranges/";
×
NEW
676
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data || []; });
×
677
    },
678
    createIPRange: function(ipRange, options) {
NEW
679
        const url = "ipranges/";
×
NEW
680
        const xmlPayload = [
×
681
            '<IPRange>',
682
            `<cidr><![CDATA[${ipRange.cidr || ''}]]></cidr>`,
×
683
            `<description><![CDATA[${ipRange.description || ''}]]></description>`,
×
684
            '</IPRange>'
685
        ].join('');
NEW
686
        return axios.post(url, xmlPayload, this.addBaseUrl(merge({
×
687
            headers: {
688
                'Content-Type': "application/xml"
689
            }
NEW
690
        }, parseOptions(options)))).then(function(response) {return response.data; });
×
691
    },
692
    updateIPRange: function(id, ipRange, options = {}) {
×
NEW
693
        const url = "ipranges/" + id;
×
NEW
694
        const xmlPayload = [
×
695
            '<IPRange>',
696
            `<cidr><![CDATA[${ipRange.cidr || ''}]]></cidr>`,
×
697
            `<description><![CDATA[${ipRange.description || ''}]]></description>`,
×
698
            '</IPRange>'
699
        ].join('');
NEW
700
        return axios.put(url, xmlPayload, this.addBaseUrl(merge({
×
701
            headers: {
702
                'Content-Type': "application/xml"
703
            }
NEW
704
        }, parseOptions(options)))).then(function(response) {return response.data; });
×
705
    },
706
    deleteIPRange: function(id, options = {}) {
×
NEW
707
        const url = "ipranges/" + id;
×
NEW
708
        return axios.delete(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
×
709
    }
710
};
711

712
export default Api;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc