save method in model fixes payload, validator catches the rest (#9758)

This commit is contained in:
Sean-McQ 2023-09-27 11:40:36 -06:00 committed by GitHub
parent 718ffd6201
commit 6f0a3d436a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 76 additions and 8 deletions

View File

@ -5712,6 +5712,7 @@ validation:
missingResource: You must specify a Resource for each resource grant
missingApiGroup: You must specify an API Group for each resource grant
missingOneResource: You must specify at least one Resource, Non-Resource URL or API Group for each resource grant
noResourceAndNonResource: Each rule may contain Resources or Non-Resource URLs but not both
service:
externalName:
none: External Name is required on an ExternalName Service.

View File

@ -2,6 +2,8 @@
import { MANAGEMENT, RBAC } from '@shell/config/types';
import CruResource from '@shell/components/CruResource';
import CreateEditView from '@shell/mixins/create-edit-view';
import FormValidation from '@shell/mixins/form-validation';
import Error from '@shell/components/form/Error';
import { RadioGroup } from '@components/Form/Radio';
import Select from '@shell/components/form/Select';
import ArrayList from '@shell/components/form/ArrayList';
@ -58,9 +60,10 @@ export default {
Tabbed,
SortableTable,
Loading,
Error
},
mixins: [CreateEditView],
mixins: [CreateEditView, FormValidation],
async fetch() {
// We don't want to get all schemas from the cluster because there are
@ -109,6 +112,9 @@ export default {
scopedResources: SCOPED_RESOURCES,
defaultValue: false,
selectFocused: null,
fvFormRuleSets: [
{ path: 'displayName', rules: ['required'] }
],
};
},
@ -524,7 +530,8 @@ export default {
:can-yaml="!isCreate"
:mode="mode"
:resource="value"
:errors="errors"
:errors="fvUnreportedValidationErrors"
:validation-passed="fvFormIsValid"
:cancel-event="true"
@error="e=>errors = e"
@finish="save"
@ -568,6 +575,7 @@ export default {
name-key="displayName"
description-key="description"
label="Name"
:rules="{ name: fvGetAndReportPathRules('displayName') }"
/>
<div
v-if="isRancherType"
@ -606,6 +614,11 @@ export default {
:label="t('rbac.roletemplate.tabs.grantResources.label')"
:weight="1"
>
<Error
:value="value.rules"
:rules="fvGetAndReportPathRules('rules')"
as-banner
/>
<ArrayList
v-model="value.rules"
label="Resources"

View File

@ -4,7 +4,6 @@ import { CATTLE_API_GROUP, SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/m
import { uniq } from '@shell/utils/array';
import { get } from '@shell/utils/object';
import SteveDescriptionModel from '@shell/plugins/steve/steve-description-class';
import Role from './rbac.authorization.k8s.io.role';
import { AS, MODE, _CLONE, _UNFLAG } from '@shell/config/query-params';
const BASE = 'user-base';
@ -16,7 +15,14 @@ const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
export default class GlobalRole extends SteveDescriptionModel {
get customValidationRules() {
return Role.customValidationRules();
return [
{
path: 'rules',
validators: [`roleTemplateRules:${ this.type }`],
nullable: false,
type: 'array',
},
];
}
get details() {
@ -137,6 +143,15 @@ export default class GlobalRole extends SteveDescriptionModel {
async save() {
const norman = await this.norman;
for (const rule of norman.rules) {
if (rule.nonResourceURLs.length) {
delete rule.resources;
delete rule.apiGroups;
} else {
delete rule.nonResourceURLs;
}
}
return norman.save();
}

View File

@ -3,7 +3,6 @@ import { get } from '@shell/utils/object';
import { DESCRIPTION } from '@shell/config/labels-annotations';
import { NORMAN } from '@shell/config/types';
import SteveDescriptionModel from '@shell/plugins/steve/steve-description-class';
import Role from './rbac.authorization.k8s.io.role';
import { AS, MODE, _CLONE, _UNFLAG } from '@shell/config/query-params';
export const CATTLE_API_GROUP = '.cattle.io';
@ -60,7 +59,14 @@ export const CREATE_VERBS = new Set(['PUT', 'blocked-PUT']);
export default class RoleTemplate extends SteveDescriptionModel {
get customValidationRules() {
return Role.customValidationRules();
return [
{
path: 'rules',
validators: [`roleTemplateRules:${ this.type }`],
nullable: false,
type: 'array',
},
];
}
get details() {
@ -185,6 +191,15 @@ export default class RoleTemplate extends SteveDescriptionModel {
async save() {
const norman = await this.norman;
for (const rule of norman.rules) {
if (rule.nonResourceURLs.length) {
delete rule.resources;
delete rule.apiGroups;
} else {
delete rule.nonResourceURLs;
}
}
return norman.save();
}

View File

@ -393,6 +393,18 @@ describe('formRules', () => {
expect(formRuleResult).toStrictEqual(expectedResult);
});
it('"roleTemplateRules" : returns correct message when type is RBAC role rule defines both resources and nonResourceURLs', () => {
const testValue: [{}] = [
{
verbs: ['verb1'], resources: ['resource1'], apiGroups: ['apiGroup1'], nonResourceURLs: ['nonResourceUrl1']
}
];
const formRuleResult = formRules.roleTemplateRules('rbac.authorization.k8s.io.role')(testValue);
const expectedResult = JSON.stringify({ message: 'validation.roleTemplate.roleTemplateRules.noResourceAndNonResource' });
expect(formRuleResult).toStrictEqual(expectedResult);
});
it('"roleTemplateRules" : returns correct message when type is RBAC role and value is missing apiGroups', () => {
const testValue: [{}] = [
{
@ -408,7 +420,7 @@ describe('formRules', () => {
it('"roleTemplateRules" : returns undefined when type is not RBAC role and value contains valid rules', () => {
const testValue: [{}] = [
{
verbs: ['verb1'], nonResourceURLs: ['nonResourceURL1'], resources: ['resource1'], apiGroups: ['apiGroup1']
verbs: ['verb1'], resources: ['resource1'], apiGroups: ['apiGroup1']
}
];
const formRuleResult = formRules.roleTemplateRules('nonrbactype')(testValue);

View File

@ -370,6 +370,10 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
return t('validation.roleTemplate.roleTemplateRules.missingVerb');
}
if (val.some((rule: any) => rule.resources?.length && rule.nonResourceURLs?.length)) {
return t('validation.roleTemplate.roleTemplateRules.noResourceAndNonResource');
}
if (type === RBAC.ROLE) {
if (val.some((rule: any) => isEmpty(rule.resources))) {
return t('validation.roleTemplate.roleTemplateRules.missingResource');

View File

@ -6,6 +6,10 @@ export function roleTemplateRules(rules = [], getters, errors, validatorArgs = [
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingVerb'));
}
if (rules.some((rule) => rule.resources?.length && rule.nonResourceURLs?.length)) {
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.noResourceAndNonResource'));
}
if (validatorArgs[0] === RBAC.ROLE) {
if (rules.some((rule) => isEmpty(rule.resources))) {
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingResource'));
@ -13,7 +17,11 @@ export function roleTemplateRules(rules = [], getters, errors, validatorArgs = [
if (rules.some((rule) => isEmpty(rule.apiGroups))) {
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingApiGroup'));
}
} else if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs) && isEmpty(rule.apiGroups))) {
} else if (rules.some((rule) => rule.resources?.length && rule.nonResourceUrls?.length)) {
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.noResourceAndNonResource'));
}
if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs) && isEmpty(rule.apiGroups))) {
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingOneResource'));
}
}