diff --git a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 index feab2bd4b0..bac60660ae 100644 --- a/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 +++ b/install_files/ansible-base/roles/build-securedrop-app-code-deb-pkg/files/usr.sbin.apache2 @@ -197,8 +197,7 @@ /var/www/securedrop/journalist_templates/js-strings.html r, /var/www/securedrop/journalist_templates/locales.html r, /var/www/securedrop/journalist_templates/login.html r, - /var/www/securedrop/journalist_templates/logo_upload_flashed.html r, - /var/www/securedrop/journalist_templates/submission_preferences_saved_flash.html r, + /var/www/securedrop/journalist_templates/preferences_saved_flash.html r, /var/www/securedrop/models.py r, /var/www/securedrop/request_that_secures_file_uploads.py r, /var/www/securedrop/rm.py r, diff --git a/securedrop/alembic/versions/92fba0be98e9_added_organization_name_field_in_.py b/securedrop/alembic/versions/92fba0be98e9_added_organization_name_field_in_.py new file mode 100644 index 0000000000..4b06b3c523 --- /dev/null +++ b/securedrop/alembic/versions/92fba0be98e9_added_organization_name_field_in_.py @@ -0,0 +1,32 @@ +"""Added organization_name field in instance_config table + +Revision ID: 92fba0be98e9 +Revises: 48a75abc0121 +Create Date: 2020-11-15 19:36:20.351993 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '92fba0be98e9' +down_revision = '48a75abc0121' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('instance_config', schema=None) as batch_op: + batch_op.add_column(sa.Column('organization_name', sa.String(length=255), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('instance_config', schema=None) as batch_op: + batch_op.drop_column('organization_name') + + # ### end Alembic commands ### diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py index d0ac1b3403..d8397eab11 100644 --- a/securedrop/journalist_app/__init__.py +++ b/securedrop/journalist_app/__init__.py @@ -156,6 +156,11 @@ def setup_g() -> 'Optional[Response]': g.html_lang = i18n.locale_to_rfc_5646(g.locale) g.locales = i18n.get_locale2name() + if app.instance_config.organization_name: + g.organization_name = app.instance_config.organization_name + else: + g.organization_name = gettext('SecureDrop') + if not app.config['V3_ONION_ENABLED'] or app.config['V2_ONION_ENABLED']: g.show_v2_onion_eol_warning = True diff --git a/securedrop/journalist_app/admin.py b/securedrop/journalist_app/admin.py index b7f481f4c7..52287aec9a 100644 --- a/securedrop/journalist_app/admin.py +++ b/securedrop/journalist_app/admin.py @@ -13,12 +13,13 @@ import i18n from db import db +from html import escape from models import (InstanceConfig, Journalist, InvalidUsernameException, FirstOrLastNameError, PasswordError) from journalist_app.decorators import admin_required from journalist_app.utils import (commit_account_changes, set_diceware_password, validate_hotp_secret, revoke_token) -from journalist_app.forms import LogoForm, NewUserForm, SubmissionPreferencesForm +from journalist_app.forms import LogoForm, NewUserForm, SubmissionPreferencesForm, OrgNameForm from sdconfig import SDConfig from passphrases import PassphraseGenerator @@ -38,6 +39,8 @@ def manage_config() -> Union[str, werkzeug.Response]: # The UI prompt ("prevent") is the opposite of the setting ("allow"): submission_preferences_form = SubmissionPreferencesForm( prevent_document_uploads=not current_app.instance_config.allow_document_uploads) + organization_name_form = OrgNameForm( + organization_name=current_app.instance_config.organization_name) logo_form = LogoForm() if logo_form.validate_on_submit(): f = logo_form.logo.data @@ -53,13 +56,14 @@ def manage_config() -> Union[str, werkzeug.Response]: flash("Unable to process the image file." " Try another one.", "logo-error") finally: - return redirect(url_for("admin.manage_config")) + return redirect(url_for("admin.manage_config") + "#config-logoimage") else: for field, errors in list(logo_form.errors.items()): for error in errors: flash(error, "logo-error") return render_template("config.html", submission_preferences_form=submission_preferences_form, + organization_name_form=organization_name_form, logo_form=logo_form) @view.route('/update-submission-preferences', methods=['POST']) @@ -71,9 +75,31 @@ def update_submission_preferences() -> Optional[werkzeug.Response]: flash(gettext("Preferences saved."), "submission-preferences-success") value = not bool(request.form.get('prevent_document_uploads')) InstanceConfig.set_allow_document_uploads(value) - return redirect(url_for('admin.manage_config')) + return redirect(url_for('admin.manage_config') + "#config-preventuploads") else: - return None + for field, errors in list(form.errors.items()): + for error in errors: + flash(gettext("Preferences not updated.") + " " + error, + "submission-preferences-error") + return redirect(url_for('admin.manage_config') + "#config-preventuploads") + + @view.route('/update-org-name', methods=['POST']) + @admin_required + def update_org_name() -> Union[str, werkzeug.Response]: + form = OrgNameForm() + if form.validate_on_submit(): + try: + value = request.form['organization_name'] + InstanceConfig.set_organization_name(escape(value, quote=True)) + flash(gettext("Preferences saved."), "org-name-success") + except Exception: + flash(gettext('Failed to update organization name.'), 'org-name-error') + return redirect(url_for('admin.manage_config') + "#config-orgname") + else: + for field, errors in list(form.errors.items()): + for error in errors: + flash(error, "org-name-error") + return redirect(url_for('admin.manage_config') + "#config-orgname") @view.route('/add', methods=('GET', 'POST')) @admin_required @@ -276,7 +302,7 @@ def new_password(user_id: int) -> werkzeug.Response: def ossec_test() -> werkzeug.Response: current_app.logger.error('This is a test OSSEC alert') flash(gettext('Test alert sent. Please check your email.'), - 'notification') - return redirect(url_for('admin.manage_config')) + 'testalert-notification') + return redirect(url_for('admin.manage_config') + "#config-testalert") return view diff --git a/securedrop/journalist_app/forms.py b/securedrop/journalist_app/forms.py index 1b7c67f72d..6e49319c4a 100644 --- a/securedrop/journalist_app/forms.py +++ b/securedrop/journalist_app/forms.py @@ -8,7 +8,7 @@ ValidationError) from wtforms.validators import InputRequired, Optional -from models import Journalist +from models import Journalist, InstanceConfig def otp_secret_validation(form: FlaskForm, field: Field) -> None: @@ -46,6 +46,17 @@ def name_length_validation(form: FlaskForm, field: Field) -> None: ) +def check_orgname(form: FlaskForm, field: Field) -> None: + if len(field.data) > InstanceConfig.MAX_ORG_NAME_LEN: + raise ValidationError( + ngettext( + 'Cannot be longer than {num} characters.', + 'Cannot be longer than {num} characters.', + InstanceConfig.MAX_ORG_NAME_LEN + ).format(num=InstanceConfig.MAX_ORG_NAME_LEN) + ) + + def check_invalid_usernames(form: FlaskForm, field: Field) -> None: if field.data in Journalist.INVALID_USERNAMES: raise ValidationError(gettext( @@ -83,6 +94,13 @@ class SubmissionPreferencesForm(FlaskForm): prevent_document_uploads = BooleanField('prevent_document_uploads') +class OrgNameForm(FlaskForm): + organization_name = StringField('organization_name', validators=[ + InputRequired(message=gettext('This field is required.')), + check_orgname + ]) + + class LogoForm(FlaskForm): logo = FileField(validators=[ FileRequired(message=gettext('File required.')), diff --git a/securedrop/journalist_templates/base.html b/securedrop/journalist_templates/base.html index 9b38ebaf92..18e939a1c3 100644 --- a/securedrop/journalist_templates/base.html +++ b/securedrop/journalist_templates/base.html @@ -3,7 +3,7 @@
-{{ gettext('Send an encrypted email to verify if OSSEC alerts work correctly:') }}
- -- - - {{ gettext('SEND TEST OSSEC ALERT') }} - -
+{{ gettext('Here you can update the image displayed on the SecureDrop web interfaces:') }}
- +
{{ gettext('Send an encrypted email to verify if OSSEC alerts work correctly:') }}
+ ++ + + {{ gettext('SEND TEST OSSEC ALERT') }} + + {% set prefs_filter = ["testalert-success","testalert-error","testalert-notification"] %} + {% include 'preferences_saved_flash.html' %} +
+ {% endblock %} diff --git a/securedrop/journalist_templates/flashed.html b/securedrop/journalist_templates/flashed.html index 4987ef5c49..49719fb62d 100644 --- a/securedrop/journalist_templates/flashed.html +++ b/securedrop/journalist_templates/flashed.html @@ -1,7 +1,7 @@ {% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %} - {% if category != "banner-warning" and category != "logo-success" and category != "logo-error" and category != "submission-preferences-success" %} + {% if category not in ["banner-warning","logo-success","logo-error","submission-preferences-success","submission-preferences-error","org-name-success","org-name-error", "testalert-success", "testalert-error", "testalert-notification"] %}