diff --git a/.secrets.baseline b/.secrets.baseline index 5e2be19d..ccfc932e 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$|^.*pgsslrootcert.yml$", "lines": null }, - "generated_at": "2020-01-09T16:55:07Z", + "generated_at": "2020-01-16T16:10:27Z", "plugins_used": [ { "base64_limit": 4.5, diff --git a/atst/app.py b/atst/app.py index d73188cf..29476ed8 100644 --- a/atst/app.py +++ b/atst/app.py @@ -222,7 +222,7 @@ def make_config(direct_config=None): config.read_dict({"default": direct_config}) # Assemble DATABASE_URI value - database_uri = "postgres://{}:{}@{}:{}/{}".format( # pragma: allowlist secret + database_uri = "postgresql://{}:{}@{}:{}/{}".format( # pragma: allowlist secret config.get("default", "PGUSER"), config.get("default", "PGPASSWORD"), config.get("default", "PGHOST"), diff --git a/atst/forms/application.py b/atst/forms/application.py index de6294cd..3fda8f8f 100644 --- a/atst/forms/application.py +++ b/atst/forms/application.py @@ -1,14 +1,14 @@ from .forms import BaseForm, remove_empty_string from wtforms.fields import StringField, TextAreaField, FieldList -from wtforms.validators import Required, Optional -from atst.forms.validators import ListItemRequired, ListItemsUnique +from wtforms.validators import Required, Optional, Length +from atst.forms.validators import ListItemRequired, ListItemsUnique, Name, AlphaNumeric from atst.utils.localization import translate class EditEnvironmentForm(BaseForm): name = StringField( label=translate("forms.environments.name_label"), - validators=[Required()], + validators=[Required(), Name(), Length(max=100)], filters=[remove_empty_string], ) @@ -16,12 +16,12 @@ class EditEnvironmentForm(BaseForm): class NameAndDescriptionForm(BaseForm): name = StringField( label=translate("forms.application.name_label"), - validators=[Required()], + validators=[Required(), Name(), Length(max=100)], filters=[remove_empty_string], ) description = TextAreaField( label=translate("forms.application.description_label"), - validators=[Optional()], + validators=[Optional(), Length(max=1_000)], filters=[remove_empty_string], ) @@ -31,6 +31,7 @@ class EnvironmentsForm(BaseForm): StringField( label=translate("forms.application.environment_names_label"), filters=[remove_empty_string], + validators=[AlphaNumeric(), Length(max=100)], ), validators=[ ListItemRequired( diff --git a/atst/forms/application_member.py b/atst/forms/application_member.py index ec873f77..62c5ddfe 100644 --- a/atst/forms/application_member.py +++ b/atst/forms/application_member.py @@ -1,5 +1,6 @@ from flask_wtf import FlaskForm from wtforms.fields import FormField, FieldList, HiddenField, BooleanField +from wtforms.validators import UUID from wtforms import Form from .member import NewForm as BaseNewMemberForm @@ -7,11 +8,13 @@ from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS from atst.forms.fields import SelectField from atst.domain.permission_sets import PermissionSets from atst.utils.localization import translate +from atst.forms.validators import AlphaNumeric +from wtforms.validators import Length class EnvironmentForm(Form): - environment_id = HiddenField() - environment_name = HiddenField() + environment_id = HiddenField(validators=[UUID()]) + environment_name = HiddenField(validators=[AlphaNumeric(), Length(max=100)]) role = SelectField( environment_name, choices=ENV_ROLES, diff --git a/atst/forms/ccpo_user.py b/atst/forms/ccpo_user.py index e9e07ec2..7fafb88c 100644 --- a/atst/forms/ccpo_user.py +++ b/atst/forms/ccpo_user.py @@ -2,12 +2,12 @@ from flask_wtf import FlaskForm from wtforms.validators import Required, Length from wtforms.fields import StringField -from atst.forms.validators import IsNumber +from atst.forms.validators import Number from atst.utils.localization import translate class CCPOUserForm(FlaskForm): dod_id = StringField( translate("forms.new_member.dod_id_label"), - validators=[Required(), Length(min=10, max=10), IsNumber()], + validators=[Required(), Length(min=10, max=10), Number()], ) diff --git a/atst/forms/edit_user.py b/atst/forms/edit_user.py index 835db7e3..9d2a8ffc 100644 --- a/atst/forms/edit_user.py +++ b/atst/forms/edit_user.py @@ -9,22 +9,26 @@ from .forms import BaseForm from .data import SERVICE_BRANCHES from atst.models.user import User from atst.utils.localization import translate +from wtforms.validators import Length +from atst.forms.validators import Number from .validators import Name, DateRange, PhoneNumber USER_FIELDS = { "first_name": StringField( - translate("forms.edit_user.first_name_label"), validators=[Name()] + translate("forms.edit_user.first_name_label"), + validators=[Name(), Length(max=100)], ), "last_name": StringField( - translate("forms.edit_user.last_name_label"), validators=[Name()] + translate("forms.edit_user.last_name_label"), + validators=[Name(), Length(max=100)], ), "email": EmailField(translate("forms.edit_user.email_label"), validators=[Email()]), "phone_number": TelField( translate("forms.edit_user.phone_number_label"), validators=[PhoneNumber()] ), - "phone_ext": StringField("Extension"), + "phone_ext": StringField("Extension", validators=[Number(), Length(max=10)]), "service_branch": SelectField( translate("forms.edit_user.service_branch_label"), choices=SERVICE_BRANCHES ), diff --git a/atst/forms/member.py b/atst/forms/member.py index a97d5852..4bfb2269 100644 --- a/atst/forms/member.py +++ b/atst/forms/member.py @@ -3,16 +3,18 @@ from wtforms.fields.html5 import EmailField, TelField from wtforms.validators import Required, Email, Length, Optional from wtforms.fields import StringField -from atst.forms.validators import IsNumber, PhoneNumber +from atst.forms.validators import Number, PhoneNumber, Name from atst.utils.localization import translate class NewForm(FlaskForm): first_name = StringField( - label=translate("forms.new_member.first_name_label"), validators=[Required()] + label=translate("forms.new_member.first_name_label"), + validators=[Required(), Name(), Length(max=100)], ) last_name = StringField( - label=translate("forms.new_member.last_name_label"), validators=[Required()] + label=translate("forms.new_member.last_name_label"), + validators=[Required(), Name(), Length(max=100)], ) email = EmailField( translate("forms.new_member.email_label"), validators=[Required(), Email()] @@ -21,8 +23,8 @@ class NewForm(FlaskForm): translate("forms.new_member.phone_number_label"), validators=[Optional(), PhoneNumber()], ) - phone_ext = StringField("Extension") + phone_ext = StringField("Extension", validators=[Number(), Length(max=10)]) dod_id = StringField( translate("forms.new_member.dod_id_label"), - validators=[Required(), Length(min=10), IsNumber()], + validators=[Required(), Length(min=10), Number()], ) diff --git a/atst/forms/portfolio.py b/atst/forms/portfolio.py index 591cc080..7c4e6644 100644 --- a/atst/forms/portfolio.py +++ b/atst/forms/portfolio.py @@ -4,6 +4,7 @@ from wtforms.fields import ( TextAreaField, ) from wtforms.validators import Length, InputRequired +from atst.forms.validators import Name from wtforms.widgets import ListWidget, CheckboxInput from .forms import BaseForm @@ -20,10 +21,13 @@ class PortfolioForm(BaseForm): min=4, max=100, message=translate("forms.portfolio.name.length_validation_message"), - ) + ), + Name(), ], ) - description = TextAreaField(translate("forms.portfolio.description.label"),) + description = TextAreaField( + translate("forms.portfolio.description.label"), validators=[Length(max=1_000)] + ) class PortfolioCreationForm(PortfolioForm): diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index fab3707c..6b209bf8 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -7,18 +7,23 @@ from wtforms.fields import ( HiddenField, ) from wtforms.fields.html5 import DateField -from wtforms.validators import Required, Length, NumberRange, ValidationError, Regexp +from wtforms.validators import ( + Required, + Length, + NumberRange, + ValidationError, +) from flask_wtf import FlaskForm -from numbers import Number +import numbers +from atst.forms.validators import Number, AlphaNumeric from .data import JEDI_CLIN_TYPES from .fields import SelectField from .forms import BaseForm, remove_empty_string from atst.utils.localization import translate -from .validators import REGEX_ALPHA_NUMERIC from flask import current_app as app -MAX_CLIN_AMOUNT = 1000000000 +MAX_CLIN_AMOUNT = 1_000_000_000 def coerce_enum(enum_inst): @@ -30,8 +35,8 @@ def coerce_enum(enum_inst): def validate_funding(form, field): if ( - isinstance(form.total_amount.data, Number) - and isinstance(field.data, Number) + isinstance(form.total_amount.data, numbers.Number) + and isinstance(field.data, numbers.Number) and form.total_amount.data < field.data ): raise ValidationError( @@ -62,7 +67,10 @@ class CLINForm(FlaskForm): coerce=coerce_enum, ) - number = StringField(label=translate("task_orders.form.clin_number_label")) + number = StringField( + label=translate("task_orders.form.clin_number_label"), + validators=[Number(), Length(max=4)], + ) start_date = DateField( translate("task_orders.form.pop_start"), description=translate("task_orders.form.pop_example"), @@ -120,7 +128,7 @@ class AttachmentForm(BaseForm): Length( max=100, message=translate("forms.attachment.filename.length_error") ), - Regexp(regex=REGEX_ALPHA_NUMERIC), + AlphaNumeric(), ], ) object_name = HiddenField( @@ -129,7 +137,7 @@ class AttachmentForm(BaseForm): Length( max=40, message=translate("forms.attachment.object_name.length_error") ), - Regexp(regex=REGEX_ALPHA_NUMERIC), + AlphaNumeric(), ], ) accept = ".pdf,application/pdf" @@ -142,6 +150,7 @@ class TaskOrderForm(BaseForm): number = StringField( label=translate("forms.task_order.number_description"), filters=[remove_empty_string], + validators=[Number(), Length(max=13)], ) pdf = FormField( AttachmentForm, diff --git a/atst/forms/validators.py b/atst/forms/validators.py index ebf29a2a..1bbcd645 100644 --- a/atst/forms/validators.py +++ b/atst/forms/validators.py @@ -2,15 +2,12 @@ from datetime import datetime import re from werkzeug.datastructures import FileStorage -from wtforms.validators import ValidationError +from wtforms.validators import ValidationError, Regexp import pendulum from atst.utils.localization import translate -REGEX_ALPHA_NUMERIC = "^[A-Za-z0-9\-_ \.]*$" - - def DateRange(lower_bound=None, upper_bound=None, message=None): def _date_range(form, field): if field.data is None: @@ -34,12 +31,13 @@ def DateRange(lower_bound=None, upper_bound=None, message=None): return _date_range -def IsNumber(message=translate("forms.validators.is_number_message")): +def Number(message=translate("forms.validators.is_number_message")): def _is_number(form, field): - try: - int(field.data) - except (ValueError, TypeError): - raise ValidationError(message) + if field.data: + try: + int(field.data) + except (ValueError, TypeError): + raise ValidationError(message) return _is_number @@ -100,3 +98,7 @@ def FileLength(max_length=50000000, message=None): field.data.seek(0) return _file_length + + +def AlphaNumeric(message=translate("forms.validators.alpha_numeric_message")): + return Regexp(regex=r"^[A-Za-z0-9\-_ \.]*$", message=message) diff --git a/js/lib/input_validations.js b/js/lib/input_validations.js index 80c16a7e..e2dc03b7 100644 --- a/js/lib/input_validations.js +++ b/js/lib/input_validations.js @@ -9,6 +9,12 @@ export default { unmask: [], validationError: 'Please enter a response', }, + clinNumber: { + mask: false, + match: /^\d{4}$/, + unmask: [], + validationError: 'Please enter a 4-digit CLIN number', + }, date: { mask: [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/], match: /(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.](19|20)\d\d/, @@ -34,6 +40,20 @@ export default { unmask: ['$', ','], validationError: 'Please enter a dollar amount', }, + defaultStringField: { + mask: false, + match: /^[A-Za-z0-9\-_ \.]{1,100}$/, + unmask: [], + validationError: + 'Please enter a response of no more than 100 alphanumeric characters', + }, + defaultTextAreaField: { + mask: false, + match: /^[A-Za-z0-9\-_ \.]{1,1000}$/, + unmask: [], + validationError: + 'Please enter a response of no more than 1000 alphanumeric characters', + }, clinDollars: { mask: createNumberMask({ prefix: '$', allowDecimal: true }), match: /^-?\d+\.?\d*$/, @@ -53,6 +73,13 @@ export default { unmask: [','], validationError: 'Please enter a number', }, + name: { + mask: false, + match: /.{1,100}/, + unmask: [], + validationError: + 'This field accepts letters, numbers, commas, apostrophes, hyphens, and periods.', + }, phoneExt: { mask: createNumberMask({ prefix: '', @@ -71,7 +98,7 @@ export default { unmask: [], validationError: 'Portfolio names can be between 4-100 characters', }, - requiredField: { + required: { mask: false, match: /.+/, unmask: [], diff --git a/styles/elements/_inputs.scss b/styles/elements/_inputs.scss index a5040e41..195d0a2b 100644 --- a/styles/elements/_inputs.scss +++ b/styles/elements/_inputs.scss @@ -230,6 +230,8 @@ &--anything, &--portfolioName, &--requiredField, + &--defaultStringField, + &--defaultTextAreaField, &--taskOrderNumber, &--email { input { diff --git a/templates/applications/fragments/add_new_environment.html b/templates/applications/fragments/add_new_environment.html index 9cbc507a..9947a854 100644 --- a/templates/applications/fragments/add_new_environment.html +++ b/templates/applications/fragments/add_new_environment.html @@ -13,7 +13,7 @@ ) }}