Skip to content

Commit

Permalink
Merge branch 'staging' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
abompard committed May 18, 2021
2 parents 59a6e2b + f36e1b9 commit 6d3c36d
Show file tree
Hide file tree
Showing 78 changed files with 19,191 additions and 4,180 deletions.
2 changes: 1 addition & 1 deletion .rstcheck.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[rstcheck]
ignore_directives = automodule
ignore_roles = issue,pr
ignore_roles = issue,pr,commit
report = warning
4 changes: 4 additions & 0 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,14 @@ When cutting a new release, follow these steps:
#. Commit the changes
#. Push the commit to the upstream Github repository (via a PR or not).
#. Change to the stable branch and cherry-pick the commit (or merge if appropriate)
#. Run the checks one last time to be sure: ``tox``,
#. Tag the commit with ``-s`` to generate a signed tag
#. Push the commit to the upstream Github repository with ``git push``,
and the new tag with ``git push --tags``
#. Generate a tarball and push to PyPI with the command ``poetry publish --build``
#. Create `the release on GitHub <https://github.com/fedora-infra/noggin/tags>`_ and copy the
release notes in there,
#. Deploy and announce.


Translations
Expand Down
32 changes: 30 additions & 2 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ Release notes

.. towncrier release notes start
v1.2.0
======
Released on 2021-05-18.


Features
^^^^^^^^

* Display the version in the page footer (:issue:`592`).
* Allow sponsors to resign from their position in the group (:issue:`599`).

Bug Fixes
^^^^^^^^^

* Lowercase the username in Forgot Password Ask controller (:issue:`573`).
* Skipped autocomplete in OTP fields (:issue:`593`).

Contributors
^^^^^^^^^^^^

Many thanks to the contributors of bug reports, pull requests, and pull request
reviews for this release:

* Aurélien Bompard
* Josseline Perdomo
* Yaron Shahrabani


v1.1.0
======

Expand Down Expand Up @@ -32,8 +60,8 @@ Bug Fixes
Documentation Improvements
^^^^^^^^^^^^^^^^^^^^^^^^^^

* Add rstcheck to check our rst files (:issue:`1c2205f`).
* Update the release docs (:issue:`96b08ea`).
* Add rstcheck to check our rst files (:commit:`1c2205f`).
* Update the release docs (:commit:`96b08ea`).
* Fix code-block format in contributing docs (:pr:`595`).

Contributors
Expand Down
1 change: 1 addition & 0 deletions news/573.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Lowercase the username in Forgot Password Ask controller
1 change: 1 addition & 0 deletions news/592.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Display the version in the page footer
1 change: 1 addition & 0 deletions news/593.bug
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Skipped autocomplete in OTP fields
1 change: 1 addition & 0 deletions news/599.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow sponsors to resign from their position in the group
1 change: 1 addition & 0 deletions news/_template.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{%- endif -%}
{%- endmacro -%}

Released on {{ versiondata.date }}.
This is a {major|feature|bugfix} release that adds [short summary].

{% for section, _ in sections.items() %}
Expand Down
2 changes: 1 addition & 1 deletion news/get-authors.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from subprocess import check_output


EXCLUDE = ["dependabot-preview[bot]", "Weblate (bot)"]
EXCLUDE = ["dependabot[bot]", "dependabot-preview[bot]", "Weblate (bot)"]

