misc: concatenate strings when possible (#56007)
This commit is contained in:
parent
208156b2c8
commit
f24096f8b8
|
@ -136,9 +136,8 @@ class OrganizationalUnit(OrganizationalUnitAbstractBase):
|
|||
if self.pk and not self.default and self.__class__.objects.get(pk=self.pk).default:
|
||||
raise ValidationError(
|
||||
_(
|
||||
'You cannot unset this organizational '
|
||||
'unit as the default, but you can set '
|
||||
'another one as the default.'
|
||||
'You cannot unset this organizational unit as the default, but you can set another one as'
|
||||
' the default.'
|
||||
)
|
||||
)
|
||||
if bool(self.clean_unused_accounts_alert) ^ bool(self.clean_unused_accounts_deletion):
|
||||
|
|
|
@ -205,9 +205,8 @@ class UserChangeForm(BaseUserForm):
|
|||
password = ReadOnlyPasswordHashField(
|
||||
label=_("Password"),
|
||||
help_text=_(
|
||||
"Raw passwords are not stored, so there is no way to see "
|
||||
"this user's password, but you can change the password "
|
||||
"using <a href=\"password/\">this form</a>."
|
||||
"Raw passwords are not stored, so there is no way to see this user's password, but you can change"
|
||||
" the password using <a href=\"password/\">this form</a>."
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -152,9 +152,7 @@ class RegistrationSerializer(serializers.Serializer):
|
|||
else:
|
||||
authorized = request.user.has_perm(perm)
|
||||
if not authorized:
|
||||
raise serializers.ValidationError(
|
||||
_('you are not authorized ' 'to create users in ' 'this ou')
|
||||
)
|
||||
raise serializers.ValidationError(_('you are not authorized to create users in this ou'))
|
||||
User = get_user_model()
|
||||
if ou:
|
||||
if app_settings.A2_EMAIL_IS_UNIQUE or app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE:
|
||||
|
@ -280,7 +278,7 @@ class Register(BaseRpcView):
|
|||
'result': 0,
|
||||
'errors': {
|
||||
'__all__': [
|
||||
'You must set at least a username, an email or ' 'a first name and a last name'
|
||||
'You must set at least a username, an email or a first name and a last name'
|
||||
]
|
||||
},
|
||||
}
|
||||
|
@ -448,7 +446,7 @@ class BaseUserSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
except smtplib.SMTPException as e:
|
||||
logging.getLogger(__name__).error(
|
||||
'registration mail could not be sent to user %s ' 'created through API: %s', instance, e
|
||||
'registration mail could not be sent to user %s created through API: %s', instance, e
|
||||
)
|
||||
return instance
|
||||
|
||||
|
@ -997,11 +995,9 @@ class RoleMembershipsAPI(ExceptionHandlerMixin, APIView):
|
|||
try:
|
||||
uuid = entry['uuid']
|
||||
except TypeError:
|
||||
raise ValidationError(_("List elements of the 'data' dict " "entry must be dictionaries"))
|
||||
raise ValidationError(_("List elements of the 'data' dict entry must be dictionaries"))
|
||||
except KeyError:
|
||||
raise ValidationError(
|
||||
_("Missing 'uuid' key for dict entry %s " "of the 'data' payload") % entry
|
||||
)
|
||||
raise ValidationError(_("Missing 'uuid' key for dict entry %s of the 'data' payload") % entry)
|
||||
try:
|
||||
self.members.add(User.objects.get(uuid=uuid))
|
||||
except User.DoesNotExist:
|
||||
|
|
|
@ -121,8 +121,10 @@ default_settings = dict(
|
|||
),
|
||||
A2_REGISTRATION_REDIRECT=Setting(
|
||||
default=None,
|
||||
definition='Forced redirection after each redirect, NEXT_URL substring is replaced'
|
||||
' by the original next_url passed to /accounts/register/',
|
||||
definition=(
|
||||
'Forced redirection after each redirect, NEXT_URL substring is replaced by the original next_url'
|
||||
' passed to /accounts/register/'
|
||||
),
|
||||
),
|
||||
A2_PROFILE_CAN_CHANGE_EMAIL=Setting(default=True, definition='Can user self change their email'),
|
||||
A2_PROFILE_CAN_EDIT_PROFILE=Setting(default=True, definition='Can user self edit their profile'),
|
||||
|
@ -226,30 +228,32 @@ default_settings = dict(
|
|||
),
|
||||
A2_LOGIN_FAILURE_COUNT_BEFORE_WARNING=Setting(
|
||||
default=0,
|
||||
definition='Failure count before logging a warning to '
|
||||
'authentic2.user_login_failure. No warning will be send if value is '
|
||||
'0.',
|
||||
definition=(
|
||||
'Failure count before logging a warning to authentic2.user_login_failure. No warning will be send'
|
||||
' if value is 0.'
|
||||
),
|
||||
),
|
||||
PUSH_PROFILE_UPDATES=Setting(default=False, definition='Push profile update to linked services'),
|
||||
TEMPLATE_VARS=Setting(default={}, definition='Variable to pass to templates'),
|
||||
A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_FACTOR=Setting(
|
||||
default=1.8,
|
||||
definition='exponential backoff factor duration as seconds until ' 'next try after a login failure',
|
||||
definition='exponential backoff factor duration as seconds until next try after a login failure',
|
||||
),
|
||||
A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_DURATION=Setting(
|
||||
default=1,
|
||||
definition='exponential backoff base factor duration as seconds '
|
||||
'until next try after a login failure',
|
||||
definition='exponential backoff base factor duration as seconds until next try after a login failure',
|
||||
),
|
||||
A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MAX_DURATION=Setting(
|
||||
default=3600,
|
||||
definition='maximum exponential backoff maximum duration as seconds until '
|
||||
'next try after a login failure',
|
||||
definition=(
|
||||
'maximum exponential backoff maximum duration as seconds until next try after a login failure'
|
||||
),
|
||||
),
|
||||
A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION=Setting(
|
||||
default=10,
|
||||
definition='minimum exponential backoff maximum duration as seconds until '
|
||||
'next try after a login failure',
|
||||
definition=(
|
||||
'minimum exponential backoff maximum duration as seconds until next try after a login failure'
|
||||
),
|
||||
),
|
||||
A2_VERIFY_SSL=Setting(default=True, definition='Verify SSL certificate in HTTP requests'),
|
||||
A2_ATTRIBUTE_KIND_TITLE_CHOICES=Setting(default=(), definition='Choices for the title attribute kind'),
|
||||
|
@ -270,24 +274,27 @@ default_settings = dict(
|
|||
),
|
||||
A2_USER_FILTER=Setting(
|
||||
default={},
|
||||
definition='Filters (as in QuerySet.filter() to apply to User queryset before ' 'authentication',
|
||||
definition='Filters (as in QuerySet.filter() to apply to User queryset before authentication',
|
||||
),
|
||||
A2_USER_EXCLUDE=Setting(
|
||||
default={},
|
||||
definition='Exclusion filter (as in QuerySet.exclude() to apply to User queryset before '
|
||||
'authentication',
|
||||
definition=(
|
||||
'Exclusion filter (as in QuerySet.exclude() to apply to User queryset before authentication'
|
||||
),
|
||||
),
|
||||
A2_USER_REMEMBER_ME=Setting(
|
||||
default=None,
|
||||
definition='Session duration as seconds when using the remember me '
|
||||
'checkbox. Truthiness activates the checkbox.',
|
||||
definition=(
|
||||
'Session duration as seconds when using the remember me checkbox. Truthiness activates the'
|
||||
' checkbox.'
|
||||
),
|
||||
),
|
||||
A2_LOGIN_REDIRECT_AUTHENTICATED_USERS_TO_HOMEPAGE=Setting(
|
||||
default=False, definition='Redirect authenticated users to homepage'
|
||||
),
|
||||
A2_LOGIN_DISPLAY_A_CANCEL_BUTTON=Setting(
|
||||
default=False,
|
||||
definition='Display a cancel button.' 'This is only applicable for Liberty single sign on requests',
|
||||
definition='Display a cancel button.This is only applicable for Liberty single sign on requests',
|
||||
),
|
||||
A2_SET_RANDOM_PASSWORD_ON_RESET=Setting(
|
||||
default=True,
|
||||
|
|
|
@ -107,7 +107,8 @@ class Migration(migrations.Migration):
|
|||
[
|
||||
'CREATE INDEX journal_event_timestamp_id_idx ON journal_event USING BRIN("timestamp", "id");',
|
||||
'CREATE INDEX journal_event_reference_ids_idx ON journal_event USING GIN("reference_ids");',
|
||||
'CREATE INDEX journal_event_reference_ct_ids_idx ON journal_event USING GIN("reference_ct_ids");',
|
||||
'CREATE INDEX journal_event_reference_ct_ids_idx ON journal_event USING'
|
||||
' GIN("reference_ct_ids");',
|
||||
],
|
||||
[
|
||||
'DROP INDEX journal_event_reference_ct_ids_idx;',
|
||||
|
|
|
@ -85,8 +85,8 @@ class SearchEngine:
|
|||
@classmethod
|
||||
def documentation(cls):
|
||||
yield _(
|
||||
'You can use colon terminated prefixes to make special searches, '
|
||||
'and you can use quote around the suffix to preserve spaces.'
|
||||
'You can use colon terminated prefixes to make special searches, and you can use quote around the'
|
||||
' suffix to preserve spaces.'
|
||||
)
|
||||
for name in dir(cls):
|
||||
documentation = getattr(getattr(cls, name), 'documentation', None)
|
||||
|
|
|
@ -35,7 +35,7 @@ def get_field_refs(format_string):
|
|||
yield field_ref
|
||||
|
||||
|
||||
UNEXPECTED_KEYS_ERROR = '{0}: unexpected ' 'key(s) {1} in configuration'
|
||||
UNEXPECTED_KEYS_ERROR = '{0}: unexpected key(s) {1} in configuration'
|
||||
FORMAT_STRING_ERROR = '{0}: template string must contain only keyword references: {1}'
|
||||
BAD_CONFIG_ERROR = 'template attribute source must contain a name and at least a template'
|
||||
TYPE_ERROR = 'template attribute must be a string'
|
||||
|
|
|
@ -61,7 +61,9 @@ class Migration(migrations.Migration):
|
|||
'is_superuser',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text='Designates that this user has all permissions without explicitly assigning them.',
|
||||
help_text=(
|
||||
'Designates that this user has all permissions without explicitly assigning them.'
|
||||
),
|
||||
verbose_name='superuser status',
|
||||
),
|
||||
),
|
||||
|
@ -92,7 +94,10 @@ class Migration(migrations.Migration):
|
|||
'is_active',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
|
||||
help_text=(
|
||||
'Designates whether this user should be treated as active. Unselect this instead'
|
||||
' of deleting accounts.'
|
||||
),
|
||||
verbose_name='active',
|
||||
),
|
||||
),
|
||||
|
@ -103,7 +108,10 @@ class Migration(migrations.Migration):
|
|||
to='auth.Group',
|
||||
verbose_name='groups',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
|
||||
help_text=(
|
||||
'The groups this user belongs to. A user will get all permissions granted to each'
|
||||
' of his/her group.'
|
||||
),
|
||||
related_name='+',
|
||||
related_query_name='+',
|
||||
),
|
||||
|
|
|
@ -13,7 +13,10 @@ class Migration(migrations.Migration):
|
|||
model_name='user',
|
||||
name='username',
|
||||
field=models.CharField(
|
||||
help_text='Required, 255 characters or fewer. Only letters, numbers, and @, ., +, -, or _ characters.',
|
||||
help_text=(
|
||||
'Required, 255 characters or fewer. Only letters, numbers, and @, ., +, -, or _'
|
||||
' characters.'
|
||||
),
|
||||
unique=True,
|
||||
max_length=255,
|
||||
verbose_name='username',
|
||||
|
|
|
@ -29,7 +29,9 @@ class Migration(migrations.Migration):
|
|||
'is_superuser',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text='Designates that this user has all permissions without explicitly assigning them.',
|
||||
help_text=(
|
||||
'Designates that this user has all permissions without explicitly assigning them.'
|
||||
),
|
||||
verbose_name='superuser status',
|
||||
),
|
||||
),
|
||||
|
@ -62,7 +64,10 @@ class Migration(migrations.Migration):
|
|||
'is_active',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
|
||||
help_text=(
|
||||
'Designates whether this user should be treated as active. Unselect this instead'
|
||||
' of deleting accounts.'
|
||||
),
|
||||
verbose_name='active',
|
||||
),
|
||||
),
|
||||
|
@ -77,7 +82,10 @@ class Migration(migrations.Migration):
|
|||
related_name='user_set',
|
||||
to='auth.Group',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
|
||||
help_text=(
|
||||
'The groups this user belongs to. A user will get all permissions granted to each'
|
||||
' of his/her group.'
|
||||
),
|
||||
verbose_name='groups',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -46,7 +46,10 @@ class Migration(migrations.Migration):
|
|||
related_name='user_set',
|
||||
to='auth.Group',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
|
||||
help_text=(
|
||||
'The groups this user belongs to. A user will get all permissions granted to each of'
|
||||
' their groups.'
|
||||
),
|
||||
verbose_name='groups',
|
||||
),
|
||||
),
|
||||
|
@ -64,7 +67,8 @@ class Migration(migrations.Migration):
|
|||
validators=[
|
||||
django.core.validators.RegexValidator(
|
||||
'^[\\w.@+-]+$',
|
||||
'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.',
|
||||
'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_'
|
||||
' characters.',
|
||||
'invalid',
|
||||
)
|
||||
],
|
||||
|
|
|
@ -59,9 +59,7 @@ class Authentic2Authentication(BasicAuthentication):
|
|||
if not client.has_api_access:
|
||||
raise AuthenticationFailed('OIDC client does not have access to the API')
|
||||
if client.identifier_policy not in (client.POLICY_UUID, client.POLICY_PAIRWISE_REVERSIBLE):
|
||||
raise AuthenticationFailed(
|
||||
'OIDC Client identifier policy does not allow access to ' 'the API'
|
||||
)
|
||||
raise AuthenticationFailed('OIDC Client identifier policy does not allow access to the API')
|
||||
user = OIDCUser(client)
|
||||
user.authenticated = True
|
||||
return (user, True)
|
||||
|
|
|
@ -315,7 +315,8 @@ def password_policy_control_messages(ctrl, attributes):
|
|||
'passwordTooShort': _('The password is too short {pwdminlength}.').format(**attributes),
|
||||
'passwordTooYoung': _('It is too soon to change the password {pwdminage}.').format(**attributes),
|
||||
'passwordInHistory': _(
|
||||
'This password is among the last {pwdhistory} password that were used and cannot be used again.'
|
||||
'This password is among the last {pwdhistory} password that were used and cannot be used'
|
||||
' again.'
|
||||
).format(**attributes),
|
||||
}
|
||||
messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error)))
|
||||
|
@ -330,7 +331,8 @@ def password_policy_control_messages(ctrl, attributes):
|
|||
messages.append(
|
||||
ngettext(
|
||||
'This password expired: this is the last time it can be used.',
|
||||
'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
|
||||
'This password expired and can only be used {graceAuthNsRemaining} times, including this'
|
||||
' one.',
|
||||
ctrl.graceAuthNsRemaining,
|
||||
).format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining)
|
||||
)
|
||||
|
@ -824,8 +826,8 @@ class LDAPBackend:
|
|||
request.failed_logins.add(user)
|
||||
else:
|
||||
log.warning(
|
||||
'could not rebind after a bind failure, unable to attach '
|
||||
'the error to the user'
|
||||
'could not rebind after a bind failure, unable to attach the error to the'
|
||||
' user'
|
||||
)
|
||||
user_login_failure(authz_id)
|
||||
pass
|
||||
|
@ -842,8 +844,8 @@ class LDAPBackend:
|
|||
return self._return_user(authz_id, password, conn, block)
|
||||
except ldap.CONNECT_ERROR:
|
||||
log.error(
|
||||
'[%s] connection to %r failed, did you forget to declare the TLS certificate '
|
||||
'in /etc/ldap/ldap.conf ?',
|
||||
'[%s] connection to %r failed, did you forget to declare the TLS certificate in'
|
||||
' /etc/ldap/ldap.conf ?',
|
||||
ldap_uri,
|
||||
block['url'],
|
||||
)
|
||||
|
@ -875,7 +877,7 @@ class LDAPBackend:
|
|||
def _parse_simple_config(self):
|
||||
if len(settings.LDAP_AUTH_SETTINGS) < 2:
|
||||
raise ImproperlyConfigured(
|
||||
'In a minimal configuration, you must at least specify ' 'url and user DN'
|
||||
'In a minimal configuration, you must at least specify url and user DN'
|
||||
)
|
||||
return {'url': settings.LDAP_AUTH_SETTINGS[0], 'basedn': settings.LDAP_AUTH_SETTINGS[1]}
|
||||
|
||||
|
@ -1295,14 +1297,14 @@ class LDAPBackend:
|
|||
].lower()
|
||||
if extra_attribute_config['loop_over_attribute'] not in attribute_map:
|
||||
log.debug(
|
||||
'loop_over_attribute %s not present (or empty) in retrieved user object attributes. Pass.'
|
||||
% extra_attribute_config['loop_over_attribute']
|
||||
'loop_over_attribute %s not present (or empty) in retrieved user object attributes.'
|
||||
' Pass.' % extra_attribute_config['loop_over_attribute']
|
||||
)
|
||||
continue
|
||||
if 'filter' not in extra_attribute_config and 'basedn' not in extra_attribute_config:
|
||||
log.warning(
|
||||
'Extra attribute %s not correctly configured : you need to defined at least one of filter or basedn parameters'
|
||||
% extra_attribute_name
|
||||
'Extra attribute %s not correctly configured : you need to defined at least one of'
|
||||
' filter or basedn parameters' % extra_attribute_name
|
||||
)
|
||||
for item in attribute_map[extra_attribute_config['loop_over_attribute']]:
|
||||
ldap_filter = extra_attribute_config.get('filter', 'objectClass=*').format(
|
||||
|
@ -1685,8 +1687,8 @@ class LDAPBackend:
|
|||
conn.start_tls_s()
|
||||
except ldap.CONNECT_ERROR:
|
||||
log.error(
|
||||
'connection to %r failed when activating TLS, did you forget '
|
||||
'to declare the TLS certificate in /etc/ldap/ldap.conf ?',
|
||||
'connection to %r failed when activating TLS, did you forget to declare the TLS'
|
||||
' certificate in /etc/ldap/ldap.conf ?',
|
||||
url,
|
||||
)
|
||||
continue
|
||||
|
@ -1695,8 +1697,8 @@ class LDAPBackend:
|
|||
continue
|
||||
except ldap.CONNECT_ERROR:
|
||||
log.error(
|
||||
'connection to %r failed when activating TLS, did you forget to '
|
||||
'declare the TLS certificate in /etc/ldap/ldap.conf ?',
|
||||
'connection to %r failed when activating TLS, did you forget to declare the TLS'
|
||||
' certificate in /etc/ldap/ldap.conf ?',
|
||||
url,
|
||||
)
|
||||
continue
|
||||
|
|
|
@ -608,8 +608,8 @@ class UserCsvImporter:
|
|||
Error(
|
||||
'unique-constraint-failed',
|
||||
_(
|
||||
'Unique constraint on column "%(column)s" failed: '
|
||||
'value already appear on line %(line)d'
|
||||
'Unique constraint on column "%(column)s" failed: value already appear on'
|
||||
' line %(line)d'
|
||||
)
|
||||
% {'column': header.name, 'line': unique_map[unique_key]},
|
||||
)
|
||||
|
|
|
@ -51,23 +51,29 @@ def copy_old_users_to_custom_user_model(apps, schema_editor):
|
|||
# Reset sequences
|
||||
if schema_editor.connection.vendor == 'postgresql':
|
||||
schema_editor.execute(
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user_groups"\',\'id\'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "custom_user_user_groups";'
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user_groups"\',\'id\'), coalesce(max("id"),'
|
||||
' 1), max("id") IS NOT null) FROM "custom_user_user_groups";'
|
||||
)
|
||||
schema_editor.execute(
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user_user_permissions"\',\'id\'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "custom_user_user_user_permissions";'
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user_user_permissions"\',\'id\'),'
|
||||
' coalesce(max("id"), 1), max("id") IS NOT null) FROM "custom_user_user_user_permissions";'
|
||||
)
|
||||
schema_editor.execute(
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user"\',\'id\'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "custom_user_user";'
|
||||
'SELECT setval(pg_get_serial_sequence(\'"custom_user_user"\',\'id\'), coalesce(max("id"), 1),'
|
||||
' max("id") IS NOT null) FROM "custom_user_user";'
|
||||
)
|
||||
elif schema_editor.connection.vendor == 'sqlite':
|
||||
schema_editor.execute(
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user) WHERE name = "custom_user_user";'
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user) WHERE name ='
|
||||
' "custom_user_user";'
|
||||
)
|
||||
schema_editor.execute(
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_groups) WHERE name = "custom_user_user_groups";'
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_groups) WHERE name ='
|
||||
' "custom_user_user_groups";'
|
||||
)
|
||||
schema_editor.execute(
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_user_permissions) WHERE name = "custom_user_user_permissions";'
|
||||
'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_user_permissions) WHERE'
|
||||
' name = "custom_user_user_permissions";'
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
@ -96,7 +102,9 @@ class Migration(migrations.Migration):
|
|||
'is_superuser',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text='Designates that this user has all permissions without explicitly assigning them.',
|
||||
help_text=(
|
||||
'Designates that this user has all permissions without explicitly assigning them.'
|
||||
),
|
||||
verbose_name='superuser status',
|
||||
),
|
||||
),
|
||||
|
@ -137,7 +145,10 @@ class Migration(migrations.Migration):
|
|||
'is_active',
|
||||
models.BooleanField(
|
||||
default=True,
|
||||
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
|
||||
help_text=(
|
||||
'Designates whether this user should be treated as active. Unselect this instead'
|
||||
' of deleting accounts.'
|
||||
),
|
||||
verbose_name='active',
|
||||
),
|
||||
),
|
||||
|
@ -152,7 +163,10 @@ class Migration(migrations.Migration):
|
|||
related_name='user_set',
|
||||
to='auth.Group',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
|
||||
help_text=(
|
||||
'The groups this user belongs to. A user will get all permissions granted to each'
|
||||
' of his/her group.'
|
||||
),
|
||||
verbose_name='groups',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -8,7 +8,10 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
sql=r'CREATE INDEX "custom_user_user_email_idx" ON "custom_user_user" (UPPER("email") text_pattern_ops);',
|
||||
sql=(
|
||||
r'CREATE INDEX "custom_user_user_email_idx" ON "custom_user_user" (UPPER("email")'
|
||||
r' text_pattern_ops);'
|
||||
),
|
||||
reverse_sql=r'DROP INDEX "custom_user_user_email_idx";',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -8,7 +8,10 @@ class Migration(migrations.Migration):
|
|||
|
||||
operations = [
|
||||
migrations.RunSQL(
|
||||
sql=r'CREATE INDEX "custom_user_user_username_idx" ON "custom_user_user" (UPPER("username") text_pattern_ops);',
|
||||
sql=(
|
||||
r'CREATE INDEX "custom_user_user_username_idx" ON "custom_user_user" (UPPER("username")'
|
||||
r' text_pattern_ops);'
|
||||
),
|
||||
reverse_sql=r'DROP INDEX "custom_user_user_username_idx";',
|
||||
),
|
||||
]
|
||||
|
|
|
@ -60,8 +60,8 @@ class Migration(migrations.Migration):
|
|||
TrigramExtension(),
|
||||
RunSQLIfExtension(
|
||||
sql=[
|
||||
"CREATE INDEX IF NOT EXISTS custom_user_user_email_trgm_idx ON custom_user_user USING gist "
|
||||
"(LOWER(email) public.gist_trgm_ops)"
|
||||
"CREATE INDEX IF NOT EXISTS custom_user_user_email_trgm_idx ON custom_user_user USING gist"
|
||||
" (LOWER(email) public.gist_trgm_ops)"
|
||||
],
|
||||
reverse_sql=['DROP INDEX custom_user_user_email_trgm_idx'],
|
||||
),
|
||||
|
|
|
@ -152,14 +152,14 @@ class User(AbstractBaseUser, PermissionMixin):
|
|||
is_staff = models.BooleanField(
|
||||
_('staff status'),
|
||||
default=False,
|
||||
help_text=_('Designates whether the user can log into this admin ' 'site.'),
|
||||
help_text=_('Designates whether the user can log into this admin site.'),
|
||||
)
|
||||
is_active = models.BooleanField(
|
||||
_('active'),
|
||||
default=True,
|
||||
help_text=_(
|
||||
'Designates whether this user should be treated as '
|
||||
'active. Unselect this instead of deleting accounts.'
|
||||
'Designates whether this user should be treated as active. Unselect this instead of deleting'
|
||||
' accounts.'
|
||||
),
|
||||
)
|
||||
ou = models.ForeignKey(
|
||||
|
@ -241,8 +241,8 @@ class User(AbstractBaseUser, PermissionMixin):
|
|||
if not (self.username or self.email or (self.first_name and self.last_name)):
|
||||
raise ValidationError(
|
||||
_(
|
||||
'An account needs at least one identifier: '
|
||||
'username, email or a full name (first and last name).'
|
||||
'An account needs at least one identifier: username, email or a full name (first and last'
|
||||
' name).'
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -296,7 +296,7 @@ class User(AbstractBaseUser, PermissionMixin):
|
|||
pass
|
||||
else:
|
||||
errors.setdefault('email', []).append(
|
||||
_('This email address is already in use. Please supply a different email ' 'address.')
|
||||
_('This email address is already in use. Please supply a different email address.')
|
||||
)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
|
|
@ -93,9 +93,8 @@ class AuthenticationForm(auth_forms.AuthenticationForm):
|
|||
if seconds_to_wait > app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION:
|
||||
seconds_to_wait -= app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION
|
||||
msg = _(
|
||||
'You made too many login errors recently, you must '
|
||||
'wait <span class="js-seconds-until">%s</span> seconds '
|
||||
'to try again.'
|
||||
'You made too many login errors recently, you must wait <span'
|
||||
' class="js-seconds-until">%s</span> seconds to try again.'
|
||||
)
|
||||
msg = msg % int(math.ceil(seconds_to_wait))
|
||||
msg = html.mark_safe(msg)
|
||||
|
|
|
@ -114,7 +114,7 @@ class RegistrationCompletionFormNoPassword(profile_forms.BaseUserForm):
|
|||
exist = True
|
||||
if exist:
|
||||
raise ValidationError(
|
||||
_('This username is already in ' 'use. Please supply a different username.')
|
||||
_('This username is already in use. Please supply a different username.')
|
||||
)
|
||||
return username
|
||||
|
||||
|
@ -133,7 +133,7 @@ class RegistrationCompletionFormNoPassword(profile_forms.BaseUserForm):
|
|||
exist = True
|
||||
if exist:
|
||||
raise ValidationError(
|
||||
_('This email address is already in ' 'use. Please supply a different email address.')
|
||||
_('This email address is already in use. Please supply a different email address.')
|
||||
)
|
||||
return BaseUserManager.normalize_email(email)
|
||||
|
||||
|
|
|
@ -304,8 +304,8 @@ class CheckPasswordInput(PasswordInput):
|
|||
class ProfileImageInput(ClearableFileInput):
|
||||
if django.VERSION < (1, 9):
|
||||
template_with_initial = (
|
||||
'%(initial_text)s: <a href="%(initial_url)s"><img src="%(initial_url)s"/></a> '
|
||||
'%(clear_template)s<br />%(input_text)s: %(input)s'
|
||||
'%(initial_text)s: <a href="%(initial_url)s"><img src="%(initial_url)s"/></a>'
|
||||
' %(clear_template)s<br />%(input_text)s: %(input)s'
|
||||
)
|
||||
else:
|
||||
template_name = "authentic2/profile_image_input.html"
|
||||
|
|
|
@ -57,9 +57,10 @@ def check_authentic2_config(app_configs, **kwargs):
|
|||
errors.append(
|
||||
Warning(
|
||||
'You should not use default SAML keys in production',
|
||||
hint='Generate new RSA keys and change the value of '
|
||||
'A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY and '
|
||||
'A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY in your setting file',
|
||||
hint=(
|
||||
'Generate new RSA keys and change the value of A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY and'
|
||||
' A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY in your setting file'
|
||||
),
|
||||
)
|
||||
)
|
||||
return errors
|
||||
|
|
|
@ -487,8 +487,7 @@ def build_assertion(request, login, provider, nid_format='transient'):
|
|||
kwargs['entity_id'] = login.remoteProviderId
|
||||
kwargs['user'] = request.user
|
||||
logger.debug(
|
||||
'sending nameID %(name_id_format)r: %(name_id_content)r to '
|
||||
'%(entity_id)s for user %(user)s' % kwargs
|
||||
'sending nameID %(name_id_format)r: %(name_id_content)r to %(entity_id)s for user %(user)s' % kwargs
|
||||
)
|
||||
register_new_saml2_session(request, login)
|
||||
add_attributes(request, entity_id, assertion, provider, nid_format)
|
||||
|
@ -529,7 +528,7 @@ def sso(request):
|
|||
break
|
||||
except (lasso.ProfileInvalidMsgError, lasso.ProfileMissingIssuerError) as e:
|
||||
logger.warning(
|
||||
'invalid message for WebSSO profile with HTTP-Redirect binding: ' '%r exception: %s',
|
||||
'invalid message for WebSSO profile with HTTP-Redirect binding: %r exception: %s',
|
||||
message,
|
||||
e,
|
||||
extra={'request': request},
|
||||
|
@ -541,8 +540,8 @@ def sso(request):
|
|||
except lasso.ProfileInvalidProtocolprofileError:
|
||||
log_info_authn_request_details(login)
|
||||
message = _(
|
||||
"SAMLv2 Single Sign On: the request cannot be "
|
||||
"answered because no valid protocol binding could be found"
|
||||
"SAMLv2 Single Sign On: the request cannot be answered because no valid protocol binding"
|
||||
" could be found"
|
||||
)
|
||||
logger.warning('the request cannot be answered because no valid protocol binding could be found')
|
||||
return HttpResponseBadRequest(message, content_type='text/plain')
|
||||
|
@ -1245,9 +1244,8 @@ def build_session_dump(liberty_sessions):
|
|||
]
|
||||
for liberty_session in liberty_sessions:
|
||||
session.append(
|
||||
'<NidAndSessionIndex ProviderID="{0.provider_id}" '
|
||||
'AssertionID="xxx" '
|
||||
'SessionIndex="{0.session_index}">'.format(liberty_session)
|
||||
'<NidAndSessionIndex ProviderID="{0.provider_id}" AssertionID="xxx"'
|
||||
' SessionIndex="{0.session_index}">'.format(liberty_session)
|
||||
)
|
||||
session.append(f'<saml:NameID Format="{liberty_session.name_id_format}" ')
|
||||
if liberty_session.name_id_qualifier:
|
||||
|
|
|
@ -476,8 +476,8 @@ class Command(BaseCommand):
|
|||
or (admin_permission.target == admin_role)
|
||||
):
|
||||
self.warning(
|
||||
'invalid admin role "%s" invalid permission "%s": '
|
||||
'not admin_scope and not self manage permission',
|
||||
'invalid admin role "%s" invalid permission "%s": not admin_scope and not self manage'
|
||||
' permission',
|
||||
admin_role,
|
||||
admin_permission,
|
||||
)
|
||||
|
|
|
@ -123,7 +123,7 @@ class Command(BaseCommand):
|
|||
parser.add_argument(
|
||||
'--password',
|
||||
default='userPassword',
|
||||
help='attribute to extract the password from, ' 'OpenLDAP hashing algorithm are recognized',
|
||||
help='attribute to extract the password from, OpenLDAP hashing algorithm are recognized',
|
||||
)
|
||||
parser.add_argument('--object-class', default='inetOrgPerson', help='object class of records to load')
|
||||
parser.add_argument(
|
||||
|
@ -139,8 +139,10 @@ class Command(BaseCommand):
|
|||
parser.add_argument(
|
||||
'--callback',
|
||||
default=None,
|
||||
help='python file containing a function callback(user, dn, entry, options, dump)'
|
||||
' it can return models that will be saved',
|
||||
help=(
|
||||
'python file containing a function callback(user, dn, entry, options, dump) it can return'
|
||||
' models that will be saved'
|
||||
),
|
||||
)
|
||||
parser.add_argument('--callback-arg', action='append', help='arguments for the callback')
|
||||
|
||||
|
|
|
@ -63,6 +63,6 @@ class Command(BaseCommand):
|
|||
u.save()
|
||||
PasswordReset.objects.get_or_create(user=u)
|
||||
return (
|
||||
'Password changed successfully for user "%s", on next login he '
|
||||
'will be forced to change its password.' % u
|
||||
'Password changed successfully for user "%s", on next login he will be forced to change its'
|
||||
' password.' % u
|
||||
)
|
||||
|
|
|
@ -228,7 +228,7 @@ class UserChangePasswordForm(CssClass, forms.ModelForm):
|
|||
and not self.cleaned_data.get('send_password_reset')
|
||||
):
|
||||
raise forms.ValidationError(
|
||||
_('You must choose password generation or type a new' ' one or send a password reset mail')
|
||||
_('You must choose password generation or type a new one or send a password reset mail')
|
||||
)
|
||||
if not self.has_email() and (
|
||||
self.cleaned_data.get('send_mail')
|
||||
|
@ -236,7 +236,7 @@ class UserChangePasswordForm(CssClass, forms.ModelForm):
|
|||
or self.cleaned_data.get('send_password_reset')
|
||||
):
|
||||
raise forms.ValidationError(
|
||||
_('User does not have a mail, we cannot send the ' 'informations to him.')
|
||||
_('User does not have a mail, we cannot send the informations to him.')
|
||||
)
|
||||
|
||||
def has_email(self):
|
||||
|
@ -347,7 +347,7 @@ class UserAddForm(UserChangePasswordForm, UserEditForm):
|
|||
)
|
||||
except smtplib.SMTPException as e:
|
||||
logger.error(
|
||||
'registration mail could not be sent to user %s created through ' 'manager: %s', user, e
|
||||
'registration mail could not be sent to user %s created through manager: %s', user, e
|
||||
)
|
||||
return user
|
||||
|
||||
|
|
|
@ -243,7 +243,8 @@ class ManagerUserDeactivation(EventTypeDefinition):
|
|||
elif user:
|
||||
if reason == LDAP_DEACTIVATION_REASON_NOT_PRESENT:
|
||||
return _(
|
||||
'automatic deactivation of user "%s" because the associated LDAP account does not exist anymore'
|
||||
'automatic deactivation of user "%s" because the associated LDAP account does not exist'
|
||||
' anymore'
|
||||
) % user_to_str(user)
|
||||
elif reason == LDAP_DEACTIVATION_REASON_OLD_SOURCE:
|
||||
return _(
|
||||
|
|
|
@ -99,10 +99,8 @@ class OrganizationalUnitDeleteView(views.BaseDeleteView):
|
|||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
'You cannot delete the default '
|
||||
'organizational unit, you must first '
|
||||
'set another default organiational '
|
||||
'unit.'
|
||||
'You cannot delete the default organizational unit, you must first set another default'
|
||||
' organiational unit.'
|
||||
),
|
||||
)
|
||||
return self.return_ajax_response(request, HttpResponseRedirect(self.get_success_url()))
|
||||
|
|
|
@ -73,7 +73,7 @@ class ServiceView(
|
|||
if self.can_change:
|
||||
if action == 'add':
|
||||
if self.object.authorized_roles.filter(pk=role.pk).exists():
|
||||
messages.warning(self.request, _('Role already authorized in this ' 'service.'))
|
||||
messages.warning(self.request, _('Role already authorized in this service.'))
|
||||
else:
|
||||
self.object.add_authorized_role(role)
|
||||
elif action == 'remove':
|
||||
|
|
|
@ -88,9 +88,8 @@ class UserTable(tables.Table):
|
|||
class RoleMembersTable(UserTable):
|
||||
direct = tables.BooleanColumn(verbose_name=_('Direct member'), orderable=False)
|
||||
via = tables.TemplateColumn(
|
||||
'{% for role in record.via %}'
|
||||
'<a href="{% url "a2-manager-role-members" pk=role.pk %}">{{ role }}</a>{% if not forloop.last %}, {% endif %}'
|
||||
'{% endfor %}',
|
||||
'{% for role in record.via %}<a href="{% url "a2-manager-role-members" pk=role.pk %}">{{ role'
|
||||
' }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}',
|
||||
verbose_name=_('Inherited from'),
|
||||
orderable=False,
|
||||
)
|
||||
|
@ -151,13 +150,10 @@ class OuUserRolesTable(tables.Table):
|
|||
orderable=False,
|
||||
)
|
||||
member = tables.TemplateColumn(
|
||||
'{%% load i18n %%}<input class="role-member{%% if not record.member and record.via %%} '
|
||||
'indeterminate{%% endif %%}"'
|
||||
' name="role-{{ record.pk }}" type="checkbox" {%% if record.member %%}checked{%% endif %%} '
|
||||
'{%% if not record.has_perm %%}disabled '
|
||||
'title="{%% trans "%s" %%}"{%% endif %%} '
|
||||
'{%% if not record.can_manage_members %%}disabled '
|
||||
'title="{%% trans "%s" %%}"{%% endif %%}/>'
|
||||
'{%% load i18n %%}<input class="role-member{%% if not record.member and record.via %%}'
|
||||
' indeterminate{%% endif %%}" name="role-{{ record.pk }}" type="checkbox" {%% if record.member'
|
||||
' %%}checked{%% endif %%} {%% if not record.has_perm %%}disabled title="{%% trans "%s" %%}"{%% endif'
|
||||
' %%} {%% if not record.can_manage_members %%}disabled title="{%% trans "%s" %%}"{%% endif %%}/>'
|
||||
% (
|
||||
ugettext_noop('You are not authorized to manage this role'),
|
||||
ugettext_noop('This role is synchronised from LDAP, changing members is not allowed.'),
|
||||
|
@ -186,9 +182,8 @@ class UserRolesTable(tables.Table):
|
|||
)
|
||||
ou = tables.Column()
|
||||
via = tables.TemplateColumn(
|
||||
'{% if not record.member %}{% for rel in record.child_relation.all %}'
|
||||
'{{ rel.child }} {% if not forloop.last %}, {% endif %}{% endfor %}'
|
||||
'{% endif %}',
|
||||
'{% if not record.member %}{% for rel in record.child_relation.all %}{{ rel.child }} {% if not'
|
||||
' forloop.last %}, {% endif %}{% endfor %}{% endif %}',
|
||||
verbose_name=_('Inherited from'),
|
||||
orderable=False,
|
||||
)
|
||||
|
|
|
@ -128,7 +128,7 @@ class UsersView(HideOUColumnMixin, BaseTableView):
|
|||
table = super().get_table(**kwargs)
|
||||
if self.search_form.not_enough_chars():
|
||||
user_qs = self.search_form.filter_by_ou(self.get_queryset())
|
||||
table.empty_text = _('Enter at least %(limit)d characters ' '(%(user_count)d users)') % {
|
||||
table.empty_text = _('Enter at least %(limit)d characters (%(user_count)d users)') % {
|
||||
'limit': self.search_form.minimum_chars,
|
||||
'user_count': user_qs.count(),
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
|
|||
else:
|
||||
yield Action(
|
||||
'force_password_change',
|
||||
_('Force password change on ' 'next login'),
|
||||
_('Force password change on next login'),
|
||||
permission='custom_user.reset_password_user',
|
||||
)
|
||||
yield Action(
|
||||
|
@ -353,7 +353,7 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
|
|||
|
||||
def action_deactivate(self, request, *args, **kwargs):
|
||||
if request.user == self.object:
|
||||
messages.warning(request, _('You cannot desactivate your own ' 'user'))
|
||||
messages.warning(request, _('You cannot desactivate your own user'))
|
||||
else:
|
||||
self.object.mark_as_inactive()
|
||||
request.journal.record('manager.user.deactivation', target_user=self.object)
|
||||
|
@ -363,7 +363,7 @@ class UserDetailView(OtherActionsMixin, BaseDetailView):
|
|||
if not user.email:
|
||||
messages.info(
|
||||
request,
|
||||
_('User has no email, it\'not possible to ' 'send him am email to reset its ' 'password'),
|
||||
_('User has no email, it\'not possible to send him am email to reset its password'),
|
||||
)
|
||||
return
|
||||
send_password_reset_mail(user, request=request)
|
||||
|
|
|
@ -552,7 +552,7 @@ class BaseAddView(
|
|||
|
||||
@property
|
||||
def title(self):
|
||||
return ('Add %s') % super().get_model_name()
|
||||
return 'Add %s' % super().get_model_name()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_view_name, kwargs={'pk': self.object.pk})
|
||||
|
|
|
@ -136,7 +136,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'logout_url',
|
||||
models.URLField(
|
||||
help_text='you can use a {} to pass the URL of the success icon, ex.: http://example.com/logout?next={}',
|
||||
help_text=(
|
||||
'you can use a {} to pass the URL of the success icon, ex.:'
|
||||
' http://example.com/logout?next={}'
|
||||
),
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name='url',
|
||||
|
@ -153,7 +156,10 @@ class Migration(migrations.Migration):
|
|||
'logout_use_iframe_timeout',
|
||||
models.PositiveIntegerField(
|
||||
default=300,
|
||||
help_text="if iframe logout is used, it's the time between the onload event for this iframe and the moment we consider its loading to be really finished",
|
||||
help_text=(
|
||||
"if iframe logout is used, it's the time between the onload event for this iframe"
|
||||
" and the moment we consider its loading to be really finished"
|
||||
),
|
||||
verbose_name='iframe logout timeout (ms)',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -13,10 +13,10 @@ class Migration(migrations.Migration):
|
|||
sql=[
|
||||
"CREATE EXTENSION IF NOT EXISTS unaccent SCHEMA public",
|
||||
"CREATE EXTENSION IF NOT EXISTS pg_trgm SCHEMA public",
|
||||
"CREATE OR REPLACE FUNCTION public.immutable_unaccent(text) RETURNS varchar AS $$ "
|
||||
"SELECT public.unaccent('public.unaccent',$1::text); $$ LANGUAGE 'sql' IMMUTABLE",
|
||||
"CREATE INDEX custom_user_name_gist_idx ON custom_user_user USING gist "
|
||||
"(LOWER(public.immutable_unaccent(first_name || ' ' || last_name)) public.gist_trgm_ops)",
|
||||
"CREATE OR REPLACE FUNCTION public.immutable_unaccent(text) RETURNS varchar AS $$ SELECT"
|
||||
" public.unaccent('public.unaccent',$1::text); $$ LANGUAGE 'sql' IMMUTABLE",
|
||||
"CREATE INDEX custom_user_name_gist_idx ON custom_user_user USING gist"
|
||||
" (LOWER(public.immutable_unaccent(first_name || ' ' || last_name)) public.gist_trgm_ops)",
|
||||
],
|
||||
reverse_sql=[
|
||||
"DROP INDEX IF EXISTS custom_user_name_gist_idx",
|
||||
|
|
|
@ -11,9 +11,8 @@ def clean_admin_tools_tables(apps, schema_editor):
|
|||
try:
|
||||
with schema_editor.connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT table_name FROM information_schema.tables "
|
||||
"WHERE table_schema = current_schema() "
|
||||
"AND table_name LIKE 'admin_tools%'"
|
||||
"SELECT table_name FROM information_schema.tables WHERE table_schema = current_schema() AND"
|
||||
" table_name LIKE 'admin_tools%'"
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
for (table_name,) in rows:
|
||||
|
|
|
@ -27,7 +27,8 @@ FOR EACH ROW EXECUTE PROCEDURE authentic2_update_atv_search_vector()'''
|
|||
def drop_trigger(apps, schema_editor):
|
||||
with schema_editor.connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
'DROP TRIGGER IF EXISTS authentic2_attributevalue_search_vector_trigger ON authentic2_attributevalue'
|
||||
'DROP TRIGGER IF EXISTS authentic2_attributevalue_search_vector_trigger ON'
|
||||
' authentic2_attributevalue'
|
||||
)
|
||||
cursor.execute('DROP FUNCTION IF EXISTS authentic2_update_atv_search_vector')
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class UserExternalId(models.Model):
|
|||
return f'{self.user} is {self.external_id} on {self.source}'
|
||||
|
||||
def __repr__(self):
|
||||
return '<UserExternalId user: {!r} source: {!r} ' 'external_id: {!r} created: {} updated: {}'.format(
|
||||
return '<UserExternalId user: {!r} source: {!r} external_id: {!r} created: {} updated: {}'.format(
|
||||
self.user_id, self.source, self.external_id, self.created, self.updated
|
||||
)
|
||||
|
||||
|
@ -93,7 +93,7 @@ class LogoutUrlAbstract(models.Model):
|
|||
logout_url = models.URLField(
|
||||
verbose_name=_('url'),
|
||||
help_text=_(
|
||||
'you can use a {} to pass the URL of the success icon, ' 'ex.: http://example.com/logout?next={}'
|
||||
'you can use a {} to pass the URL of the success icon, ex.: http://example.com/logout?next={}'
|
||||
),
|
||||
max_length=255,
|
||||
blank=True,
|
||||
|
@ -105,9 +105,8 @@ class LogoutUrlAbstract(models.Model):
|
|||
logout_use_iframe_timeout = models.PositiveIntegerField(
|
||||
verbose_name=_('iframe logout timeout (ms)'),
|
||||
help_text=_(
|
||||
'if iframe logout is used, it\'s the time between the '
|
||||
'onload event for this iframe and the moment we consider its '
|
||||
'loading to be really finished'
|
||||
'if iframe logout is used, it\'s the time between the onload event for this iframe and the moment'
|
||||
' we consider its loading to be really finished'
|
||||
),
|
||||
default=300,
|
||||
)
|
||||
|
|
|
@ -125,9 +125,9 @@ def password_help_text(password='', only_errors=False):
|
|||
if criteria:
|
||||
html_criteria = ['<span class="a2-password-policy-rule">%s</span>' % criter for criter in criteria]
|
||||
return _(
|
||||
'In order to create a secure password, please use at least: '
|
||||
'<span class="a2-password-policy-container">%s</span>'
|
||||
) % (''.join(html_criteria))
|
||||
'In order to create a secure password, please use at least: <span'
|
||||
' class="a2-password-policy-container">%s</span>'
|
||||
) % ''.join(html_criteria)
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ def update_metadata(modeladmin, request, queryset):
|
|||
provider.update_metadata()
|
||||
except ValidationError as e:
|
||||
params = {'name': provider, 'error_msg': ', '.join(e.messages)}
|
||||
messages.error(request, _('Updating SAML provider %(name)s failed: ' '%(error_msg)s') % params)
|
||||
messages.error(request, _('Updating SAML provider %(name)s failed: %(error_msg)s') % params)
|
||||
else:
|
||||
count += 1
|
||||
messages.info(
|
||||
|
|
|
@ -247,7 +247,7 @@ def check_id_and_issue_instant(request_response_or_assertion, now=None):
|
|||
logger.warning('missing ID')
|
||||
return False
|
||||
if not nonce.accept_nonce(_id, 'SAML', 2 * NONCE_TIMEOUT):
|
||||
logger.warning("ID '%r' already used, request/response/assertion " "refused", _id)
|
||||
logger.warning("ID '%r' already used, request/response/assertion refused", _id)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -332,7 +332,7 @@ def retrieve_metadata_and_create(request, provider_id, sp_or_idp):
|
|||
metadata = get_url(provider_id)
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
'SAML metadata autoload: failure to retrieve metadata ' 'for entity id %s: %s', provider_id, e
|
||||
'SAML metadata autoload: failure to retrieve metadata for entity id %s: %s', provider_id, e
|
||||
)
|
||||
return None
|
||||
logger.debug('loaded %d bytes', len(metadata))
|
||||
|
@ -346,15 +346,13 @@ def retrieve_metadata_and_create(request, provider_id, sp_or_idp):
|
|||
p.full_clean(exclude=['entity_id', 'protocol_conformance'])
|
||||
except ValidationError as e:
|
||||
logging.error(
|
||||
'SAML metadata autoload: retrieved metadata for entity ' 'id %s are invalid, %s',
|
||||
'SAML metadata autoload: retrieved metadata for entity id %s are invalid, %s',
|
||||
provider_id,
|
||||
e.args,
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
logging.exception(
|
||||
'SAML metadata autoload: retrieved metadata ' 'validation raised an unknown exception'
|
||||
)
|
||||
logging.exception('SAML metadata autoload: retrieved metadata validation raised an unknown exception')
|
||||
return None
|
||||
p.save()
|
||||
logger.debug('%s saved', p)
|
||||
|
@ -581,8 +579,8 @@ def get_session_not_on_or_after(assertion):
|
|||
session_not_on_or_afters.append(saml2utils.iso8601_to_datetime(value))
|
||||
except ValueError:
|
||||
logging.getLogger(__name__).error(
|
||||
'unable to parse SessionNotOnOrAfter value %s, will '
|
||||
'use default value for session length.',
|
||||
'unable to parse SessionNotOnOrAfter value %s, will use default value for session'
|
||||
' length.',
|
||||
value,
|
||||
)
|
||||
if session_not_on_or_afters:
|
||||
|
|
|
@ -2061,7 +2061,9 @@ ATTRIBUTE_MAPPING = {
|
|||
# macedir .org or eduGain
|
||||
"schacHomeOrganizationType": {
|
||||
"oid": "1.3.6.1.4.1.25178.1.2.10",
|
||||
"display_name": "Identifies the type of organisation specified in the person's schacHomeOrganization attribute.",
|
||||
"display_name": (
|
||||
"Identifies the type of organisation specified in the person's schacHomeOrganization attribute."
|
||||
),
|
||||
"type": "http://www.w3.org/2001/XMLSchema#string",
|
||||
"syntax": "1.3.6.1.4.1.1466.115.121.1.15",
|
||||
},
|
||||
|
|
|
@ -283,24 +283,29 @@ class Command(BaseCommand):
|
|||
'--source',
|
||||
dest='source',
|
||||
default=None,
|
||||
help='Tag the loaded providers with the given source string, '
|
||||
'existing providers with the same tag will be removed if they do not exist '
|
||||
'anymore in the metadata file.',
|
||||
help=(
|
||||
'Tag the loaded providers with the given source string, existing providers with the same tag'
|
||||
' will be removed if they do not exist anymore in the metadata file.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--reset-attributes',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='When loading shibboleth attribute filter policies, start by '
|
||||
'removing all existing SAML attributes for each provider',
|
||||
help=(
|
||||
'When loading shibboleth attribute filter policies, start by removing all existing SAML'
|
||||
' attributes for each provider'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dont-load-attribute-consuming-service',
|
||||
dest='load_attribute_consuming_service',
|
||||
default=True,
|
||||
action='store_false',
|
||||
help='Prevent loading of the attribute policy from '
|
||||
'AttributeConsumingService nodes in the metadata file.',
|
||||
help=(
|
||||
'Prevent loading of the attribute policy from AttributeConsumingService nodes in the metadata'
|
||||
' file.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--shibboleth-attribute-filter-policy',
|
||||
|
@ -367,14 +372,13 @@ Any other kind of attribute filter policy is unsupported.
|
|||
sp_policy = SPOptionsIdPPolicy.objects.get(name=sp_policy_name)
|
||||
if verbosity > 1:
|
||||
print(
|
||||
'Service providers are set with the following SAML2 \
|
||||
options policy: %s'
|
||||
% sp_policy
|
||||
'Service providers are set with the following SAML2 '
|
||||
' options policy: %s' % sp_policy
|
||||
)
|
||||
except SPOptionsIdPPolicy.DoesNoextExist:
|
||||
if verbosity > 0:
|
||||
print(
|
||||
_('SAML2 service provider options ' 'policy with name %s not found')
|
||||
_('SAML2 service provider options policy with name %s not found')
|
||||
% sp_policy_name,
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
|
|
@ -115,7 +115,10 @@ class Migration(migrations.Migration):
|
|||
'transient_is_persistent',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='This IdP sends a transient NameID but you want a persistent behaviour for your SP',
|
||||
verbose_name=(
|
||||
'This IdP sends a transient NameID but you want a persistent behaviour for'
|
||||
' your SP'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -132,7 +135,10 @@ class Migration(migrations.Migration):
|
|||
'enable_binding_for_sso_response',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Binding for Authnresponse (taken from metadata by the IdP if not enabled)',
|
||||
verbose_name=(
|
||||
'Binding for Authnresponse (taken from metadata by the IdP if not'
|
||||
' enabled)'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -151,7 +157,10 @@ class Migration(migrations.Migration):
|
|||
'enable_http_method_for_slo_request',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='HTTP method for single logout request (taken from metadata if not enabled)',
|
||||
verbose_name=(
|
||||
'HTTP method for single logout request (taken from metadata if not'
|
||||
' enabled)'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -167,7 +176,10 @@ class Migration(migrations.Migration):
|
|||
'enable_http_method_for_defederation_request',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='HTTP method for federation termination request (taken from metadata if not enabled)',
|
||||
verbose_name=(
|
||||
'HTTP method for federation termination request (taken from metadata'
|
||||
' if not enabled)'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -382,14 +394,20 @@ class Migration(migrations.Migration):
|
|||
'enable_following_idp_options_policy',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='The following options policy will apply except if a policy for all identity provider is defined.',
|
||||
verbose_name=(
|
||||
'The following options policy will apply except if a policy for all identity'
|
||||
' provider is defined.'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
'enable_following_authorization_policy',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='The following authorization policy will apply except if a policy for all identity provider is defined.',
|
||||
verbose_name=(
|
||||
'The following authorization policy will apply except if a policy for all'
|
||||
' identity provider is defined.'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -439,14 +457,20 @@ class Migration(migrations.Migration):
|
|||
'enable_following_sp_options_policy',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='The following options policy will apply except if a policy for all service provider is defined.',
|
||||
verbose_name=(
|
||||
'The following options policy will apply except if a policy for all service'
|
||||
' provider is defined.'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
'enable_following_attribute_policy',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='The following attribute policy will apply except if a policy for all service provider is defined.',
|
||||
verbose_name=(
|
||||
'The following attribute policy will apply except if a policy for all service'
|
||||
' provider is defined.'
|
||||
),
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -658,7 +682,10 @@ class Migration(migrations.Migration):
|
|||
'needs_iframe_logout',
|
||||
models.BooleanField(
|
||||
default=False,
|
||||
help_text='logout URL are normally loaded inside an <img> HTML tag, some service provider need to use an iframe',
|
||||
help_text=(
|
||||
'logout URL are normally loaded inside an <img> HTML tag, some service provider'
|
||||
' need to use an iframe'
|
||||
),
|
||||
verbose_name='needs iframe logout',
|
||||
),
|
||||
),
|
||||
|
@ -666,7 +693,10 @@ class Migration(migrations.Migration):
|
|||
'iframe_logout_timeout',
|
||||
models.PositiveIntegerField(
|
||||
default=300,
|
||||
help_text="if iframe logout is used, it's the time between the onload event for this iframe and the moment we consider its loading to be really finished",
|
||||
help_text=(
|
||||
"if iframe logout is used, it's the time between the onload event for this iframe"
|
||||
" and the moment we consider its loading to be really finished"
|
||||
),
|
||||
verbose_name='iframe logout timeout',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -265,16 +265,16 @@ class SPOptionsIdPPolicy(models.Model):
|
|||
needs_iframe_logout = models.BooleanField(
|
||||
verbose_name=_('needs iframe logout'),
|
||||
help_text=_(
|
||||
'logout URL are normally loaded inside an <img> HTML tag, some service provider need to use an iframe'
|
||||
'logout URL are normally loaded inside an <img> HTML tag, some service provider need to use an'
|
||||
' iframe'
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
iframe_logout_timeout = models.PositiveIntegerField(
|
||||
verbose_name=_('iframe logout timeout'),
|
||||
help_text=_(
|
||||
'if iframe logout is used, it\'s the time between the '
|
||||
'onload event for this iframe and the moment we consider its '
|
||||
'loading to be really finished'
|
||||
'if iframe logout is used, it\'s the time between the onload event for this iframe and the moment'
|
||||
' we consider its loading to be really finished'
|
||||
),
|
||||
default=300,
|
||||
)
|
||||
|
@ -460,8 +460,7 @@ class LibertyServiceProvider(models.Model):
|
|||
enabled = models.BooleanField(verbose_name=_('Enabled'), default=False, db_index=True)
|
||||
enable_following_sp_options_policy = models.BooleanField(
|
||||
verbose_name=_(
|
||||
'The following options policy will apply except '
|
||||
'if a policy for all service provider is defined.'
|
||||
'The following options policy will apply except if a policy for all service provider is defined.'
|
||||
),
|
||||
default=False,
|
||||
)
|
||||
|
|
|
@ -355,8 +355,8 @@ def authnresponse_checking(login, subject_confirmation, logger, saml_request_id=
|
|||
if irt:
|
||||
if not_before is not None:
|
||||
logger.error(
|
||||
'assertion in response to an AuthnRequest, \
|
||||
notBefore MUST not be present in SubjectConfirmationData'
|
||||
'assertion in response to an AuthnRequest, notBefore MUST not be present in'
|
||||
' SubjectConfirmationData'
|
||||
)
|
||||
return False
|
||||
elif not_before is not None and not not_before.endswith('Z'):
|
||||
|
@ -381,8 +381,7 @@ def authnresponse_checking(login, subject_confirmation, logger, saml_request_id=
|
|||
return False
|
||||
|
||||
logger.debug(
|
||||
'assertion validity timeslice respected \
|
||||
%s <= %s < %s '
|
||||
'assertion validity timeslice respected %s <= %s < %s '
|
||||
% (not_before, str(now), not_on_or_after)
|
||||
)
|
||||
return True
|
||||
|
@ -401,8 +400,7 @@ def get_attributes_from_assertion(assertion, logger):
|
|||
name = attribute.name.decode('ascii')
|
||||
except Exception:
|
||||
logger.warning(
|
||||
'get_attributes_from_assertion: error decoding name of \
|
||||
attribute %s'
|
||||
'get_attributes_from_assertion: error decoding name of attribute %s'
|
||||
% attribute.dump()
|
||||
)
|
||||
else:
|
||||
|
@ -412,8 +410,10 @@ def get_attributes_from_assertion(assertion, logger):
|
|||
if attribute.friendlyName:
|
||||
nickname = attribute.friendlyName
|
||||
except Exception as e:
|
||||
message = 'get_attributes_from_assertion: name or format of an \
|
||||
attribute failed to decode as ascii: %s due to %s'
|
||||
message = (
|
||||
'get_attributes_from_assertion: name or format of an '
|
||||
' attribute failed to decode as ascii: %s due to %s'
|
||||
)
|
||||
logger.warning(message % (attribute.dump(), str(e)))
|
||||
try:
|
||||
values = attribute.attributeValue
|
||||
|
@ -426,8 +426,10 @@ def get_attributes_from_assertion(assertion, logger):
|
|||
content = ''.join(content)
|
||||
attributes[(name, format)].append(content.decode('utf8'))
|
||||
except Exception as e:
|
||||
message = 'get_attributes_from_assertion: value of an \
|
||||
attribute failed to decode as ascii: %s due to %s'
|
||||
message = (
|
||||
'get_attributes_from_assertion: value of an attribute failed'
|
||||
' to decode as ascii: %s due to %s'
|
||||
)
|
||||
logger.warning(message % (attribute.dump(), str(e)))
|
||||
attributes['__issuer'] = assertion.issuer.content
|
||||
attributes['__nameid'] = assertion.subject.nameID.content
|
||||
|
|
|
@ -170,12 +170,7 @@ def int_to_cryptobinary(integer):
|
|||
def get_xmldsig_rsa_key_value(publickey):
|
||||
mod = get_rsa_public_key_modulus(publickey)
|
||||
exp = get_rsa_public_key_exponent(publickey)
|
||||
return (
|
||||
'<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#">\n\t'
|
||||
'<Modulus>%s</Modulus>\n\t'
|
||||
'<Exponent>%s</Exponent>\n</RSAKeyValue>'
|
||||
% (
|
||||
int_to_cryptobinary(mod),
|
||||
int_to_cryptobinary(exp),
|
||||
)
|
||||
return '<RSAKeyValue' ' xmlns="http://www.w3.org/2000/09/xmldsig#">\n\t<Modulus>%s</Modulus>\n\t<Exponent>%s</Exponent>\n</RSAKeyValue>' % (
|
||||
int_to_cryptobinary(mod),
|
||||
int_to_cryptobinary(exp),
|
||||
)
|
||||
|
|
|
@ -245,7 +245,10 @@ LOGGING = {
|
|||
},
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '[%(asctime)s] %(ip)s %(user)s %(request_id)s %(levelname)s %(name)s.%(funcName)s: %(message)s',
|
||||
'format': (
|
||||
'[%(asctime)s] %(ip)s %(user)s %(request_id)s %(levelname)s %(name)s.%(funcName)s:'
|
||||
' %(message)s'
|
||||
),
|
||||
'datefmt': '%Y-%m-%d %a %H:%M:%S',
|
||||
},
|
||||
'verbose_db': {
|
||||
|
|
|
@ -154,7 +154,7 @@ def load_backend(path, kwargs):
|
|||
raise ImproperlyConfigured('Error importing idp backend %s: "%s"' % (module, e))
|
||||
except ValueError:
|
||||
raise ImproperlyConfigured(
|
||||
'Error importing idp backends. Is IDP_BACKENDS a correctly ' 'defined list or tuple?'
|
||||
'Error importing idp backends. Is IDP_BACKENDS a correctly defined list or tuple?'
|
||||
)
|
||||
try:
|
||||
cls = getattr(mod, attr)
|
||||
|
@ -944,7 +944,7 @@ def send_password_reset_mail(
|
|||
**kwargs,
|
||||
)
|
||||
logger.info(
|
||||
'password reset request for user %s, email sent to %s ' 'with token %s', user, user.email, token.uuid
|
||||
'password reset request for user %s, email sent to %s with token %s', user, user.email, token.uuid
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ def check_cookie_works(request):
|
|||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
'Cookies are disabled in your browser, please activate them or you will not be able to log in.'
|
||||
'Cookies are disabled in your browser, please activate them or you will not be able to'
|
||||
' log in.'
|
||||
),
|
||||
)
|
||||
return False
|
||||
|
|
|
@ -208,10 +208,8 @@ class EmailChangeView(cbv.TemplateNamesMixin, FormView):
|
|||
messages.info(
|
||||
self.request,
|
||||
_(
|
||||
'Your request for changing your email '
|
||||
'is received. An email of validation '
|
||||
'was sent to you. Please click on the '
|
||||
'link contained inside.'
|
||||
'Your request for changing your email is received. An email of validation was sent to you.'
|
||||
' Please click on the link contained inside.'
|
||||
),
|
||||
)
|
||||
logger.info('email change request')
|
||||
|
@ -726,8 +724,8 @@ class PasswordResetView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'An email has already been sent to %s. Click "Validate" again if '
|
||||
'you really want it to be sent again.'
|
||||
'An email has already been sent to %s. Click "Validate" again if you really want it to be'
|
||||
' sent again.'
|
||||
)
|
||||
% email,
|
||||
)
|
||||
|
@ -745,8 +743,8 @@ class PasswordResetView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'Multiple emails have already been sent to this address. Further attempts are '
|
||||
'blocked, please check your spam folder or try again later.'
|
||||
'Multiple emails have already been sent to this address. Further attempts are blocked,'
|
||||
' please check your spam folder or try again later.'
|
||||
),
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
@ -761,8 +759,8 @@ class PasswordResetView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'Multiple password reset attempts have already been made from this IP address. No '
|
||||
'further email will be sent, please check your spam folder or try again later.'
|
||||
'Multiple password reset attempts have already been made from this IP address. No further'
|
||||
' email will be sent, please check your spam folder or try again later.'
|
||||
),
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
@ -911,8 +909,8 @@ class BaseRegistrationView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'An email has already been sent to %s. Click "Validate" again if '
|
||||
'you really want it to be sent again.'
|
||||
'An email has already been sent to %s. Click "Validate" again if you really want it to be'
|
||||
' sent again.'
|
||||
)
|
||||
% email,
|
||||
)
|
||||
|
@ -929,8 +927,8 @@ class BaseRegistrationView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'Multiple emails have already been sent to this address. Further attempts are '
|
||||
'blocked, please check your spam folder or try again later.'
|
||||
'Multiple emails have already been sent to this address. Further attempts are blocked,'
|
||||
' please check your spam folder or try again later.'
|
||||
),
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
@ -944,8 +942,8 @@ class BaseRegistrationView(FormView):
|
|||
form.add_error(
|
||||
'email',
|
||||
_(
|
||||
'Multiple registration attempts have already been made from this IP address. No '
|
||||
'further email will be sent, please check your spam folder or try again later.'
|
||||
'Multiple registration attempts have already been made from this IP address. No further'
|
||||
' email will be sent, please check your spam folder or try again later.'
|
||||
),
|
||||
)
|
||||
return self.form_invalid(form)
|
||||
|
|
|
@ -390,11 +390,10 @@ class LoginOrLinkView(View):
|
|||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
'Your FranceConnect email address \'%s\' is already used by another '
|
||||
'account, so we cannot create an account for you. Please connect '
|
||||
'with you existing account or create an '
|
||||
'account with another email address then link it to FranceConnect '
|
||||
'using your account management page.'
|
||||
'Your FranceConnect email address \'%s\' is already used by another account, so we'
|
||||
' cannot create an account for you. Please connect with you existing account or'
|
||||
' create an account with another email address then link it to FranceConnect using'
|
||||
' your account management page.'
|
||||
)
|
||||
% email,
|
||||
)
|
||||
|
@ -403,10 +402,10 @@ class LoginOrLinkView(View):
|
|||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
'Your FranceConnect email address "%(email)s" is already used by the FranceConnect '
|
||||
'account of "%(user)s", so we cannot create an account for you. Please create '
|
||||
'an account with another email address then link it to FranceConnect '
|
||||
'using your account management page.'
|
||||
'Your FranceConnect email address "%(email)s" is already used by the FranceConnect'
|
||||
' account of "%(user)s", so we cannot create an account for you. Please create an'
|
||||
' account with another email address then link it to FranceConnect using your account'
|
||||
' management page.'
|
||||
)
|
||||
% {'email': email, 'user': user.get_full_name()},
|
||||
)
|
||||
|
|
|
@ -59,8 +59,7 @@ class OIDCBackend(ModelBackend):
|
|||
key_or_keyset = provider.jwkset
|
||||
if not key_or_keyset:
|
||||
logger.warning(
|
||||
'auth_oidc: idtoken signature algorithm is RSA but '
|
||||
'no JWKSet is defined on provider %s',
|
||||
'auth_oidc: idtoken signature algorithm is RSA but no JWKSet is defined on provider %s',
|
||||
id_token.iss,
|
||||
)
|
||||
return None
|
||||
|
@ -70,8 +69,8 @@ class OIDCBackend(ModelBackend):
|
|||
key_or_keyset = JWK(kty='oct', k=k.decode('ascii'))
|
||||
if not provider.client_secret:
|
||||
logger.warning(
|
||||
'auth_oidc: idtoken signature algorithm is HMAC but '
|
||||
'no client_secret is defined on provider %s',
|
||||
'auth_oidc: idtoken signature algorithm is HMAC but no client_secret is defined on'
|
||||
' provider %s',
|
||||
id_token.iss,
|
||||
)
|
||||
return None
|
||||
|
@ -80,7 +79,7 @@ class OIDCBackend(ModelBackend):
|
|||
key_or_keyset = provider.jwkset
|
||||
if not key_or_keyset:
|
||||
logger.warning(
|
||||
'auth_oidc: idtoken signature algorithm is EC but ' 'no JWKSet is defined on provider %s',
|
||||
'auth_oidc: idtoken signature algorithm is EC but no JWKSet is defined on provider %s',
|
||||
id_token.iss,
|
||||
)
|
||||
return None
|
||||
|
@ -112,7 +111,7 @@ class OIDCBackend(ModelBackend):
|
|||
return None
|
||||
if id_token.azp != provider.client_id:
|
||||
logger.warning(
|
||||
'auth_oidc: multiple audience and azp %r does not match client_id' ' %r',
|
||||
'auth_oidc: multiple audience and azp %r does not match client_id %r',
|
||||
id_token.azp,
|
||||
provider.client_id,
|
||||
)
|
||||
|
@ -121,8 +120,7 @@ class OIDCBackend(ModelBackend):
|
|||
if provider.max_auth_age:
|
||||
if not id_token.iat:
|
||||
logger.warning(
|
||||
'auth_oidc: provider configured for fresh authentication but iat is '
|
||||
'missing from idtoken'
|
||||
'auth_oidc: provider configured for fresh authentication but iat is missing from idtoken'
|
||||
)
|
||||
return None
|
||||
duration = now() - id_token.iat
|
||||
|
@ -169,9 +167,7 @@ class OIDCBackend(ModelBackend):
|
|||
user_info = None
|
||||
if need_user_info:
|
||||
if not access_token:
|
||||
logger.warning(
|
||||
'auth_oidc: need user info for some claims, but no access token was ' 'returned'
|
||||
)
|
||||
logger.warning('auth_oidc: need user info for some claims, but no access token was returned')
|
||||
return None
|
||||
try:
|
||||
response = requests.get(
|
||||
|
@ -199,14 +195,14 @@ class OIDCBackend(ModelBackend):
|
|||
logger.warning('claim \'%r\' is templated, it cannot be set as required')
|
||||
elif claim_mapping.idtoken_claim and claim not in id_token:
|
||||
logger.warning(
|
||||
'auth_oidc: cannot create user missing required claim %r in ' 'id_token (%r)',
|
||||
'auth_oidc: cannot create user missing required claim %r in id_token (%r)',
|
||||
claim,
|
||||
id_token,
|
||||
)
|
||||
return None
|
||||
elif not user_info or claim not in user_info:
|
||||
logger.warning(
|
||||
'auth_oidc: cannot create user missing required claim %r in ' 'user_info (%r)',
|
||||
'auth_oidc: cannot create user missing required claim %r in user_info (%r)',
|
||||
claim,
|
||||
user_info,
|
||||
)
|
||||
|
@ -269,8 +265,8 @@ class OIDCBackend(ModelBackend):
|
|||
pass
|
||||
except User.MultipleObjectsReturned:
|
||||
logger.error(
|
||||
'auth_oidc: cannot create user with sub "%s", '
|
||||
'too many users with the same email "%s" in ou "%s"',
|
||||
'auth_oidc: cannot create user with sub "%s", too many users with the same email "%s"'
|
||||
' in ou "%s"',
|
||||
id_token.sub,
|
||||
email,
|
||||
provider.ou,
|
||||
|
@ -295,7 +291,7 @@ class OIDCBackend(ModelBackend):
|
|||
oidc_account.save()
|
||||
else:
|
||||
logger.warning(
|
||||
'auth_oidc: cannot create user for sub %r as issuer %r does not' ' allow it',
|
||||
'auth_oidc: cannot create user for sub %r as issuer %r does not allow it',
|
||||
id_token.sub,
|
||||
id_token.iss,
|
||||
)
|
||||
|
|
|
@ -38,7 +38,7 @@ class Command(BaseCommand):
|
|||
parser.add_argument('name')
|
||||
parser.add_argument('--issuer', help='do automatic registration of the issuer')
|
||||
parser.add_argument(
|
||||
'--openid-configuration', help='file containing the OpenID Connect ' 'configuration of the OP'
|
||||
'--openid-configuration', help='file containing the OpenID Connect configuration of the OP'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--claim-mapping', default=[], action='append', help='mapping from claim to attribute'
|
||||
|
@ -103,7 +103,7 @@ class Command(BaseCommand):
|
|||
tup = claim_mapping.split()
|
||||
if len(tup) < 2:
|
||||
raise CommandError(
|
||||
'invalid claim mapping %r. it must contain at least a claim and ' 'an attribute name'
|
||||
'invalid claim mapping %r. it must contain at least a claim and an attribute name'
|
||||
)
|
||||
claim, attribute = tup[:2]
|
||||
claim_options = [x.strip() for x in tup[2:]]
|
||||
|
|
|
@ -69,7 +69,7 @@ def get_provider_by_issuer(issuer):
|
|||
|
||||
|
||||
def parse_id_token(encoded, provider):
|
||||
""" May raise any subclass of jwcrypto.common.JWException """
|
||||
"""May raise any subclass of jwcrypto.common.JWException"""
|
||||
jwt = JWT()
|
||||
jwt.deserialize(encoded, None)
|
||||
header = jwt.token.jose_header
|
||||
|
@ -207,7 +207,7 @@ def register_issuer(name, issuer=None, openid_configuration=None, verify=True, t
|
|||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
raise ValueError(
|
||||
_('Unable to reach the OpenID Connect configuration for %(issuer)s: ' '%(error)s')
|
||||
_('Unable to reach the OpenID Connect configuration for %(issuer)s: %(error)s')
|
||||
% {
|
||||
'issuer': issuer,
|
||||
'error': e,
|
||||
|
@ -231,15 +231,15 @@ def register_issuer(name, issuer=None, openid_configuration=None, verify=True, t
|
|||
}
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ValueError(_('Invalid OpenID Connect configuration for %(issuer)s: ' '%(error)s') % (issuer, e))
|
||||
raise ValueError(_('Invalid OpenID Connect configuration for %(issuer)s: %(error)s') % (issuer, e))
|
||||
if 'code' not in openid_configuration['response_types_supported']:
|
||||
raise ValueError(_('authorization code flow is unsupported: code response type is ' 'unsupported'))
|
||||
raise ValueError(_('authorization code flow is unsupported: code response type is unsupported'))
|
||||
try:
|
||||
response = requests.get(openid_configuration['jwks_uri'], verify=verify, timeout=None)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
raise ValueError(
|
||||
_('Unable to reach the OpenID Connect JWKSet for %(issuer)s: ' '%(url)s %(error)s')
|
||||
_('Unable to reach the OpenID Connect JWKSet for %(issuer)s: %(url)s %(error)s')
|
||||
% {
|
||||
'issuer': issuer,
|
||||
'url': openid_configuration['jwks_uri'],
|
||||
|
@ -289,7 +289,7 @@ def get_openid_configuration_url(issuer):
|
|||
parsed = urllib.parse.urlparse(issuer)
|
||||
if parsed.query or parsed.fragment or parsed.scheme != 'https':
|
||||
raise ValueError(
|
||||
_('invalid issuer URL, it must use the https:// scheme and not have a ' 'query or fragment')
|
||||
_('invalid issuer URL, it must use the https:// scheme and not have a query or fragment')
|
||||
)
|
||||
issuer = urllib.parse.urlunparse(
|
||||
(parsed.scheme, parsed.netloc, parsed.path.rstrip('/'), None, None, None)
|
||||
|
|
|
@ -208,8 +208,8 @@ class LoginCallback(View):
|
|||
error = None
|
||||
error_description = None
|
||||
logger.warning(
|
||||
'auth_oidc: token_endpoint returned HTTP error status '
|
||||
'%(status_code)s for %(issuer)s with content %(content)s'
|
||||
'auth_oidc: token_endpoint returned HTTP error status %(status_code)s for %(issuer)s with'
|
||||
' content %(content)s'
|
||||
% {
|
||||
'issuer': provider.issuer,
|
||||
'status_code': status_code,
|
||||
|
@ -220,7 +220,8 @@ class LoginCallback(View):
|
|||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
'Authentication on %(name)s failed with error "%(error)s", report %(request_id)s to an administrator. '
|
||||
'Authentication on %(name)s failed with error "%(error)s", report %(request_id)s to'
|
||||
' an administrator. '
|
||||
)
|
||||
% {
|
||||
'name': provider.name,
|
||||
|
@ -231,7 +232,7 @@ class LoginCallback(View):
|
|||
else:
|
||||
messages.warning(
|
||||
request,
|
||||
_('Provider %(name)s is down, report %(request_id)s to ' 'an administrator. ')
|
||||
_('Provider %(name)s is down, report %(request_id)s to an administrator. ')
|
||||
% {
|
||||
'name': provider.name,
|
||||
'request_id': request.request_id,
|
||||
|
@ -249,7 +250,7 @@ class LoginCallback(View):
|
|||
)
|
||||
messages.warning(
|
||||
request,
|
||||
_('Provider %(name)s is down, report %(request_id)s to ' 'an administrator. ')
|
||||
_('Provider %(name)s is down, report %(request_id)s to an administrator. ')
|
||||
% {
|
||||
'name': provider.name,
|
||||
'request_id': request.request_id,
|
||||
|
@ -265,7 +266,7 @@ class LoginCallback(View):
|
|||
)
|
||||
messages.warning(
|
||||
request,
|
||||
_('Provider %(name)s is down, report %(request_id)s to ' 'an administrator. ')
|
||||
_('Provider %(name)s is down, report %(request_id)s to an administrator. ')
|
||||
% {
|
||||
'name': provider.name,
|
||||
'request_id': request.request_id,
|
||||
|
@ -284,7 +285,7 @@ class LoginCallback(View):
|
|||
)
|
||||
messages.warning(
|
||||
request,
|
||||
_('Provider %(name)s is down, report %(request_id)s to ' 'an administrator. ')
|
||||
_('Provider %(name)s is down, report %(request_id)s to an administrator. ')
|
||||
% {
|
||||
'name': provider.name,
|
||||
'request_id': request.request_id,
|
||||
|
@ -324,7 +325,7 @@ class LoginCallback(View):
|
|||
if provider:
|
||||
messages.warning(
|
||||
request,
|
||||
_('Login with %(name)s failed, report %(request_id)s ' 'to an administrator.')
|
||||
_('Login with %(name)s failed, report %(request_id)s to an administrator.')
|
||||
% {
|
||||
'name': provider.name,
|
||||
'request_id': request.request_id,
|
||||
|
@ -333,7 +334,7 @@ class LoginCallback(View):
|
|||
else:
|
||||
messages.warning(
|
||||
request,
|
||||
_('Login with OpenIDConnect failed, report %s to an ' 'administrator') % request.request_id,
|
||||
_('Login with OpenIDConnect failed, report %s to an administrator') % request.request_id,
|
||||
)
|
||||
return self.continue_to_next_url(request)
|
||||
|
||||
|
|
|
@ -37,7 +37,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
'logout_url',
|
||||
models.URLField(
|
||||
help_text='you can use a {} to pass the URL of the success icon, ex.: http://example.com/logout?next={}',
|
||||
help_text=(
|
||||
'you can use a {} to pass the URL of the success icon, ex.:'
|
||||
' http://example.com/logout?next={}'
|
||||
),
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name='url',
|
||||
|
@ -54,7 +57,10 @@ class Migration(migrations.Migration):
|
|||
'logout_use_iframe_timeout',
|
||||
models.PositiveIntegerField(
|
||||
default=300,
|
||||
help_text="if iframe logout is used, it's the time between the onload event for this iframe and the moment we consider its loading to be really finished",
|
||||
help_text=(
|
||||
"if iframe logout is used, it's the time between the onload event for this iframe"
|
||||
" and the moment we consider its loading to be really finished"
|
||||
),
|
||||
verbose_name='iframe logout timeout (ms)',
|
||||
),
|
||||
),
|
||||
|
|
|
@ -159,9 +159,8 @@ class OIDCClient(Service):
|
|||
except ValueError:
|
||||
raise ValidationError(
|
||||
_(
|
||||
'Redirect URIs must have the same domain or you must define a '
|
||||
'sector identifier URI if you want to use pairwise'
|
||||
'identifiers'
|
||||
'Redirect URIs must have the same domain or you must define a sector identifier URI'
|
||||
' if you want to use pairwiseidentifiers'
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -220,9 +219,7 @@ class OIDCClient(Service):
|
|||
raise ValueError('all redirect_uri do not have the same hostname')
|
||||
elif self.authorization_mode == self.AUTHORIZATION_MODE_BY_OU:
|
||||
if not self.ou:
|
||||
raise ValidationError(
|
||||
_('OU-based authorization requires that the client be ' 'within an OU.')
|
||||
)
|
||||
raise ValidationError(_('OU-based authorization requires that the client be within an OU.'))
|
||||
sector_identifier = self.ou.slug
|
||||
else:
|
||||
raise NotImplementedError('unknown self.authorization_mode %s' % self.authorization_mode)
|
||||
|
|
|
@ -288,8 +288,8 @@ def authorize_for_client(request, client, redirect_uri):
|
|||
if client.authorization_flow == client.FLOW_RESOURCE_OWNER_CRED:
|
||||
raise InvalidRequest(
|
||||
_(
|
||||
'Client is configured for resource owner password credentials grant, '
|
||||
'authorize endpoint is not usable'
|
||||
'Client is configured for resource owner password credentials grant, authorize endpoint is'
|
||||
' not usable'
|
||||
)
|
||||
)
|
||||
if client.authorization_flow == client.FLOW_AUTHORIZATION_CODE:
|
||||
|
@ -590,7 +590,7 @@ def idtoken_from_user_credential(request):
|
|||
|
||||
if request.META.get('CONTENT_TYPE') != 'application/x-www-form-urlencoded':
|
||||
raise InvalidRequest(
|
||||
_('Wrong content type. request content type must be ' '\'application/x-www-form-urlencoded\''),
|
||||
_('Wrong content type. request content type must be \'application/x-www-form-urlencoded\''),
|
||||
client=client,
|
||||
)
|
||||
username = request.POST.get('username')
|
||||
|
@ -602,16 +602,15 @@ def idtoken_from_user_credential(request):
|
|||
if not all((username, request.POST.get('password'))):
|
||||
raise InvalidRequest(
|
||||
_(
|
||||
'Request must bear both username and password as '
|
||||
'parameters using the "application/x-www-form-urlencoded" '
|
||||
'media type'
|
||||
'Request must bear both username and password as parameters using the'
|
||||
' "application/x-www-form-urlencoded" media type'
|
||||
),
|
||||
client=client,
|
||||
)
|
||||
|
||||
if client.authorization_flow != models.OIDCClient.FLOW_RESOURCE_OWNER_CRED:
|
||||
raise UnauthorizedClient(
|
||||
_('Client is not configured for resource owner password ' 'credential grant'), client=client
|
||||
_('Client is not configured for resource owner password credential grant'), client=client
|
||||
)
|
||||
|
||||
exponential_backoff = ExponentialRetryTimeout(
|
||||
|
@ -626,7 +625,7 @@ def idtoken_from_user_credential(request):
|
|||
seconds_to_wait = a2_app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MAX_DURATION
|
||||
if seconds_to_wait:
|
||||
raise InvalidRequest(
|
||||
_('Too many attempts with erroneous RO password, you must wait ' '%s seconds to try again.')
|
||||
_('Too many attempts with erroneous RO password, you must wait %s seconds to try again.')
|
||||
% int(math.ceil(seconds_to_wait)),
|
||||
client=client,
|
||||
)
|
||||
|
|
|
@ -283,16 +283,15 @@ class PermissionMixin(models.Model):
|
|||
is_superuser = models.BooleanField(
|
||||
_('superuser status'),
|
||||
default=False,
|
||||
help_text=_('Designates that this user has all permissions ' 'without explicitly assigning them.'),
|
||||
help_text=_('Designates that this user has all permissions without explicitly assigning them.'),
|
||||
)
|
||||
groups = models.ManyToManyField(
|
||||
to=Group,
|
||||
verbose_name=_('groups'),
|
||||
blank=True,
|
||||
help_text=_(
|
||||
'The groups this user belongs to. A user will get '
|
||||
'all permissions granted to each of his/her '
|
||||
'group.'
|
||||
'The groups this user belongs to. A user will get all permissions granted to each of his/her'
|
||||
' group.'
|
||||
),
|
||||
related_name="user_set",
|
||||
related_query_name="user",
|
||||
|
|
|
@ -512,7 +512,7 @@ def migration(request, transactional_db):
|
|||
return executor._create_project_state(with_applied_migrations=True).apps
|
||||
|
||||
def apply(self, targets):
|
||||
""" Migrate forwards to the "targets" migration """
|
||||
"""Migrate forwards to the "targets" migration"""
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.migrate(targets)
|
||||
executor.loader.build_graph()
|
||||
|
|
|
@ -79,11 +79,7 @@ payload_decoded = {
|
|||
header_rsa = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ'
|
||||
header_ec = 'eyJhbGciOiJFUzI1NiIsImtpZCI6ImpiMjBDZzgifQ'
|
||||
header_hmac = 'eyJhbGciOiJIUzI1NiJ9'
|
||||
payload = (
|
||||
'eyJhdWQiOiJzNkJoZFJrcXQzIiwiZXhwIjoyMjAxMDk0Mjc4LCJpYXQiOjEzMTEyOD'
|
||||
'A5NzAsImlzcyI6Imh0dHA6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJub25jZSI6Im4t'
|
||||
'MFM2X1d6QTJNaiIsInN1YiI6IjI0ODI4OTc2MTAwMSJ9'
|
||||
)
|
||||
payload = 'eyJhdWQiOiJzNkJoZFJrcXQzIiwiZXhwIjoyMjAxMDk0Mjc4LCJpYXQiOjEzMTEyODA5NzAsImlzcyI6Imh0dHA6Ly9zZXJ2ZXIuZXhhbXBsZS5jb20iLCJub25jZSI6Im4tMFM2X1d6QTJNaiIsInN1YiI6IjI0ODI4OTc2MTAwMSJ9'
|
||||
|
||||
|
||||
def test_parse_id_token(code, oidc_provider, oidc_provider_jwkset):
|
||||
|
|
|
@ -306,12 +306,13 @@ def test_check_and_repair_managers_of_roles(db, capsys):
|
|||
assert 'Managers of Role 1" wrong ou, should be "Default organizational unit"' in captured.out
|
||||
assert 'invalid permission "Management / role / Role 1": not manage_members operation' in captured.out
|
||||
assert (
|
||||
'invalid permission "Management / role / Role 1": ' 'not admin_scope and not self manage permission'
|
||||
) in captured.out
|
||||
'invalid permission "Management / role / Role 1": not admin_scope and not self manage permission'
|
||||
in captured.out
|
||||
)
|
||||
assert (
|
||||
'invalid admin role "Managers of Role 1" '
|
||||
'wrong ou, should be "Default organizational unit" is "None"'
|
||||
) in captured.out
|
||||
'invalid admin role "Managers of Role 1" wrong ou, should be "Default organizational unit" is "None"'
|
||||
in captured.out
|
||||
)
|
||||
|
||||
perm1.refresh_from_db()
|
||||
assert perm1.ou is None
|
||||
|
|
|
@ -23,7 +23,7 @@ def test_sha256_hasher():
|
|||
hasher = hashers.SHA256PasswordHasher()
|
||||
hashed = hasher.encode('admin', '')
|
||||
assert hasher.verify('admin', hashed)
|
||||
assert hashed == 'sha256$$8c6976e5b5410415b' 'de908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
|
||||
assert hashed == 'sha256$$8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
|
||||
|
||||
|
||||
def test_openldap_hashers():
|
||||
|
|
|
@ -86,17 +86,7 @@ class CasTests(Authentic2TestCase):
|
|||
self.factory = RequestFactory()
|
||||
|
||||
def test_long_urls(self):
|
||||
self.service.urls = (
|
||||
'https://casclient.com/%C3%A9/'
|
||||
'lorem/ipsum/dolor/sit/amet/consectetur/adipiscing/elit/sed/do/'
|
||||
'eiusmod/tempor/incididunt/ut/labore/et/dolore/magna/aliqua/ut/'
|
||||
'enim/ad/minim/veniam/quis/nostrud/exercitation/ullamco/laboris/'
|
||||
'nisi/ut/aliquip/ex/ea/commodo/consequat/duis/aute/irure/dolor/'
|
||||
'in/reprehenderit/in/voluptate/velit/esse/cillum/dolore/eu/'
|
||||
'fugiat/nulla/pariatur/excepteur/sint/occaecat/cupidatat/non/'
|
||||
'proident/sunt/in/culpa/qui/officia/deserunt/mollit/anim/id/est/'
|
||||
'laborum'
|
||||
)
|
||||
self.service.urls = 'https://casclient.com/%C3%A9/lorem/ipsum/dolor/sit/amet/consectetur/adipiscing/elit/sed/do/eiusmod/tempor/incididunt/ut/labore/et/dolore/magna/aliqua/ut/enim/ad/minim/veniam/quis/nostrud/exercitation/ullamco/laboris/nisi/ut/aliquip/ex/ea/commodo/consequat/duis/aute/irure/dolor/in/reprehenderit/in/voluptate/velit/esse/cillum/dolore/eu/fugiat/nulla/pariatur/excepteur/sint/occaecat/cupidatat/non/proident/sunt/in/culpa/qui/officia/deserunt/mollit/anim/id/est/laborum'
|
||||
self.service.save()
|
||||
|
||||
def test_service_matching(self):
|
||||
|
|
|
@ -434,101 +434,90 @@ class Scenario:
|
|||
)
|
||||
constraints += (
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/" "@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/" "@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/@FriendlyName",
|
||||
'First name',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='first-name']/saml:AttributeValue",
|
||||
'John',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/" "@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/" "@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/@FriendlyName",
|
||||
'Last name',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='last-name']/saml:AttributeValue",
|
||||
'Doe',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/" "@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/" "@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/@FriendlyName",
|
||||
'code',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='code_code']/saml:AttributeValue",
|
||||
'1234',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/" "@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/" "@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/@FriendlyName",
|
||||
'mobile',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='mobile']/saml:AttributeValue",
|
||||
'5678',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/" "@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/" "@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/@FriendlyName",
|
||||
'Avatar',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='avatar']/saml:AttributeValue",
|
||||
re.compile('^http://testserver/media/profile-image/.*$'),
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
|
||||
"@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
|
||||
"@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/@FriendlyName",
|
||||
'Verified attributes',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='verified_attributes']/saml:AttributeValue",
|
||||
{'code_code', 'mobile'},
|
||||
),
|
||||
)
|
||||
if user is not None and self.sp.admin_role in user.roles.all():
|
||||
constraints += (
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
|
||||
"@NameFormat",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/@NameFormat",
|
||||
lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC,
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
|
||||
"@FriendlyName",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/@FriendlyName",
|
||||
'Superuser status',
|
||||
),
|
||||
(
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/"
|
||||
"saml:AttributeValue",
|
||||
"/saml:Assertion/saml:AttributeStatement/saml:Attribute[@Name='superuser']/saml:AttributeValue",
|
||||
'true',
|
||||
),
|
||||
)
|
||||
|
@ -772,8 +761,9 @@ def test_make_edu_person_targeted_id(db, settings, rf):
|
|||
settings.A2_IDP_SAML2_EDU_PERSON_TARGETED_ID_SALT = 'b'
|
||||
settings.A2_IDP_SAML2_EDU_PERSON_TARGETED_ID_ATTRIBUTE = 'username'
|
||||
|
||||
assert saml2_endpoints.make_edu_person_targeted_id_value(provider, user) == (
|
||||
'_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5'
|
||||
assert (
|
||||
saml2_endpoints.make_edu_person_targeted_id_value(provider, user)
|
||||
== '_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5'
|
||||
)
|
||||
|
||||
assert (
|
||||
|
@ -785,7 +775,7 @@ def test_make_edu_person_targeted_id(db, settings, rf):
|
|||
assert edpt is not None
|
||||
node = lasso.Node.newFromXmlNode(force_str(ET.tostring(edpt)))
|
||||
assert isinstance(node, lasso.Saml2NameID)
|
||||
assert force_text(node.content) == ('_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5')
|
||||
assert force_text(node.content) == '_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5'
|
||||
assert node.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
|
||||
|
||||
assert node.nameQualifier == 'http://testserver/idp/saml2/metadata'
|
||||
|
@ -818,7 +808,7 @@ def test_add_attributes_edu_person_targeted_id_nid_format(db, settings, rf, add_
|
|||
assert len(attributes[edu_name]) == 1
|
||||
node = lasso.Node.newFromXmlNode(force_str(list(attributes[edu_name])[0]))
|
||||
assert isinstance(node, lasso.Saml2NameID)
|
||||
assert force_text(node.content) == ('_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5')
|
||||
assert force_text(node.content) == '_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5'
|
||||
assert node.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
|
||||
assert node.nameQualifier == 'http://testserver/idp/saml2/metadata'
|
||||
assert node.spNameQualifier == 'https://sp.com/'
|
||||
|
@ -850,7 +840,7 @@ def test_add_attributes_edu_person_targeted_id_attribute(db, settings, rf, add_a
|
|||
assert len(attributes['edupersontargetedid']) == 1
|
||||
node = lasso.Node.newFromXmlNode(force_str(list(attributes['edupersontargetedid'])[0]))
|
||||
assert isinstance(node, lasso.Saml2NameID)
|
||||
assert force_text(node.content) == ('_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5')
|
||||
assert force_text(node.content) == '_A485C0ACEEF43A6D39145F5CFE25D9D3B6F15DC6443F412263C76D81C72DA8D5'
|
||||
assert node.format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
|
||||
assert node.nameQualifier == 'http://testserver/idp/saml2/metadata'
|
||||
assert node.spNameQualifier == 'https://sp.com/'
|
||||
|
|
|
@ -474,7 +474,7 @@ def test_global_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'deletion of authorization of single sign on with "service" of ' 'user "Johnny doe"',
|
||||
'message': 'deletion of authorization of single sign on with "service" of user "Johnny doe"',
|
||||
'timestamp': 'Jan. 2, 2020, 3 a.m.',
|
||||
'type': 'manager.user.sso.authorization.deletion',
|
||||
'user': 'agent',
|
||||
|
@ -510,13 +510,13 @@ def test_global_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'inheritance addition from parent role "role2" to child role ' '"role1"',
|
||||
'message': 'inheritance addition from parent role "role2" to child role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 9 a.m.',
|
||||
'type': 'manager.role.inheritance.addition',
|
||||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'inheritance removal from parent role "role2" to child role ' '"role1"',
|
||||
'message': 'inheritance removal from parent role "role2" to child role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 10 a.m.',
|
||||
'type': 'manager.role.inheritance.removal',
|
||||
'user': 'agent',
|
||||
|
@ -534,7 +534,7 @@ def test_global_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'addition of user "user (111111)" as administrator of role ' '"role1"',
|
||||
'message': 'addition of user "user (111111)" as administrator of role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 1 p.m.',
|
||||
'type': 'manager.role.administrator.user.addition',
|
||||
'user': 'agent',
|
||||
|
@ -561,16 +561,24 @@ def test_global_journal(app, superuser, events):
|
|||
'timestamp': 'Jan. 2, 2020, 5 p.m.',
|
||||
'type': 'manager.user.deactivation',
|
||||
'user': '-',
|
||||
'message': 'automatic deactivation of user "Johnny doe" because the associated LDAP account does not exist anymore',
|
||||
'message': (
|
||||
'automatic deactivation of user "Johnny doe" because the associated LDAP account does not'
|
||||
' exist anymore'
|
||||
),
|
||||
},
|
||||
{
|
||||
'timestamp': 'Jan. 2, 2020, 6 p.m.',
|
||||
'type': 'manager.user.deactivation',
|
||||
'user': '-',
|
||||
'message': 'automatic deactivation of user "Johnny doe" because the associated LDAP source has been deleted',
|
||||
'message': (
|
||||
'automatic deactivation of user "Johnny doe" because the associated LDAP source has been'
|
||||
' deleted'
|
||||
),
|
||||
},
|
||||
{
|
||||
'message': 'automatic activation of user "Johnny doe" because the associated LDAP account reappeared',
|
||||
'message': (
|
||||
'automatic activation of user "Johnny doe" because the associated LDAP account reappeared'
|
||||
),
|
||||
'timestamp': 'Jan. 2, 2020, 7 p.m.',
|
||||
'type': 'manager.user.activation',
|
||||
'user': '-',
|
||||
|
@ -683,7 +691,7 @@ def test_user_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': "password reset request by administrator sent to " '"user@example.com"',
|
||||
'message': "password reset request by administrator sent to \"user@example.com\"",
|
||||
'timestamp': 'Jan. 1, 2020, 9 p.m.',
|
||||
'type': 'manager.user.password.reset.request',
|
||||
'user': 'agent',
|
||||
|
@ -719,7 +727,7 @@ def test_user_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'deletion of authorization of single sign on with "service" by ' "administrator",
|
||||
'message': 'deletion of authorization of single sign on with "service" by administrator',
|
||||
'timestamp': 'Jan. 2, 2020, 3 a.m.',
|
||||
'type': 'manager.user.sso.authorization.deletion',
|
||||
'user': 'agent',
|
||||
|
@ -928,13 +936,13 @@ def test_roles_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'inheritance addition from parent role "role2" to child role ' '"role1"',
|
||||
'message': 'inheritance addition from parent role "role2" to child role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 9 a.m.',
|
||||
'type': 'manager.role.inheritance.addition',
|
||||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'inheritance removal from parent role "role2" to child role ' '"role1"',
|
||||
'message': 'inheritance removal from parent role "role2" to child role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 10 a.m.',
|
||||
'type': 'manager.role.inheritance.removal',
|
||||
'user': 'agent',
|
||||
|
@ -952,7 +960,7 @@ def test_roles_journal(app, superuser, events):
|
|||
'user': 'agent',
|
||||
},
|
||||
{
|
||||
'message': 'addition of user "user (111111)" as administrator of role ' '"role1"',
|
||||
'message': 'addition of user "user (111111)" as administrator of role "role1"',
|
||||
'timestamp': 'Jan. 2, 2020, 1 p.m.',
|
||||
'type': 'manager.role.administrator.user.addition',
|
||||
'user': 'agent',
|
||||
|
@ -1047,7 +1055,8 @@ def test_search(app, superuser, events):
|
|||
assert table_content == [
|
||||
'automatic activation of user "Johnny doe" because the associated LDAP account reappeared',
|
||||
'automatic deactivation of user "Johnny doe" because the associated LDAP source has been deleted',
|
||||
'automatic deactivation of user "Johnny doe" because the associated LDAP account does not exist anymore',
|
||||
'automatic deactivation of user "Johnny doe" because the associated LDAP account does not exist'
|
||||
' anymore',
|
||||
'deactivation of user "Johnny doe"',
|
||||
'activation of user "Johnny doe"',
|
||||
'mandatory password change at next login unset for user "Johnny doe"',
|
||||
|
|
Loading…
Reference in New Issue