diff --git a/backend/src/main/java/com/park/utmstack/config/Constants.java b/backend/src/main/java/com/park/utmstack/config/Constants.java index 07b0bcf66..ae801f44c 100644 --- a/backend/src/main/java/com/park/utmstack/config/Constants.java +++ b/backend/src/main/java/com/park/utmstack/config/Constants.java @@ -2,9 +2,7 @@ import com.park.utmstack.domain.index_pattern.enums.SystemIndexPattern; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; public final class Constants { @@ -130,7 +128,6 @@ public final class Constants { // Defines the index pattern for querying Elasticsearch statistics indexes. // ---------------------------------------------------------------------------------- public static final String STATISTICS_INDEX_PATTERN = "v11-statistics-*"; - public static final String API_ACCESS_LOGS = ".utmstack-api-logs"; // Logging public static final String TRACE_ID_KEY = "traceId"; @@ -142,10 +139,7 @@ public final class Constants { public static final String DURATION_KEY = "duration"; public static final String CAUSE_KEY = "cause"; public static final String LAYER_KEY = "layer"; - - public static final String API_KEY_HEADER = "Utm-Api-Key"; - public static final List API_ENDPOINT_IGNORE = Collections.emptyList(); - + public static final String TFA_EXEMPTION_HEADER = "X-Bypass-TFA"; private Constants() { } diff --git a/backend/src/main/java/com/park/utmstack/security/jwt/TokenProvider.java b/backend/src/main/java/com/park/utmstack/security/jwt/TokenProvider.java index 246a5dad0..e48f9baed 100644 --- a/backend/src/main/java/com/park/utmstack/security/jwt/TokenProvider.java +++ b/backend/src/main/java/com/park/utmstack/security/jwt/TokenProvider.java @@ -1,6 +1,7 @@ package com.park.utmstack.security.jwt; +import com.park.utmstack.config.Constants; import com.park.utmstack.security.AuthoritiesConstants; import com.park.utmstack.util.CipherUtil; import io.jsonwebtoken.*; @@ -16,10 +17,12 @@ import org.springframework.stereotype.Component; import tech.jhipster.config.JHipsterProperties; +import javax.servlet.http.HttpServletRequest; import java.security.Key; import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.Optional; import java.util.stream.Collectors; @Component @@ -116,4 +119,16 @@ public boolean validateToken(String authToken) { } return false; } + + public boolean canBypassTwoFactorAuth(HttpServletRequest request) { + boolean tfaExemptionRequested = Boolean.parseBoolean(request.getHeader(Constants.TFA_EXEMPTION_HEADER)); + + boolean forceTfaAuth = Boolean.parseBoolean( + Optional.ofNullable(System.getenv(Constants.PROP_TFA_ENABLE)).orElse("true") + ); + + return tfaExemptionRequested || !forceTfaAuth; + } + + } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java new file mode 100644 index 000000000..108df0e4d --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigDTO.java @@ -0,0 +1,20 @@ +package com.park.utmstack.service.dto.collectors.dto; + +import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotNull; +import java.util.List; + +@Setter +@Getter +public class CollectorConfigDTO { + @NotNull + CollectorDTO collector; + @NotNull + private Long moduleId; + @NotNull + private List keys; + +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java deleted file mode 100644 index 40af52707..000000000 --- a/backend/src/main/java/com/park/utmstack/service/dto/collectors/dto/CollectorConfigKeysDTO.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.park.utmstack.service.dto.collectors.dto; - -import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; - -import javax.validation.constraints.NotNull; -import java.util.List; - -public class CollectorConfigKeysDTO { - @NotNull - CollectorDTO collector; - @NotNull - private Long moduleId; - @NotNull - private List keys; - - public Long getModuleId() { - return moduleId; - } - - public void setModuleId(Long moduleId) { - this.moduleId = moduleId; - } - - public List getKeys() { - return keys; - } - - public void setKeys(List keys) { - this.keys = keys; - } - - public CollectorDTO getCollector() { - return collector; - } - - public void setCollector(CollectorDTO collector) { - this.collector = collector; - } -} diff --git a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java index ffc3156ac..02d1bca1b 100644 --- a/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java +++ b/backend/src/main/java/com/park/utmstack/service/validators/collector/CollectorValidatorService.java @@ -1,7 +1,7 @@ package com.park.utmstack.service.validators.collector; import com.park.utmstack.domain.application_modules.UtmModuleGroupConfiguration; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import org.springframework.stereotype.Service; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -13,12 +13,12 @@ public class CollectorValidatorService implements Validator { @Override public boolean supports(Class clazz) { - return CollectorConfigKeysDTO.class.equals(clazz); + return CollectorConfigDTO.class.equals(clazz); } @Override public void validate(Object target, Errors errors) { - CollectorConfigKeysDTO updateConfigurationKeysBody = (CollectorConfigKeysDTO) target; + CollectorConfigDTO updateConfigurationKeysBody = (CollectorConfigDTO) target; Map hostNames = updateConfigurationKeysBody.getKeys().stream() .filter(config -> config.getConfName().equals("Hostname")) diff --git a/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java b/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java index e73efc8bd..cb0a403b6 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java @@ -77,7 +77,8 @@ public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM, H throw new TooMuchLoginAttemptsException(String.format("Authentication blocked: IP %s exceeded login attempt threshold", ip)); } - boolean authenticated = !Boolean.parseBoolean(Constants.CFG.get(Constants.PROP_TFA_ENABLE)); + boolean isTfaExempted = this.tokenProvider.canBypassTwoFactorAuth(request); + boolean authenticated = !Boolean.parseBoolean(Constants.CFG.get(Constants.PROP_TFA_ENABLE)) || isTfaExempted; UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword()); diff --git a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java index 7f97bc9b5..728af040e 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/collectors/UtmCollectorResource.java @@ -16,14 +16,13 @@ import com.park.utmstack.service.application_modules.UtmModuleGroupService; import com.park.utmstack.service.collectors.CollectorOpsService; import com.park.utmstack.service.collectors.UtmCollectorService; -import com.park.utmstack.service.dto.collectors.dto.CollectorConfigKeysDTO; +import com.park.utmstack.service.dto.collectors.dto.CollectorConfigDTO; import com.park.utmstack.service.dto.collectors.dto.CollectorDTO; import com.park.utmstack.service.dto.collectors.CollectorModuleEnum; import com.park.utmstack.service.dto.collectors.dto.ListCollectorsResponseDTO; import com.park.utmstack.service.dto.network_scan.AssetGroupDTO; import com.park.utmstack.service.validators.collector.CollectorValidatorService; import com.park.utmstack.util.UtilResponse; -import com.park.utmstack.web.rest.application_modules.UtmModuleGroupConfigurationResource; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.errors.InternalServerErrorException; import com.park.utmstack.web.rest.network_scan.UtmNetworkScanResource; @@ -94,9 +93,7 @@ public UtmCollectorResource(CollectorOpsService collectorService, * persist the configurations. */ @PostMapping("/collector-config") - public ResponseEntity upsertCollectorConfig( - @Valid @RequestBody CollectorConfigKeysDTO collectorConfig, - CollectorDTO collectorDTO) { + public ResponseEntity upsertCollectorConfig(@Valid @RequestBody CollectorConfigDTO collectorConfig) { final String ctx = CLASSNAME + ".upsertCollectorConfig"; try { @@ -112,11 +109,11 @@ public ResponseEntity upsertCollectorConfig( // Get the actual configuration just in case of error when updating local db. CollectorConfig cacheConfig = collectorService.getCollectorConfig( - ConfigRequest.newBuilder().setModule(CollectorModule.valueOf(collectorDTO.getModule().toString())).build(), - AuthResponse.newBuilder().setId(collectorDTO.getId()).setKey(collectorDTO.getCollectorKey()).build()); + ConfigRequest.newBuilder().setModule(CollectorModule.valueOf(collectorConfig.getCollector().getModule().toString())).build(), + AuthResponse.newBuilder().setId(collectorConfig.getCollector().getId()).setKey(collectorConfig.getCollector().getCollectorKey()).build()); // Map the configurations to gRPC CollectorConfig and try to insert/update the collector config collectorService.upsertCollectorConfig(collectorService.mapToCollectorConfig( - collectorService.mapPasswordConfiguration(collectorConfig.getKeys()), collectorDTO)); + collectorService.mapPasswordConfiguration(collectorConfig.getKeys()), collectorConfig.getCollector())); // If the update is fine via gRPC, then update the configurations in local db. try { moduleGroupConfigurationService.updateConfigurationKeys(collectorConfig.getModuleId(), collectorConfig.getKeys()); diff --git a/frontend/src/app/app-management/api-keys/api-keys.component.html b/frontend/src/app/app-management/api-keys/api-keys.component.html deleted file mode 100644 index e2a5158ff..000000000 --- a/frontend/src/app/app-management/api-keys/api-keys.component.html +++ /dev/null @@ -1,151 +0,0 @@ -
-
-
-
- API Keys -
- - The API key is a simple encrypted string that identifies you in the application. With this key, you can access the REST API. - -
- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Name - - Allowed IPs - - Expires At - - Created At - ACTIONS
{{ key.name }}{{ key.allowedIp?.join(', ') || '—' }} - - - - - - - - - - - - - {{ key.expiresAt ? (key.expiresAt | date:'dd/MM/yy HH:mm':'UTC') : '—' }} - {{ key.createdAt | date:'dd/MM/yy HH:mm' :'UTC' }} - - - -
- -
-
- - -
-
-
- -
-
- - - - -
- -
-
-
- - - - - -
-
- Copy it now because it will be shown only once! -
-
- {{ maskSecrets(generatedApiKey) }} - -
-
- - {{ 'Keep this key safeKeep this key safe' }} -
-
-
- -
-
diff --git a/frontend/src/app/app-management/api-keys/api-keys.component.scss b/frontend/src/app/app-management/api-keys/api-keys.component.scss deleted file mode 100644 index b3751c83a..000000000 --- a/frontend/src/app/app-management/api-keys/api-keys.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -:host{ - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - height: 100%; -} diff --git a/frontend/src/app/app-management/api-keys/api-keys.component.ts b/frontend/src/app/app-management/api-keys/api-keys.component.ts deleted file mode 100644 index 82d9e3450..000000000 --- a/frontend/src/app/app-management/api-keys/api-keys.component.ts +++ /dev/null @@ -1,232 +0,0 @@ -import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; -import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap'; -import * as moment from 'moment'; -import {UtmToastService} from '../../shared/alert/utm-toast.service'; -import { - ModalConfirmationComponent -} from '../../shared/components/utm/util/modal-confirmation/modal-confirmation.component'; -import {ITEMS_PER_PAGE} from '../../shared/constants/pagination.constants'; -import {SortEvent} from '../../shared/directives/sortable/type/sort-event'; -import {ApiKeyModalComponent} from './shared/components/api-key-modal/api-key-modal.component'; -import {ApiKeyResponse} from './shared/models/ApiKeyResponse'; -import {ApiKeysService} from './shared/service/api-keys.service'; - -@Component({ - selector: 'app-api-keys', - templateUrl: './api-keys.component.html', - styleUrls: ['./api-keys.component.scss'] -}) -export class ApiKeysComponent implements OnInit { - - generating: string[] = []; - noData = false; - apiKeys: ApiKeyResponse[] = []; - loading = false; - generatedApiKey = ''; - @ViewChild('generatedModal') generatedModal!: TemplateRef; - generatedModalRef!: NgbModalRef; - copied = false; - readonly itemsPerPage = ITEMS_PER_PAGE; - totalItems = 0; - page = 0; - size = this.itemsPerPage; - - request = { - sort: 'createdAt,desc', - page: this.page, - size: this.size - }; - - constructor( private toastService: UtmToastService, - private apiKeyService: ApiKeysService, - private modalService: NgbModal - ) {} - - ngOnInit(): void { - this.loadKeys(); - } - - loadKeys(): void { - this.loading = true; - this.apiKeyService.list(this.request).subscribe({ - next: (res) => { - this.totalItems = Number(res.headers.get('X-Total-Count')); - this.apiKeys = res.body || []; - this.noData = this.apiKeys.length === 0; - this.loading = false; - }, - error: () => { - this.loading = false; - this.apiKeys = []; - } - }); - } - - copyToClipboard(): void { - if (!this.generatedApiKey) { return; } - - if (navigator && (navigator as any).clipboard && (navigator as any).clipboard.writeText) { - (navigator as any).clipboard.writeText(this.generatedApiKey) - .then(() => this.copied = true) - .catch(err => { - console.error('Error al copiar con clipboard API', err); - this.fallbackCopy(this.generatedApiKey); - }); - } else { - this.fallbackCopy(this.generatedApiKey); - } - } - - private fallbackCopy(text: string): void { - try { - const textarea = document.createElement('textarea'); - textarea.value = text; - - textarea.style.position = 'fixed'; - textarea.style.top = '0'; - textarea.style.left = '0'; - textarea.style.opacity = '0'; - - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); - - const successful = document.execCommand('copy'); - document.body.removeChild(textarea); - - if (successful) { - this.showCopiedFeedback(); - } else { - console.warn('Fallback copy failed'); - } - } catch (err) { - console.error('Error en fallback copy', err); - } - } - - private showCopiedFeedback(): void { - this.copied = true; - setTimeout(() => this.copied = false, 2000); - } - - openCreateModal(): void { - const modalRef = this.modalService.open(ApiKeyModalComponent, { centered: true }); - - modalRef.result.then((key: ApiKeyResponse) => { - if (key) { - this.generateKey(key); - } - }); - } - - editKey(key: ApiKeyResponse): void { - const modalRef = this.modalService.open(ApiKeyModalComponent, {centered: true}); - modalRef.componentInstance.apiKey = key; - - modalRef.result.then((key: ApiKeyResponse) => { - if (key) { - this.generateKey(key); - } - }); - } - - deleteKey(apiKey: ApiKeyResponse): void { - const modalRef = this.modalService.open(ModalConfirmationComponent, {centered: true}); - modalRef.componentInstance.header = `Delete API Key: ${apiKey.name}`; - modalRef.componentInstance.message = 'Are you sure you want to delete this API key?'; - modalRef.componentInstance.confirmBtnType = 'delete'; - modalRef.componentInstance.type = 'danger'; - modalRef.componentInstance.confirmBtnText = 'Delete'; - modalRef.componentInstance.confirmBtnIcon = 'icon-cross-circle'; - - modalRef.result.then(reason => { - if (reason === 'ok') { - this.delete(apiKey); - } - }); - } - - delete(apiKey: ApiKeyResponse): void { - this.apiKeyService.delete(apiKey.id).subscribe({ - next: () => { - this.toastService.showSuccess('API key deleted successfully.'); - this.loadKeys(); - }, - error: (err) => { - this.toastService.showError('Error', 'An error occurred while deleting the API key.'); - throw err; - } - }); - } - - getDaysUntilExpire(expiresAt: string): number { - if (!expiresAt) { - return -1; - } - - const today = moment().startOf('day'); - const expireDate = moment(expiresAt).startOf('day'); - return expireDate.diff(today, 'days'); - } - - onSortBy($event: SortEvent) { - this.request.sort = $event.column + ',' + $event.direction; - this.loadKeys(); - } - - maskSecrets(str: string): string { - if (!str || str.length <= 10) { - return str; - } - const prefix = str.substring(0, 10); - const maskLength = str.length - 30; - const maskedPart = '*'.repeat(maskLength); - return prefix + maskedPart; - } - - generateKey(apiKey: ApiKeyResponse): void { - this.generating.push(apiKey.id); - this.apiKeyService.generateApiKey(apiKey.id).subscribe(response => { - this.generatedApiKey = response.body ? response.body : ""; - this.generatedModalRef = this.modalService.open(this.generatedModal, {centered: true}); - const index = this.generating.indexOf(apiKey.id); - if (index > -1) { - this.generating.splice(index, 1); - } - this.loadKeys(); - }); - } - - isApiKeyExpired(expiresAt?: string | null ): boolean { - if (!expiresAt) { - return false; - } - const expirationTime = new Date(expiresAt).getTime(); - return expirationTime < Date.now(); - } - - close() { - this.generatedModalRef.close(); - this.copied = false; - this.generatedApiKey = ''; - } - - loadPage($event: number) { - this.page = $event - 1; - this.request = { - ...this.request, - page: this.page - }; - this.loadKeys(); - } - - onItemsPerPageChange($event: number) { - this.request = { - ...this.request, - size: $event, - page: 0 - }; - this.page = 0; - this.loadKeys(); - } -} diff --git a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.html b/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.html deleted file mode 100644 index 7e1399bbb..000000000 --- a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.html +++ /dev/null @@ -1,127 +0,0 @@ - - -
- -
- {{ errorMsg }} -
- -
-
- - -
- -
- -
- -
- -
-
-
- -
- - -
- - -
- -
- {{ ipInputError }} -
- -
    -
  • - -
    -
    - - {{ ip.value }} - {{ getIpType(ip.value) }} -
    -
    - -
    -
    -
  • -
-
- - -
- -
- - - -
-
- - - diff --git a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.scss b/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.scss deleted file mode 100644 index e31427b61..000000000 --- a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.scss +++ /dev/null @@ -1,12 +0,0 @@ -.disabled-rounded-start { - border-top-left-radius: 0 !important; - border-bottom-left-radius: 0 !important; -} -.disabled-rounded-end { - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -.mt-4 { - margin-top: 9rem !important; -} diff --git a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.ts b/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.ts deleted file mode 100644 index 8e83c56b6..000000000 --- a/frontend/src/app/app-management/api-keys/shared/components/api-key-modal/api-key-modal.component.ts +++ /dev/null @@ -1,139 +0,0 @@ -import {HttpErrorResponse} from '@angular/common/http'; -import {Component, Input, OnInit} from '@angular/core'; -import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import {UtmToastService} from '../../../../../shared/alert/utm-toast.service'; -import {ApiKeyResponse} from '../../models/ApiKeyResponse'; -import { ApiKeysService } from '../../service/api-keys.service'; -import {IpFormsValidators} from "../../../../../shared/util/custom-form-validators"; - -@Component({ - selector: 'app-api-key-modal', - templateUrl: './api-key-modal.component.html', - styleUrls: ['./api-key-modal.component.scss'] -}) -export class ApiKeyModalComponent implements OnInit { - - @Input() apiKey: ApiKeyResponse = null; - - apiKeyForm: FormGroup; - ipInput = ''; - loading = false; - errorMsg = ''; - isSaving: string | string[] | Set | { [p: string]: any }; - minDate = { year: new Date().getFullYear(), month: new Date().getMonth() + 1, day: new Date().getDate() }; - ipInputError = ''; - - constructor( public activeModal: NgbActiveModal, - private apiKeyService: ApiKeysService, - private fb: FormBuilder, - private toastService: UtmToastService) { - } - - ngOnInit(): void { - - const expiresAtDate = this.apiKey && this.apiKey.expiresAt ? new Date(this.apiKey.expiresAt) : null; - const expiresAtNgbDate = expiresAtDate ? { - year: expiresAtDate.getUTCFullYear(), - month: expiresAtDate.getUTCMonth() + 1, - day: expiresAtDate.getUTCDate() - } : null; - - this.apiKeyForm = this.fb.group({ - name: [ this.apiKey ? this.apiKey.name : '', Validators.required], - allowedIp: this.fb.array(this.apiKey ? this.apiKey.allowedIp : []), - expiresAt: [expiresAtNgbDate, Validators.required], - }); - } - - get allowedIp(): FormArray { - return this.apiKeyForm.get('allowedIp') as FormArray; - } - - addIp(): void { - const trimmedIp = this.ipInput.trim(); - - if (!trimmedIp) { - this.ipInputError = 'Please enter an IP address or CIDR'; // Error is assigned - return; - } - - const tempControl = this.fb.control(trimmedIp, [IpFormsValidators.ipOrCidr()]); - - if (tempControl.invalid) { - if (tempControl.hasError('invalidIp')) { - this.ipInputError = 'Invalid IP address format'; - } else if (tempControl.hasError('invalidCidr')) { - this.ipInputError = 'Invalid CIDR format'; - } - return; - } - - const isDuplicate = this.allowedIp.controls.some( - control => control.value === trimmedIp - ); - - if (isDuplicate) { - this.ipInputError = 'This IP is already added'; - return; - } - - this.allowedIp.push(this.fb.control(trimmedIp, [IpFormsValidators.ipOrCidr()])); - this.ipInput = ''; - this.ipInputError = ''; - } - - removeIp(index: number): void { - this.allowedIp.removeAt(index); - } - - create(): void { - this.errorMsg = ''; - - if (this.apiKeyForm.invalid) { - this.errorMsg = 'Name is required.'; - return; - } - - this.loading = true; - - const rawDate = this.apiKeyForm.get('expiresAt').value; - let formattedDate = rawDate; - - if (rawDate && typeof rawDate === 'object') { - formattedDate = `${rawDate.year}-${String(rawDate.month).padStart(2, '0')}-${String(rawDate.day).padStart(2, '0')}T00:00:00.000Z`; - } - - const payload = { - ...this.apiKeyForm.value, - expiresAt: formattedDate, - }; - - const save = this.apiKey ? this.apiKeyService.update(this.apiKey.id, payload) : - this.apiKeyService.create(payload); - - save.subscribe({ - next: (response) => { - this.loading = false; - this.activeModal.close(response.body as ApiKeyResponse); - }, - error: (err: HttpErrorResponse) => { - this.loading = false; - if (err.status === 409) { - this.toastService.showError('Error', 'An API key with this name already exists.'); - } else if (err.status === 500) { - this.toastService.showError('Error', 'Server error occurred while creating the API key.'); - } - } - }); - } - - getIpType(value: string): string { - if (!value) { return ''; } - if (value.includes('/')) { - return value.includes(':') ? 'IPv6 CIDR' : 'IPv4 CIDR'; - } - return value.includes(':') ? 'IPv6' : 'IPv4'; - } -} - diff --git a/frontend/src/app/app-management/api-keys/shared/models/ApiKeyResponse.ts b/frontend/src/app/app-management/api-keys/shared/models/ApiKeyResponse.ts deleted file mode 100644 index 3f6b890d7..000000000 --- a/frontend/src/app/app-management/api-keys/shared/models/ApiKeyResponse.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface ApiKeyResponse { - id: string; - name: string; - allowedIp: string[]; - createdAt: string; - expiresAt?: string; - generatedAt?: string; -} diff --git a/frontend/src/app/app-management/api-keys/shared/models/ApiKeyUpsert.ts b/frontend/src/app/app-management/api-keys/shared/models/ApiKeyUpsert.ts deleted file mode 100644 index 7ae630a43..000000000 --- a/frontend/src/app/app-management/api-keys/shared/models/ApiKeyUpsert.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ApiKeyUpsert { - id: string; - name: string; - allowedIp?: string[]; - expiresAt?: Date; -} diff --git a/frontend/src/app/app-management/api-keys/shared/service/api-keys.service.ts b/frontend/src/app/app-management/api-keys/shared/service/api-keys.service.ts deleted file mode 100644 index e239d5e4d..000000000 --- a/frontend/src/app/app-management/api-keys/shared/service/api-keys.service.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpResponse } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { SERVER_API_URL } from '../../../../app.constants'; -import { ApiKeyResponse } from '../models/ApiKeyResponse'; -import { ApiKeyUpsert } from '../models/ApiKeyUpsert'; -import {createRequestOption} from "../../../../shared/util/request-util"; - -/** - * Service for managing API keys - */ -@Injectable({ - providedIn: 'root' -}) -export class ApiKeysService { - public resourceUrl = SERVER_API_URL + 'api/api-keys'; - - constructor(private http: HttpClient) {} - - /** - * Create a new API key - */ - create(dto: ApiKeyUpsert): Observable> { - return this.http.post( - this.resourceUrl, - dto, - { observe: 'response' } - ); - } - - /** - * Generate (or renew) a plain API key for the given id - * Returns the plain text key (only once) - */ - generate(id: string): Observable> { - return this.http.post( - `${this.resourceUrl}/${id}/generate`, - {}, - { observe: 'response', responseType: 'text' } - ); - } - - /** - * Get API key by id - */ - get(id: string): Observable> { - return this.http.get( - `${this.resourceUrl}/${id}`, - { observe: 'response' } - ); - } - - /** - * List all API keys (with optional pagination) - */ - list(params?: any): Observable> { - const httpParams = createRequestOption(params); - return this.http.get( - this.resourceUrl, - { observe: 'response', params: httpParams }, - ); - } - - /** - * Update an existing API key - */ - update(id: string, dto: ApiKeyUpsert): Observable> { - return this.http.put( - `${this.resourceUrl}/${id}`, - dto, - { observe: 'response' } - ); - } - - /** - * Delete API key - */ - delete(id: string): Observable> { - return this.http.delete( - `${this.resourceUrl}/${id}`, - { observe: 'response' } - ); - } - - generateApiKey(apiKeyId: string): Observable> { - return this.http.post(`${this.resourceUrl}/${apiKeyId}/generate`, null, { - observe: 'response', - responseType: 'text' - }); - } - - /** - * Search API key usage in Elasticsearch - */ - usage(params: { - filters?: any[]; - top: number; - indexPattern: string; - includeChildren?: boolean; - page?: number; - size?: number; - }): Observable { - return this.http.get( - `${this.resourceUrl}/usage`, - { - params: { - top: params.top.toString(), - indexPattern: params.indexPattern, - includeChildren: params.includeChildren.toString() || 'false', - page: params.page.toString() || '0', - size: params.size.toString() || '10' - } - } - ); - } -} - diff --git a/frontend/src/app/app-management/app-management-routing.module.ts b/frontend/src/app/app-management/app-management-routing.module.ts index 5747a2235..73af5c968 100644 --- a/frontend/src/app/app-management/app-management-routing.module.ts +++ b/frontend/src/app/app-management/app-management-routing.module.ts @@ -15,7 +15,6 @@ import {IndexPatternListComponent} from './index-pattern/index-pattern-list/inde import {MenuComponent} from './menu/menu.component'; import {RolloverConfigComponent} from './rollover-config/rollover-config.component'; import {UtmApiDocComponent} from './utm-api-doc/utm-api-doc.component'; -import {ApiKeysComponent} from "./api-keys/api-keys.component"; const routes: Routes = [ {path: '', redirectTo: 'settings', pathMatch: 'full'}, @@ -115,16 +114,7 @@ const routes: Routes = [ data: { authorities: [ADMIN_ROLE] }, - }, - { - path: 'api-keys', - component: ApiKeysComponent, - canActivate: [UserRouteAccessService], - data: { - authorities: [ADMIN_ROLE] - }, - } - ], + }], }, ]; diff --git a/frontend/src/app/app-management/app-management.module.ts b/frontend/src/app/app-management/app-management.module.ts index 79ce3a029..6e26285a0 100644 --- a/frontend/src/app/app-management/app-management.module.ts +++ b/frontend/src/app/app-management/app-management.module.ts @@ -11,8 +11,6 @@ import {ComplianceManagementModule} from '../compliance/compliance-management/co import {NavBehavior} from '../shared/behaviors/nav.behavior'; import {VersionUpdateBehavior} from '../shared/behaviors/version-update.behavior'; import {UtmSharedModule} from '../shared/utm-shared.module'; -import {ApiKeysComponent} from './api-keys/api-keys.component'; -import {ApiKeyModalComponent} from './api-keys/shared/components/api-key-modal/api-key-modal.component'; import {AppConfigComponent} from './app-config/app-config.component'; import {AppLogsComponent} from './app-logs/app-logs.component'; import {AppManagementRoutingModule} from './app-management-routing.module'; @@ -48,8 +46,6 @@ import {UtmApiDocComponent} from './utm-api-doc/utm-api-doc.component'; @NgModule({ declarations: [ - ApiKeysComponent, - ApiKeyModalComponent, AppManagementComponent, AppManagementSidebarComponent, IndexPatternHelpComponent, @@ -85,7 +81,6 @@ import {UtmApiDocComponent} from './utm-api-doc/utm-api-doc.component'; HealthDetailComponent, MenuDeleteDialogComponent, TokenActivateComponent, - ApiKeyModalComponent, IndexDeleteComponent], imports: [ CommonModule, diff --git a/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html b/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html index 60e12d3be..5d39a9d66 100644 --- a/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html +++ b/frontend/src/app/app-management/layout/app-management-sidebar/app-management-sidebar.component.html @@ -115,16 +115,6 @@ - - -   - API Keys - - - - Save collector + Save configuration diff --git a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts index ec445e924..ad85ba394 100644 --- a/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts +++ b/frontend/src/app/app-module/conf/int-generic-group-config/int-generic-group-config.component.ts @@ -285,14 +285,12 @@ export class IntGenericGroupConfigComponent implements OnInit { this.configs.push(...configurations); }); const body = { - collectorConfig: { + collector: { + ... collectorDto, + group: null, + }, moduleId: this.moduleId, keys: this.configs - }, - collector: { - ... collectorDto, - group: null, - }, }; this.collectorService.create(body).subscribe(response => { this.savingConfig = false; diff --git a/frontend/src/app/app-module/guides/guide-as400/constants.ts b/frontend/src/app/app-module/guides/guide-as400/constants.ts index 5b1c43b73..45663c52f 100644 --- a/frontend/src/app/app-module/guides/guide-as400/constants.ts +++ b/frontend/src/app/app-module/guides/guide-as400/constants.ts @@ -7,7 +7,7 @@ export const PLATFORM = [ `cd "C:\\Program Files\\UTMStack\\UTMStack Collectors\\AS400"; ` + `& curl.exe -k -o ".\\windows-as400-collector.zip" ` + `"https://V_IP:9001/private/dependencies/collector/windows-as400-collector.zip"; ` + - `Expand-Archive -Path ".\\windows-as400-collector.zip" -DestinationPath "."; ` + + `Expand-Archive -Path ".\\windows-as400-collector.zip" -DestinationPath "."; ` + `Remove-Item ".\\windows-as400-collector.zip"; Start-Process ".\\utmstack_collectors_installer.exe" ` + `-ArgumentList 'install', 'as400', 'V_IP', 'V_TOKEN' -NoNewWindow -Wait`, @@ -34,7 +34,7 @@ export const PLATFORM = [ `https://V_IP:9001/private/dependencies/collector/linux-as400-collector.zip ` + `&& unzip linux-as400-collector.zip && rm linux-as400-collector.zip && chmod -R 755 ` + `utmstack_collectors_installer && ./utmstack_collectors_installer install as400 ` + - `V_IP V_TOKEN"`, + `V_IP V_TOKEN"`, uninstall: `sudo bash -c " cd /opt/utmstack-linux-collectors/as400 && ./utmstack_collectors_installer ` + diff --git a/frontend/src/app/app-module/shared/services/utm-module-collector.service.ts b/frontend/src/app/app-module/shared/services/utm-module-collector.service.ts index 9e1a37398..b5b5fc330 100644 --- a/frontend/src/app/app-module/shared/services/utm-module-collector.service.ts +++ b/frontend/src/app/app-module/shared/services/utm-module-collector.service.ts @@ -20,8 +20,7 @@ export class UtmModuleCollectorService { } create(body: any): Observable> { - const options = createRequestOption(body.collector); - return this.http.post(`${this.resourceUrl}/collector-config/`, body.collectorConfig, {params: options, observe: 'response'}); + return this.http.post(`${this.resourceUrl}/collector-config/`, body, {observe: 'response'}); } update(conf: UtmModuleGroupType): Observable> { diff --git a/frontend/src/app/shared/util/custom-form-validators.ts b/frontend/src/app/shared/util/custom-form-validators.ts deleted file mode 100644 index b82b574d6..000000000 --- a/frontend/src/app/shared/util/custom-form-validators.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms'; - -export class IpFormsValidators { - - static ipOrCidr(): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - if (!control.value) { - return null; - } - - const value = control.value.trim(); - - if (value.includes('/')) { - return IpFormsValidators.validateCIDR(value) ? null : { invalidCidr: true }; - } - - const isValidIPv4 = IpFormsValidators.validateIPv4(value); - const isValidIPv6 = IpFormsValidators.validateIPv6(value); - - return (isValidIPv4 || isValidIPv6) ? null : { invalidIp: true }; - }; - } - - private static validateIPv4(ip: string): boolean { - const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; - const match = ip.match(ipv4Regex); - - if (!match) { - return false; - } - - for (let i = 1; i <= 4; i++) { - const octet = parseInt(match[i], 10); - if (octet < 0 || octet > 255) { - return false; - } - } - - return true; - } - - private static validateIPv6(ip: string): boolean { - // tslint:disable-next-line:max-line-length - const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; - - return ipv6Regex.test(ip); - } - - private static validateCIDR(cidr: string): boolean { - const parts = cidr.split('/'); - - if (parts.length !== 2) { - return false; - } - - const [ip, prefix] = parts; - const prefixNum = parseInt(prefix, 10); - - const isIPv4 = ip.includes('.') && !ip.includes(':'); - const isIPv6 = ip.includes(':'); - - if (isIPv4) { - if (!IpFormsValidators.validateIPv4(ip)) { - return false; - } - - if (isNaN(prefixNum) || prefixNum < 0 || prefixNum > 32) { - return false; - } - } else if (isIPv6) { - - if (!IpFormsValidators.validateIPv6(ip)) { - return false; - } - - if (isNaN(prefixNum) || prefixNum < 0 || prefixNum > 128) { - return false; - } - } else { - return false; - } - - return true; - } - -}