last_tag = check_output(
"git tag | sort -n | tail -n 1", shell=True, universal_newlines=True
Expand Down
15 changes: 15 additions & 0 deletions noggin/controller/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os

from flask import Blueprint, g, render_template, session

from noggin import __version__
from noggin.utility.templates import gravatar


Expand All @@ -13,9 +16,21 @@ def page_not_found(e):

@blueprint.app_context_processor
def inject_global_template_vars():
version = __version__
if (
"OPENSHIFT_BUILD_COMMIT" in os.environ
and "OPENSHIFT_BUILD_REFERENCE" in os.environ
):
version_ext = [
os.environ['OPENSHIFT_BUILD_REFERENCE'],
os.environ['OPENSHIFT_BUILD_COMMIT'][:7],
]
version = f"{version} ({':'.join(version_ext)})"

return dict(
gravatar=gravatar,
ipa=g.ipa if 'ipa' in g else None,
current_user=g.current_user if 'current_user' in g else None,
current_username=session.get('noggin_username'),
noggin_version=version,
)
2 changes: 1 addition & 1 deletion noggin/controller/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


def handle_login_form(form):
username = form.username.data.lower()
username = form.username.data
password = form.password.data

if form.otp.data:
Expand Down
42 changes: 37 additions & 5 deletions noggin/controller/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from flask import flash, g, redirect, render_template, url_for
from flask_babel import _

from noggin.form.add_group_member import AddGroupMemberForm
from noggin.form.remove_group_member import RemoveGroupMemberForm
from noggin.form.group import AddGroupMemberForm, RemoveGroupMemberForm
from noggin.representation.group import Group
from noggin.representation.user import User
from noggin.security.ipa import raise_on_failed
from noggin.utility import messaging
from noggin.utility.controllers import group_or_404, with_ipa
from noggin.utility.pagination import paginated_find
Expand Down Expand Up @@ -67,7 +67,8 @@ def group_add_member(ipa, groupname):
)
return redirect(url_for('.group', groupname=groupname))
try:
ipa.group_add_member(a_cn=groupname, o_user=username)
result = ipa.group_add_member(a_cn=groupname, o_user=username)
raise_on_failed(result)
except python_freeipa.exceptions.ValidationError as e:
# e.message is a dict that we have to process ourselves for now:
# https://github.com/opennode/python-freeipa/issues/24
Expand Down Expand Up @@ -126,12 +127,13 @@ def group_remove_member(ipa, groupname):
if form.validate_on_submit():
username = form.username.data
try:
ipa.group_remove_member(a_cn=groupname, o_user=username)
result = ipa.group_remove_member(groupname, o_user=username)
raise_on_failed(result)
except python_freeipa.exceptions.ValidationError as e:
# e.message is a dict that we have to process ourselves for now:
# https://github.com/opennode/python-freeipa/issues/24
for error in e.message['member']['user']:
flash('Unable to remove user %s: %s' % (error[0], error[1]), 'danger')
flash(f"Unable to remove user {error[0]}: {error[1]}", "danger")
return redirect(url_for('.group', groupname=groupname))
flash_text = _(
'You got it! %(username)s has been removed from %(groupname)s.',
Expand All @@ -156,6 +158,36 @@ def group_remove_member(ipa, groupname):
return redirect(url_for('.group', groupname=groupname))


@bp.route('/group/<groupname>/sponsors/remove', methods=['POST'])
@with_ipa()
def group_remove_sponsor(ipa, groupname):
group = Group(group_or_404(ipa, groupname))
# Don't allow removing the last sponsor
if len(group.sponsors) < 2:
flash("Removing the last sponsor is not allowed.", "danger")
return redirect(url_for('.group', groupname=groupname))
# Only removing onelself from sponsors is allowed
username = g.current_user.username
try:
result = ipa.group_remove_member_manager(groupname, o_user=username)
raise_on_failed(result)
except python_freeipa.exceptions.ValidationError as e:
# e.message is a dict that we have to process ourselves for now:
# https://github.com/opennode/python-freeipa/issues/24
for error in e.message['membermanager']['user']:
flash(f"Unable to remove user {error[0]}: {error[1]}", "danger")
return redirect(url_for('.group', groupname=groupname))
flash(
_(
'You got it! %(username)s is no longer a sponsor of %(groupname)s.',
username=username,
groupname=groupname,
),
'success',
)
return redirect(url_for('.group', groupname=groupname))


@bp.route('/groups/')
@with_ipa()
def groups(ipa):
Expand Down
11 changes: 9 additions & 2 deletions noggin/controller/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,19 @@


def _send_validation_email(user):
ttl = current_app.config["ACTIVATION_TOKEN_EXPIRATION"]
token = make_token(
{"sub": user.username, "mail": user.mail},
audience=Audience.email_validation,
ttl=current_app.config["ACTIVATION_TOKEN_EXPIRATION"],
ttl=ttl,
)
email_context = {"token": token, "user": user}
valid_until = datetime.datetime.utcnow() + datetime.timedelta(minutes=ttl)
email_context = {
"token": token,
"user": user,
"ttl": ttl,
"valid_until": valid_until,
}
email = Message(
body=render_template("email-validation.txt", **email_context),
html=render_template("email-validation.html", **email_context),
Expand Down
2 changes: 1 addition & 1 deletion noggin/controller/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def user_settings_otp_disable(ipa, username):
):
flash(_('Sorry, You cannot disable your last active token.'), 'warning')
else:
flash('Cannot disable the token.', 'danger')
flash(_('Cannot disable the token.'), 'danger')
current_app.logger.error(
f'Something went wrong disabling an OTP token for user {username}: {e}'
)
Expand Down
4 changes: 4 additions & 0 deletions noggin/form/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def strip(value):
return value.strip() if value else value


def lower(value):
return value.lower() if value else value


class CSVListField(Field):
widget = TextInput()

Expand Down
2 changes: 1 addition & 1 deletion noggin/form/edit_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class UserSettingsAddOTPForm(ModestForm):
description=_("please reauthenticate so we know it is you"),
)

