Coverity-CWE 330: Use of Insufficiently Random Values
[osm/NG-UI.git] / src / services / SharedService.ts
1 /*
2  Copyright 2020 TATA ELXSI
3
4  Licensed under the Apache License, Version 2.0 (the 'License');
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15
16  Author: KUMARAN M (kumaran.m@tataelxsi.co.in), RAJESH S (rajesh.s@tataelxsi.co.in), BARATH KUMAR R (barath.r@tataelxsi.co.in)
17  */
18 /**
19  * @file Provider for Shared Service
20  */
21 import { isNullOrUndefined } from 'util';
22 import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
23 import { EventEmitter, Injectable, Output } from '@angular/core';
24 import { FormArray, FormGroup } from '@angular/forms';
25 import { Router } from '@angular/router';
26 import { TranslateService } from '@ngx-translate/core';
27 import {
28     CONSTANTNUMBER,
29     DOMAINS,
30     ERRORDATA,
31     FILESETTINGS,
32     GETAPIURLHEADER,
33     PACKAGEINFO,
34     PAGERSMARTTABLE,
35     SMARTTABLECLASS,
36     TARSETTINGS,
37     TYPESECTION
38 } from 'CommonModel';
39 import { environment } from 'environment';
40 import * as HttpStatus from 'http-status-codes';
41 import * as untar from 'js-untar';
42 import { ActiveToast, ToastrService } from 'ngx-toastr';
43 import * as pako from 'pako';
44 import { RestService } from 'RestService';
45 import { Observable } from 'rxjs';
46 import { map } from 'rxjs/operators';
47
48 /** This is added globally by the tar.js library */
49 // eslint-disable-next-line @typescript-eslint/no-explicit-any
50 declare const Tar: any;
51
52 /**
53  * An Injectable is a class adorned with the @Injectable decorator function.
54  * @Injectable takes a metadata object that tells Angular how to compile and run module code
55  */
56 @Injectable({
57     providedIn: 'root'
58 })
59 /** Exporting a class @exports SharedService */
60 export class SharedService {
61     /** call the parent using event information @private */
62     @Output() public dataEvent: EventEmitter<{}> = new EventEmitter<{}>();
63
64     /** Variables to hold regexp pattern for URL */
65     public REGX_URL_PATTERN: RegExp = new RegExp(/^(http?|ftp|https):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z0-9]{2,15})(:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4})))*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/);
66
67     /** Variables to hold regexp pattern for IP Address */
68     public REGX_IP_PATTERN: RegExp = new RegExp(/^(?:(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(?!$)|$)){4}$/);
69
70     /** Variables to hold regexp pattern for Port Number */
71     public REGX_PORT_PATTERN: RegExp = new RegExp(/^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$/);
72
73     /** Variables to hold regexp pattern for DPID */
74     public REGX_DPID_PATTERN: RegExp = new RegExp(/^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){7}$/);
75
76     /** Variable to hold regexp pattern for password */
77     public REGX_PASSWORD_PATTERN: RegExp = new RegExp(/^.*(?=.{8,})((?=.*[!@#$%^&*()\-_=+{};:,<.>]){1})(?=.*\d)((?=.*[a-z]){1})((?=.*[A-Z]){1}).*$/);
78
79     /** Variables to hold regexp pattern for Latitude */
80     public REGX_LAT_PATTERN: RegExp = new RegExp(/^(\+|-)?(?:90(?:(?:\.0{1,6})?)|(?:[0-9]|[1-8][0-9])(?:(?:\.[0-9]{1,15})?))$/);
81
82     /** Variables to hold regexp pattern for Longitude */
83     public REGX_LONG_PATTERN: RegExp = new RegExp(/^(\+|-)?(?:180(?:(?:\.0{1,6})?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\.[0-9]{1,15})?))$/);
84
85     /** Variables to hold maxlength for the description @public */
86     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
87     public MAX_LENGTH_DESCRIPTION: number = 500;
88
89     /** Variables to hold maxlength for the name @public */
90     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
91     public MAX_LENGTH_NAME: number = 50;
92
93     /** FormGroup instance added to the form @ html @public */
94     public formGroup: FormGroup;
95
96     /** Controls the go to top button on scroll  @public */
97     public showGotoTop: boolean;
98
99     /** Holds OSM Version value @public */
100     public osmVersion: string;
101
102     /** Holds Last Login Toaster Message @public */
103     public lastLoginMessage: string;
104
105     /** Holds Failed Attempts Toaster Message @public */
106     public failedAttemptsMessage: string;
107
108     /** Holds No Of Days Toaster Message @public */
109     public daysMessage: string;
110
111     /** express number for time manupulation -2 */
112     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
113     private epochTimeMinus2: number = -2;
114
115     /** express number for time manupulation 1000 */
116     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
117     private epochTime1000: number = 1000;
118
119     /** express number for time manupulation 60 */
120     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
121     private epochTime60: number = 60;
122
123     /** express number for time manupulation 24 */
124     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
125     private epochTime24: number = 24;
126
127     /** Random string generator length */
128     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
129     private randomStringLength: number = 4;
130
131     /** express number for rgb manipulation */
132     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
133     private colourHour: number = 10;
134
135     /** express number for rgb manipulation*/
136     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
137     private colourMin: number = 5;
138
139     /** Instance of the rest service @private */
140     private restService: RestService;
141
142     /** Service holds the router information @private */
143     private router: Router;
144
145     /** Check for the root directory @private */
146     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
147     private directoryCount: number = 2;
148
149     /** express number for time manupulation 1000 */
150     private toasterSettings: {} = {
151         enableHtml: true,
152         closeButton: true,
153         timeOut: 2000
154     };
155
156     /** Contains tranlsate instance @private */
157     private translateService: TranslateService;
158
159     /** Contains toaster instance @private */
160     private toaster: ToastrService;
161
162     constructor(restService: RestService, router: Router, translateService: TranslateService, toaster: ToastrService) {
163         this.restService = restService;
164         this.router = router;
165         this.translateService = translateService;
166         this.toaster = toaster;
167     }
168
169     /** convert epoch time function @public */
170     public convertEpochTime(unixtimestamp: number): string {
171         if (!isNullOrUndefined(unixtimestamp)) {
172             const monthsArr: string[] = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
173                 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
174             const date: Date = new Date(unixtimestamp * this.epochTime1000);
175             const year: number = date.getFullYear();
176             const month: string = monthsArr[date.getMonth()];
177             const day: number = date.getDate();
178             const hours: number = date.getHours();
179             const minutes: string = '0' + date.getMinutes();
180             const seconds: string = '0' + date.getSeconds();
181             // eslint-disable-next-line deprecation/deprecation
182             return month + '-' + day + '-' + year + ' ' + hours + ':' + minutes.substr(this.epochTimeMinus2) + ':'
183                 // eslint-disable-next-line deprecation/deprecation
184                 + seconds.substr(this.epochTimeMinus2);
185         }
186         return this.translateService.instant('NODATE');
187     }
188
189     /** convert epoch time function to No of days @public */
190     public converEpochToDays(date: string): number {
191         if (!isNullOrUndefined(date)) {
192             const today: Date = new Date();
193             const accountDate: Date = new Date(date);
194             const toasterDate: number = (accountDate.getTime() -
195                 today.getTime()) / this.epochTime1000 / this.epochTime60 / this.epochTime60 / this.epochTime24;
196             if (toasterDate >= 0 || toasterDate < 1) {
197                 return Math.round(toasterDate);
198             }
199             return Math.floor(toasterDate);
200         }
201         return this.translateService.instant('N/A');
202     }
203
204     /** show toaster for password & account expiry @public */
205     public showToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
206         accountNoOfDays: string, passwordExpireMessage: string, accountExpireMessage: string,
207         passwordMessage: string, accountMessage: string): ActiveToast<string> {
208         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
209         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
210         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
211             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
212             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage +
213             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
214             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
215     }
216
217     /** show toaster for password expiry @public */
218     public passwordToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
219         passwordExpireMessage: string, passwordMessage: string): ActiveToast<string> {
220         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
221         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
222         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
223             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
224             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage,
225             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
226     }
227
228     /** show toaster for account expiry @public */
229     public accountToaster(lastLogin: string, failedAttempts: string,
230         accountNoOfDays: string, accountExpireMessage: string, accountMessage: string): ActiveToast<string> {
231         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
232         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
233         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
234             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
235             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
236             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
237     }
238
239     /** Download Files function @public */
240     public downloadFiles(name: string, binaryData: Blob[], filetype: string): void {
241         const downloadLink: HTMLAnchorElement = document.createElement('a');
242         downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: filetype }));
243         if (name !== undefined) {
244             // eslint-disable-next-line @typescript-eslint/no-explicit-any
245             const newVariable: any = window.navigator;
246             if (newVariable.msSaveOrOpenBlob) {
247                 newVariable.msSaveBlob(new Blob(binaryData, { type: filetype }), 'OSM_Export_' + name + '.tar.gz');
248             } else {
249                 downloadLink.setAttribute('download', 'OSM_Export_' + name + '.tar.gz');
250                 document.body.appendChild(downloadLink);
251                 downloadLink.click();
252             }
253         }
254     }
255
256     /** Call this method after delete perform action is completed in the ng-smart-table data @public */
257     public callData(): void {
258         this.dataEvent.emit();
259     }
260
261     /** Generate random string @public */
262     public randomString(): string {
263         let result: string = '';
264         for (let randomStringRef: number = this.randomStringLength; randomStringRef > 0; --randomStringRef) {
265             result += new Date().getSeconds();
266         }
267         return result;
268     }
269
270     /** Function to read uploaded file String @public */
271     public async getFileString(files: FileList, fileType: string): Promise<string | ArrayBuffer> {
272         const reader: FileReader = new FileReader();
273         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
274             if (this.vaildataFileInfo(files[0], fileType)) {
275                 this.readFileContent(reader, files[0], fileType);
276             } else {
277                 reject('typeError');
278             }
279             reader.onload = (): void => {
280                 if (reader.result === null) {
281                     reject('contentError');
282                 }
283                 resolve(reader.result);
284             };
285             reader.onerror = (event: Event): void => {
286                 reject('contentError');
287             };
288         });
289     }
290
291     /** Method to handle tar and tar.gz file for shared YAML file content @public */
292     public async targzFile(packageInfo: PACKAGEINFO): Promise<string | ArrayBuffer> {
293         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
294             const httpOptions: GETAPIURLHEADER = this.getHttpOptions();
295             let apiUrl: string = '';
296             apiUrl = packageInfo.packageType === 'nsd' ? environment.NSDESCRIPTORS_URL + '/' + packageInfo.id + '/nsd_content' :
297                 environment.VNFPACKAGES_URL + '/' + packageInfo.id + '/package_content';
298             this.restService.getResource(apiUrl, httpOptions).subscribe((response: ArrayBuffer): void => {
299                 try {
300                     // eslint-disable-next-line @typescript-eslint/no-explicit-any
301                     const tar: any = new Tar();
302                     const originalInput: Uint8Array = pako.inflate(response, { to: 'Uint8Array' });
303                     untar(originalInput.buffer).then((extractedFiles: TARSETTINGS[]): void => {
304                         const getFoldersFiles: {}[] = extractedFiles;
305                         const folderNameStr: string = extractedFiles[0].name;
306                         getFoldersFiles.forEach((value: TARSETTINGS): void => {
307                             const fileValueObj: FILESETTINGS = this.createFileValueObject(value);
308                             const getRootFolder: string[] = value.name.split('/');
309                             if (value.name.startsWith(folderNameStr) &&
310                                 (value.name.endsWith('.yaml') || value.name.endsWith('.yml')) &&
311                                 getRootFolder.length === this.directoryCount) {
312                                 tar.append(value.name, packageInfo.descriptor, fileValueObj);
313                             } else {
314                                 if (value.type !== 'L') {
315                                     tar.append(value.name, new Uint8Array(value.buffer), fileValueObj);
316                                 }
317                             }
318                         });
319                         const out: Uint8Array = tar.out;
320                         const originalOutput: Uint8Array = pako.gzip(out);
321                         resolve(originalOutput.buffer);
322                     }, (err: string): void => {
323                         reject('');
324                     });
325                 } catch (e) {
326                     reject('');
327                 }
328             }, (error: HttpErrorResponse): void => {
329                 if (error.status === HttpStatus.NOT_FOUND || error.status === HttpStatus.UNAUTHORIZED) {
330                     this.router.navigateByUrl('404', { skipLocationChange: true }).catch((): void => {
331                         // Catch Navigation Error
332                     });
333                 } else {
334                     this.restService.handleError(error, 'get');
335                     reject('');
336                 }
337             });
338         });
339     }
340
341     /** Method to return the file information @public */
342     public createFileValueObject(value: TARSETTINGS): FILESETTINGS {
343         return {
344             type: value.type,
345             linkname: value.linkname,
346             owner: value.uname,
347             group: value.gname
348         };
349     }
350
351     /** Method to check given string is JSON or not @public */
352     public checkJson(jsonString: string): boolean {
353         jsonString = jsonString.replace(/'/g, '"');
354         try {
355             JSON.parse(jsonString);
356         } catch (e) {
357             return false;
358         }
359         return true;
360     }
361
362     /** Clean the form before submit @public */
363     public cleanForm(formGroup: FormGroup, formName?: String): void {
364         Object.keys(formGroup.controls).forEach((key: string) => {
365             if ((!isNullOrUndefined((formGroup.get(key) as FormArray | FormGroup).controls)) && key !== 'config') {
366                 // eslint-disable-next-line @typescript-eslint/no-shadow
367                 for (const { item, index } of (formGroup.get(key).value).map((item: {}, index: number) => ({ item, index }))) {
368                     // eslint-disable-next-line security/detect-object-injection
369                     const newFormGroup: FormGroup = (formGroup.get(key) as FormArray).controls[index] as FormGroup;
370                     this.cleanForm(newFormGroup);
371                 }
372             } else if (formGroup.get(key).value !== undefined && formGroup.get(key).value !== null && key !== 'config') {
373                 if (!Array.isArray(formGroup.get(key).value)) {
374                     if (typeof formGroup.get(key).value === 'string') {
375                         formGroup.get(key).setValue(formGroup.get(key).value.trim());
376                     }
377                 }
378             } else if (key === 'config' && formName === 'vim') {
379                 const newFormGroup: FormGroup = formGroup.get(key) as FormGroup;
380                 this.cleanForm(newFormGroup);
381             }
382         });
383     }
384
385     /** Method to return the config of pager value for ngSmarttable @public */
386     public paginationPagerConfig(): PAGERSMARTTABLE {
387         return {
388             display: true,
389             perPage: environment.paginationNumber
390         };
391     }
392
393     /** Method to return the class for the table for ngSmarttable @public */
394     public tableClassConfig(): SMARTTABLECLASS {
395         return {
396             class: 'table list-data'
397         };
398     }
399
400     /** Method to return all languages name and its code @public */
401     public languageCodeList(): {}[] {
402         return [
403             { code: 'en', language: 'English' },
404             { code: 'es', language: 'Spanish' },
405             { code: 'pt', language: 'Portuguese' },
406             { code: 'de', language: 'German' }
407         ];
408     }
409
410     /** Fetch OSM Version @public */
411     public fetchOSMVersion(): void {
412         this.restService.getResource(environment.OSM_VERSION_URL).subscribe((res: { version: string }): void => {
413             const version: string[] = res.version.split('+');
414             if (!isNullOrUndefined(version[0])) {
415                 this.osmVersion = version[0];
416             } else {
417                 this.osmVersion = null;
418             }
419         }, (error: ERRORDATA): void => {
420             this.osmVersion = null;
421             this.restService.handleError(error, 'get');
422         });
423     }
424
425     /** Random RGB color code generator @public */
426     public generateColor(): string {
427         const x: number = Math.floor((new Date().getHours()) * this.colourHour);
428         const y: number = Math.floor((new Date().getMinutes()) * this.colourMin);
429         const z: number = Math.floor((new Date().getSeconds()) * this.colourMin);
430         return 'rgb(' + x + ',' + y + ',' + z + ')';
431     }
432
433     /** Add custom name/tag to the dropdown @public */
434     public addCustomTag(tag: string): string {
435         return tag;
436     }
437
438     /** Fetch file extension @public */
439     public fetchFileExtension(fileInfo: FileList): string {
440         return fileInfo[0].name.substring(fileInfo[0].name.lastIndexOf('.') + 1);
441     }
442
443     /** Get domain name @private */
444     public getDomainName(): Observable<TYPESECTION[]> {
445         return this.restService.getResource(environment.DOMAIN_URL).pipe(map((domains: DOMAINS): TYPESECTION[] => {
446             const domainList: TYPESECTION[] = [];
447             try {
448                 let domainNames: string[] = [];
449                 if (!isNullOrUndefined(domains.project_domain_name)) {
450                     domainNames = domainNames.concat(domains.project_domain_name.split(','));
451                 }
452                 if (!isNullOrUndefined(domains.user_domain_name)) {
453                     domainNames = domainNames.concat(domains.user_domain_name.split(','));
454                 }
455                 domainNames = Array.from(new Set(domainNames));
456                 if (domainNames.length > 0) {
457                     domainNames.forEach((domainName: string): void => {
458                         if (!domainName.endsWith(':ro')) {
459                             domainList.push({ title: domainName, value: domainName });
460                         }
461                     });
462                 }
463                 return domainList;
464             } catch (e) {
465                 return domainList;
466             }
467         }));
468     }
469
470     /** Sorting the list based on date @public */
471     public compareFunction = (dir: number, a: string, b: string): number => {
472         const first: number = new Date(a).getTime();
473         const second: number = new Date(b).getTime();
474         if (first < second) {
475             return -1 * dir;
476         }
477         if (first > second) {
478             return dir;
479         }
480         return 0;
481     };
482
483     /** Method to validate file extension and size @private */
484     private vaildataFileInfo(fileInfo: File, fileType: string): boolean {
485         const extension: string = fileInfo.name.substring(fileInfo.name.lastIndexOf('.') + 1);
486         const packageSize: number = CONSTANTNUMBER.oneMB * environment.packageSize;
487         if (fileType === 'yaml' && (extension.toLowerCase() === 'yaml' || extension.toLowerCase() === 'yml')
488             && fileInfo.size <= packageSize) {
489             return true;
490         } else if (extension.toLowerCase() === fileType && fileInfo.size <= packageSize) {
491             return true;
492         }
493         return false;
494     }
495
496     /** Method to read file content based on type @private */
497     private readFileContent(reader: FileReader, fileInfo: File, fileType: string): void {
498         if (fileType === 'gz') {
499             reader.readAsArrayBuffer(fileInfo);
500         } else {
501             reader.readAsText(fileInfo);
502         }
503     }
504
505     /** Method to handle http options @public */
506     private getHttpOptions(): GETAPIURLHEADER {
507         return {
508             headers: new HttpHeaders({
509                 Accept: 'application/gzip, application/json',
510                 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
511             }),
512             responseType: 'arraybuffer'
513         };
514     }
515 }