/* * Copyright 2024 Harness, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import produce from 'immer' import * as Yup from 'yup' import { compact, get, isEmpty, set } from 'lodash-es' import type { AccessKeySecretKey, Anonymous, UserPassword } from '@harnessio/react-har-service-client' import { Parent } from '@ar/common/types' import { URL_REGEX } from '@ar/constants' import type { Scope } from '@ar/MFEAppTypes' import type { StringKeys } from '@ar/frameworks/strings' import { SecretValueType } from '@ar/components/MultiTypeSecretInput/MultiTypeSecretInput' import { UpstreamRepositoryURLInputSource, UpstreamProxyAuthenticationMode, type UpstreamRegistryRequest } from '../../types' export function getSecretSpacePath(referenceString: string, scope?: Scope) { if (!scope) return referenceString if (referenceString.startsWith('account.')) { return compact([scope.accountId]).join('/') } if (referenceString.startsWith('org.')) { return compact([scope.accountId, scope.orgIdentifier]).join('/') } return compact([scope.accountId, scope.orgIdentifier, scope.projectIdentifier]).join('/') } export function getReferenceStringFromSecretSpacePath(identifier: string, secretSpacePath: string) { const [accountId, orgIdentifier, projectIdentifier] = secretSpacePath.split('/') if (projectIdentifier) return identifier if (orgIdentifier) return `org.${identifier}` if (accountId) return `account.${identifier}` return identifier } function convertSecretInputToFormFields( formData: UpstreamRegistryRequest, secretField: string, secretSpacePathField: string, scope?: Scope ) { const password = get(formData, secretField) set(formData, secretSpacePathField, getSecretSpacePath(get(password, 'referenceString', ''), scope)) set(formData, secretField, get(password, 'identifier')) } function convertMultiTypeSecretInputToFormFields( formData: UpstreamRegistryRequest, typeField: string, textField: string, secretField: string, secretSpacePathField: string, scope?: Scope ) { const accessKeyType = get(formData, typeField) if (accessKeyType === SecretValueType.TEXT) { const value = get(formData, textField, '') set(formData, textField, value) set(formData, typeField, undefined) set(formData, secretField, undefined) set(formData, secretSpacePathField, undefined) } else { const accessKeySecret = get(formData, secretField) set(formData, secretSpacePathField, getSecretSpacePath(get(accessKeySecret, 'referenceString', ''), scope)) set(formData, secretField, get(accessKeySecret, 'identifier')) set(formData, typeField, undefined) set(formData, textField, undefined) } } export function getFormattedFormDataForAuthType( values: UpstreamRegistryRequest, parent?: Parent, scope?: Scope ): UpstreamRegistryRequest { return produce(values, (draft: UpstreamRegistryRequest) => { if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) { set(draft, 'config.auth.authType', draft.config.authType) if (parent === Parent.Enterprise) { convertSecretInputToFormFields(draft, 'config.auth.secretIdentifier', 'config.auth.secretSpacePath', scope) } } else if (draft.config.authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY) { set(draft, 'config.auth.authType', draft.config.authType) if (parent === Parent.Enterprise) { convertSecretInputToFormFields( draft, 'config.auth.secretKeyIdentifier', 'config.auth.secretKeySpacePath', scope ) convertMultiTypeSecretInputToFormFields( draft, 'config.auth.accessKeyType', 'config.auth.accessKey', 'config.auth.accessKeySecretIdentifier', 'config.auth.accessKeySecretSpacePath', scope ) } } else if (draft.config.authType === UpstreamProxyAuthenticationMode.ANONYMOUS) { set(draft, 'config.auth', null) } if (draft.config.source === UpstreamRepositoryURLInputSource.Dockerhub) { set(draft, 'config.url', '') } }) } export function getSecretScopeDetailsByIdentifier(identifier: string, secretSpacePath: string) { const referenceString = getReferenceStringFromSecretSpacePath(identifier, secretSpacePath) const [, orgIdentifier, projectIdentifier] = secretSpacePath.split('/') return { identifier: identifier, name: identifier, referenceString, orgIdentifier, projectIdentifier } } function convertFormFieldsToSecreteInput( formData: UpstreamRegistryRequest, secretField: string, secretSpacePathField: string ) { const secretIdentifier = get(formData, secretField, '') const secretSpacePath = get(formData, secretSpacePathField, '') set(formData, secretField, getSecretScopeDetailsByIdentifier(secretIdentifier, secretSpacePath)) } function convertFormFieldsToMultiTypeSecretInput( formData: UpstreamRegistryRequest, typeField: string, formField: string, secretIdentifierField: string, secretSpacePathField: string ) { const value = get(formData, formField) if (value) { set(formData, formField, value) set(formData, typeField, SecretValueType.TEXT) set(formData, secretIdentifierField, undefined) set(formData, secretSpacePathField, undefined) } else { const secretIdentifierValue = get(formData, secretIdentifierField, '') const secretSpacePathValue = get(formData, secretSpacePathField, '') set(formData, typeField, SecretValueType.ENCRYPTED) set(formData, secretIdentifierField, getSecretScopeDetailsByIdentifier(secretIdentifierValue, secretSpacePathValue)) set(formData, formField, undefined) } } export function getFormattedInitialValuesForAuthType(values: UpstreamRegistryRequest, parent?: Parent) { return produce(values, (draft: UpstreamRegistryRequest) => { if (draft.config.authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD) { if (parent === Parent.Enterprise) { convertFormFieldsToSecreteInput(draft, 'config.auth.secretIdentifier', 'config.auth.secretSpacePath') } } if (draft.config.authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY) { if (parent === Parent.Enterprise) { convertFormFieldsToSecreteInput(draft, 'config.auth.secretKeyIdentifier', 'config.auth.secretKeySpacePath') convertFormFieldsToMultiTypeSecretInput( draft, 'config.auth.accessKeyType', 'config.auth.accessKey', 'config.auth.accessKeySecretIdentifier', 'config.auth.accessKeySecretSpacePath' ) } else { const accessKeyType = isEmpty(get(draft, 'config.auth.accessKey')) ? SecretValueType.ENCRYPTED : SecretValueType.TEXT set(draft, 'config.auth.accessKeyType', accessKeyType) } } }) } export function getValidationSchemaForUpstreamForm(getString: (key: StringKeys, vars?: Record) => string) { return { config: Yup.object().shape({ authType: Yup.string() .required() .oneOf([ UpstreamProxyAuthenticationMode.ANONYMOUS, UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD, UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY ]), auth: Yup.object() .when(['authType'], { is: (authType: UpstreamProxyAuthenticationMode) => authType === UpstreamProxyAuthenticationMode.USER_NAME_AND_PASSWORD, then: (schema: Yup.ObjectSchema) => schema.shape({ userName: Yup.string().trim().required(getString('validationMessages.userNameRequired')), secretIdentifier: Yup.string().trim().required(getString('validationMessages.passwordRequired')) }), otherwise: Yup.object().optional().nullable() }) .when(['authType'], { is: (authType: UpstreamProxyAuthenticationMode) => authType === UpstreamProxyAuthenticationMode.ACCESS_KEY_AND_SECRET_KEY, then: (schema: Yup.ObjectSchema) => schema.shape({ accessKey: Yup.string() .trim() .test('access-key-validation', getString('validationMessages.accessKeyRequired'), function (value) { if (this.parent.accessKeyType === SecretValueType.TEXT) { return !isEmpty(value) } return true }), accessKeySecretIdentifier: Yup.string() .trim() .test( 'access-key-secret-validation', getString('validationMessages.accessKeyRequired'), function (value) { if (this.parent.accessKeyType === SecretValueType.ENCRYPTED) { return !isEmpty(value) } return true } ), secretKeyIdentifier: Yup.string().trim().required(getString('validationMessages.secretKeyRequired')) }), otherwise: Yup.object().optional().nullable() }) .nullable(), url: Yup.string().when(['source'], { is: (source: UpstreamRepositoryURLInputSource) => [UpstreamRepositoryURLInputSource.Custom, UpstreamRepositoryURLInputSource.AwsEcr].includes(source), then: (schema: Yup.StringSchema) => schema .trim() .required(getString('validationMessages.urlRequired')) .matches(URL_REGEX, getString('validationMessages.urlPattern')), otherwise: (schema: Yup.StringSchema) => schema.trim().notRequired() }) }), cleanupPolicy: Yup.array() .of( Yup.object().shape({ name: Yup.string().trim().required(getString('validationMessages.cleanupPolicy.nameRequired')), expireDays: Yup.number() .required(getString('validationMessages.cleanupPolicy.expireDaysRequired')) .positive(getString('validationMessages.cleanupPolicy.positiveExpireDays')) }) ) .optional() .nullable() } }