otp = PasswordField(
otp = StringField(
_('One-Time Password'),
validators=[Optional()],
description=_("Enter your One-Time Password"),
Expand Down
9 changes: 8 additions & 1 deletion noggin/form/add_group_member.py → noggin/form/group.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from flask_babel import lazy_gettext as _
from wtforms import StringField
from wtforms import HiddenField, StringField
from wtforms.validators import DataRequired

from .base import BaseForm
Expand All @@ -10,3 +10,10 @@ class AddGroupMemberForm(BaseForm):
'New Member Username',
validators=[DataRequired(message=_('New member username must not be empty'))],
)


class RemoveGroupMemberForm(BaseForm):
username = HiddenField(
_('Username'),
validators=[DataRequired(message=_('Username must not be empty'))],
)
6 changes: 5 additions & 1 deletion noggin/form/login_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
from wtforms.validators import DataRequired, Optional

from .base import ModestForm, SubmitButtonField
from .validators import no_mixed_case


class LoginUserForm(ModestForm):
username = StringField(
_('Username'),
validators=[DataRequired(message=_('You must provide a user name'))],
validators=[
DataRequired(message=_('You must provide a user name')),
no_mixed_case,
],
)

password = PasswordField(
Expand Down
3 changes: 2 additions & 1 deletion noggin/form/password_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from wtforms import PasswordField, StringField
from wtforms.validators import DataRequired, EqualTo, Optional

from .base import BaseForm
from .base import BaseForm, lower
from .validators import PasswordLength


Expand Down Expand Up @@ -36,4 +36,5 @@ class ForgottenPasswordForm(BaseForm):
_('Username'),
validators=[DataRequired(message=_('User name must not be empty'))],
description=_("Enter your username to reset your password"),
filters=[lower],
)
7 changes: 5 additions & 2 deletions noggin/form/register_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from wtforms.fields.html5 import EmailField
from wtforms.validators import DataRequired, EqualTo

from noggin.form.validators import Email, PasswordLength
from noggin.form.validators import Email, no_mixed_case, PasswordLength

from .base import BaseForm, ModestForm, strip, SubmitButtonField

Expand All @@ -23,7 +23,10 @@ class RegisterUserForm(ModestForm):

username = StringField(
_('Username'),
validators=[DataRequired(message=_('User name must not be empty'))],
validators=[
DataRequired(message=_('User name must not be empty')),
no_mixed_case,
],
filters=[strip],
)

Expand Down
12 changes: 0 additions & 12 deletions noggin/form/remove_group_member.py

This file was deleted.

5 changes: 5 additions & 0 deletions noggin/form/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ def __call__(self, form, field):
message=self.message,
)
validator(form, field)


def no_mixed_case(form, field):
if field.data != field.data.lower():
raise ValidationError(_("Mixed case is not allowed, try lower case."))
13 changes: 12 additions & 1 deletion noggin/security/ipa.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import python_freeipa
from cryptography.fernet import Fernet
from python_freeipa.client_meta import ClientMeta as IPAClient
from python_freeipa.exceptions import BadRequest
from python_freeipa.exceptions import BadRequest, ValidationError
from requests import RequestException


Expand Down Expand Up @@ -112,6 +112,17 @@ def fasagreement_disable(self, agreement, **kwargs):
self._request('fasagreement_disable', agreement, kwargs)


def raise_on_failed(result):
failed = result.get("failed", {})
num_failed = sum(
sum(len(failures) for failures in object_types.values())
for object_types in failed.values()
)
if num_failed == 0:
return # no actual failure
raise ValidationError(failed)


# Construct an IPA client from app config, but don't attempt to log in with it
# or to form a session of any kind with it. This is useful for one-off cases
# like password resets where a session isn't actually required.
Expand Down
2 changes: 1 addition & 1 deletion noggin/templates/_login_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{{ macros.with_errors(login_form.password, class="validate", placeholder="Password", tabindex="2", label=False) }}
</div>
<div class="form-group mb-0">
{{ macros.with_errors(login_form.otp, class="validate", placeholder="One-Time Password (if you have one)", tabindex="3", label=False) }}
{{ macros.with_errors(login_form.otp, class="validate", placeholder="One-Time Password (if you have one)", tabindex="3", autocomplete="off", label=False) }}
</div>
<div class="form-group mb-0 text-right">
{% if lost_otp_token is not defined %}
Expand Down
2 changes: 1 addition & 1 deletion noggin/templates/forgot-password-change.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<h5 id="pageheading">{{ _('Password Reset for %(username)s', username=username) }}</h5>
<div class="form-group">{{ macros.with_errors(form.password, tabindex="1")}}</div>
<div class="form-group">{{ macros.with_errors(form.password_confirm, tabindex="2")}}</div>
<div class="form-group">{{ macros.with_errors(form.otp, tabindex="3")}}</div>
<div class="form-group">{{ macros.with_errors(form.otp, tabindex="3", autocomplete="off")}}</div>
</div>
<div class="card-footer d-flex justify-content-between">
<div>{{ macros.non_field_errors(form) }}</div>
Expand Down
Loading

0 comments on commit 6d3c36d

Please sign in to comment.