Feature 10941: User Management Enhancements
[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     /** Max length of Uint8Array */
132     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
133     private unit8Array: number = 255;
134
135     /** Instance of the rest service @private */
136     private restService: RestService;
137
138     /** Service holds the router information @private */
139     private router: Router;
140
141     /** Random color string generator length @private */
142     // eslint-disable-next-line @typescript-eslint/no-magic-numbers
143     private colorStringLength: number = 256;
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             return Math.floor((accountDate.getTime() -
195                 today.getTime()) / this.epochTime1000 / this.epochTime60 / this.epochTime60 / this.epochTime24);
196         }
197         return this.translateService.instant('N/A');
198     }
199
200     /** show toaster for password & account expiry @public */
201     public showToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
202         accountNoOfDays: string, passwordExpireMessage: string, accountExpireMessage: string,
203         passwordMessage: string, accountMessage: string): ActiveToast<string> {
204         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
205         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
206         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
207             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
208             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage +
209             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
210             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
211     }
212
213     /** show toaster for password expiry @public */
214     public passwordToaster(lastLogin: string, failedAttempts: string, passwordNoOfDays: string,
215         passwordExpireMessage: string, passwordMessage: string): ActiveToast<string> {
216         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
217         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
218         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
219             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
220             '</br>' + passwordExpireMessage + '&nbsp' + passwordNoOfDays + '&nbsp' + passwordMessage,
221             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
222     }
223
224     /** show toaster for account expiry @public */
225     public accountToaster(lastLogin: string, failedAttempts: string,
226         accountNoOfDays: string, accountExpireMessage: string, accountMessage: string): ActiveToast<string> {
227         this.lastLoginMessage = this.translateService.instant('PAGE.LOGIN.LASTACCESS');
228         this.failedAttemptsMessage = this.translateService.instant('PAGE.LOGIN.FAILED');
229         return this.toaster.info(this.lastLoginMessage + ':' + '&nbsp' + lastLogin +
230             '</br>' + this.failedAttemptsMessage + ':' + '&nbsp' + failedAttempts +
231             '</br>' + accountExpireMessage + '&nbsp' + accountNoOfDays + '&nbsp' + accountMessage,
232             this.translateService.instant('PAGE.LOGIN.LOGINHISTORY'), this.toasterSettings);
233     }
234
235     /** Download Files function @public */
236     public downloadFiles(name: string, binaryData: Blob[], filetype: string): void {
237         const downloadLink: HTMLAnchorElement = document.createElement('a');
238         downloadLink.href = window.URL.createObjectURL(new Blob(binaryData, { type: filetype }));
239         if (name !== undefined) {
240             // eslint-disable-next-line @typescript-eslint/no-explicit-any
241             const newVariable: any = window.navigator;
242             if (newVariable.msSaveOrOpenBlob) {
243                 newVariable.msSaveBlob(new Blob(binaryData, { type: filetype }), 'OSM_Export_' + name + '.tar.gz');
244             } else {
245                 downloadLink.setAttribute('download', 'OSM_Export_' + name + '.tar.gz');
246                 document.body.appendChild(downloadLink);
247                 downloadLink.click();
248             }
249         }
250     }
251
252     /** Call this method after delete perform action is completed in the ng-smart-table data @public */
253     public callData(): void {
254         this.dataEvent.emit();
255     }
256
257     /** Generate random string @public */
258     public randomString(): string {
259         const chars: string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
260         let result: string = '';
261         for (let randomStringRef: number = this.randomStringLength; randomStringRef > 0; --randomStringRef) {
262             result += chars[Math.floor(Math.random() * chars.length)];
263         }
264         return result;
265     }
266
267     /** Function to read uploaded file String @public */
268     public async getFileString(files: FileList, fileType: string): Promise<string | ArrayBuffer> {
269         const reader: FileReader = new FileReader();
270         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
271             if (this.vaildataFileInfo(files[0], fileType)) {
272                 this.readFileContent(reader, files[0], fileType);
273             } else {
274                 reject('typeError');
275             }
276             reader.onload = (): void => {
277                 if (reader.result === null) {
278                     reject('contentError');
279                 }
280                 resolve(reader.result);
281             };
282             reader.onerror = (event: Event): void => {
283                 reject('contentError');
284             };
285         });
286     }
287
288     /** Method to handle tar and tar.gz file for shared YAML file content @public */
289     public async targzFile(packageInfo: PACKAGEINFO): Promise<string | ArrayBuffer> {
290         return new Promise<string | ArrayBuffer>((resolve: Function, reject: Function): void => {
291             const httpOptions: GETAPIURLHEADER = this.getHttpOptions();
292             let apiUrl: string = '';
293             apiUrl = packageInfo.packageType === 'nsd' ? environment.NSDESCRIPTORS_URL + '/' + packageInfo.id + '/nsd_content' :
294                 environment.VNFPACKAGES_URL + '/' + packageInfo.id + '/package_content';
295             this.restService.getResource(apiUrl, httpOptions).subscribe((response: ArrayBuffer): void => {
296                 try {
297                     // eslint-disable-next-line @typescript-eslint/no-explicit-any
298                     const tar: any = new Tar();
299                     const originalInput: Uint8Array = pako.inflate(response, { to: 'Uint8Array' });
300                     untar(originalInput.buffer).then((extractedFiles: TARSETTINGS[]): void => {
301                         const getFoldersFiles: {}[] = extractedFiles;
302                         const folderNameStr: string = extractedFiles[0].name;
303                         getFoldersFiles.forEach((value: TARSETTINGS): void => {
304                             const fileValueObj: FILESETTINGS = this.createFileValueObject(value);
305                             const getRootFolder: string[] = value.name.split('/');
306                             if (value.name.startsWith(folderNameStr) &&
307                                 (value.name.endsWith('.yaml') || value.name.endsWith('.yml')) &&
308                                 getRootFolder.length === this.directoryCount) {
309                                 tar.append(value.name, packageInfo.descriptor, fileValueObj);
310                             } else {
311                                 if (value.type !== 'L') {
312                                     tar.append(value.name, new Uint8Array(value.buffer), fileValueObj);
313                                 }
314                             }
315                         });
316                         const out: Uint8Array = tar.out;
317                         const originalOutput: Uint8Array = pako.gzip(out);
318                         resolve(originalOutput.buffer);
319                     }, (err: string): void => {
320                         reject('');
321                     });
322                 } catch (e) {
323                     reject('');
324                 }
325             }, (error: HttpErrorResponse): void => {
326                 if (error.status === HttpStatus.NOT_FOUND || error.status === HttpStatus.UNAUTHORIZED) {
327                     this.router.navigateByUrl('404', { skipLocationChange: true }).catch((): void => {
328                         // Catch Navigation Error
329                     });
330                 } else {
331                     this.restService.handleError(error, 'get');
332                     reject('');
333                 }
334             });
335         });
336     }
337
338     /** Method to return the file information @public */
339     public createFileValueObject(value: TARSETTINGS): FILESETTINGS {
340         return {
341             type: value.type,
342             linkname: value.linkname,
343             owner: value.uname,
344             group: value.gname
345         };
346     }
347
348     /** Method to check given string is JSON or not @public */
349     public checkJson(jsonString: string): boolean {
350         jsonString = jsonString.replace(/'/g, '"');
351         try {
352             JSON.parse(jsonString);
353         } catch (e) {
354             return false;
355         }
356         return true;
357     }
358
359     /** Clean the form before submit @public */
360     public cleanForm(formGroup: FormGroup, formName?: String): void {
361         Object.keys(formGroup.controls).forEach((key: string) => {
362             if ((!isNullOrUndefined((formGroup.get(key) as FormArray | FormGroup).controls)) && key !== 'config') {
363                 // eslint-disable-next-line @typescript-eslint/no-shadow
364                 for (const { item, index } of (formGroup.get(key).value).map((item: {}, index: number) => ({ item, index }))) {
365                     // eslint-disable-next-line security/detect-object-injection
366                     const newFormGroup: FormGroup = (formGroup.get(key) as FormArray).controls[index] as FormGroup;
367                     this.cleanForm(newFormGroup);
368                 }
369             } else if (formGroup.get(key).value !== undefined && formGroup.get(key).value !== null && key !== 'config') {
370                 if (!Array.isArray(formGroup.get(key).value)) {
371                     if (typeof formGroup.get(key).value === 'string') {
372                         formGroup.get(key).setValue(formGroup.get(key).value.trim());
373                     }
374                 }
375             } else if (key === 'config' && formName === 'vim') {
376                 const newFormGroup: FormGroup = formGroup.get(key) as FormGroup;
377                 this.cleanForm(newFormGroup);
378             }
379         });
380     }
381
382     /** Method to return the config of pager value for ngSmarttable @public */
383     public paginationPagerConfig(): PAGERSMARTTABLE {
384         return {
385             display: true,
386             perPage: environment.paginationNumber
387         };
388     }
389
390     /** Method to return the class for the table for ngSmarttable @public */
391     public tableClassConfig(): SMARTTABLECLASS {
392         return {
393             class: 'table list-data'
394         };
395     }
396
397     /** Method to return all languages name and its code @public */
398     public languageCodeList(): {}[] {
399         return [
400             { code: 'en', language: 'English' },
401             { code: 'es', language: 'Spanish' },
402             { code: 'pt', language: 'Portuguese' },
403             { code: 'de', language: 'German' }
404         ];
405     }
406
407     /** Fetch OSM Version @public */
408     public fetchOSMVersion(): void {
409         this.restService.getResource(environment.OSM_VERSION_URL).subscribe((res: { version: string }): void => {
410             const version: string[] = res.version.split('+');
411             if (!isNullOrUndefined(version[0])) {
412                 this.osmVersion = version[0];
413             } else {
414                 this.osmVersion = null;
415             }
416         }, (error: ERRORDATA): void => {
417             this.osmVersion = null;
418             this.restService.handleError(error, 'get');
419         });
420     }
421
422     /** Random RGB color code generator @public */
423     public generateColor(): string {
424         const x: number = Math.floor(Math.random() * this.colorStringLength);
425         const y: number = Math.floor(Math.random() * this.colorStringLength);
426         const z: number = Math.floor(Math.random() * this.colorStringLength);
427         return 'rgb(' + x + ',' + y + ',' + z + ')';
428     }
429
430     /** Add custom name/tag to the dropdown @public */
431     public addCustomTag(tag: string): string {
432         return tag;
433     }
434
435     /** Fetch file extension @public */
436     public fetchFileExtension(fileInfo: FileList): string {
437         return fileInfo[0].name.substring(fileInfo[0].name.lastIndexOf('.') + 1);
438     }
439
440     /** Get domain name @private */
441     public getDomainName(): Observable<TYPESECTION[]> {
442         return this.restService.getResource(environment.DOMAIN_URL).pipe(map((domains: DOMAINS): TYPESECTION[] => {
443             const domainList: TYPESECTION[] = [];
444             try {
445                 let domainNames: string[] = [];
446                 if (!isNullOrUndefined(domains.project_domain_name)) {
447                     domainNames = domainNames.concat(domains.project_domain_name.split(','));
448                 }
449                 if (!isNullOrUndefined(domains.user_domain_name)) {
450                     domainNames = domainNames.concat(domains.user_domain_name.split(','));
451                 }
452                 domainNames = Array.from(new Set(domainNames));
453                 if (domainNames.length > 0) {
454                     domainNames.forEach((domainName: string): void => {
455                         if (!domainName.endsWith(':ro')) {
456                             domainList.push({ title: domainName, value: domainName });
457                         }
458                     });
459                 }
460                 return domainList;
461             } catch (e) {
462                 return domainList;
463             }
464         }));
465     }
466
467     /** Method to validate file extension and size @private */
468     private vaildataFileInfo(fileInfo: File, fileType: string): boolean {
469         const extension: string = fileInfo.name.substring(fileInfo.name.lastIndexOf('.') + 1);
470         const packageSize: number = CONSTANTNUMBER.oneMB * environment.packageSize;
471         if (fileType === 'yaml' && (extension.toLowerCase() === 'yaml' || extension.toLowerCase() === 'yml')
472             && fileInfo.size <= packageSize) {
473             return true;
474         } else if (extension.toLowerCase() === fileType && fileInfo.size <= packageSize) {
475             return true;
476         }
477         return false;
478     }
479
480     /** Method to read file content based on type @private */
481     private readFileContent(reader: FileReader, fileInfo: File, fileType: string): void {
482         if (fileType === 'gz') {
483             reader.readAsArrayBuffer(fileInfo);
484         } else {
485             reader.readAsText(fileInfo);
486         }
487     }
488
489     /** Method to handle http options @public */
490     private getHttpOptions(): GETAPIURLHEADER {
491         return {
492             headers: new HttpHeaders({
493                 Accept: 'application/gzip, application/json',
494                 'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0'
495             }),
496             responseType: 'arraybuffer'
497         };
498     }
499 }