From 156d733aee2a1a4f48a6bfd65f15926a5e071767 Mon Sep 17 00:00:00 2001 From: graham-dds Date: Thu, 16 Jan 2020 13:01:22 -0500 Subject: [PATCH] Add missing regex and validation for StringFields This commit adds further validation for StringFields that were missing it. This mostly amounted to being Regex patters and max lengths. --- .secrets.baseline | 2 +- atst/forms/application.py | 11 +- atst/forms/application_member.py | 7 +- atst/forms/ccpo_user.py | 4 +- atst/forms/edit_user.py | 10 +- atst/forms/member.py | 12 ++- atst/forms/portfolio.py | 8 +- atst/forms/task_order.py | 27 +++-- atst/forms/validators.py | 11 +- tests/factories.py | 6 +- tests/forms/test_application_member.py | 14 +-- tests/forms/test_validators.py | 10 +- tests/routes/applications/test_settings.py | 23 ++-- tests/routes/task_orders/test_new.py | 6 +- tests/test_access.py | 2 +- translations.yaml | 117 +++++++++++---------- 16 files changed, 150 insertions(+), 120 deletions(-) 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/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 50cbe3cd..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,7 +31,7 @@ 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): if field.data: try: @@ -101,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/tests/factories.py b/tests/factories.py index e47aa897..d9af7c40 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -37,6 +37,10 @@ def random_task_order_number(): return "".join(random.choices(string.digits, k=10)) +def random_clin_number(): + return "".join(random.choices(string.digits, k=4)) + + def random_past_date(year_min=1, year_max=5): return _random_date(year_min, year_max, operator.sub) @@ -327,7 +331,7 @@ class CLINFactory(Base): model = CLIN task_order = factory.SubFactory(TaskOrderFactory) - number = factory.LazyFunction(random_task_order_number) + number = factory.LazyFunction(random_clin_number) start_date = datetime.date.today() end_date = factory.LazyFunction(random_future_date) total_amount = factory.LazyFunction(lambda *args: random.randint(50000, 999999)) diff --git a/tests/forms/test_application_member.py b/tests/forms/test_application_member.py index fc056223..180c6949 100644 --- a/tests/forms/test_application_member.py +++ b/tests/forms/test_application_member.py @@ -1,13 +1,12 @@ -from wtforms.validators import ValidationError +import uuid -from atst.domain.permission_sets import PermissionSets -from atst.forms.data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS +from atst.forms.data import ENV_ROLES from atst.forms.application_member import * def test_environment_form(): form_data = { - "environment_id": 123, + "environment_id": str(uuid.uuid4()), "environment_name": "testing", "role": ENV_ROLES[0][0], "disabled": True, @@ -17,12 +16,13 @@ def test_environment_form(): def test_environment_form_default_no_access(): - form_data = {"environment_id": 123, "environment_name": "testing"} + env_id = str(uuid.uuid4()) + form_data = {"environment_id": env_id, "environment_name": "testing"} form = EnvironmentForm(data=form_data) assert form.validate() assert form.data == { - "environment_id": 123, + "environment_id": env_id, "environment_name": "testing", "role": None, "disabled": False, @@ -31,7 +31,7 @@ def test_environment_form_default_no_access(): def test_environment_form_invalid(): form_data = { - "environment_id": 123, + "environment_id": str(uuid.uuid4()), "environment_name": "testing", "role": "not a real choice", } diff --git a/tests/forms/test_validators.py b/tests/forms/test_validators.py index 302f609e..8f290484 100644 --- a/tests/forms/test_validators.py +++ b/tests/forms/test_validators.py @@ -4,16 +4,16 @@ import pytest from atst.forms.validators import * -class TestIsNumber: +class TestNumber: @pytest.mark.parametrize("valid", ["0", "12", "-12"]) - def test_IsNumber_accepts_integers(self, valid, dummy_form, dummy_field): - validator = IsNumber() + def test_Number_accepts_integers(self, valid, dummy_form, dummy_field): + validator = Number() dummy_field.data = valid validator(dummy_form, dummy_field) @pytest.mark.parametrize("invalid", ["12.1", "two"]) - def test_IsNumber_rejects_anything_else(self, invalid, dummy_form, dummy_field): - validator = IsNumber() + def test_Number_rejects_anything_else(self, invalid, dummy_form, dummy_field): + validator = Number() dummy_field.data = invalid with pytest.raises(ValidationError): validator(dummy_form, dummy_field) diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index 523a1346..d990f6ad 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -401,10 +401,10 @@ def test_create_member(monkeypatch, client, user_session, session): "user_data-last_name": user.last_name, "user_data-dod_id": user.dod_id, "user_data-email": user.email, - "environment_roles-0-environment_id": env.id, + "environment_roles-0-environment_id": str(env.id), "environment_roles-0-role": "ADMIN", "environment_roles-0-environment_name": env.name, - "environment_roles-1-environment_id": env_1.id, + "environment_roles-1-environment_id": str(env_1.id), "environment_roles-1-role": NO_ACCESS, "environment_roles-1-environment_name": env_1.name, "perms_env_mgmt": True, @@ -527,13 +527,13 @@ def test_update_member(client, user_session, session): application_role_id=app_role.id, ), data={ - "environment_roles-0-environment_id": env.id, + "environment_roles-0-environment_id": str(env.id), "environment_roles-0-role": "CONTRIBUTOR", "environment_roles-0-environment_name": env.name, - "environment_roles-1-environment_id": env_1.id, + "environment_roles-1-environment_id": str(env_1.id), "environment_roles-1-environment_name": env_1.name, "environment_roles-1-disabled": "True", - "environment_roles-2-environment_id": env_2.id, + "environment_roles-2-environment_id": str(env_2.id), "environment_roles-2-role": "BILLING_READ", "environment_roles-2-environment_name": env_2.name, "perms_env_mgmt": True, @@ -694,10 +694,10 @@ def test_handle_create_member(monkeypatch, set_g, session): "user_data-last_name": user.last_name, "user_data-dod_id": user.dod_id, "user_data-email": user.email, - "environment_roles-0-environment_id": env.id, + "environment_roles-0-environment_id": str(env.id), "environment_roles-0-role": "ADMIN", "environment_roles-0-environment_name": env.name, - "environment_roles-1-environment_id": env_1.id, + "environment_roles-1-environment_id": str(env_1.id), "environment_roles-1-role": NO_ACCESS, "environment_roles-1-environment_name": env_1.name, "perms_env_mgmt": True, @@ -731,10 +731,10 @@ def test_handle_update_member_success(set_g): form_data = ImmutableMultiDict( { - "environment_roles-0-environment_id": env.id, + "environment_roles-0-environment_id": str(env.id), "environment_roles-0-role": "ADMIN", "environment_roles-0-environment_name": env.name, - "environment_roles-1-environment_id": env_1.id, + "environment_roles-1-environment_id": str(env_1.id), "environment_roles-1-role": NO_ACCESS, "environment_roles-1-environment_name": env_1.name, "perms_env_mgmt": True, @@ -742,6 +742,7 @@ def test_handle_update_member_success(set_g): "perms_del_env": True, } ) + handle_update_member(application.id, app_role.id, form_data) assert len(application.roles) == 1 @@ -771,10 +772,10 @@ def test_handle_update_member_with_error(set_g, monkeypatch, mock_logger): form_data = ImmutableMultiDict( { - "environment_roles-0-environment_id": env.id, + "environment_roles-0-environment_id": str(env.id), "environment_roles-0-role": "ADMIN", "environment_roles-0-environment_name": env.name, - "environment_roles-1-environment_id": env_1.id, + "environment_roles-1-environment_id": str(env_1.id), "environment_roles-1-role": NO_ACCESS, "environment_roles-1-environment_name": env_1.name, "perms_env_mgmt": True, diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 446376b7..0aef88ed 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -219,13 +219,13 @@ def test_task_orders_submit_form_step_three_add_clins(client, user_session, task user_session(task_order.portfolio.owner) form_data = { "clins-0-jedi_clin_type": "JEDI_CLIN_1", - "clins-0-number": "12312", + "clins-0-number": "1212", "clins-0-start_date": "01/01/2020", "clins-0-end_date": "01/01/2021", "clins-0-obligated_amount": "5000", "clins-0-total_amount": "10000", "clins-1-jedi_clin_type": "JEDI_CLIN_1", - "clins-1-number": "12312", + "clins-1-number": "1212", "clins-1-start_date": "01/01/2020", "clins-1-end_date": "01/01/2021", "clins-1-obligated_amount": "5000", @@ -269,7 +269,7 @@ def test_task_orders_submit_form_step_three_add_clins_existing_to( user_session(task_order.portfolio.owner) form_data = { "clins-0-jedi_clin_type": "JEDI_CLIN_1", - "clins-0-number": "12312", + "clins-0-number": "1212", "clins-0-start_date": "01/01/2020", "clins-0-end_date": "01/01/2021", "clins-0-obligated_amount": "5000", diff --git a/tests/test_access.py b/tests/test_access.py index 19a8bec3..b0dac527 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -668,7 +668,7 @@ def test_task_orders_new_post_routes(post_url_assert_status): "task_orders.submit_form_step_three_add_clins", { "clins-0-jedi_clin_type": "JEDI_CLIN_1", - "clins-0-number": "12312", + "clins-0-number": "1212", "clins-0-start_date": "01/01/2020", "clins-0-end_date": "01/01/2021", "clins-0-obligated_amount": "5000", diff --git a/translations.yaml b/translations.yaml index 360fc984..f587d8a1 100644 --- a/translations.yaml +++ b/translations.yaml @@ -1,17 +1,17 @@ # How to use text containing html tags in .html files: - # In the template add the `safe` filter when referencing the string - # from the template file. ie: +# In the template add the `safe` filter when referencing the string +# from the template file. ie: - # login: - # title: A title with a link! +# login: +# title: A title with a link! - # `{{ "login.title" | translate | safe }}` +# `{{ "login.title" | translate | safe }}` audit_log: events: default: - change: '{from} to {to}' - changes: 'Changes:' - details: 'Details:' + change: "{from} to {to}" + changes: "Changes:" + details: "Details:" base_public: login: Log in title_tag: JEDI Cloud @@ -43,7 +43,7 @@ common: confirm: Confirm continue: Continue delete: Delete - delete_confirm: 'Please type the word {word} to confirm:' + delete_confirm: "Please type the word {word} to confirm:" dod_id: DoD ID disable: Disable email: Email @@ -87,12 +87,12 @@ flash: application: created: title: Application Saved - message: '{application_name} has been successfully created. You may continue on to provision environments and assign team members now, or come back and complete these tasks at a later time.' - updated: 'You have successfully updated the {application_name} application.' + message: "{application_name} has been successfully created. You may continue on to provision environments and assign team members now, or come back and complete these tasks at a later time." + updated: "You have successfully updated the {application_name} application." name_error: - message: 'The application name {name} has already been used in this portfolio. Please enter a unique name.' + message: "The application name {name} has already been used in this portfolio. Please enter a unique name." env_name_error: - message: 'The environment name {name} has already been used in this application. Please enter a unique name.' + message: "The environment name {name} has already been used in this application. Please enter a unique name." application_invite: error: title: Application invitation error @@ -117,7 +117,7 @@ flash: message: You have successfully given {user_name} CCPO permissions. removed: message: You have successfully removed {user_name}'s CCPO permissions. - delete_member_success: 'You have successfully deleted {member_name} from the portfolio.' + delete_member_success: "You have successfully deleted {member_name} from the portfolio." deleted_member: Portfolio member deleted environment_added: 'The environment "{environment_name}" has been added to the application.' environment: @@ -139,7 +139,7 @@ flash: new_portfolio_member: title: "{user_name}'s invitation has been sent" message: "{user_name}'s access to this Portfolio is pending until they sign in for the first time." - new_ppoc_message: 'You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC.' + new_ppoc_message: "You have successfully added {ppoc_name} as the primary point of contact. You are no longer the PPoC." new_ppoc_title: Primary point of contact updated portfolio_member: revoked: @@ -162,7 +162,7 @@ flash: message: Your session expired due to inactivity. Please log in again to continue. success: Success! task_order_number_error: - message: 'The TO number has already been entered for a JEDI task order #{to_number}. Please double-check the TO number you are entering. If you believe this is in error, please contact support@cloud.mil.' + message: "The TO number has already been entered for a JEDI task order #{to_number}. Please double-check the TO number you are entering. If you believe this is in error, please contact support@cloud.mil." task_order: insufficient_funds: title: Insufficient Funds @@ -172,7 +172,7 @@ flash: new_application_member: title: "{user_name}'s invitation has been sent" message: "{user_name}'s access to this Application is pending until they sign in for the first time." - updated_application_team_settings: 'You have updated the {application_name} team settings.' + updated_application_team_settings: "You have updated the {application_name} team settings." user: complete_profile: title: You must complete your profile @@ -180,7 +180,7 @@ flash: updated: title: User information updated. footer: - login: 'Last login:' + login: "Last login:" forms: application: description_label: Application Description @@ -189,7 +189,7 @@ forms: environment_names_unique_validation_message: Environment names must be unique. name_label: Application Name assign_ppoc: - dod_id: 'Select new primary point of contact:' + dod_id: "Select new primary point of contact:" environments: name_label: Environment Name edit_user: @@ -293,8 +293,9 @@ forms: is_number_message: Please enter a valid number. list_item_required_message: Please provide at least one. list_items_unique_message: Items must be unique - name_message: 'This field accepts letters, numbers, commas, apostrophes, hyphens, and periods.' + name_message: "This field accepts letters, numbers, commas, apostrophes, hyphens, and periods." phone_number_message: Please enter a valid 5 or 10 digit phone number. + alpha_numeric_message: This field may only contain alphanumeric characters. fragments: edit_application_form: explain: AT-AT allows you to create multiple applications within a portfolio. Each application can then be broken down into its own customizable environments. @@ -308,7 +309,7 @@ fragments: title: Warning! assign_user_button_text: Assign member confirm_alert: - title: 'Once you assign a new PPoC, you will no longer be able to request portfolio deactivation or manage the PPoC role.' + title: "Once you assign a new PPoC, you will no longer be able to request portfolio deactivation or manage the PPoC role." subtitle: The PPoC has the ability to edit all aspects of a portfolio and is the only one who can manage the PPoC role. title: Primary point of contact (PPoC) update_btn: Update @@ -321,7 +322,7 @@ login: ccpo_logo_alt_text: Cloud Computing Program Office Logo certificate_selection: learn_more: Learn more - message: 'When you are prompted to select a certificate, please select Email Certificate from the provided choices.' + message: "When you are prompted to select a certificate, please select Email Certificate from the provided choices." title: Certificate Selection h1_title: Access the JEDI cloud login_button: Sign in with CAC @@ -340,23 +341,23 @@ portfolios: admin: activity_log_title: Activity log alert_header: Are you sure you want to delete this member? - alert_message: 'The member will be removed from the portfolio, but their log history will be retained.' + alert_message: "The member will be removed from the portfolio, but their log history will be retained." alert_title: Warning! You are about to delete a member from the portfolio. defense_component_label: Department of Defense Component portfolio_name: Portfolio name members: perms_portfolio_mgmt: - 'False': View Portfolio - 'True': Edit Portfolio + "False": View Portfolio + "True": Edit Portfolio perms_app_mgmt: - 'False': View Applications - 'True': Edit Applications + "False": View Applications + "True": Edit Applications perms_funding: - 'False': View Funding - 'True': Edit Funding + "False": View Funding + "True": Edit Funding perms_reporting: - 'False': View Reporting - 'True': Edit Reporting + "False": View Reporting + "True": Edit Reporting applications: add_environment: Add an Environment add_member: Add Team Member @@ -406,8 +407,8 @@ portfolios: csp_link: Cloud Service Provider Link enter_env_name: "Enter environment name:" environments_heading: Application Environments - existing_application_title: '{application_name} Application Settings' - member_count: '{count} members' + existing_application_title: "{application_name} Application Settings" + member_count: "{count} members" new_application_title: New Application settings: name_description: Application name and description @@ -444,14 +445,14 @@ portfolios: new: verify: Verify Member Information perms_team_mgmt: - 'False': View Team - 'True': Edit Team + "False": View Team + "True": Edit Team perms_env_mgmt: - 'False': View Environments - 'True': Edit Environments + "False": View Environments + "True": Edit Environments perms_del_env: - 'False': "" - 'True': Delete Application + "False": "" + "True": Delete Application roles: ADMIN: Admin BILLING_READ: Billing Read-only @@ -460,7 +461,7 @@ portfolios: new: title: New Portfolio cta_step_1: Name and Describe Portfolio - sticky_header_context: 'Step {step} of 1' + sticky_header_context: "Step {step} of 1" save: Save Portfolio members: archive_button: Delete member @@ -477,7 +478,7 @@ portfolios: sub_message: can_create_applications: This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started. cannot_create_applications: This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments. - action_label: 'Add a new application' + action_label: "Add a new application" total_value: header: Total Portfolio Value tooltip: Total portfolio value is all obligated and projected funds for all task orders in this portfolio. @@ -492,10 +493,10 @@ task_orders: total_amount: CLIN Value obligated: Amount Obligated JEDICLINType: - JEDI_CLIN_1: 'Unclassified IaaS and PaaS (IDIQ CLIN 0001)' - JEDI_CLIN_2: 'Classified IaaS and PaaS (IDIQ CLIN 0002)' - JEDI_CLIN_3: 'Unclassified Cloud Support Package (IDIQ CLIN 0003)' - JEDI_CLIN_4: 'Classified Cloud Support Package (IDIQ CLIN 0004)' + JEDI_CLIN_1: "Unclassified IaaS and PaaS (IDIQ CLIN 0001)" + JEDI_CLIN_2: "Classified IaaS and PaaS (IDIQ CLIN 0002)" + JEDI_CLIN_3: "Unclassified Cloud Support Package (IDIQ CLIN 0003)" + JEDI_CLIN_4: "Classified Cloud Support Package (IDIQ CLIN 0004)" tooltip: obligated_funds: Funds committed to fund your portfolio. This may represent 100% of your total Task Order value, or a portion of it. total_value: All obligated and projected funds for the Task Order’s Base and Option CLINs. @@ -510,7 +511,7 @@ task_orders: clin_funding: CLIN Funding clin_number_label: CLIN clin_type_label: Corresponding IDIQ CLIN - clin_remove_text: 'Do you want to remove ' + clin_remove_text: "Do you want to remove " clin_remove_confirm: Yes, remove CLIN clin_remove_cancel: No, go back draft_alert_title: Your information has been saved @@ -527,18 +528,18 @@ task_orders: title: Upload your approved Task Order (TO) description: Upload your approved Task Order here. You are required to confirm you have the appropriate signature. You will have the ability to add additional approved Task Orders with more funding to this Portfolio in the future. step_3: - next_button: 'Next: Review Task Order' - percent_obligated: '% of Funds Obligated' + next_button: "Next: Review Task Order" + percent_obligated: "% of Funds Obligated" step_4: documents: Documents clins: CLIN Summary step_5: cta_text: Verify Your Information description: Prior to submitting the Task Order, you must acknowledge, by marking the appropriate box below, that the uploaded Task Order is signed by an appropriate, duly warranted Contracting Officer who has the authority to execute the uploaded Task Order on your Agency’s behalf and has authorized you to upload the Task Order in accordance with Agency policy and procedures. You must further acknowledge, by marking the appropriate box below, that all information entered herein matches that of the submitted Task Order. - next_button: 'Submit Task Order' - sticky_header_text: 'Add a Task Order' + next_button: "Submit Task Order" + sticky_header_text: "Add a Task Order" sticky_header_review_text: Review Changes - sticky_header_context: 'Step {step} of 5' + sticky_header_context: "Step {step} of 5" empty_state: header: Add approved task orders message: Upload your approved Task Order here. You are required to confirm you have the appropriate signature. You will have the ability to add additional approved Task Orders with more funding to this Portfolio in the future. @@ -550,19 +551,19 @@ task_orders: acknowledge: title: Acknowledge Statement text: I acknowledge, by executing the confirmation above and submitting this verification, that I am subject to potential penalties that may include fines, imprisonment, or both, under the U.S. law and regulations for any false statement or misrepresentation in association with this Task Order submission or on any accompanying documentation. - status_empty_state: 'This Portfolio has no {status} Task Orders.' - status_list_title: '{status} Task Orders' + status_empty_state: "This Portfolio has no {status} Task Orders." + status_list_title: "{status} Task Orders" summary: obligated: Total Obligated total: Total Value expended: Total Expended JEDICLINType: - JEDI_CLIN_1: 'IDIQ CLIN 0001 Unclassified IaaS/PaaS' - JEDI_CLIN_2: 'IDIQ CLIN 0002 Classified IaaS/PaaS' - JEDI_CLIN_3: 'IDIQ CLIN 0003 Unclassified Cloud Support Package' - JEDI_CLIN_4: 'IDIQ CLIN 0004 Classified Cloud Support Package' + JEDI_CLIN_1: "IDIQ CLIN 0001 Unclassified IaaS/PaaS" + JEDI_CLIN_2: "IDIQ CLIN 0002 Classified IaaS/PaaS" + JEDI_CLIN_3: "IDIQ CLIN 0003 Unclassified Cloud Support Package" + JEDI_CLIN_4: "IDIQ CLIN 0004 Classified Cloud Support Package" testing: example_string: Hello World - example_with_variables: 'Hello, {name}!' + example_with_variables: "Hello, {name}!" nested: example: Hello nested example