Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.11.2 merge #2488

Merged
merged 10 commits into from
Nov 29, 2023
2 changes: 1 addition & 1 deletion dashboard/src/components/access/InviteUsersToProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ limitations under the License.
import AccessService from '@/components/access/AccessService';
import MsgBoxMixin from '@/components/utils/modal/MsgBoxMixin';

const validEmail = /^[a-z0-9.]{1,64}@[a-z0-9.]{1,64}$/i;
const validEmail = /^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]{1,64}@[a-zA-Z0-9.-]{1,64}$/i;
const stripNames = /<([^\s<>@]+@[^\s<>@]+)>/;
export default {
name: 'InviteUsersToProject',
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/badges/EditBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ limitations under the License.
<div class="media-body">
<div class="form-group">
<label for="badgeName">* Badge Name</label>
<ValidationProvider rules="required|minNameLength|maxBadgeNameLength|uniqueName|customNameValidator"
<ValidationProvider rules="required|minNameLength|maxBadgeNameLength|nullValueNotAllowed|uniqueName|customNameValidator"
v-slot="{errors}" name="Badge Name" :debounce="250">
<input v-focus class="form-control" id="badgeName" type="text" v-model="badgeInternal.name"
@input="updateBadgeId" aria-required="true" data-cy="badgeName"
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/projects/EditProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ limitations under the License.
<div class="col-12">
<div class="form-group">
<label for="projectIdInput">* {{ nameLabelTxt }}</label>
<ValidationProvider rules="required|minNameLength|maxProjectNameLength|uniqueName|customNameValidator"
<ValidationProvider rules="required|minNameLength|maxProjectNameLength|uniqueName|customNameValidator|nullValueNotAllowed"
v-slot="{errors}"
:debounce="250"
name="Project Name">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ limitations under the License.
<b-container v-if="!loading" fluid data-cy="editQuestionModal">
<ReloadMessage v-if="restoredFromStorage" @discard-changes="discardChanges" />

<ValidationProvider rules="required|maxDescriptionLength|customDescriptionValidator" :debounce="250" v-slot="{errors}" name="Question">
<ValidationProvider rules="required|maxDescriptionLength|nullValueNotAllowed|customDescriptionValidator" :debounce="250" v-slot="{errors}" name="Question">
<markdown-editor v-if="showQuestion && questionDefInternal"
:quiz-id="quizId"
label="Question"
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/quiz/testCreation/EditQuiz.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ limitations under the License.
<div class="form-group">
<label for="quizNameInput">* Name</label>
<ValidationProvider
rules="required|minNameLength|maxQuizNameLength|uniqueName|customNameValidator"
rules="required|minNameLength|maxQuizNameLength|nullValueNotAllowed|uniqueName|customNameValidator"
:debounce="500"
v-slot="{errors}"
name="Quiz Name">
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/skills/EditSkill.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ limitations under the License.
<div class="col-12 col-lg">
<div class="form-group">
<label for="skillName">* Skill Name</label>
<ValidationProvider rules="required|minNameLength|maxSkillNameLength|uniqueName|customNameValidator" :debounce="250" v-slot="{errors}" name="Skill Name" ref="skillNameProvider">
<ValidationProvider rules="required|minNameLength|maxSkillNameLength|nullValueNotAllowed|uniqueName|customNameValidator" :debounce="250" v-slot="{errors}" name="Skill Name" ref="skillNameProvider">
<input type="text" class="form-control" id="skillName" @input="updateSkillId"
v-model="skillInternal.name" v-focus
aria-required="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ limitations under the License.
<div class="col-12">
<div class="form-group">
<label for="groupNameInput">* Group Name</label>
<ValidationProvider rules="required|minNameLength|maxSkillNameLength|uniqueGroupName|customNameValidator"
<ValidationProvider rules="required|minNameLength|maxSkillNameLength|nullValueNotAllowed|uniqueGroupName|customNameValidator"
v-slot="{errors}"
:debounce="250"
name="Group Name">
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/subjects/EditSubject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ limitations under the License.
<div class="form-group">
<label for="subjName">Subject Name</label>
<ValidationProvider
rules="required|minNameLength|maxSubjectNameLength|uniqueName|customNameValidator" :debounce="250"
rules="required|minNameLength|maxSubjectNameLength|nullValueNotAllowed|uniqueName|customNameValidator" :debounce="250"
v-slot="{ errors }" name="Subject Name">
<input type="text" class="form-control" id="subjName" @input="updateSubjectId"
v-model="subjectInternal.name" v-on:input="updateSubjectId"
Expand Down
2 changes: 1 addition & 1 deletion dashboard/src/components/utils/inputForm/IdInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ limitations under the License.
},
data() {
return {
rules: `required|minIdLength|maxIdLength|${this.isSkillId ? 'skill_id_validator' : 'id_validator'}`,
rules: `required|minIdLength|maxIdLength|nullValueNotAllowed|${this.isSkillId ? 'skill_id_validator' : 'id_validator'}`,
canEdit: false,
internalValue: this.value,
};
Expand Down
27 changes: 27 additions & 0 deletions dashboard/src/validators/NotNullValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2020 SkillTree
*
* 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
*
* https://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 { extend } from 'vee-validate';

const validator = {
message: (field) => `Null is not allowed for ${field}`,
validate(value) {
return !value || value.trim().toLowerCase() !== 'null';
},
};

extend('nullValueNotAllowed', validator);

export default validator;
1 change: 1 addition & 0 deletions dashboard/src/validators/RegisterValidators.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import './OptionalNumericValidator';
import './CustomNameValidator';
import './IdValidator';
import './SkillIdValidator';
import './NotNullValidator';
import './UrlValidator';
import store from '../store/store';

Expand Down
30 changes: 30 additions & 0 deletions e2e-tests/cypress/e2e/projects_modal_validation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,34 @@ describe('Projects Modal Validation Tests', () => {
cy.get('[data-cy="projectDescriptionError"]').contains('Mocked up validation failure')
});

it('null word is not allowed for project ID or project name', () => {
cy.visit('/administrator/');
cy.get('[data-cy="newProjectButton"]').click()
cy.get('[data-cy="projectName"]')
.type('null');
cy.get('[data-cy=projectNameError]')
.contains('Null is not allowed for Project Name')
.should('be.visible');
cy.get('[data-cy="idError"]')
.contains('Null is not allowed for Project ID')
.should('be.visible');
cy.get('[data-cy=saveProjectButton]')
.should('be.disabled');
cy.get('[data-cy="projectName"]').clear().type('one')
cy.get('[data-cy=projectNameError]').should('not.be.visible')
cy.get('[data-cy=idError]').should('not.be.visible')

// verify validator is case-insensitive and trims whitespace
cy.get('[data-cy="projectName"]').clear()
.type(' NUlL ');
cy.get('[data-cy=projectNameError]')
.contains('Null is not allowed for Project Name')
.should('be.visible');
cy.get('[data-cy="idError"]')
.contains('Null is not allowed for Project ID')
.should('be.visible');
cy.get('[data-cy=saveProjectButton]')
.should('be.disabled');
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class AdminController {
@RequestBody NameExistsRequest existsRequest) {
String subjectName = existsRequest.name?.trim()
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(subjectName, "Subject Name")
SkillsValidator.isNotBlank(subjectName, "Subject Name", projectId, null, true )

def sanitize = InputSanitizer.sanitize(subjectName)
return subjAdminService.existsBySubjectName(InputSanitizer.sanitize(projectId), sanitize)
Expand All @@ -342,7 +342,7 @@ class AdminController {
@RequestBody NameExistsRequest nameExistsRequest) {
String badgeName = nameExistsRequest.name?.trim()
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(badgeName, "Badge Name")
SkillsValidator.isNotBlank(badgeName, "Badge Name", projectId, null, true)
return badgeAdminService.existsByBadgeName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(badgeName))
}

Expand All @@ -353,7 +353,7 @@ class AdminController {
@RequestBody NameExistsRequest existsRequest) {
String skillName = existsRequest.name?.trim()
SkillsValidator.isNotBlank(projectId, "Project Id")
SkillsValidator.isNotBlank(skillName, "Skill Name")
SkillsValidator.isNotBlank(skillName, "Skill Name", projectId, null, true)
return skillsAdminService.existsBySkillName(InputSanitizer.sanitize(projectId), InputSanitizer.sanitize(skillName))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import org.apache.commons.lang3.StringUtils

class QuizValidator {

static void isNotBlank(String value, String attrName, String quizId = null) {
if (StringUtils.isBlank(value) || value?.trim().equalsIgnoreCase("null")) {
static void isNotBlank(String value, String attrName, String quizId = null, boolean allowNullValueStr = false) {
if (StringUtils.isBlank(value) || (!allowNullValueStr && value?.trim().equalsIgnoreCase("null"))) {
throw new SkillQuizException("${attrName} was not provided.".toString(), quizId, ErrorCode.BadParam)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import org.apache.commons.lang3.StringUtils

class SkillsValidator {

static void isNotBlank(String value, String attrName, String projectId = null, String skillId = null) {
if (StringUtils.isBlank(value) || value?.trim().equalsIgnoreCase("null")) {
static void isNotBlank(String value, String attrName, String projectId = null, String skillId = null, boolean allowNullValueStr = false) {
if (StringUtils.isBlank(value) || (!allowNullValueStr && value?.trim().equalsIgnoreCase("null"))) {
throw new SkillException("${attrName} was not provided.".toString(), projectId, skillId, ErrorCode.BadParam)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@ class QuizDefService {
QuizValidator.isNotNull(questionDefRequest.answers, "answers", quizId)
QuizValidator.isTrue(questionDefRequest.answers.size() >= 2, "Must have at least 2 answers", quizId)
questionDefRequest.answers.each {
QuizValidator.isNotBlank(it.answer, "answers.answer", quizId)
QuizValidator.isNotBlank(it.answer, "answers.answer", quizId, true)
propsBasedValidator.quizValidationMaxStrLength(PublicProps.UiProp.maxQuizTextAnswerLength, "Answer", it.answer, quizDef.quizId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -689,15 +689,15 @@ interface UserPointsRepo extends CrudRepository<UserPoints, Integer> {
)
SELECT COUNT(*)
FROM (
SELECT DISTINCT up.user_id
SELECT up.user_id, SUM(up.points) as total_points
from user_points up, user_attrs usattr
where
up.user_id = usattr.user_id and
up.skill_ref_id in (select id from subj_skills) and
up.points >= :minimumPoints and
(lower(CONCAT(usattr.first_name, ' ', usattr.last_name, ' (', usattr.user_id_for_display, ')')) like lower(CONCAT('%', :userId, '%')) OR
lower(usattr.user_id_for_display) like lower(CONCAT('%', :userId, '%')))
) AS temp
group by up.user_id
) AS temp WHERE total_points >= :minimumPoints
''', nativeQuery = true)
Long countDistinctUsersByProjectIdAndSubjectIdAndUserIdLike(@Param("projectId") String projectId, @Param("subjectId") String subjectId, @Param("userId") String userId, @Param("minimumPoints") int minimumPoints)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ class ConstraintViolationSpecs extends DefaultIntSpec {
Map proj = SkillsFactory.createProject()
Map subject = SkillsFactory.createSubject()
Map skill = SkillsFactory.createSkill()
Map skill2 = SkillsFactory.createSkill(1, 1, 2, )
Map skill2 = SkillsFactory.createSkill(1, 1, 2,)
Map copy = new HashMap(skill)
copy.skillId = skill2.skillId.toUpperCase()
copy.name = "somethingElse"
Expand Down Expand Up @@ -460,4 +460,48 @@ class ConstraintViolationSpecs extends DefaultIntSpec {
exception.message.contains("errorCode:ConstraintViolation")
}

}
def "subject name exists does not fail on the null word"() {
def proj = SkillsFactory.createProject(1)
skillsService.createProject(proj)
when:
def exist = skillsService.subjectNameExists([projectId: proj.projectId, subjectName: "null"])

then:
exist == false
}

def "project name exists does not fail on the null word"() {
when:
def exist = skillsService.projectNameExists([projectName: "null"])
then:
exist == false
}

def "badge name exists does not fail on the null word"() {
def proj = SkillsFactory.createProject(1)
skillsService.createProject(proj)
when:
def exist = skillsService.badgeNameExists([projectId: proj.projectId, badgeName: "null"])
then:
exist == false
}

def "skill name exists does not fail on the null word"() {
def proj = SkillsFactory.createProject(1)
skillsService.createProject(proj)
def subj = SkillsFactory.createSubject(1, 1)
skillsService.createSubject(subj)
when:
def exist = skillsService.skillNameExists([projectId: proj.projectId, skillName: "null"])
then:
exist == false
}

def "quiz name exists does not fail on the null word"() {
when:
def exist = skillsService.quizNameExist("null")
then:
exist.body == false
}

}
Loading