Projet

Général

Profil

0001-misc-apply-black-52457.patch

Valentin Deniaud, 30 mars 2021 11:39

Télécharger (1,78 Mo)

Voir les différences:

Subject: [PATCH 1/6] misc: apply black (#52457)

 debian/debian_config.py                       |   91 +-
 debian/multitenant/config.py                  |   46 +-
 debian/multitenant/debian_config.py           |   14 +-
 doc/conf.py                                   |  118 +-
 merge-coverage.py                             |  399 +-
 setup.py                                      |  156 +-
 src/authentic2/a2_rbac/admin.py               |   37 +-
 src/authentic2/a2_rbac/app_settings.py        |    1 +
 src/authentic2/a2_rbac/apps.py                |   28 +-
 src/authentic2/a2_rbac/fields.py              |   10 +-
 src/authentic2/a2_rbac/management.py          |   48 +-
 src/authentic2/a2_rbac/managers.py            |   65 +-
 .../a2_rbac/migrations/0001_initial.py        |  150 +-
 ...3_partial_unique_index_on_name_and_slug.py |   12 +-
 .../migrations/0004_auto_20150523_0028.py     |    6 +-
 .../migrations/0005_auto_20150526_1406.py     |    9 +-
 .../migrations/0008_auto_20150810_1953.py     |    6 +-
 ...0009_partial_unique_index_on_permission.py |   10 +-
 .../migrations/0010_auto_20160209_1417.py     |   27 +-
 .../migrations/0014_auto_20170711_1024.py     |    6 +-
 .../migrations/0016_auto_20171208_1429.py     |    6 +-
 ...nizationalunit_user_add_password_policy.py |    6 +-
 .../0020_partial_unique_index_on_name.py      |   11 +-
 .../migrations/0021_auto_20200317_1514.py     |    8 +-
 .../migrations/0022_auto_20200402_1101.py     |   22 +-
 .../migrations/0024_fix_self_admin_perm.py    |    4 +-
 src/authentic2/a2_rbac/models.py              |  239 +-
 src/authentic2/a2_rbac/signal_handlers.py     |   22 +-
 src/authentic2/a2_rbac/utils.py               |   14 +-
 src/authentic2/admin.py                       |   91 +-
 src/authentic2/api_mixins.py                  |    5 +-
 src/authentic2/api_urls.py                    |   14 +-
 src/authentic2/api_views.py                   |  435 +-
 src/authentic2/app.py                         |    4 +-
 src/authentic2/app_settings.py                |  330 +-
 .../apps/journal/migrations/0001_initial.py   |    2 +-
 src/authentic2/apps/journal/models.py         |   12 +-
 src/authentic2/apps/journal/search_engine.py  |    8 +-
 .../migrations/0001_initial.py                |  411 +-
 .../migrations/0002_auto_20150409_1840.py     |   15 +-
 .../migrations/0003_auto_20150526_2239.py     |    8 +-
 src/authentic2/attribute_kinds.py             |   19 +-
 src/authentic2/attributes_ng/engine.py        |   36 +-
 .../attributes_ng/sources/__init__.py         |    5 +-
 .../attributes_ng/sources/django_user.py      |    4 +-
 .../attributes_ng/sources/format.py           |   11 +-
 .../attributes_ng/sources/function.py         |   10 +-
 src/authentic2/attributes_ng/sources/ldap.py  |    4 +-
 .../attributes_ng/sources/service_roles.py    |   12 +-
 .../auth2_ssl/migrations/0001_initial.py      |   10 +-
 .../auth_migrations_18/0001_initial.py        |   89 +-
 .../0002_auto_20150323_1720.py                |   12 +-
 .../0003_auto_20150410_1657.py                |    2 +-
 .../auth_migrations_18/0004_user.py           |   81 +-
 .../0005_auto_20150526_2303.py                |   24 +-
 src/authentic2/authentication.py              |   25 +-
 src/authentic2/authenticators.py              |   22 +-
 src/authentic2/backends/ldap_backend.py       |  475 +-
 src/authentic2/backends/models_backend.py     |    9 +-
 src/authentic2/cbv.py                         |   40 +-
 src/authentic2/compat/cookies.py              |    1 +
 src/authentic2/compat/misc.py                 |    6 +
 src/authentic2/compat_lasso.py                |    2 +
 src/authentic2/context_processors.py          |    4 +-
 src/authentic2/cors.py                        |    2 -
 src/authentic2/crypto.py                      |   27 +-
 src/authentic2/csv_import.py                  |  140 +-
 src/authentic2/custom_user/apps.py            |   44 +-
 .../management/commands/changepassword.py     |    8 +-
 .../management/commands/fix-attributes.py     |    7 +-
 src/authentic2/custom_user/managers.py        |   61 +-
 .../custom_user/migrations/0001_initial.py    |  137 +-
 .../migrations/0002_auto_20150410_1823.py     |   32 +-
 .../migrations/0003_auto_20150504_1410.py     |    5 +-
 .../custom_user/migrations/0004_user_ou.py    |    4 +-
 .../migrations/0005_auto_20150522_1527.py     |    8 +-
 .../migrations/0006_auto_20150527_1212.py     |    6 +-
 .../migrations/0007_auto_20150610_1527.py     |    6 +-
 .../migrations/0009_auto_20150810_1953.py     |    6 +-
 .../migrations/0012_user_modified.py          |    7 +-
 .../migrations/0015_auto_20170707_1653.py     |    6 +-
 .../migrations/0017_auto_20200305_1645.py     |    7 +-
 .../migrations/0020_deleteduser.py            |   22 +-
 .../migrations/0022_index_email.py            |    2 +-
 .../migrations/0023_index_username.py         |    2 +-
 .../0024_index_email_by_trigrams.py           |    6 +-
 .../migrations/0026_remove_user_deleted.py    |    3 +-
 src/authentic2/custom_user/models.py          |  163 +-
 src/authentic2/data_transfer.py               |   82 +-
 src/authentic2/decorators.py                  |   55 +-
 .../disco_service/disco_responder.py          |   16 +-
 src/authentic2/exponential_retry_timeout.py   |   26 +-
 src/authentic2/forms/authentication.py        |   35 +-
 src/authentic2/forms/fields.py                |   20 +-
 src/authentic2/forms/mixins.py                |    3 +-
 src/authentic2/forms/passwords.py             |   22 +-
 src/authentic2/forms/profile.py               |   14 +-
 src/authentic2/forms/registration.py          |   10 +-
 src/authentic2/forms/widgets.py               |   52 +-
 src/authentic2/hashers.py                     |   72 +-
 src/authentic2/hooks.py                       |    8 +-
 src/authentic2/idp/interactions.py            |   12 +-
 src/authentic2/idp/migrations/0001_initial.py |  113 +-
 .../idp/migrations/0002_auto_20150526_2239.py |   24 +-
 src/authentic2/idp/saml/__init__.py           |   23 +-
 src/authentic2/idp/saml/app_settings.py       |   29 +-
 src/authentic2/idp/saml/backend.py            |   87 +-
 src/authentic2/idp/saml/saml2_endpoints.py    |  431 +-
 src/authentic2/idp/saml/urls.py               |   31 +-
 src/authentic2/idp/saml/views.py              |    3 +-
 src/authentic2/idp/urls.py                    |    3 +-
 src/authentic2/journal_event_types.py         |    6 +-
 src/authentic2/log_filters.py                 |    7 +-
 src/authentic2/logger.py                      |    6 +-
 .../management/commands/check-and-repair.py   |  168 +-
 .../commands/clean-unused-accounts.py         |   13 +-
 .../deactivate-orphaned-ldap-users.py         |    1 -
 .../management/commands/export_site.py        |    5 +-
 .../management/commands/import_site.py        |   22 +-
 .../management/commands/load-ldif.py          |   43 +-
 .../management/commands/resetpassword.py      |   16 +-
 .../management/commands/sync-ldap-users.py    |    1 +
 src/authentic2/manager/app_settings.py        |    2 +
 src/authentic2/manager/apps.py                |    5 +-
 src/authentic2/manager/fields.py              |   20 +-
 src/authentic2/manager/forms.py               |  197 +-
 src/authentic2/manager/journal_event_types.py |   12 +-
 src/authentic2/manager/ou_views.py            |   38 +-
 src/authentic2/manager/resources.py           |   20 +-
 src/authentic2/manager/role_views.py          |  266 +-
 src/authentic2/manager/service_views.py       |   14 +-
 src/authentic2/manager/tables.py              |   68 +-
 src/authentic2/manager/urls.py                |  249 +-
 src/authentic2/manager/user_import.py         |   15 +-
 src/authentic2/manager/user_views.py          |  285 +-
 src/authentic2/manager/views.py               |  181 +-
 src/authentic2/manager/widgets.py             |    9 +-
 src/authentic2/managers.py                    |    4 +
 src/authentic2/middleware.py                  |   31 +-
 src/authentic2/migrations/0001_initial.py     |  107 +-
 .../migrations/0003_auto_20150409_1840.py     |   12 +-
 src/authentic2/migrations/0004_service.py     |    5 +-
 src/authentic2/migrations/0005_service_ou.py  |    4 +-
 .../migrations/0006_conditional_slug_index.py |    7 +-
 .../migrations/0007_auto_20150523_0028.py     |    8 +-
 .../migrations/0008_auto_20160204_1415.py     |    4 +-
 .../migrations/0009_auto_20160211_2247.py     |    1 +
 .../migrations/0012_auto_20160211_2255.py     |    4 +-
 .../migrations/0013_auto_20160211_2258.py     |   12 +-
 .../migrations/0015_auto_20160621_1711.py     |    4 +-
 .../migrations/0018_auto_20170524_0842.py     |   17 +-
 .../migrations/0021_attribute_order.py        |    7 +-
 .../migrations/0022_attribute_scopes.py       |    8 +-
 .../migrations/0024_auto_20190617_1113.py     |    3 +-
 .../migrations/0025_auto_20191009_1047.py     |    7 +-
 src/authentic2/migrations/0026_token.py       |   16 +-
 .../migrations/0027_remove_deleteduser.py     |    5 +-
 .../migrations/0028_trigram_unaccent_index.py |    2 +-
 .../migrations/0029_auto_20201013_1614.py     |    6 +-
 .../0030_clean_admin_tools_tables.py          |   10 +-
 .../0031_add_search_vector_to_attributes.py   |   23 +-
 src/authentic2/migrations/__init__.py         |   26 +-
 src/authentic2/models.py                      |  196 +-
 .../nonce/migrations/0001_initial.py          |   11 +-
 src/authentic2/nonce/utils.py                 |   89 +-
 src/authentic2/passwords.py                   |   37 +-
 src/authentic2/plugins.py                     |   33 +-
 src/authentic2/saml/__init__.py               |    1 +
 src/authentic2/saml/admin.py                  |   42 +-
 src/authentic2/saml/admin_views.py            |   12 +-
 src/authentic2/saml/app_settings.py           |    4 +-
 src/authentic2/saml/common.py                 |  220 +-
 src/authentic2/saml/fields.py                 |   25 +-
 src/authentic2/saml/forms.py                  |   32 +-
 src/authentic2/saml/lasso_helper.py           |   13 +-
 .../saml/management/commands/mapping.py       | 4235 ++++++++---------
 .../saml/management/commands/sync-metadata.py |  157 +-
 src/authentic2/saml/managers.py               |   23 +-
 .../saml/migrations/0001_initial.py           |  562 ++-
 .../migrations/0002_auto_20150320_1245.py     |    8 +-
 .../0002_ease_federation_migration.py         |   15 +-
 src/authentic2/saml/migrations/0003_merge.py  |    3 +-
 .../migrations/0004_auto_20150410_1438.py     |    7 +-
 ...e_liberty_provider_inherit_from_service.py |    5 +-
 .../migrations/0006_restore_foreign_keys.py   |    3 +
 .../0007_copy_service_ptr_id_to_old_id.py     |   49 +-
 .../migrations/0008_alter_foreign_keys.py     |   16 +-
 src/authentic2/saml/migrations/0009_auto.py   |   18 +-
 src/authentic2/saml/migrations/0010_auto.py   |    4 +-
 src/authentic2/saml/migrations/0011_auto.py   |   28 +-
 .../migrations/0012_auto_20150526_2239.py     |   12 +-
 .../migrations/0013_auto_20150617_1004.py     |   10 +-
 .../migrations/0014_auto_20150617_1216.py     |    1 +
 .../migrations/0015_auto_20150915_2032.py     |   46 +-
 src/authentic2/saml/models.py                 |  303 +-
 src/authentic2/saml/saml2utils.py             |  117 +-
 src/authentic2/saml/shibboleth/utils.py       |    1 -
 src/authentic2/saml/utils.py                  |    2 -
 src/authentic2/saml/x509utils.py              |   34 +-
 src/authentic2/serializers.py                 |    1 +
 src/authentic2/settings.py                    |   31 +-
 src/authentic2/urls.py                        |  134 +-
 src/authentic2/user_login_failure.py          |    7 +-
 src/authentic2/utils/__init__.py              |  465 +-
 src/authentic2/utils/evaluate.py              |   70 +-
 src/authentic2/utils/lazy.py                  |    6 +-
 src/authentic2/utils/lookups.py               |    2 +
 src/authentic2/utils/service.py               |    3 +-
 src/authentic2/utils/switch_user.py           |    2 -
 src/authentic2/utils/views.py                 |   19 +-
 src/authentic2/validators.py                  |    8 +-
 src/authentic2/views.py                       |  487 +-
 src/authentic2_auth_fc/api_views.py           |    8 +-
 src/authentic2_auth_fc/app_settings.py        |   33 +-
 src/authentic2_auth_fc/apps.py                |   15 +-
 src/authentic2_auth_fc/authenticators.py      |   73 +-
 src/authentic2_auth_fc/backends.py            |   14 +-
 .../migrations/0001_initial.py                |   15 +-
 .../migrations/0002_auto_20200416_1439.py     |    4 +-
 .../migrations/0003_fcaccount_order1.py       |    6 +-
 src/authentic2_auth_fc/models.py              |   23 +-
 src/authentic2_auth_fc/utils.py               |   13 +-
 src/authentic2_auth_fc/views.py               |  166 +-
 src/authentic2_auth_oidc/admin.py             |   33 +-
 src/authentic2_auth_oidc/app_settings.py      |    2 +
 src/authentic2_auth_oidc/apps.py              |   36 +-
 src/authentic2_auth_oidc/authenticators.py    |   11 +-
 src/authentic2_auth_oidc/backends.py          |  162 +-
 .../commands/oidc-register-issuer.py          |   45 +-
 src/authentic2_auth_oidc/managers.py          |    2 +
 .../migrations/0001_initial.py                |  114 +-
 ..._oidcprovider_token_revocation_endpoint.py |    4 +-
 .../migrations/0004_auto_20171017_1522.py     |   10 +-
 src/authentic2_auth_oidc/models.py            |  166 +-
 src/authentic2_auth_oidc/utils.py             |  117 +-
 src/authentic2_auth_oidc/views.py             |  146 +-
 src/authentic2_auth_saml/adapters.py          |   28 +-
 src/authentic2_auth_saml/app_settings.py      |    2 +
 src/authentic2_auth_saml/apps.py              |   14 +-
 src/authentic2_auth_saml/authenticators.py    |   23 +-
 src/authentic2_auth_saml/backends.py          |    5 +-
 src/authentic2_auth_saml/urls.py              |    5 +-
 src/authentic2_idp_cas/admin.py               |   59 +-
 src/authentic2_idp_cas/app_settings.py        |    1 +
 src/authentic2_idp_cas/apps.py                |    1 -
 src/authentic2_idp_cas/constants.py           |   71 +-
 .../migrations/0001_initial.py                |  104 +-
 .../migrations/0002_auto_20150410_1438.py     |    9 +-
 .../migrations/0003_auto_20150415_2223.py     |    9 +-
 .../migrations/0004_create_services.py        |    6 +-
 .../0005_alter_field_service_ptr.py           |    1 +
 .../migrations/0006_copy_proxy_m2m.py         |    6 +-
 .../migrations/0007_alter_service.py          |   22 +-
 .../migrations/0008_alter_foreign_keys.py     |    3 +
 .../migrations/0009_alter_related_models.py   |    9 +-
 .../0010_copy_service_ptr_id_to_old_id.py     |    3 +
 .../0011_remove_old_id_restore_proxy.py       |    8 +-
 .../0012_copy_service_proxy_to_m2m.py         |    6 +-
 .../0013_delete_model_service_proxy2.py       |    2 +-
 .../migrations/0015_auto_20170406_1825.py     |    8 +-
 src/authentic2_idp_cas/models.py              |   33 +-
 src/authentic2_idp_cas/urls.py                |    6 +-
 src/authentic2_idp_cas/utils.py               |    2 +-
 src/authentic2_idp_cas/views.py               |  149 +-
 src/authentic2_idp_oidc/admin.py              |   37 +-
 src/authentic2_idp_oidc/app_settings.py       |   18 +-
 src/authentic2_idp_oidc/apps.py               |  237 +-
 .../migrations/0001_initial.py                |  129 +-
 .../migrations/0002_auto_20170121_2346.py     |    6 +-
 .../migrations/0003_auto_20170329_1259.py     |    7 +-
 .../migrations/0005_authorization_mode.py     |   14 +-
 .../migrations/0006_auto_20170720_1054.py     |   12 +-
 .../0008_oidcclient_idtoken_duration.py       |    6 +-
 .../migrations/0010_oidcclaim.py              |   28 +-
 .../migrations/0011_auto_20180808_1546.py     |   14 +-
 .../migrations/0012_auto_20200122_2258.py     |   14 +-
 .../migrations/0013_auto_20200630_1007.py     |    4 +-
 src/authentic2_idp_oidc/models.py             |  257 +-
 src/authentic2_idp_oidc/urls.py               |   24 +-
 src/authentic2_idp_oidc/utils.py              |   45 +-
 src/authentic2_idp_oidc/views.py              |  362 +-
 src/django_rbac/apps.py                       |   19 +-
 src/django_rbac/backends.py                   |   55 +-
 src/django_rbac/context_processors.py         |    1 +
 src/django_rbac/managers.py                   |   52 +-
 src/django_rbac/migrations/0001_initial.py    |   11 +-
 ...ionalunit_permission_role_roleparenting.py |  102 +-
 .../0003_add_max_aggregate_for_postgres.py    |    3 +-
 src/django_rbac/models.py                     |  167 +-
 src/django_rbac/signal_handlers.py            |    6 +-
 src/django_rbac/utils.py                      |   15 +-
 tests/auth_fc/conftest.py                     |   34 +-
 tests/auth_fc/test_auth_fc.py                 |    7 +-
 tests/cache_urls.py                           |    8 +-
 tests/conftest.py                             |  171 +-
 tests/test_a2_rbac.py                         |  134 +-
 tests/test_admin.py                           |   47 +-
 tests/test_all.py                             |  249 +-
 tests/test_api.py                             |  541 ++-
 tests/test_attribute_kinds.py                 |   55 +-
 tests/test_auth_oidc.py                       |  223 +-
 tests/test_auth_saml.py                       |   39 +-
 tests/test_commands.py                        |   94 +-
 tests/test_concurrency.py                     |   15 +-
 tests/test_crypto.py                          |    8 +-
 tests/test_csv_import.py                      |   41 +-
 tests/test_custom_user.py                     |   14 +-
 tests/test_customfields.py                    |   25 +-
 tests/test_data_transfer.py                   |  302 +-
 tests/test_fields.py                          |    6 +-
 tests/test_hashers.py                         |   14 +-
 tests/test_idp_cas.py                         |  310 +-
 tests/test_idp_oidc.py                        |  976 ++--
 tests/test_idp_saml2.py                       |  455 +-
 tests/test_import_export_site_cmd.py          |   46 +-
 tests/test_journal.py                         |   27 +-
 tests/test_large_userbase.py                  |   17 +-
 tests/test_ldap.py                            | 1083 +++--
 tests/test_login.py                           |   10 +-
 tests/test_manager.py                         |  134 +-
 tests/test_manager_journal.py                 |   66 +-
 tests/test_natural_key.py                     |   13 +-
 tests/test_ou_manager.py                      |   12 +-
 tests/test_password_reset.py                  |    1 +
 tests/test_passwords.py                       |    5 +-
 tests/test_profile.py                         |   91 +-
 tests/test_registration.py                    |   99 +-
 tests/test_role_manager.py                    |   65 +-
 tests/test_saml_x509utils.py                  |    5 +-
 tests/test_template.py                        |   12 +-
 tests/test_user_manager.py                    |  240 +-
 tests/test_user_model.py                      |    8 +-
 tests/test_utils.py                           |   15 +-
 tests/test_utils_evaluate.py                  |   14 +-
 tests/test_validators.py                      |   22 +-
 tests/test_views.py                           |   15 +-
 tests/utils.py                                |   57 +-
 tests_rbac/conftest.py                        |    4 +-
 tests_rbac/test_rbac.py                       |  124 +-
 339 files changed, 13976 insertions(+), 10737 deletions(-)
debian/debian_config.py
27 27
    'disable_existing_loggers': True,
28 28
    'filters': {
29 29
        'cleaning': {
30
            '()':  'authentic2.utils.CleanLogMessage',
30
            '()': 'authentic2.utils.CleanLogMessage',
31 31
        },
32 32
        'request_context': {
33
            '()':  'authentic2.log_filters.RequestContextFilter',
33
            '()': 'authentic2.log_filters.RequestContextFilter',
34 34
        },
35 35
        'force_debug': {
36 36
            '()': 'authentic2.log_filters.ForceDebugFilter',
37
        }
37
        },
38 38
    },
39 39
    'formatters': {
40 40
        'syslog': {
......
124 124
def extract_settings_from_environ():
125 125
    import json
126 126
    from django.core.exceptions import ImproperlyConfigured
127
    global MANAGERS, DATABASES, SENTRY_TRANSPORT, SENTRY_DSN, INSTALLED_APPS, \
128
            SECURE_PROXY_SSL_HEADER, CACHES, SESSION_ENGINE, LDAP_AUTH_SETTINGS
127

  
128
    global MANAGERS, DATABASES, SENTRY_TRANSPORT, SENTRY_DSN, INSTALLED_APPS, SECURE_PROXY_SSL_HEADER, CACHES, SESSION_ENGINE, LDAP_AUTH_SETTINGS
129 129

  
130 130
    BOOLEAN_ENVS = (
131
           'DEBUG',
132
           'DEBUG_PROPAGATE_EXCEPTIONS',
133
           'SESSION_EXPIRE_AT_BROWSER_CLOSE',
134
           'SESSION_COOKIE_SECURE',
135
           'EMAIL_USE_TLS',
136
           'USE_X_FORWARDED_HOST',
137
           'DISCO_SERVICE',
138
           'DISCO_USE_OF_METADATA',
139
           'SHOW_DISCO_IN_MD',
140
           'SSLAUTH_CREATE_USER',
141
           'PUSH_PROFILE_UPDATES',
142
           'A2_ACCEPT_EMAIL_AUTHENTICATION',
143
           'A2_CAN_RESET_PASSWORD',
144
           'A2_REGISTRATION_CAN_DELETE_ACCOUNT',
145
           'A2_REGISTRATION_EMAIL_IS_UNIQUE',
146
           'REGISTRATION_OPEN',
147
           'A2_AUTH_PASSWORD_ENABLE',
148
           'SSLAUTH_ENABLE',
149
           'A2_IDP_SAML2_ENABLE',
131
        'DEBUG',
132
        'DEBUG_PROPAGATE_EXCEPTIONS',
133
        'SESSION_EXPIRE_AT_BROWSER_CLOSE',
134
        'SESSION_COOKIE_SECURE',
135
        'EMAIL_USE_TLS',
136
        'USE_X_FORWARDED_HOST',
137
        'DISCO_SERVICE',
138
        'DISCO_USE_OF_METADATA',
139
        'SHOW_DISCO_IN_MD',
140
        'SSLAUTH_CREATE_USER',
141
        'PUSH_PROFILE_UPDATES',
142
        'A2_ACCEPT_EMAIL_AUTHENTICATION',
143
        'A2_CAN_RESET_PASSWORD',
144
        'A2_REGISTRATION_CAN_DELETE_ACCOUNT',
145
        'A2_REGISTRATION_EMAIL_IS_UNIQUE',
146
        'REGISTRATION_OPEN',
147
        'A2_AUTH_PASSWORD_ENABLE',
148
        'SSLAUTH_ENABLE',
149
        'A2_IDP_SAML2_ENABLE',
150 150
    )
151 151

  
152 152
    def to_boolean(name, default=True):
......
215 215
                globals()[path_env] = tuple(os.environ[path_env].split(':')) + tuple(old)
216 216

  
217 217
    INT_ENVS = (
218
            'SESSION_COOKIE_AGE',
219
            'EMAIL_PORT',
220
            'AUTHENTICATION_EVENT_EXPIRATION',
221
            'LOCAL_METADATA_CACHE_TIMEOUT',
222
            'ACCOUNT_ACTIVATION_DAYS',
223
            'PASSWORD_RESET_TIMEOUT_DAYS',
218
        'SESSION_COOKIE_AGE',
219
        'EMAIL_PORT',
220
        'AUTHENTICATION_EVENT_EXPIRATION',
221
        'LOCAL_METADATA_CACHE_TIMEOUT',
222
        'ACCOUNT_ACTIVATION_DAYS',
223
        'PASSWORD_RESET_TIMEOUT_DAYS',
224 224
    )
225 225

  
226 226
    def to_int(name, default):
......
239 239
            except ValueError:
240 240
                raise ImproperlyConfigured('environement variable %s must be an integer' % int_env)
241 241

  
242

  
243 242
    ADMINS = ()
244 243
    if 'ADMINS' in os.environ:
245 244
        ADMINS = filter(None, os.environ.get('ADMINS').split(':'))
246
        ADMINS = [ admin.split(';') for admin in ADMINS ]
245
        ADMINS = [admin.split(';') for admin in ADMINS]
247 246
        for admin in ADMINS:
248
            assert len(admin) == 2, 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon'
247
            assert (
248
                len(admin) == 2
249
            ), 'ADMINS setting must be a colon separated list of name and emails separated by a semi-colon'
249 250
            assert '@' in admin[1], 'ADMINS setting pairs second value must be emails'
250 251
        MANAGERS = ADMINS
251 252

  
252

  
253 253
    for key in os.environ:
254 254
        if key.startswith('DATABASE_'):
255 255
            prefix, db_key = key.split('_', 1)
......
271 271
        try:
272 272
            import memcache
273 273
        except:
274
            raise ImproperlyConfigured('Python memcache library is not installed, please do: pip install memcache')
274
            raise ImproperlyConfigured(
275
                'Python memcache library is not installed, please do: pip install memcache'
276
            )
275 277
        CACHES = {
276
                'default': {
277
                    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
278
                    'LOCATION': '127.0.0.1:11211',
279
                    'KEY_PREFIX': 'authentic2',
280
                    }
281
                }
278
            'default': {
279
                'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
280
                'LOCATION': '127.0.0.1:11211',
281
                'KEY_PREFIX': 'authentic2',
282
            }
283
        }
282 284
        SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
283 285

  
284 286
    # extract any key starting with setting
285 287
    for key in os.environ:
286 288
        if key.startswith('SETTING_'):
287
            setting_key = key[len('SETTING_'):]
289
            setting_key = key[len('SETTING_') :]
288 290
            value = os.environ[key]
289 291
            try:
290
                 value = int(value)
292
                value = int(value)
291 293
            except ValueError:
292
                 pass
294
                pass
293 295
            globals()[setting_key] = value
294 296

  
297

  
295 298
extract_settings_from_environ()
296 299

  
297 300
CONFIG_FILE = '/etc/authentic2/config.py'
debian/multitenant/config.py
15 15
# SECURITY WARNING: don't run with debug turned on in production!
16 16
DEBUG = False
17 17

  
18
#ADMINS = (
18
# ADMINS = (
19 19
#        # ('User 1', 'watchdog@example.net'),
20 20
#        # ('User 2', 'janitor@example.net'),
21
#)
21
# )
22 22

  
23 23
# ALLOWED_HOSTS must be correct in production!
24 24
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
25 25
ALLOWED_HOSTS = [
26
        '*',
26
    '*',
27 27
]
28 28

  
29 29
# Databases
30 30
# Default: a local database named "authentic"
31 31
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
32 32
# Warning: don't change ENGINE
33
#DATABASES['default']['NAME'] = 'authentic2_multitenant'
34
#DATABASES['default']['USER'] = 'authentic-multitenant'
35
#DATABASES['default']['PASSWORD'] = '******'
36
#DATABASES['default']['HOST'] = 'localhost'
37
#DATABASES['default']['PORT'] = '5432'
33
# DATABASES['default']['NAME'] = 'authentic2_multitenant'
34
# DATABASES['default']['USER'] = 'authentic-multitenant'
35
# DATABASES['default']['PASSWORD'] = '******'
36
# DATABASES['default']['HOST'] = 'localhost'
37
# DATABASES['default']['PORT'] = '5432'
38 38

  
39 39
LANGUAGE_CODE = 'fr-fr'
40 40
TIME_ZONE = 'Europe/Paris'
41 41

  
42 42
# Sentry / Raven configuration
43
#RAVEN_CONFIG = {
43
# RAVEN_CONFIG = {
44 44
#    'dsn': '',
45
#}
45
# }
46 46

  
47 47
# Email configuration
48
#EMAIL_SUBJECT_PREFIX = '[authentic] '
49
#SERVER_EMAIL = 'root@authentic.example.org'
50
#DEFAULT_FROM_EMAIL = 'webmaster@authentic.example.org'
48
# EMAIL_SUBJECT_PREFIX = '[authentic] '
49
# SERVER_EMAIL = 'root@authentic.example.org'
50
# DEFAULT_FROM_EMAIL = 'webmaster@authentic.example.org'
51 51

  
52 52
# SMTP configuration
53
#EMAIL_HOST = 'localhost'
54
#EMAIL_HOST_USER = ''
55
#EMAIL_HOST_PASSWORD = ''
56
#EMAIL_PORT = 25
53
# EMAIL_HOST = 'localhost'
54
# EMAIL_HOST_USER = ''
55
# EMAIL_HOST_PASSWORD = ''
56
# EMAIL_PORT = 25
57 57

  
58 58
# HTTPS Security
59
#CSRF_COOKIE_SECURE = True
60
#SESSION_COOKIE_SECURE = True
59
# CSRF_COOKIE_SECURE = True
60
# SESSION_COOKIE_SECURE = True
61 61

  
62 62
# Idp
63 63
# SAML 2.0 IDP
64
#A2_IDP_SAML2_ENABLE = False
64
# A2_IDP_SAML2_ENABLE = False
65 65
# CAS 1.0 / 2.0 IDP
66
#A2_IDP_CAS_ENABLE = False
66
# A2_IDP_CAS_ENABLE = False
67 67

  
68 68
# Authentifications
69
#A2_AUTH_PASSWORD_ENABLE = True
70
#A2_SSLAUTH_ENABLE = False
69
# A2_AUTH_PASSWORD_ENABLE = True
70
# A2_SSLAUTH_ENABLE = False
debian/multitenant/debian_config.py
21 21
# Add authentic2 hobo agent
22 22
INSTALLED_APPS = ('hobo.agent.authentic2',) + INSTALLED_APPS
23 23

  
24
LOGGING['filters'].update({
25
    'cleaning': {
26
        '()':  'authentic2.utils.CleanLogMessage',
27
    },
28
})
24
LOGGING['filters'].update(
25
    {
26
        'cleaning': {
27
            '()': 'authentic2.utils.CleanLogMessage',
28
        },
29
    }
30
)
29 31
for handler in LOGGING['handlers'].values():
30 32
    handler.setdefault('filters', []).append('cleaning')
31 33

  
......
52 54

  
53 55
HOBO_SKELETONS_DIR = os.path.join(VAR_DIR, 'skeletons')
54 56

  
55
CONFIG_FILE='/etc/%s/config.py' % PROJECT_NAME
57
CONFIG_FILE = '/etc/%s/config.py' % PROJECT_NAME
56 58
if os.path.exists(CONFIG_FILE):
57 59
    with open(CONFIG_FILE) as fd:
58 60
        exec(fd.read())
doc/conf.py
16 16
# If extensions (or modules to document with autodoc) are in another directory,
17 17
# add these directories to sys.path here. If the directory is relative to the
18 18
# documentation root, use os.path.abspath to make it absolute, like shown here.
19
#sys.path.insert(0, os.path.abspath('.'))
19
# sys.path.insert(0, os.path.abspath('.'))
20 20

  
21 21
# -- General configuration -----------------------------------------------------
22 22

  
23 23
# If your documentation needs a minimal Sphinx version, state it here.
24
#needs_sphinx = '1.0'
24
# needs_sphinx = '1.0'
25 25

  
26 26
# Add any Sphinx extension module names here, as strings. They can be extensions
27 27
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.imgmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
28
extensions = [
29
    'sphinx.ext.autodoc',
30
    'sphinx.ext.doctest',
31
    'sphinx.ext.intersphinx',
32
    'sphinx.ext.todo',
33
    'sphinx.ext.coverage',
34
    'sphinx.ext.imgmath',
35
    'sphinx.ext.ifconfig',
36
    'sphinx.ext.viewcode',
37
]
29 38

  
30 39
# Add any paths that contain templates here, relative to this directory.
31 40
templates_path = ['_templates']
......
34 43
source_suffix = '.rst'
35 44

  
36 45
# The encoding of source files.
37
#source_encoding = 'utf-8-sig'
46
# source_encoding = 'utf-8-sig'
38 47

  
39 48
# The master toctree document.
40 49
master_doc = 'index'
......
54 63

  
55 64
# The language for content autogenerated by Sphinx. Refer to documentation
56 65
# for a list of supported languages.
57
#language = None
66
# language = None
58 67

  
59 68
# There are two options for replacing |today|: either, you set today to some
60 69
# non-false value, then it is used:
61
#today = ''
70
# today = ''
62 71
# Else, today_fmt is used as the format for a strftime call.
63
#today_fmt = '%B %d, %Y'
72
# today_fmt = '%B %d, %Y'
64 73

  
65 74
# List of patterns, relative to source directory, that match files and
66 75
# directories to ignore when looking for source files.
67 76
exclude_patterns = ['_build']
68 77

  
69 78
# The reST default role (used for this markup: `text`) to use for all documents.
70
#default_role = None
79
# default_role = None
71 80

  
72 81
# If true, '()' will be appended to :func: etc. cross-reference text.
73
#add_function_parentheses = True
82
# add_function_parentheses = True
74 83

  
75 84
# If true, the current module name will be prepended to all description
76 85
# unit titles (such as .. function::).
77
#add_module_names = True
86
# add_module_names = True
78 87

  
79 88
# If true, sectionauthor and moduleauthor directives will be shown in the
80 89
# output. They are ignored by default.
81
#show_authors = False
90
# show_authors = False
82 91

  
83 92
# The name of the Pygments (syntax highlighting) style to use.
84 93
pygments_style = 'sphinx'
85 94

  
86 95
# A list of ignored prefixes for module index sorting.
87
#modindex_common_prefix = []
96
# modindex_common_prefix = []
88 97

  
89 98

  
90 99
# -- Options for HTML output ---------------------------------------------------
......
96 105
# Theme options are theme-specific and customize the look and feel of a theme
97 106
# further.  For a list of options available for each theme, see the
98 107
# documentation.
99
#html_theme_options = {}
108
# html_theme_options = {}
100 109

  
101 110
# Add any paths that contain custom themes here, relative to this directory.
102
#html_theme_path = []
111
# html_theme_path = []
103 112

  
104 113
# The name for this set of Sphinx documents.  If None, it defaults to
105 114
# "<project> v<release> documentation".
106
#html_title = None
115
# html_title = None
107 116

  
108 117
# A shorter title for the navigation bar.  Default is the same as html_title.
109
#html_short_title = None
118
# html_short_title = None
110 119

  
111 120
# The name of an image file (relative to this directory) to place at the top
112 121
# of the sidebar.
......
115 124
# The name of an image file (within the static path) to use as favicon of the
116 125
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
117 126
# pixels large.
118
#html_favicon = None
127
# html_favicon = None
119 128

  
120 129
# Add any paths that contain custom static files (such as style sheets) here,
121 130
# relative to this directory. They are copied after the builtin static files,
......
124 133

  
125 134
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126 135
# using the given strftime format.
127
#html_last_updated_fmt = '%b %d, %Y'
136
# html_last_updated_fmt = '%b %d, %Y'
128 137

  
129 138
# If true, SmartyPants will be used to convert quotes and dashes to
130 139
# typographically correct entities.
131
#html_use_smartypants = True
140
# html_use_smartypants = True
132 141

  
133 142
# Custom sidebar templates, maps document names to template names.
134
#html_sidebars = {}
143
# html_sidebars = {}
135 144

  
136 145
# Additional templates that should be rendered to pages, maps page names to
137 146
# template names.
138
#html_additional_pages = {}
147
# html_additional_pages = {}
139 148

  
140 149
# If false, no module index is generated.
141
#html_domain_indices = True
150
# html_domain_indices = True
142 151

  
143 152
# If false, no index is generated.
144
#html_use_index = True
153
# html_use_index = True
145 154

  
146 155
# If true, the index is split into individual pages for each letter.
147
#html_split_index = False
156
# html_split_index = False
148 157

  
149 158
# If true, links to the reST sources are added to the pages.
150
#html_show_sourcelink = True
159
# html_show_sourcelink = True
151 160

  
152 161
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153
#html_show_sphinx = True
162
# html_show_sphinx = True
154 163

  
155 164
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156
#html_show_copyright = True
165
# html_show_copyright = True
157 166

  
158 167
# If true, an OpenSearch description file will be output, and all pages will
159 168
# contain a <link> tag referring to it.  The value of this option must be the
160 169
# base URL from which the finished HTML is served.
161
#html_use_opensearch = ''
170
# html_use_opensearch = ''
162 171

  
163 172
# This is the file name suffix for HTML files (e.g. ".xhtml").
164
#html_file_suffix = None
173
# html_file_suffix = None
165 174

  
166 175
# Output file base name for HTML help builder.
167 176
htmlhelp_basename = 'Authentic2doc'
......
170 179
# -- Options for LaTeX output --------------------------------------------------
171 180

  
172 181
latex_elements = {
173
# The paper size ('letterpaper' or 'a4paper').
174
#'papersize': 'letterpaper',
175

  
176
# The font size ('10pt', '11pt' or '12pt').
177
#'pointsize': '10pt',
178

  
179
# Additional stuff for the LaTeX preamble.
180
#'preamble': '',
182
    # The paper size ('letterpaper' or 'a4paper').
183
    #'papersize': 'letterpaper',
184
    # The font size ('10pt', '11pt' or '12pt').
185
    #'pointsize': '10pt',
186
    # Additional stuff for the LaTeX preamble.
187
    #'preamble': '',
181 188
}
182 189

  
183 190
# Grouping the document tree into LaTeX files. List of tuples
184 191
# (source start file, target name, title, author, documentclass [howto/manual]).
185 192
latex_documents = [
186
  ('index', 'Authentic2.tex', u'Authentic2 Documentation',
187
   u'Entr\'ouvert', 'manual'),
193
    ('index', 'Authentic2.tex', u'Authentic2 Documentation', u'Entr\'ouvert', 'manual'),
188 194
]
189 195

  
190 196
# The name of an image file (relative to this directory) to place at the top of
......
193 199

  
194 200
# For "manual" documents, if this is true, then toplevel headings are parts,
195 201
# not chapters.
196
#latex_use_parts = False
202
# latex_use_parts = False
197 203

  
198 204
# If true, show page references after internal links.
199
#latex_show_pagerefs = False
205
# latex_show_pagerefs = False
200 206

  
201 207
# If true, show URL addresses after external links.
202
#latex_show_urls = False
208
# latex_show_urls = False
203 209

  
204 210
# Documents to append as an appendix to all manuals.
205
#latex_appendices = []
211
# latex_appendices = []
206 212

  
207 213
# If false, no module index is generated.
208
#latex_domain_indices = True
214
# latex_domain_indices = True
209 215

  
210 216

  
211 217
# -- Options for manual page output --------------------------------------------
212 218

  
213 219
# One entry per manual page. List of tuples
214 220
# (source start file, name, description, authors, manual section).
215
man_pages = [
216
    ('index', 'authentic2', u'Authentic2 Documentation',
217
     [u'Mikaël Ates'], 1)
218
]
221
man_pages = [('index', 'authentic2', u'Authentic2 Documentation', [u'Mikaël Ates'], 1)]
219 222

  
220 223
# If true, show URL addresses after external links.
221
#man_show_urls = False
224
# man_show_urls = False
222 225

  
223 226

  
224 227
# -- Options for Texinfo output ------------------------------------------------
......
227 230
# (source start file, target name, title, author,
228 231
#  dir menu entry, description, category)
229 232
texinfo_documents = [
230
  ('index', 'Authentic2', u'Authentic2 Documentation', u'Mikaël Ates',
231
   'Authentic2', 'One line description of project.', 'Miscellaneous'),
233
    (
234
        'index',
235
        'Authentic2',
236
        u'Authentic2 Documentation',
237
        u'Mikaël Ates',
238
        'Authentic2',
239
        'One line description of project.',
240
        'Miscellaneous',
241
    ),
232 242
]
233 243

  
234 244
# Documents to append as an appendix to all manuals.
235
#texinfo_appendices = []
245
# texinfo_appendices = []
236 246

  
237 247
# If false, no module index is generated.
238
#texinfo_domain_indices = True
248
# texinfo_domain_indices = True
239 249

  
240 250
# How to display URL addresses: 'footnote', 'no', or 'inline'.
241
#texinfo_show_urls = 'footnote'
251
# texinfo_show_urls = 'footnote'
242 252

  
243 253

  
244 254
# Example configuration for intersphinx: refer to the Python standard library.
merge-coverage.py
9 9
from shutil import copyfile
10 10
from optparse import OptionParser
11 11

  
12
### This file came from the https://github.com/flow123d/flow123d repo they were nice enough to spend time to write this. 
12
### This file came from the https://github.com/flow123d/flow123d repo they were nice enough to spend time to write this.
13 13
### It is copied here for other people to use on its own.
14 14

  
15 15
# parse arguments
16
newline = 10*'\t';
17
parser = OptionParser(usage="%prog [options] [file1 file2 ... filen]", version="%prog 1.0",
18
    epilog = "If no files are specified all xml files in current directory will be selected. \n" +
19
             "Useful when there is not known precise file name only location")
20

  
21
parser.add_option("-o", "--output",     dest="filename",    default="coverage-merged.xml",
22
    help="output file xml name", metavar="FILE")
23
parser.add_option("-p", "--path",       dest="path",        default="./",
24
    help="xml location, default current directory", metavar="FILE")
25
parser.add_option("-l", "--log",        dest="loglevel",    default="DEBUG",
26
    help="Log level DEBUG, INFO, WARNING, ERROR, CRITICAL")
27
parser.add_option("-f", "--filteronly", dest="filteronly",  default=False, action='store_true',
28
    help="If set all files will be filtered by keep rules otherwise "+
29
		 "all given files will be merged and filtered.")
30
parser.add_option("-s", "--suffix",     dest="suffix",      default='',
31
    help="Additional suffix which will be added to filtered files so they original files can be preserved")
32
parser.add_option("-k", "--keep",       dest="packagefilters", default=None,  metavar="NAME", action="append",
33
    help="preserves only specific packages. e.g.: " + newline + 
34
         "'python merge.py -k src.la.*'" + newline + 
35
         "will keep all packgages in folder " +
36
         "src/la/ and all subfolders of this folders. " + newline +
37
         "There can be mutiple rules e.g.:" + newline + 
38
         "'python merge.py -k src.la.* -k unit_tests.la.'" + newline +
39
         "Format of the rule is simple dot (.) separated names with wildcard (*) allowed, e.g: " + newline +
40
         "package.subpackage.*")
16
newline = 10 * '\t'
17
parser = OptionParser(
18
    usage="%prog [options] [file1 file2 ... filen]",
19
    version="%prog 1.0",
20
    epilog="If no files are specified all xml files in current directory will be selected. \n"
21
    + "Useful when there is not known precise file name only location",
22
)
23

  
24
parser.add_option(
25
    "-o",
26
    "--output",
27
    dest="filename",
28
    default="coverage-merged.xml",
29
    help="output file xml name",
30
    metavar="FILE",
31
)
32
parser.add_option(
33
    "-p", "--path", dest="path", default="./", help="xml location, default current directory", metavar="FILE"
34
)
35
parser.add_option(
36
    "-l", "--log", dest="loglevel", default="DEBUG", help="Log level DEBUG, INFO, WARNING, ERROR, CRITICAL"
37
)
38
parser.add_option(
39
    "-f",
40
    "--filteronly",
41
    dest="filteronly",
42
    default=False,
43
    action='store_true',
44
    help="If set all files will be filtered by keep rules otherwise "
45
    + "all given files will be merged and filtered.",
46
)
47
parser.add_option(
48
    "-s",
49
    "--suffix",
50
    dest="suffix",
51
    default='',
52
    help="Additional suffix which will be added to filtered files so they original files can be preserved",
53
)
54
parser.add_option(
55
    "-k",
56
    "--keep",
57
    dest="packagefilters",
58
    default=None,
59
    metavar="NAME",
60
    action="append",
61
    help="preserves only specific packages. e.g.: "
62
    + newline
63
    + "'python merge.py -k src.la.*'"
64
    + newline
65
    + "will keep all packgages in folder "
66
    + "src/la/ and all subfolders of this folders. "
67
    + newline
68
    + "There can be mutiple rules e.g.:"
69
    + newline
70
    + "'python merge.py -k src.la.* -k unit_tests.la.'"
71
    + newline
72
    + "Format of the rule is simple dot (.) separated names with wildcard (*) allowed, e.g: "
73
    + newline
74
    + "package.subpackage.*",
75
)
41 76
(options, args) = parser.parse_args()
42 77

  
43 78

  
......
45 80
path = options.path
46 81
xmlfiles = args
47 82
loglevel = getattr(logging, options.loglevel.upper())
48
finalxml = os.path.join (path, options.filename)
83
finalxml = os.path.join(path, options.filename)
49 84
filteronly = options.filteronly
50 85
filtersuffix = options.suffix
51 86
packagefilters = options.packagefilters
52
logging.basicConfig (level=loglevel, format='%(levelname)s %(asctime)s: %(message)s', datefmt='%x %X')
53

  
87
logging.basicConfig(level=loglevel, format='%(levelname)s %(asctime)s: %(message)s', datefmt='%x %X')
54 88

  
55 89

  
56 90
if not xmlfiles:
57
	for filename in os.listdir (path):
58
	    if not filename.endswith ('.xml'): continue
59
	    fullname = os.path.join (path, filename)
60
	    if fullname == finalxml: continue
61
	    xmlfiles.append (fullname)
62

  
63
	if not xmlfiles:
64
		print('No xml files found!')
65
		sys.exit (1)
91
    for filename in os.listdir(path):
92
        if not filename.endswith('.xml'):
93
            continue
94
        fullname = os.path.join(path, filename)
95
        if fullname == finalxml:
96
            continue
97
        xmlfiles.append(fullname)
98

  
99
    if not xmlfiles:
100
        print('No xml files found!')
101
        sys.exit(1)
66 102

  
67 103
else:
68
	xmlfiles=[path+filename for filename in xmlfiles]
69

  
104
    xmlfiles = [path + filename for filename in xmlfiles]
70 105

  
71 106

  
72 107
# constants
73
PACKAGES_LIST = 'packages/package';
108
PACKAGES_LIST = 'packages/package'
74 109
PACKAGES_ROOT = 'packages'
75
CLASSES_LIST = 'classes/class';
110
CLASSES_LIST = 'classes/class'
76 111
CLASSES_ROOT = 'classes'
77
METHODS_LIST = 'methods/method';
112
METHODS_LIST = 'methods/method'
78 113
METHODS_ROOT = 'methods'
79
LINES_LIST = 'lines/line';
114
LINES_LIST = 'lines/line'
80 115
LINES_ROOT = 'lines'
81 116

  
82 117

  
118
def merge_xml(xmlfile1, xmlfile2, outputfile):
119
    # parse
120
    xml1 = ET.parse(xmlfile1)
121
    xml2 = ET.parse(xmlfile2)
83 122

  
84
def merge_xml (xmlfile1, xmlfile2, outputfile):
85
	# parse
86
	xml1 = ET.parse(xmlfile1)
87
	xml2 = ET.parse(xmlfile2)
88

  
89
	# get packages
90
	packages1 = filter_xml(xml1)
91
	packages2 = filter_xml(xml2)
123
    # get packages
124
    packages1 = filter_xml(xml1)
125
    packages2 = filter_xml(xml2)
92 126

  
93
	# find root
94
	packages1root = xml1.find(PACKAGES_ROOT)
127
    # find root
128
    packages1root = xml1.find(PACKAGES_ROOT)
95 129

  
130
    # merge packages
131
    merge(packages1root, packages1, packages2, 'name', merge_packages)
96 132

  
97
	# merge packages
98
	merge (packages1root, packages1, packages2, 'name', merge_packages);
133
    # write result to output file
134
    xml1.write(outputfile, encoding="UTF-8", xml_declaration=True)
99 135

  
100
	# write result to output file
101
	xml1.write (outputfile,  encoding="UTF-8", xml_declaration=True)
102 136

  
137
def filter_xml(xmlfile):
138
    xmlroot = xmlfile.getroot()
139
    packageroot = xmlfile.find(PACKAGES_ROOT)
140
    packages = xmlroot.findall(PACKAGES_LIST)
103 141

  
104
def filter_xml (xmlfile):
105
	xmlroot = xmlfile.getroot()
106
	packageroot = xmlfile.find(PACKAGES_ROOT)
107
	packages = xmlroot.findall (PACKAGES_LIST)
142
    # delete nodes from tree AND from list
143
    included = []
144
    if packagefilters:
145
        logging.debug('excluding packages:')
146
    for pckg in packages:
147
        name = pckg.get('name')
148
        if not include_package(name):
149
            logging.debug('excluding package "{0}"'.format(name))
150
            packageroot.remove(pckg)
151
        else:
152
            included.append(pckg)
153
    return included
108 154

  
109
	# delete nodes from tree AND from list
110
	included = []
111
	if packagefilters: logging.debug ('excluding packages:')
112
	for pckg in packages:
113
		name = pckg.get('name')
114
		if not include_package (name):
115
			logging.debug ('excluding package "{0}"'.format(name))
116
			packageroot.remove (pckg)
117
		else:
118
			included.append (pckg)
119
	return included
120 155

  
156
def prepare_packagefilters():
157
    if not packagefilters:
158
        return None
121 159

  
122
def prepare_packagefilters ():
123
	if not packagefilters:
124
		return None
160
    # create simple regexp from given filter
161
    for i in range(len(packagefilters)):
162
        packagefilters[i] = '^' + packagefilters[i].replace('.', '\.').replace('*', '.*') + '$'
125 163

  
126
	# create simple regexp from given filter
127
	for i in range (len (packagefilters)):
128
		packagefilters[i] = '^' + packagefilters[i].replace ('.', '\.').replace ('*', '.*') + '$'
129 164

  
165
def include_package(name):
166
    if not packagefilters:
167
        return True
130 168

  
169
    for packagefilter in packagefilters:
170
        if re.search(packagefilter, name):
171
            return True
172
    return False
131 173

  
132
def include_package (name):
133
	if not packagefilters:
134
		return True
135 174

  
136
	for packagefilter in packagefilters:
137
		if re.search(packagefilter, name):
138
			return True
139
	return False
175
def get_attributes_chain(obj, attrs):
176
    """Return a joined arguments of object based on given arguments"""
140 177

  
141
def get_attributes_chain (obj, attrs):
142
	"""Return a joined arguments of object based on given arguments"""
178
    if type(attrs) is list:
179
        result = ''
180
        for attr in attrs:
181
            result += obj.attrib[attr]
182
        return result
183
    else:
184
        return obj.attrib[attrs]
143 185

  
144
	if type(attrs) is list:
145
		result = ''
146
		for attr in attrs:
147
			result += obj.attrib[attr]
148
		return result
149
	else:
150
		return 	obj.attrib[attrs]
151 186

  
187
def merge(root, list1, list2, attr, merge_function):
188
    """Groups given lists based on group attributes. Process of merging items with same key is handled by
189
    passed merge_function. Returns list1."""
190
    for item2 in list2:
191
        found = False
192
        for item1 in list1:
193
            if get_attributes_chain(item1, attr) == get_attributes_chain(item2, attr):
194
                item1 = merge_function(item1, item2)
195
                found = True
196
                break
197
        if found:
198
            continue
199
        else:
200
            root.append(item2)
152 201

  
153
def merge (root, list1, list2, attr, merge_function):
154
	""" Groups given lists based on group attributes. Process of merging items with same key is handled by
155
		passed merge_function. Returns list1. """
156
	for item2 in list2:
157
		found = False
158
		for item1 in list1:
159
			if get_attributes_chain(item1, attr) == get_attributes_chain(item2, attr):
160
				item1 = merge_function (item1, item2)
161
				found = True
162
				break
163
		if found:
164
			continue
165
		else:
166
			root.append(item2)
167 202

  
203
def merge_packages(package1, package2):
204
    """Merges two packages. Returns package1."""
205
    classes1 = package1.findall(CLASSES_LIST)
206
    classes2 = package2.findall(CLASSES_LIST)
207
    if classes1 or classes2:
208
        merge(package1.find(CLASSES_ROOT), classes1, classes2, ['filename', 'name'], merge_classes)
168 209

  
169
def merge_packages (package1, package2):
170
	"""Merges two packages. Returns package1."""
171
	classes1 = package1.findall (CLASSES_LIST);
172
	classes2 = package2.findall (CLASSES_LIST);
173
	if classes1 or classes2:
174
		merge (package1.find (CLASSES_ROOT), classes1, classes2, ['filename','name'], merge_classes);
210
    return package1
175 211

  
176
	return package1
177 212

  
213
def merge_classes(class1, class2):
214
    """Merges two classes. Returns class1."""
178 215

  
179
def merge_classes (class1, class2):
180
	"""Merges two classes. Returns class1."""
216
    lines1 = class1.findall(LINES_LIST)
217
    lines2 = class2.findall(LINES_LIST)
218
    if lines1 or lines2:
219
        merge(class1.find(LINES_ROOT), lines1, lines2, 'number', merge_lines)
181 220

  
182
	lines1 = class1.findall (LINES_LIST);
183
	lines2 = class2.findall (LINES_LIST);
184
	if lines1 or lines2:
185
		merge (class1.find (LINES_ROOT), lines1, lines2, 'number', merge_lines);
221
    methods1 = class1.findall(METHODS_LIST)
222
    methods2 = class2.findall(METHODS_LIST)
223
    if methods1 or methods2:
224
        merge(class1.find(METHODS_ROOT), methods1, methods2, 'name', merge_methods)
186 225

  
187
	methods1 = class1.findall (METHODS_LIST)
188
	methods2 = class2.findall (METHODS_LIST)
189
	if methods1 or methods2:
190
		merge (class1.find (METHODS_ROOT), methods1, methods2, 'name', merge_methods);
226
    return class1
191 227

  
192
	return class1
193 228

  
229
def merge_methods(method1, method2):
230
    """Merges two methods. Returns method1."""
194 231

  
195
def merge_methods (method1, method2):
196
	"""Merges two methods. Returns method1."""
232
    lines1 = method1.findall(LINES_LIST)
233
    lines2 = method2.findall(LINES_LIST)
234
    merge(method1.find(LINES_ROOT), lines1, lines2, 'number', merge_lines)
197 235

  
198
	lines1 = method1.findall (LINES_LIST);
199
	lines2 = method2.findall (LINES_LIST);
200
	merge (method1.find (LINES_ROOT), lines1, lines2, 'number', merge_lines);
201 236

  
237
def merge_lines(line1, line2):
238
    """Merges two lines by summing their hits. Returns line1."""
202 239

  
203
def merge_lines (line1, line2):
204
	"""Merges two lines by summing their hits. Returns line1."""
240
    # merge hits
241
    value = int(line1.get('hits')) + int(line2.get('hits'))
242
    line1.set('hits', str(value))
205 243

  
206
	# merge hits
207
	value = int (line1.get('hits')) + int (line2.get('hits'))
208
	line1.set ('hits', str(value))
244
    # merge conditionals
245
    con1 = line1.get('condition-coverage')
246
    con2 = line2.get('condition-coverage')
247
    if con1 is not None and con2 is not None:
248
        con1value = int(con1.split('%')[0])
249
        con2value = int(con2.split('%')[0])
250
        # bigger coverage on second line, swap their conditionals
251
        if con2value > con1value:
252
            line1.set('condition-coverage', str(con2))
253
            line1.__setitem__(0, line2.__getitem__(0))
209 254

  
210
	# merge conditionals
211
	con1 = line1.get('condition-coverage')
212
	con2 = line2.get('condition-coverage')
213
	if (con1 is not None and con2 is not None):
214
		con1value = int(con1.split('%')[0])
215
		con2value = int(con2.split('%')[0])
216
		# bigger coverage on second line, swap their conditionals
217
		if (con2value > con1value):
218
			line1.set ('condition-coverage', str(con2))
219
			line1.__setitem__(0, line2.__getitem__(0))
255
    return line1
220 256

  
221
	return line1
222 257

  
223 258
# prepare filters
224
prepare_packagefilters ()
259
prepare_packagefilters()
225 260

  
226 261

  
227 262
if filteronly:
228
	# filter all given files
229
	currfile = 1
230
	totalfiles = len (xmlfiles)
231
	for xmlfile in xmlfiles:
232
		xml = ET.parse(xmlfile)
233
		filter_xml(xml)
234
		logging.debug ('{1}/{2} filtering: {0}'.format (xmlfile, currfile, totalfiles))
235
		xml.write (xmlfile + filtersuffix,  encoding="UTF-8", xml_declaration=True)
236
		currfile += 1
263
    # filter all given files
264
    currfile = 1
265
    totalfiles = len(xmlfiles)
266
    for xmlfile in xmlfiles:
267
        xml = ET.parse(xmlfile)
268
        filter_xml(xml)
269
        logging.debug('{1}/{2} filtering: {0}'.format(xmlfile, currfile, totalfiles))
270
        xml.write(xmlfile + filtersuffix, encoding="UTF-8", xml_declaration=True)
271
        currfile += 1
237 272
else:
238
	# merge all given files
239
	totalfiles = len (xmlfiles)
240

  
241
	# special case if only one file was given
242
	# filter given file and save it
243
	if (totalfiles == 1):
244
		logging.warning ('Only one file given!')
245
		xmlfile = xmlfiles.pop(0)
246
		xml = ET.parse(xmlfile)
247
		filter_xml(xml)
248
		xml.write (finalxml,  encoding="UTF-8", xml_declaration=True)
249
		sys.exit (0)
250

  
251

  
252
	currfile = 1
253
	logging.debug ('{2}/{3} merging: {0} & {1}'.format (xmlfiles[0], xmlfiles[1], currfile, totalfiles-1))
254
	merge_xml (xmlfiles[0], xmlfiles[1], finalxml)
255

  
256

  
257
	currfile = 2
258
	for i in range (totalfiles-2):
259
		xmlfile = xmlfiles[i+2]
260
		logging.debug ('{2}/{3} merging: {0} & {1}'.format (finalxml, xmlfile, currfile, totalfiles-1))
261
		merge_xml (finalxml, xmlfile, finalxml)
262
		currfile += 1
273
    # merge all given files
274
    totalfiles = len(xmlfiles)
275

  
276
    # special case if only one file was given
277
    # filter given file and save it
278
    if totalfiles == 1:
279
        logging.warning('Only one file given!')
280
        xmlfile = xmlfiles.pop(0)
281
        xml = ET.parse(xmlfile)
282
        filter_xml(xml)
283
        xml.write(finalxml, encoding="UTF-8", xml_declaration=True)
284
        sys.exit(0)
285

  
286
    currfile = 1
287
    logging.debug('{2}/{3} merging: {0} & {1}'.format(xmlfiles[0], xmlfiles[1], currfile, totalfiles - 1))
288
    merge_xml(xmlfiles[0], xmlfiles[1], finalxml)
289

  
290
    currfile = 2
291
    for i in range(totalfiles - 2):
292
        xmlfile = xmlfiles[i + 2]
293
        logging.debug('{2}/{3} merging: {0} & {1}'.format(finalxml, xmlfile, currfile, totalfiles - 1))
294
        merge_xml(finalxml, xmlfile, finalxml)
295
        currfile += 1
setup.py
31 31
        try:
32 32
            os.environ.pop('DJANGO_SETTINGS_MODULE', None)
33 33
            from django.core.management import call_command
34

  
34 35
            for dir in glob.glob('src/*'):
35 36
                for path, dirs, files in os.walk(dir):
36 37
                    if 'locale' not in dirs:
......
74 75

  
75 76

  
76 77
def get_version():
77
    '''Use the VERSION, if absent generates a version with git describe, if not
78
       tag exists, take 0.0- and add the length of the commit log.
79
    '''
78
    """Use the VERSION, if absent generates a version with git describe, if not
79
    tag exists, take 0.0- and add the length of the commit log.
80
    """
80 81
    if os.path.exists('VERSION'):
81 82
        with open('VERSION', 'r') as v:
82 83
            return v.read()
83 84
    if os.path.exists('.git'):
84
        p = subprocess.Popen(['git', 'describe', '--dirty=.dirty','--match=v*'], stdout=subprocess.PIPE,
85
                             stderr=subprocess.PIPE)
85
        p = subprocess.Popen(
86
            ['git', 'describe', '--dirty=.dirty', '--match=v*'],
87
            stdout=subprocess.PIPE,
88
            stderr=subprocess.PIPE,
89
        )
86 90
        result = p.communicate()[0]
87 91
        if p.returncode == 0:
88 92
            result = result.decode('ascii').strip()[1:]  # strip spaces/newlines and initial v
......
93 97
                version = result
94 98
            return version
95 99
        else:
96
            return '0.0.post%s' % len(
97
                subprocess.check_output(
98
                    ['git', 'rev-list', 'HEAD']).splitlines())
100
            return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines())
99 101
    return '0.0'
100 102

  
101 103

  
102
setup(name="authentic2",
103
      version=get_version(),
104
      license="AGPLv3+",
105
      description="Authentic 2, a versatile identity management server",
106
      url="http://dev.entrouvert.org/projects/authentic/",
107
      author="Entr'ouvert",
108
      author_email="authentic@listes.entrouvert.com",
109
      maintainer="Benjamin Dauvergne",
110
      maintainer_email="bdauvergne@entrouvert.com",
111
      scripts=('authentic2-ctl',),
112
      packages=find_packages('src'),
113
      package_dir={
114
          '': 'src',
115
      },
116
      include_package_data=True,
117
      install_requires=[
118
          'django>=1.11,<2.3',
119
          'requests>=2.3',
120
          'requests-oauthlib',
121
          'django-model-utils>=2.4,<4',
122
          'dnspython>=1.10',
123
          'Django-Select2>5,<6',
124
          'django-tables2>=1.0,<2.0',
125
          'django-ratelimit',
126
          'gadjo>=0.53',
127
          'django-import-export>=1,<2',
128
          'djangorestframework>=3.3,<3.10',
129
          'six>=1',
130
          'Markdown>=2.1',
131
          'python-ldap',
132
          'django-filter>1,<2.3',
133
          'pycryptodomex',
134
          'django-mellon>=1.22',
135
          'ldaptools',
136
          'jwcrypto>=0.3.1,<1',
137
          'cryptography',
138
          'XStatic-jQuery<2',
139
          'XStatic-jquery-ui',
140
          'xstatic-select2',
141
          'pillow',
142
          'tablib',
143
          'chardet',
144
          'attrs>17',
145
          'atomicwrites',
146
      ],
147
      zip_safe=False,
148
      classifiers=[
149
          "Development Status :: 5 - Production/Stable",
150
          "Environment :: Web Environment",
151
          "Framework :: Django",
152
          'Intended Audience :: End Users/Desktop',
153
          'Intended Audience :: Developers',
154
          'Intended Audience :: System Administrators',
155
          'Intended Audience :: Information Technology',
156
          'Intended Audience :: Legal Industry',
157
          'Intended Audience :: Science/Research',
158
          'Intended Audience :: Telecommunications Industry',
159
          "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
160
          "Operating System :: OS Independent",
161
          "Programming Language :: Python",
162
          "Topic :: System :: Systems Administration :: Authentication/Directory",
163
      ],
164
      cmdclass={
165
          'build': build,
166
          'install_lib': install_lib,
167
          'compile_translations': compile_translations,
168
          'sdist': sdist,
169
      })
104
setup(
105
    name="authentic2",
106
    version=get_version(),
107
    license="AGPLv3+",
108
    description="Authentic 2, a versatile identity management server",
109
    url="http://dev.entrouvert.org/projects/authentic/",
110
    author="Entr'ouvert",
111
    author_email="authentic@listes.entrouvert.com",
112
    maintainer="Benjamin Dauvergne",
113
    maintainer_email="bdauvergne@entrouvert.com",
114
    scripts=('authentic2-ctl',),
115
    packages=find_packages('src'),
116
    package_dir={
117
        '': 'src',
118
    },
119
    include_package_data=True,
120
    install_requires=[
121
        'django>=1.11,<2.3',
122
        'requests>=2.3',
123
        'requests-oauthlib',
124
        'django-model-utils>=2.4,<4',
125
        'dnspython>=1.10',
126
        'Django-Select2>5,<6',
127
        'django-tables2>=1.0,<2.0',
128
        'django-ratelimit',
129
        'gadjo>=0.53',
130
        'django-import-export>=1,<2',
131
        'djangorestframework>=3.3,<3.10',
132
        'six>=1',
133
        'Markdown>=2.1',
134
        'python-ldap',
135
        'django-filter>1,<2.3',
136
        'pycryptodomex',
137
        'django-mellon>=1.22',
138
        'ldaptools',
139
        'jwcrypto>=0.3.1,<1',
140
        'cryptography',
141
        'XStatic-jQuery<2',
142
        'XStatic-jquery-ui',
143
        'xstatic-select2',
144
        'pillow',
145
        'tablib',
146
        'chardet',
147
        'attrs>17',
148
        'atomicwrites',
149
    ],
150
    zip_safe=False,
151
    classifiers=[
152
        "Development Status :: 5 - Production/Stable",
153
        "Environment :: Web Environment",
154
        "Framework :: Django",
155
        'Intended Audience :: End Users/Desktop',
156
        'Intended Audience :: Developers',
157
        'Intended Audience :: System Administrators',
158
        'Intended Audience :: Information Technology',
159
        'Intended Audience :: Legal Industry',
160
        'Intended Audience :: Science/Research',
161
        'Intended Audience :: Telecommunications Industry',
162
        "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
163
        "Operating System :: OS Independent",
164
        "Programming Language :: Python",
165
        "Topic :: System :: Systems Administration :: Authentication/Directory",
166
    ],
167
    cmdclass={
168
        'build': build,
169
        'install_lib': install_lib,
170
        'compile_translations': compile_translations,
171
        'sdist': sdist,
172
    },
173
)
src/authentic2/a2_rbac/admin.py
27 27
    fields = ['parent']
28 28

  
29 29
    def get_queryset(self, request):
30
        return super(RoleParentInline, self).get_queryset(request) \
31
            .filter(direct=True)
30
        return super(RoleParentInline, self).get_queryset(request).filter(direct=True)
32 31

  
33 32

  
34 33
class RoleChildInline(admin.TabularInline):
......
37 36
    fields = ['child']
38 37

  
39 38
    def get_queryset(self, request):
40
        return super(RoleChildInline, self).get_queryset(request) \
41
            .filter(direct=True)
39
        return super(RoleChildInline, self).get_queryset(request).filter(direct=True)
42 40

  
43 41

  
44 42
class RoleAttributeInline(admin.TabularInline):
......
47 45

  
48 46
class RoleAdmin(admin.ModelAdmin):
49 47
    inlines = [RoleChildInline, RoleParentInline]
50
    fields = ('uuid', 'name', 'slug', 'description', 'ou', 'members',
51
              'permissions', 'admin_scope_ct', 'admin_scope_id', 'service')
48
    fields = (
49
        'uuid',
50
        'name',
51
        'slug',
52
        'description',
53
        'ou',
54
        'members',
55
        'permissions',
56
        'admin_scope_ct',
57
        'admin_scope_id',
58
        'service',
59
    )
52 60
    readonly_fields = ('uuid',)
53 61
    prepopulated_fields = {"slug": ("name",)}
54 62
    filter_horizontal = ('members', 'permissions')
......
59 67

  
60 68

  
61 69
class OrganizationalUnitAdmin(admin.ModelAdmin):
62
    fields = ('uuid', 'name', 'slug', 'description', 'username_is_unique',
63
              'email_is_unique', 'default', 'validate_emails',
64
              'user_can_reset_password', 'user_add_password_policy')
70
    fields = (
71
        'uuid',
72
        'name',
73
        'slug',
74
        'description',
75
        'username_is_unique',
76
        'email_is_unique',
77
        'default',
78
        'validate_emails',
79
        'user_can_reset_password',
80
        'user_add_password_policy',
81
    )
65 82
    readonly_fields = ('uuid',)
66 83
    prepopulated_fields = {"slug": ("name",)}
67 84
    list_display = ('name', 'slug')
......
74 91

  
75 92
    def name(self, obj):
76 93
        return six.text_type(obj)
94

  
77 95
    name.short_description = _('name')
78 96

  
97

  
79 98
admin.site.register(models.Role, RoleAdmin)
80 99
admin.site.register(models.OrganizationalUnit, OrganizationalUnitAdmin)
81 100
admin.site.register(models.Permission, PermissionAdmin)
src/authentic2/a2_rbac/app_settings.py
28 28

  
29 29
    def _setting(self, name, dflt):
30 30
        from django.conf import settings
31

  
31 32
        return getattr(settings, name, dflt)
32 33

  
33 34
    def _setting_with_prefix(self, name, dflt):
src/authentic2/a2_rbac/apps.py
27 27
        from authentic2.models import Service
28 28

  
29 29
        # update rbac on save to contenttype, ou and roles
30
        post_save.connect(
31
            signal_handlers.update_rbac_on_ou_post_save,
32
            sender=models.OrganizationalUnit)
33
        post_delete.connect(
34
            signal_handlers.update_rbac_on_ou_post_delete,
35
            sender=models.OrganizationalUnit)
30
        post_save.connect(signal_handlers.update_rbac_on_ou_post_save, sender=models.OrganizationalUnit)
31
        post_delete.connect(signal_handlers.update_rbac_on_ou_post_delete, sender=models.OrganizationalUnit)
36 32
        # keep service role and service ou field in sync
37 33
        for subclass in Service.__subclasses__():
38
            post_save.connect(
39
                signal_handlers.update_service_role_ou,
40
                sender=subclass)
41
        post_save.connect(
42
            signal_handlers.update_service_role_ou,
43
            sender=Service)
44
        post_migrate.connect(
45
            signal_handlers.create_default_ou,
46
            sender=self)
47
        post_migrate.connect(
48
            signal_handlers.create_default_permissions,
49
            sender=self)
50
        post_migrate.connect(
51
            signal_handlers.post_migrate_update_rbac,
52
            sender=self)
34
            post_save.connect(signal_handlers.update_service_role_ou, sender=subclass)
35
        post_save.connect(signal_handlers.update_service_role_ou, sender=Service)
36
        post_migrate.connect(signal_handlers.create_default_ou, sender=self)
37
        post_migrate.connect(signal_handlers.create_default_permissions, sender=self)
38
        post_migrate.connect(signal_handlers.post_migrate_update_rbac, sender=self)
src/authentic2/a2_rbac/fields.py
19 19

  
20 20

  
21 21
class UniqueBooleanField(NullBooleanField):
22
    '''BooleanField allowing only one True value in the table, and preventing
23
       problems with multiple False values by implicitely converting them to
24
       None.'''
22
    """BooleanField allowing only one True value in the table, and preventing
23
    problems with multiple False values by implicitely converting them to
24
    None."""
25

  
25 26
    def __init__(self, *args, **kwargs):
26 27
        kwargs['unique'] = True
27 28
        kwargs['blank'] = True
......
44 45
        return value
45 46

  
46 47
    def get_db_prep_value(self, value, connection, prepared=False):
47
        value = super(UniqueBooleanField, self).get_db_prep_value(
48
                value, connection, prepared=prepared)
48
        value = super(UniqueBooleanField, self).get_db_prep_value(value, connection, prepared=prepared)
49 49
        if value is False:
50 50
            return None
51 51
        return value
src/authentic2/a2_rbac/management.py
33 33
    Role = get_role_model()
34 34

  
35 35
    if app_settings.MANAGED_CONTENT_TYPES == ():
36
        Role.objects.filter(slug='a2-managers-of-{ou.slug}'.format(ou=ou)) \
37
            .delete()
36
        Role.objects.filter(slug='a2-managers-of-{ou.slug}'.format(ou=ou)).delete()
38 37
    else:
39 38
        ou_admin_role = ou.get_admin_role()
40 39

  
......
55 54
            continue
56 55
        else:
57 56
            ou_ct_admin_role = Role.objects.get_admin_role(
58
                instance=ct,
59
                ou=ou,
60
                name=name,
61
                slug=ou_slug,
62
                update_slug=True,
63
                update_name=True)
64
        if not app_settings.MANAGED_CONTENT_TYPES or \
65
                key in app_settings.MANAGED_CONTENT_TYPES:
57
                instance=ct, ou=ou, name=name, slug=ou_slug, update_slug=True, update_name=True
58
            )
59
        if not app_settings.MANAGED_CONTENT_TYPES or key in app_settings.MANAGED_CONTENT_TYPES:
66 60
            ou_ct_admin_role.add_child(ou_admin_role)
67 61
        else:
68 62
            ou_ct_admin_role.remove_child(ou_admin_role)
......
72 66

  
73 67

  
74 68
def update_ous_admin_roles():
75
    '''Create general admin roles linked to all organizational units,
76
       they give general administrative rights to all mamanged content types
77
       scoped to the given organizational unit.
78
    '''
69
    """Create general admin roles linked to all organizational units,
70
    they give general administrative rights to all mamanged content types
71
    scoped to the given organizational unit.
72
    """
79 73
    OU = get_ou_model()
80 74
    ou_all = OU.objects.all()
81 75
    if len(ou_all) < 2:
......
85 79
    for ou in ou_all:
86 80
        update_ou_admin_roles(ou)
87 81

  
82

  
88 83
MANAGED_CT = {
89 84
    ('a2_rbac', 'role'): {
90 85
        'name': _('Manager of roles'),
......
108 103

  
109 104

  
110 105
def update_content_types_roles():
111
    '''Create general and scoped management roles for all managed content
112
       types.
113
    '''
106
    """Create general and scoped management roles for all managed content
107
    types.
108
    """
114 109
    cts = ContentType.objects.all()
115 110
    Role = get_role_model()
116 111
    view_user_perm = utils.get_view_user_perm()
......
120 115
    if app_settings.MANAGED_CONTENT_TYPES == ():
121 116
        Role.objects.filter(slug=slug).delete()
122 117
    else:
123
        admin_role, created = Role.objects.get_or_create(
124
            slug=slug,
125
            defaults=dict(
126
                name=ugettext('Manager')))
118
        admin_role, created = Role.objects.get_or_create(slug=slug, defaults=dict(name=ugettext('Manager')))
127 119
        admin_role.add_self_administration()
128 120
        if not created and admin_role.name != ugettext('Manager'):
129 121
            admin_role.name = ugettext('Manager')
......
136 128
        # General admin role
137 129
        name = six.text_type(MANAGED_CT[ct_tuple]['name'])
138 130
        slug = '_a2-' + slugify(name)
139
        if app_settings.MANAGED_CONTENT_TYPES is not None and ct_tuple not in \
140
                app_settings.MANAGED_CONTENT_TYPES:
131
        if (
132
            app_settings.MANAGED_CONTENT_TYPES is not None
133
            and ct_tuple not in app_settings.MANAGED_CONTENT_TYPES
134
        ):
141 135
            Role.objects.filter(slug=slug).delete()
142 136
            continue
143
        ct_admin_role = Role.objects.get_admin_role(instance=ct, name=name,
144
                                                    slug=slug,
145
                                                    update_name=True,
146
                                                    update_slug=True,
147
                                                    create=True)
137
        ct_admin_role = Role.objects.get_admin_role(
138
            instance=ct, name=name, slug=slug, update_name=True, update_slug=True, create=True
139
        )
148 140
        if MANAGED_CT[ct_tuple].get('must_view_user'):
149 141
            ct_admin_role.permissions.add(view_user_perm)
150 142
        if MANAGED_CT[ct_tuple].get('must_manage_authorizations_user'):
src/authentic2/a2_rbac/managers.py
28 28

  
29 29

  
30 30
class RoleManager(BaseRoleManager):
31
    def get_admin_role(self, instance, name, slug, ou=None, operation=ADMIN_OP,
32
                       update_name=False, update_slug=False, permissions=(),
33
                       self_administered=False, create=True):
31
    def get_admin_role(
32
        self,
33
        instance,
34
        name,
35
        slug,
36
        ou=None,
37
        operation=ADMIN_OP,
38
        update_name=False,
39
        update_slug=False,
40
        permissions=(),
41
        self_administered=False,
42
        create=True,
43
    ):
34 44
        '''Get or create the role of manager's of this object instance'''
35 45
        kwargs = {}
36
        assert not ou or isinstance(instance, ContentType), (
37
            'get_admin_role(ou=...) can only be used with ContentType instances: %s %s %s' % (name, ou, instance)
46
        assert not ou or isinstance(
47
            instance, ContentType
48
        ), 'get_admin_role(ou=...) can only be used with ContentType instances: %s %s %s' % (
49
            name,
50
            ou,
51
            instance,
38 52
        )
39 53

  
40 54
        # Does the permission need to be scoped by ou ? Yes if the target is a
......
57 71
                target_ct=ContentType.objects.get_for_model(instance),
58 72
                target_id=instance.pk,
59 73
                defaults=defaults,
60
                **kwargs)
74
                **kwargs,
75
            )
61 76
        else:
62 77
            try:
63 78
                perm = Permission.objects.get(
64 79
                    operation=op,
65 80
                    target_ct=ContentType.objects.get_for_model(instance),
66 81
                    target_id=instance.pk,
67
                    **kwargs)
82
                    **kwargs,
83
                )
68 84
            except Permission.DoesNotExist:
69 85
                return None
70 86

  
......
75 91
            mirror_role_ou = instance.ou
76 92
        else:
77 93
            mirror_role_ou = None
78
        admin_role = self.get_mirror_role(perm, name, slug, ou=mirror_role_ou,
79
                                          update_name=update_name,
80
                                          update_slug=update_slug,
81
                                          create=create)
94
        admin_role = self.get_mirror_role(
95
            perm,
96
            name,
97
            slug,
98
            ou=mirror_role_ou,
99
            update_name=update_name,
100
            update_slug=update_slug,
101
            create=create,
102
        )
82 103

  
83 104
        if not admin_role:
84 105
            return None
......
92 113
            admin_role.permissions.set(permissions)
93 114
        return admin_role
94 115

  
95
    def get_mirror_role(self, instance, name, slug, ou=None,
96
                        update_name=False, update_slug=False, create=True):
97
        '''Get or create a role which mirrors another model, for example a
98
           permission.
99
        '''
116
    def get_mirror_role(
117
        self, instance, name, slug, ou=None, update_name=False, update_slug=False, create=True
118
    ):
119
        """Get or create a role which mirrors another model, for example a
120
        permission.
121
        """
100 122
        ct = ContentType.objects.get_for_model(instance)
101 123
        update_fields = {}
102 124
        kwargs = {}
......
111 133

  
112 134
        if create:
113 135
            role, _ = self.prefetch_related('permissions').update_or_create(
114
                admin_scope_ct=ct,
115
                admin_scope_id=instance.pk,
116
                defaults=update_fields,
117
                **kwargs)
136
                admin_scope_ct=ct, admin_scope_id=instance.pk, defaults=update_fields, **kwargs
137
            )
118 138
        else:
119 139
            try:
120 140
                role = self.prefetch_related('permissions').get(
121
                    admin_scope_ct=ct,
122
                    admin_scope_id=instance.pk,
123
                    **kwargs)
141
                    admin_scope_ct=ct, admin_scope_id=instance.pk, **kwargs
142
                )
124 143
            except self.model.DoesNotExist:
125 144
                return None
126 145
            for field, value in update_fields.items():
src/authentic2/a2_rbac/migrations/0001_initial.py
20 20
        migrations.CreateModel(
21 21
            name='OrganizationalUnit',
22 22
            fields=[
23
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
24
                ('uuid', models.CharField(default=authentic2.utils.get_hex_uuid, unique=True, max_length=32, verbose_name='uuid')),
23
                (
24
                    'id',
25
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
26
                ),
27
                (
28
                    'uuid',
29
                    models.CharField(
30
                        default=authentic2.utils.get_hex_uuid, unique=True, max_length=32, verbose_name='uuid'
31
                    ),
32
                ),
25 33
                ('name', models.CharField(max_length=256, verbose_name='name')),
26 34
                ('slug', models.SlugField(max_length=256, verbose_name='slug')),
27 35
                ('description', models.TextField(verbose_name='description', blank=True)),
28
                ('default', authentic2.a2_rbac.fields.UniqueBooleanField(verbose_name='Default organizational unit')),
36
                (
37
                    'default',
38
                    authentic2.a2_rbac.fields.UniqueBooleanField(verbose_name='Default organizational unit'),
39
                ),
29 40
            ],
30 41
            options={
31 42
                'verbose_name': 'organizational unit',
......
36 47
        migrations.CreateModel(
37 48
            name='Permission',
38 49
            fields=[
39
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
50
                (
51
                    'id',
52
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
53
                ),
40 54
                ('target_id', models.PositiveIntegerField()),
41
                ('operation', models.ForeignKey(verbose_name='operation', to='django_rbac.Operation', on_delete=models.CASCADE)),
42
                ('ou', models.ForeignKey(related_name='scoped_permission', verbose_name='organizational unit', to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE)),
43
                ('target_ct', models.ForeignKey(related_name='+', to='contenttypes.ContentType', on_delete=models.CASCADE)),
55
                (
56
                    'operation',
57
                    models.ForeignKey(
58
                        verbose_name='operation', to='django_rbac.Operation', on_delete=models.CASCADE
59
                    ),
60
                ),
61
                (
62
                    'ou',
63
                    models.ForeignKey(
64
                        related_name='scoped_permission',
65
                        verbose_name='organizational unit',
66
                        to=settings.RBAC_OU_MODEL,
67
                        null=True,
68
                        on_delete=models.CASCADE,
69
                    ),
70
                ),
71
                (
72
                    'target_ct',
73
                    models.ForeignKey(
74
                        related_name='+', to='contenttypes.ContentType', on_delete=models.CASCADE
75
                    ),
76
                ),
44 77
            ],
45 78
            options={
46 79
                'verbose_name': 'permission',
......
51 84
        migrations.CreateModel(
52 85
            name='Role',
53 86
            fields=[
54
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
55
                ('uuid', models.CharField(default=authentic2.utils.get_hex_uuid, unique=True, max_length=32, verbose_name='uuid')),
87
                (
88
                    'id',
89
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
90
                ),
91
                (
92
                    'uuid',
93
                    models.CharField(
94
                        default=authentic2.utils.get_hex_uuid, unique=True, max_length=32, verbose_name='uuid'
95
                    ),
96
                ),
56 97
                ('name', models.CharField(max_length=256, verbose_name='name')),
57 98
                ('slug', models.SlugField(max_length=256, verbose_name='slug')),
58 99
                ('description', models.TextField(verbose_name='description', blank=True)),
59
                ('admin_scope_id', models.PositiveIntegerField(null=True, verbose_name='administrative scope id', blank=True)),
60
                ('admin_scope_ct', models.ForeignKey(verbose_name='administrative scope content type', blank=True, to='contenttypes.ContentType', null=True, on_delete=models.CASCADE)),
61
                ('members', models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL, blank=True)),
62
                ('ou', models.ForeignKey(verbose_name='organizational unit', blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE)),
63
                ('permissions', models.ManyToManyField(related_name='role', to=settings.RBAC_PERMISSION_MODEL, blank=True)),
64
                ('service', models.ForeignKey(verbose_name='service', blank=True, to='authentic2.Service', null=True, on_delete=models.CASCADE)),
100
                (
101
                    'admin_scope_id',
102
                    models.PositiveIntegerField(
103
                        null=True, verbose_name='administrative scope id', blank=True
104
                    ),
105
                ),
106
                (
107
                    'admin_scope_ct',
108
                    models.ForeignKey(
109
                        verbose_name='administrative scope content type',
110
                        blank=True,
111
                        to='contenttypes.ContentType',
112
                        null=True,
113
                        on_delete=models.CASCADE,
114
                    ),
115
                ),
116
                (
117
                    'members',
118
                    models.ManyToManyField(related_name='roles', to=settings.AUTH_USER_MODEL, blank=True),
119
                ),
120
                (
121
                    'ou',
122
                    models.ForeignKey(
123
                        verbose_name='organizational unit',
124
                        blank=True,
125
                        to=settings.RBAC_OU_MODEL,
126
                        null=True,
127
                        on_delete=models.CASCADE,
128
                    ),
129
                ),
130
                (
131
                    'permissions',
132
                    models.ManyToManyField(
133
                        related_name='role', to=settings.RBAC_PERMISSION_MODEL, blank=True
134
                    ),
135
                ),
136
                (
137
                    'service',
138
                    models.ForeignKey(
139
                        verbose_name='service',
140
                        blank=True,
141
                        to='authentic2.Service',
142
                        null=True,
143
                        on_delete=models.CASCADE,
144
                    ),
145
                ),
65 146
            ],
66 147
            options={
67 148
                'ordering': ('ou', 'service', 'name'),
......
73 154
        migrations.CreateModel(
74 155
            name='RoleAttribute',
75 156
            fields=[
76
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
157
                (
158
                    'id',
159
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
160
                ),
77 161
                ('name', models.CharField(max_length=64, verbose_name='name')),
78
                ('kind', models.CharField(max_length=32, verbose_name='kind', choices=[('string', 'string')])),
162
                (
163
                    'kind',
164
                    models.CharField(max_length=32, verbose_name='kind', choices=[('string', 'string')]),
165
                ),
79 166
                ('value', models.TextField(verbose_name='value')),
80
                ('role', models.ForeignKey(related_name='attributes', verbose_name='role', to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE)),
167
                (
168
                    'role',
169
                    models.ForeignKey(
170
                        related_name='attributes',
171
                        verbose_name='role',
172
                        to=settings.RBAC_ROLE_MODEL,
173
                        on_delete=models.CASCADE,
174
                    ),
175
                ),
81 176
            ],
82 177
            options={
83 178
                'verbose_name': 'role attribute',
......
88 183
        migrations.CreateModel(
89 184
            name='RoleParenting',
90 185
            fields=[
91
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
186
                (
187
                    'id',
188
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
189
                ),
92 190
                ('direct', models.BooleanField(blank=True, default=True)),
93
                ('child', models.ForeignKey(related_name='parent_relation', to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE)),
94
                ('parent', models.ForeignKey(related_name='child_relation', to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE)),
191
                (
192
                    'child',
193
                    models.ForeignKey(
194
                        related_name='parent_relation', to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE
195
                    ),
196
                ),
197
                (
198
                    'parent',
199
                    models.ForeignKey(
200
                        related_name='child_relation', to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE
201
                    ),
202
                ),
95 203
            ],
96 204
            options={
97 205
                'verbose_name': 'role parenting relation',
src/authentic2/a2_rbac/migrations/0003_partial_unique_index_on_name_and_slug.py
4 4
from django.db import models, migrations
5 5
from authentic2.migrations import CreatePartialIndexes
6 6

  
7

  
7 8
class Migration(migrations.Migration):
8 9

  
9 10
    dependencies = [
......
11 12
    ]
12 13

  
13 14
    operations = [
14
        CreatePartialIndexes('Role', 'a2_rbac_role', 'a2_rbac_role_unique_idx',
15
                             ('ou_id', 'service_id'), ('slug',),
16
                             null_columns=('admin_scope_ct_id',)),
15
        CreatePartialIndexes(
16
            'Role',
17
            'a2_rbac_role',
18
            'a2_rbac_role_unique_idx',
19
            ('ou_id', 'service_id'),
20
            ('slug',),
21
            null_columns=('admin_scope_ct_id',),
22
        ),
17 23
    ]
src/authentic2/a2_rbac/migrations/0004_auto_20150523_0028.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='organizationalunit',
16
            options={'ordering': ('name',), 'verbose_name': 'organizational unit', 'verbose_name_plural': 'organizational units'},
16
            options={
17
                'ordering': ('name',),
18
                'verbose_name': 'organizational unit',
19
                'verbose_name_plural': 'organizational units',
20
            },
17 21
        ),
18 22
        migrations.AlterUniqueTogether(
19 23
            name='role',
src/authentic2/a2_rbac/migrations/0005_auto_20150526_1406.py
14 14
        migrations.AlterField(
15 15
            model_name='role',
16 16
            name='service',
17
            field=models.ForeignKey(related_name='roles', verbose_name='service', blank=True, to='authentic2.Service', null=True, on_delete=models.CASCADE),
17
            field=models.ForeignKey(
18
                related_name='roles',
19
                verbose_name='service',
20
                blank=True,
21
                to='authentic2.Service',
22
                null=True,
23
                on_delete=models.CASCADE,
24
            ),
18 25
            preserve_default=True,
19 26
        ),
20 27
    ]
src/authentic2/a2_rbac/migrations/0008_auto_20150810_1953.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='organizationalunit',
16
            options={'ordering': ('default', 'name'), 'verbose_name': 'organizational unit', 'verbose_name_plural': 'organizational units'},
16
            options={
17
                'ordering': ('default', 'name'),
18
                'verbose_name': 'organizational unit',
19
                'verbose_name_plural': 'organizational units',
20
            },
17 21
        ),
18 22
    ]
src/authentic2/a2_rbac/migrations/0009_partial_unique_index_on_permission.py
4 4
from django.db import models, migrations
5 5
from authentic2.migrations import CreatePartialIndexes
6 6

  
7

  
7 8
class Migration(migrations.Migration):
8 9

  
9 10
    dependencies = [
......
11 12
    ]
12 13

  
13 14
    operations = [
14
        CreatePartialIndexes('Permission', 'a2_rbac_permission', 'a2_rbac_permission_null_ou_unique_idx',
15
                             ('ou_id',), ('operation_id', 'target_ct_id', 'target_id'))
15
        CreatePartialIndexes(
16
            'Permission',
17
            'a2_rbac_permission',
18
            'a2_rbac_permission_null_ou_unique_idx',
19
            ('ou_id',),
20
            ('operation_id', 'target_ct_id', 'target_id'),
21
        )
16 22
    ]
src/authentic2/a2_rbac/migrations/0010_auto_20160209_1417.py
6 6

  
7 7

  
8 8
def deduplicate_admin_roles(apps, schema_editor):
9
    '''Find duplicated admin roles, only keep the one with the lowest id and
10
       copy all members, parent and children of other duplicated roles to it,
11
       then delete duplicates with greater id.
12
    '''
9
    """Find duplicated admin roles, only keep the one with the lowest id and
10
    copy all members, parent and children of other duplicated roles to it,
11
    then delete duplicates with greater id.
12
    """
13 13
    Role = apps.get_model('a2_rbac', 'Role')
14 14
    RoleParenting = apps.get_model('a2_rbac', 'RoleParenting')
15
    qs = Role.objects.filter(admin_scope_ct__isnull=False,
16
                             admin_scope_id__isnull=False).order_by('id')
15
    qs = Role.objects.filter(admin_scope_ct__isnull=False, admin_scope_id__isnull=False).order_by('id')
17 16

  
18 17
    roles = defaultdict(lambda: [])
19 18
    for role in qs:
......
26 25
        children = set()
27 26
        for role in duplicates:
28 27
            members |= set(role.members.all())
29
            parents |= set(
30
                rp.parent for rp in RoleParenting.objects.filter(child=role, direct=True))
31
            children |= set(
32
                rp.child for rp in RoleParenting.objects.filter(parent=role, direct=True))
28
            parents |= set(rp.parent for rp in RoleParenting.objects.filter(child=role, direct=True))
29
            children |= set(rp.child for rp in RoleParenting.objects.filter(parent=role, direct=True))
33 30
        duplicates[0].members = members
34 31
        for parent in parents:
35
            RoleParenting.objects.get_or_crate(
36
                parent=parent,
37
                child=duplicates[0],
38
                direct=True)
32
            RoleParenting.objects.get_or_crate(parent=parent, child=duplicates[0], direct=True)
39 33
        for child in children:
40
            RoleParenting.objects.get_or_create(
41
                parent=duplicates[0],
42
                child=child,
43
                direct=True)
34
            RoleParenting.objects.get_or_create(parent=duplicates[0], child=child, direct=True)
44 35
        for role in duplicates[1:]:
45 36
            role.delete()
46 37

  
src/authentic2/a2_rbac/migrations/0014_auto_20170711_1024.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='organizationalunit',
16
            options={'ordering': ('-default', 'name'), 'verbose_name': 'organizational unit', 'verbose_name_plural': 'organizational units'},
16
            options={
17
                'ordering': ('-default', 'name'),
18
                'verbose_name': 'organizational unit',
19
                'verbose_name_plural': 'organizational units',
20
            },
17 21
        ),
18 22
    ]
src/authentic2/a2_rbac/migrations/0016_auto_20171208_1429.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='organizationalunit',
16
            options={'ordering': ('name',), 'verbose_name': 'organizational unit', 'verbose_name_plural': 'organizational units'},
16
            options={
17
                'ordering': ('name',),
18
                'verbose_name': 'organizational unit',
19
                'verbose_name_plural': 'organizational units',
20
            },
17 21
        ),
18 22
    ]
src/authentic2/a2_rbac/migrations/0018_organizationalunit_user_add_password_policy.py
14 14
        migrations.AddField(
15 15
            model_name='organizationalunit',
16 16
            name='user_add_password_policy',
17
            field=models.IntegerField(default=0, verbose_name='User creation password policy', choices=[(0, 'Send reset link'), (1, 'Manual password definition')]),
17
            field=models.IntegerField(
18
                default=0,
19
                verbose_name='User creation password policy',
20
                choices=[(0, 'Send reset link'), (1, 'Manual password definition')],
21
            ),
18 22
        ),
19 23
    ]
src/authentic2/a2_rbac/migrations/0020_partial_unique_index_on_name.py
12 12
    ]
13 13

  
14 14
    operations = [
15
        CreatePartialIndexes('Role', 'a2_rbac_role', 'a2_rbac_role_name_unique_idx',
16
                             ('ou_id',), ('name',),
17
                             null_columns=('admin_scope_ct_id',)),
15
        CreatePartialIndexes(
16
            'Role',
17
            'a2_rbac_role',
18
            'a2_rbac_role_name_unique_idx',
19
            ('ou_id',),
20
            ('name',),
21
            null_columns=('admin_scope_ct_id',),
22
        ),
18 23
    ]
src/authentic2/a2_rbac/migrations/0021_auto_20200317_1514.py
16 16
        migrations.AlterField(
17 17
            model_name='organizationalunit',
18 18
            name='uuid',
19
            field=models.CharField(default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'),
19
            field=models.CharField(
20
                default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
21
            ),
20 22
        ),
21 23
        migrations.AlterField(
22 24
            model_name='role',
23 25
            name='uuid',
24
            field=models.CharField(default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'),
26
            field=models.CharField(
27
                default=django_rbac.utils.get_hex_uuid, max_length=32, unique=True, verbose_name='uuid'
28
            ),
25 29
        ),
26 30
    ]
src/authentic2/a2_rbac/migrations/0022_auto_20200402_1101.py
16 16
        migrations.AddField(
17 17
            model_name='organizationalunit',
18 18
            name='clean_unused_accounts_alert',
19
            field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(30, 'Ensure that this value is greater than 30 days, or leave blank for deactivating.')], verbose_name='Days after which the user receives an account deletion alert'),
19
            field=models.PositiveIntegerField(
20
                blank=True,
21
                null=True,
22
                validators=[
23
                    django.core.validators.MinValueValidator(
24
                        30, 'Ensure that this value is greater than 30 days, or leave blank for deactivating.'
25
                    )
26
                ],
27
                verbose_name='Days after which the user receives an account deletion alert',
28
            ),
20 29
        ),
21 30
        migrations.AddField(
22 31
            model_name='organizationalunit',
23 32
            name='clean_unused_accounts_deletion',
24
            field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(30, 'Ensure that this value is greater than 30 days, or leave blank for deactivating.')], verbose_name='Delay in days before cleaning unused accounts'),
33
            field=models.PositiveIntegerField(
34
                blank=True,
35
                null=True,
36
                validators=[
37
                    django.core.validators.MinValueValidator(
38
                        30, 'Ensure that this value is greater than 30 days, or leave blank for deactivating.'
39
                    )
40
                ],
41
                verbose_name='Delay in days before cleaning unused accounts',
42
            ),
25 43
        ),
26 44
    ]
src/authentic2/a2_rbac/migrations/0024_fix_self_admin_perm.py
39 39
        ('a2_rbac', '0023_role_can_manage_members'),
40 40
    ]
41 41

  
42
    operations = [
43
        migrations.RunPython(update_self_administration_perm, migrations.RunPython.noop)
44
    ]
42
    operations = [migrations.RunPython(update_self_administration_perm, migrations.RunPython.noop)]
src/authentic2/a2_rbac/models.py
23 23
from django.db import models
24 24
from django.contrib.contenttypes.models import ContentType
25 25

  
26
from django_rbac.models import (RoleAbstractBase, PermissionAbstractBase,
27
                                OrganizationalUnitAbstractBase, RoleParentingAbstractBase, VIEW_OP,
28
                                Operation)
26
from django_rbac.models import (
27
    RoleAbstractBase,
28
    PermissionAbstractBase,
29
    OrganizationalUnitAbstractBase,
30
    RoleParentingAbstractBase,
31
    VIEW_OP,
32
    Operation,
33
)
29 34
from django_rbac import utils as rbac_utils
30 35

  
31 36
from authentic2.decorators import errorcollector
32 37

  
33 38
try:
34
    from django.contrib.contenttypes.fields import GenericForeignKey, \
35
        GenericRelation
39
    from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
36 40
except ImportError:
37 41
    # Django < 1.8
38
    from django.contrib.contenttypes.generic import GenericForeignKey, \
39
        GenericRelation
42
    from django.contrib.contenttypes.generic import GenericForeignKey, GenericRelation
40 43

  
41 44
from authentic2.decorators import GlobalCache
42 45

  
......
53 56
        (MANUAL_PASSWORD_POLICY, _('Manual password definition')),
54 57
    )
55 58

  
56
    PolicyValue = namedtuple('PolicyValue', [
57
        'generate_password', 'reset_password_at_next_login',
58
        'send_mail', 'send_password_reset'])
59
    PolicyValue = namedtuple(
60
        'PolicyValue',
61
        ['generate_password', 'reset_password_at_next_login', 'send_mail', 'send_password_reset'],
62
    )
59 63

  
60 64
    USER_ADD_PASSWD_POLICY_VALUES = {
61 65
        RESET_LINK_POLICY: PolicyValue(False, False, False, True),
62 66
        MANUAL_PASSWORD_POLICY: PolicyValue(False, False, True, False),
63 67
    }
64 68

  
65
    username_is_unique = models.BooleanField(
66
        blank=True,
67
        default=False,
68
        verbose_name=_('Username is unique'))
69
    email_is_unique = models.BooleanField(
70
        blank=True,
71
        default=False,
72
        verbose_name=_('Email is unique'))
73
    default = fields.UniqueBooleanField(
74
        verbose_name=_('Default organizational unit'))
69
    username_is_unique = models.BooleanField(blank=True, default=False, verbose_name=_('Username is unique'))
70
    email_is_unique = models.BooleanField(blank=True, default=False, verbose_name=_('Email is unique'))
71
    default = fields.UniqueBooleanField(verbose_name=_('Default organizational unit'))
75 72

  
76
    validate_emails = models.BooleanField(
77
        blank=True,
78
        default=False,
79
        verbose_name=_('Validate emails'))
73
    validate_emails = models.BooleanField(blank=True, default=False, verbose_name=_('Validate emails'))
80 74

  
81
    show_username = models.BooleanField(
82
        blank=True,
83
        default=True,
84
        verbose_name=_('Show username'))
75
    show_username = models.BooleanField(blank=True, default=True, verbose_name=_('Show username'))
85 76

  
86
    admin_perms = GenericRelation(rbac_utils.get_permission_model_name(),
87
                                  content_type_field='target_ct',
88
                                  object_id_field='target_id')
77
    admin_perms = GenericRelation(
78
        rbac_utils.get_permission_model_name(), content_type_field='target_ct', object_id_field='target_id'
79
    )
89 80

  
90
    user_can_reset_password = models.NullBooleanField(
91
        verbose_name=_('Users can reset password'))
81
    user_can_reset_password = models.NullBooleanField(verbose_name=_('Users can reset password'))
92 82

  
93 83
    user_add_password_policy = models.IntegerField(
94
        verbose_name=_('User creation password policy'),
95
        choices=USER_ADD_PASSWD_POLICY_CHOICES,
96
        default=0)
84
        verbose_name=_('User creation password policy'), choices=USER_ADD_PASSWD_POLICY_CHOICES, default=0
85
    )
97 86

  
98 87
    clean_unused_accounts_alert = models.PositiveIntegerField(
99 88
        verbose_name=_('Days after which the user receives an account deletion alert'),
100
        validators=[MinValueValidator(
101
            30, _('Ensure that this value is greater than 30 days, or leave blank for deactivating.')
102
        )],
89
        validators=[
90
            MinValueValidator(
91
                30, _('Ensure that this value is greater than 30 days, or leave blank for deactivating.')
92
            )
93
        ],
103 94
        null=True,
104
        blank=True)
95
        blank=True,
96
    )
105 97

  
106 98
    clean_unused_accounts_deletion = models.PositiveIntegerField(
107 99
        verbose_name=_('Delay in days before cleaning unused accounts'),
108
        validators=[MinValueValidator(
109
            30, _('Ensure that this value is greater than 30 days, or leave blank for deactivating.')
110
        )],
100
        validators=[
101
            MinValueValidator(
102
                30, _('Ensure that this value is greater than 30 days, or leave blank for deactivating.')
103
            )
104
        ],
111 105
        null=True,
112
        blank=True)
106
        blank=True,
107
    )
113 108

  
114 109
    objects = managers.OrganizationalUnitManager()
115 110

  
......
130 125
            if self.pk:
131 126
                qs = qs.exclude(pk=self.pk)
132 127
            qs.update(default=None)
133
        if self.pk and not self.default \
134
           and self.__class__.objects.get(pk=self.pk).default:
135
            raise ValidationError(_('You cannot unset this organizational '
136
                                    'unit as the default, but you can set '
137
                                    'another one as the default.'))
128
        if self.pk and not self.default and self.__class__.objects.get(pk=self.pk).default:
129
            raise ValidationError(
130
                _(
131
                    'You cannot unset this organizational '
132
                    'unit as the default, but you can set '
133
                    'another one as the default.'
134
                )
135
            )
138 136
        if bool(self.clean_unused_accounts_alert) ^ bool(self.clean_unused_accounts_deletion):
139 137
            raise ValidationError(_('Deletion and alert delays must be set together.'))
140
        if self.clean_unused_accounts_alert and \
141
                self.clean_unused_accounts_alert >= self.clean_unused_accounts_deletion:
138
        if (
139
            self.clean_unused_accounts_alert
140
            and self.clean_unused_accounts_alert >= self.clean_unused_accounts_deletion
141
        ):
142 142
            raise ValidationError(_('Deletion alert delay must be less than actual deletion delay.'))
143 143
        super(OrganizationalUnit, self).clean()
144 144

  
145 145
    def get_admin_role(self):
146
        '''Get or create the generic admin role for this organizational
147
           unit.
148
        '''
146
        """Get or create the generic admin role for this organizational
147
        unit.
148
        """
149 149
        name = _('Managers of "{ou}"').format(ou=self)
150 150
        slug = '_a2-managers-of-{ou.slug}'.format(ou=self)
151 151
        return Role.objects.get_admin_role(
152
            instance=self, name=name, slug=slug, operation=VIEW_OP,
153
            update_name=True, update_slug=True, create=True)
152
            instance=self,
153
            name=name,
154
            slug=slug,
155
            operation=VIEW_OP,
156
            update_name=True,
157
            update_slug=True,
158
            create=True,
159
        )
154 160

  
155 161
    def delete(self, *args, **kwargs):
156 162
        Permission.objects.filter(ou=self).delete()
......
166 172

  
167 173
    def export_json(self):
168 174
        return {
169
            'uuid': self.uuid, 'slug': self.slug, 'name': self.name,
170
            'description': self.description, 'default': self.default,
175
            'uuid': self.uuid,
176
            'slug': self.slug,
177
            'name': self.name,
178
            'description': self.description,
179
            'default': self.default,
171 180
            'email_is_unique': self.email_is_unique,
172 181
            'username_is_unique': self.username_is_unique,
173
            'validate_emails': self.validate_emails
182
            'validate_emails': self.validate_emails,
174 183
        }
175 184

  
176 185
    def __str__(self):
......
185 194
        verbose_name = _('permission')
186 195
        verbose_name_plural = _('permissions')
187 196

  
188
    mirror_roles = GenericRelation(rbac_utils.get_role_model_name(),
189
                                   content_type_field='admin_scope_ct',
190
                                   object_id_field='admin_scope_id')
197
    mirror_roles = GenericRelation(
198
        rbac_utils.get_role_model_name(),
199
        content_type_field='admin_scope_ct',
200
        object_id_field='admin_scope_id',
201
    )
191 202

  
192 203

  
193 204
Permission._meta.natural_key = [
......
202 213
        null=True,
203 214
        blank=True,
204 215
        verbose_name=_('administrative scope content type'),
205
        on_delete=models.CASCADE)
216
        on_delete=models.CASCADE,
217
    )
206 218
    admin_scope_id = models.PositiveIntegerField(
207
        verbose_name=_('administrative scope id'),
208
        null=True,
209
        blank=True)
210
    admin_scope = GenericForeignKey(
211
        'admin_scope_ct',
212
        'admin_scope_id')
219
        verbose_name=_('administrative scope id'), null=True, blank=True
220
    )
221
    admin_scope = GenericForeignKey('admin_scope_ct', 'admin_scope_id')
213 222
    service = models.ForeignKey(
214 223
        to='authentic2.Service',
215 224
        verbose_name=_('service'),
216 225
        null=True,
217 226
        blank=True,
218 227
        related_name='roles',
219
        on_delete=models.CASCADE)
220
    external_id = models.TextField(
221
        verbose_name=_('external id'),
222
        blank=True,
223
        db_index=True)
228
        on_delete=models.CASCADE,
229
    )
230
    external_id = models.TextField(verbose_name=_('external id'), blank=True, db_index=True)
224 231

  
225
    admin_perms = GenericRelation(rbac_utils.get_permission_model_name(),
226
                                  content_type_field='target_ct',
227
                                  object_id_field='target_id')
232
    admin_perms = GenericRelation(
233
        rbac_utils.get_permission_model_name(), content_type_field='target_ct', object_id_field='target_id'
234
    )
228 235

  
229 236
    can_manage_members = models.BooleanField(
230
        default=True,
231
        verbose_name=_('Allow adding or deleting role members'))
237
        default=True, verbose_name=_('Allow adding or deleting role members')
238
    )
232 239

  
233 240
    def get_admin_role(self, create=True):
234 241
        from . import utils
......
240 247

  
241 248
        admin_role = self.__class__.objects.get_admin_role(
242 249
            self,
243
            name=_('Managers of role "{role}"').format(
244
                role=six.text_type(self)),
245
            slug='_a2-managers-of-role-{role}'.format(
246
                role=slugify(six.text_type(self))),
250
            name=_('Managers of role "{role}"').format(role=six.text_type(self)),
251
            slug='_a2-managers-of-role-{role}'.format(role=slugify(six.text_type(self))),
247 252
            permissions=(view_user_perm,),
248 253
            self_administered=True,
249 254
            update_name=True,
250 255
            update_slug=True,
251 256
            create=create,
252
            operation=MANAGE_MEMBERS_OP)
257
            operation=MANAGE_MEMBERS_OP,
258
        )
253 259
        return admin_role
254 260

  
255 261
    def validate_unique(self, exclude=None):
......
294 300
            operation=operation,
295 301
            target_ct=ContentType.objects.get_for_model(self),
296 302
            target_id=self.pk,
297
            ou__is_null=True)
303
            ou__is_null=True,
304
        )
298 305
        return self.permissions.filter(pk=self_perm.pk).exists()
299 306

  
300 307
    def add_self_administration(self, op=None):
......
304 311
        Permission = rbac_utils.get_permission_model()
305 312
        operation = rbac_utils.get_operation(op)
306 313
        self_perm, created = Permission.objects.get_or_create(
307
            operation=operation,
308
            target_ct=ContentType.objects.get_for_model(self),
309
            target_id=self.pk)
314
            operation=operation, target_ct=ContentType.objects.get_for_model(self), target_id=self.pk
315
        )
310 316
        self.permissions.through.objects.get_or_create(role=self, permission=self_perm)
311 317
        return self_perm
312 318

  
......
318 324
    class Meta:
319 325
        verbose_name = _('role')
320 326
        verbose_name_plural = _('roles')
321
        ordering = ('ou', 'service', 'name',)
322
        unique_together = (
323
            ('admin_scope_ct', 'admin_scope_id'),
327
        ordering = (
328
            'ou',
329
            'service',
330
            'name',
324 331
        )
332
        unique_together = (('admin_scope_ct', 'admin_scope_id'),)
325 333

  
326 334
    def natural_key(self):
327 335
        return [
......
344 352

  
345 353
    def export_json(self, attributes=False, parents=False, permissions=False):
346 354
        d = {
347
            'uuid': self.uuid, 'slug': self.slug, 'name': self.name,
348
            'description': self.description, 'external_id': self.external_id,
355
            'uuid': self.uuid,
356
            'slug': self.slug,
357
            'name': self.name,
358
            'description': self.description,
359
            'external_id': self.external_id,
349 360
            'ou': self.ou and self.ou.natural_key_json(),
350
            'service': self.service and self.service.natural_key_json()
361
            'service': self.service and self.service.natural_key_json(),
351 362
        }
352 363

  
353 364
        if attributes:
......
383 394
        verbose_name_plural = _('role parenting relations')
384 395

  
385 396
    def __str__(self):
386
        return u'{0} {1}> {2}'.format(self.parent.name, '-' if self.direct else '~',
387
                                      self.child.name)
397
        return u'{0} {1}> {2}'.format(self.parent.name, '-' if self.direct else '~', self.child.name)
388 398

  
389 399

  
390 400
class RoleAttribute(models.Model):
391
    KINDS = (
392
        ('string', _('string')),
393
    )
401
    KINDS = (('string', _('string')),)
394 402
    role = models.ForeignKey(
395
        to=Role,
396
        verbose_name=_('role'),
397
        related_name='attributes',
398
        on_delete=models.CASCADE)
399
    name = models.CharField(
400
        max_length=64,
401
        verbose_name=_('name'))
402
    kind = models.CharField(
403
        max_length=32,
404
        choices=KINDS,
405
        verbose_name=_('kind'))
406
    value = models.TextField(
407
        verbose_name=_('value'))
403
        to=Role, verbose_name=_('role'), related_name='attributes', on_delete=models.CASCADE
404
    )
405
    name = models.CharField(max_length=64, verbose_name=_('name'))
406
    kind = models.CharField(max_length=32, choices=KINDS, verbose_name=_('kind'))
407
    value = models.TextField(verbose_name=_('value'))
408 408

  
409 409
    class Meta:
410
        verbose_name = ('role attribute')
410
        verbose_name = 'role attribute'
411 411
        verbose_name_plural = _('role attributes')
412
        unique_together = (
413
            ('role', 'name', 'kind', 'value'),
414
        )
412
        unique_together = (('role', 'name', 'kind', 'value'),)
415 413

  
416 414
    def to_json(self):
417 415
        return {'name': self.name, 'kind': self.kind, 'value': self.value}
418 416

  
419 417

  
420
GenericRelation(Permission,
421
                content_type_field='target_ct',
422
                object_id_field='target_id').contribute_to_class(ContentType, 'admin_perms')
418
GenericRelation(Permission, content_type_field='target_ct', object_id_field='target_id').contribute_to_class(
419
    ContentType, 'admin_perms'
420
)
423 421

  
424 422

  
425 423
CHANGE_PASSWORD_OP = Operation.register(name=_('Change password'), slug='change_password')
......
427 425
ACTIVATE_OP = Operation.register(name=_('Activation'), slug='activate')
428 426
CHANGE_EMAIL_OP = Operation.register(name=pgettext_lazy('operation', 'Change email'), slug='change_email')
429 427
MANAGE_MEMBERS_OP = Operation.register(name=_('Manage role members'), slug='manage_members')
430
MANAGE_AUTHORIZATIONS_OP = Operation.register(
431
    name=_('Manage service consents'), slug='manage_authorizations')
428
MANAGE_AUTHORIZATIONS_OP = Operation.register(name=_('Manage service consents'), slug='manage_authorizations')
src/authentic2/a2_rbac/signal_handlers.py
25 25
from django_rbac.managers import defer_update_transitive_closure
26 26

  
27 27

  
28
def create_default_ou(app_config, verbosity=2, interactive=True,
29
                      using=DEFAULT_DB_ALIAS, **kwargs):
28
def create_default_ou(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
30 29
    if not router.allow_migrate(using, get_ou_model()):
31 30
        return
32 31
    # be sure new objects names are localized using the default locale
......
40 39
            defaults={
41 40
                'default': True,
42 41
                'name': _('Default organizational unit'),
43
            })
42
            },
43
        )
44 44
        # Update all existing models having an ou field to the default ou
45 45
        for app in apps.get_app_configs():
46 46
            for model in app.get_models():
......
50 50
                model.objects.filter(ou__isnull=True).update(ou=default_ou)
51 51

  
52 52

  
53
def post_migrate_update_rbac(app_config, verbosity=2, interactive=True,
54
                             using=DEFAULT_DB_ALIAS, **kwargs):
53
def post_migrate_update_rbac(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
55 54
    # be sure new objects names are localized using the default locale
56 55
    from .management import update_ous_admin_roles, update_content_types_roles
57 56

  
......
84 83
    get_role_model().objects.filter(service=instance).update(ou=instance.ou)
85 84

  
86 85

  
87
def create_default_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS,
88
                               **kwargs):
89
    from .models import (CHANGE_PASSWORD_OP, RESET_PASSWORD_OP, ACTIVATE_OP, CHANGE_EMAIL_OP,
90
                         MANAGE_MEMBERS_OP, MANAGE_AUTHORIZATIONS_OP)
86
def create_default_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs):
87
    from .models import (
88
        CHANGE_PASSWORD_OP,
89
        RESET_PASSWORD_OP,
90
        ACTIVATE_OP,
91
        CHANGE_EMAIL_OP,
92
        MANAGE_MEMBERS_OP,
93
        MANAGE_AUTHORIZATIONS_OP,
94
    )
91 95

  
92 96
    if not router.allow_migrate(using, get_ou_model()):
93 97
        return
src/authentic2/a2_rbac/utils.py
37 37
        operation=rbac_utils.get_operation(VIEW_OP),
38 38
        target_ct=ContentType.objects.get_for_model(ContentType),
39 39
        target_id=ContentType.objects.get_for_model(User).pk,
40
        ou__isnull=ou is None, ou=ou)
40
        ou__isnull=ou is None,
41
        ou=ou,
42
    )
41 43
    return view_user_perm
42 44

  
43 45

  
......
48 50
            operation=rbac_utils.get_operation(SEARCH_OP),
49 51
            target_ct=ContentType.objects.get_for_model(ou),
50 52
            target_id=ou.pk,
51
            ou__isnull=True)
53
            ou__isnull=True,
54
        )
52 55
    else:
53 56
        OU = rbac_utils.get_ou_model()
54 57
        Permission = rbac_utils.get_permission_model()
......
56 59
            operation=rbac_utils.get_operation(SEARCH_OP),
57 60
            target_ct=ContentType.objects.get_for_model(ContentType),
58 61
            target_id=ContentType.objects.get_for_model(OU).pk,
59
            ou__isnull=True)
62
            ou__isnull=True,
63
        )
60 64
    return view_ou_perm
61 65

  
62 66

  
......
67 71
        operation=rbac_utils.get_operation(models.MANAGE_AUTHORIZATIONS_OP),
68 72
        target_ct=ContentType.objects.get_for_model(ContentType),
69 73
        target_id=ContentType.objects.get_for_model(User).pk,
70
        ou__isnull=ou is None, ou=ou)
74
        ou__isnull=ou is None,
75
        ou=ou,
76
    )
71 77
    return manage_authorizations_user_perm
src/authentic2/admin.py
30 30
from django.contrib.auth.forms import ReadOnlyPasswordHashField
31 31

  
32 32
from .nonce.models import Nonce
33
from . import (models, app_settings, decorators, attribute_kinds,
34
               utils)
33
from . import models, app_settings, decorators, attribute_kinds, utils
35 34
from .forms.profile import BaseUserForm, modelform_factory
36 35
from .custom_user.models import User, DeletedUser
37 36

  
38 37

  
39 38
def cleanup_action(modeladmin, request, queryset):
40 39
    queryset.cleanup()
40

  
41

  
41 42
cleanup_action.short_description = _('Cleanup expired objects')
42 43

  
43 44

  
......
52 53
class NonceModelAdmin(admin.ModelAdmin):
53 54
    list_display = ("value", "context", "not_on_or_after")
54 55

  
56

  
55 57
admin.site.register(Nonce, NonceModelAdmin)
56 58

  
57 59

  
58 60
class AttributeValueAdmin(admin.ModelAdmin):
59 61
    list_display = ('content_type', 'owner', 'attribute', 'content')
60 62

  
63

  
61 64
admin.site.register(models.AttributeValue, AttributeValueAdmin)
62 65

  
63 66

  
64 67
class LogoutUrlAdmin(admin.ModelAdmin):
65 68
    list_display = ('provider', 'logout_url', 'logout_use_iframe', 'logout_use_iframe_timeout')
66 69

  
70

  
67 71
admin.site.register(models.LogoutUrl, LogoutUrlAdmin)
68 72

  
69 73

  
......
73 77
    date_hierarchy = 'when'
74 78
    search_fields = ('who', 'nonce', 'how')
75 79

  
80

  
76 81
admin.site.register(models.AuthenticationEvent, AuthenticationEventAdmin)
77 82

  
78 83

  
......
82 87
    date_hierarchy = 'created'
83 88
    search_fields = ('user__username', 'source', 'external_id')
84 89

  
90

  
85 91
admin.site.register(models.UserExternalId, UserExternalIdAdmin)
86 92

  
87 93

  
......
92 98
)
93 99

  
94 100
if settings.SESSION_ENGINE in DB_SESSION_ENGINES:
101

  
95 102
    class SessionAdmin(admin.ModelAdmin):
96 103
        def _session_data(self, obj):
97 104
            return pprint.pformat(obj.get_decoded()).replace('\n', '<br>\n')
105

  
98 106
        _session_data.allow_tags = True
99 107
        _session_data.short_description = _('session data')
100 108
        list_display = ['session_key', 'ips', 'user', '_session_data', 'expire_date']
......
107 115
            content = session.get_decoded()
108 116
            ips = content.get('ips', set())
109 117
            return ', '.join(ips)
118

  
110 119
        ips.short_description = _('IP adresses')
111 120

  
112 121
        def user(self, session):
113 122
            from django.contrib import auth
114 123
            from django.contrib.auth import models as auth_models
124

  
115 125
            content = session.get_decoded()
116 126
            if auth.SESSION_KEY not in content:
117 127
                return
......
125 135
            except Exception:
126 136
                user = _('deleted user %r') % user_id
127 137
            return user
138

  
128 139
        user.short_description = _('user')
129 140

  
130 141
        def clear_expired(self, request, queryset):
131 142
            queryset.filter(expire_date__lt=timezone.now()).delete()
143

  
132 144
        clear_expired.short_description = _('clear expired sessions')
133 145

  
134 146
    admin.site.register(Session, SessionAdmin)
......
140 152
    parameter_name = 'external'
141 153

  
142 154
    def lookups(self, request, model_admin):
143
        return (
144
            ('1', _('Yes')),
145
            ('0', _('No'))
146
        )
155
        return (('1', _('Yes')), ('0', _('No')))
147 156

  
148 157
    def queryset(self, request, queryset):
149 158
        """
......
194 203

  
195 204
    password = ReadOnlyPasswordHashField(
196 205
        label=_("Password"),
197
        help_text=_("Raw passwords are not stored, so there is no way to see "
198
                    "this user's password, but you can change the password "
199
                    "using <a href=\"password/\">this form</a>."))
206
        help_text=_(
207
            "Raw passwords are not stored, so there is no way to see "
208
            "this user's password, but you can change the password "
209
            "using <a href=\"password/\">this form</a>."
210
        ),
211
    )
200 212

  
201 213
    class Meta:
202 214
        model = User
......
230 242
    A form that creates a user, with no privileges, from the given username and
231 243
    password.
232 244
    """
245

  
233 246
    error_messages = {
234 247
        'password_mismatch': _("The two password fields didn't match."),
235 248
        'missing_credential': _("You must at least give a username or an email to your user"),
236 249
    }
237
    password1 = forms.CharField(
238
        label=_("Password"),
239
        widget=forms.PasswordInput)
250
    password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
240 251
    password2 = forms.CharField(
241 252
        label=_("Password confirmation"),
242 253
        widget=forms.PasswordInput,
243
        help_text=_("Enter the same password as above, for verification."))
254
        help_text=_("Enter the same password as above, for verification."),
255
    )
244 256

  
245 257
    class Meta:
246 258
        model = User
......
275 287
    fieldsets = (
276 288
        (None, {'fields': ('uuid', 'ou', 'password')}),
277 289
        (_('Personal info'), {'fields': ('username', 'first_name', 'last_name', 'email')}),
278
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
279
                                       'groups')}),
290
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups')}),
280 291
        (_('Important dates'), {'fields': ('last_login', 'date_joined', 'deactivation')}),
281 292
    )
282 293
    add_fieldsets = (
283
        (None, {
284
            'classes': ('wide',),
285
            'fields': ('ou', 'username', 'first_name', 'last_name', 'email', 'password1', 'password2')}),
294
        (
295
            None,
296
            {
297
                'classes': ('wide',),
298
                'fields': ('ou', 'username', 'first_name', 'last_name', 'email', 'password1', 'password2'),
299
            },
300
        ),
286 301
    )
287 302
    readonly_fields = ('uuid',)
288 303
    list_filter = UserAdmin.list_filter + (UserRealmListFilter, ExternalUserListFilter)
......
303 318
            fieldsets = list(fieldsets)
304 319
            fieldsets.insert(
305 320
                insertion_idx,
306
                (_('Attributes'), {'fields': [at.name for at in qs if at.name not in
307
                                              ['first_name', 'last_name']]}))
321
                (
322
                    _('Attributes'),
323
                    {'fields': [at.name for at in qs if at.name not in ['first_name', 'last_name']]},
324
                ),
325
            )
308 326
        return fieldsets
309 327

  
310 328
    def get_form(self, request, obj=None, **kwargs):
311
        self.form = modelform_factory(self.model, form=UserChangeForm,
312
                                      fields=models.Attribute.objects.values_list('name',
313
                                                                                  flat=True))
314
        self.add_form = modelform_factory(self.model, form=UserCreationForm,
315
                                          fields=models.Attribute.objects.filter(required=True)
316
                                          .values_list('name', flat=True))
329
        self.form = modelform_factory(
330
            self.model, form=UserChangeForm, fields=models.Attribute.objects.values_list('name', flat=True)
331
        )
332
        self.add_form = modelform_factory(
333
            self.model,
334
            form=UserCreationForm,
335
            fields=models.Attribute.objects.filter(required=True).values_list('name', flat=True),
336
        )
317 337
        if 'fields' in kwargs:
318 338
            fields = kwargs.pop('fields')
319 339
        else:
......
332 352
        timestamp = timezone.now()
333 353
        for user in queryset:
334 354
            user.mark_as_inactive(timestamp=timestamp)
355

  
335 356
    mark_as_inactive.short_description = _('Mark as inactive')
336 357

  
358

  
337 359
admin.site.register(User, AuthenticUserAdmin)
338 360

  
339 361

  
......
355 377

  
356 378
class AttributeAdmin(admin.ModelAdmin):
357 379
    form = AttributeForm
358
    list_display = ('label', 'disabled', 'name', 'kind', 'order', 'required',
359
                    'asked_on_registration', 'user_editable', 'user_visible')
380
    list_display = (
381
        'label',
382
        'disabled',
383
        'name',
384
        'kind',
385
        'order',
386
        'required',
387
        'asked_on_registration',
388
        'user_editable',
389
        'user_visible',
390
    )
360 391
    list_editable = ('order',)
361 392

  
362 393
    def get_queryset(self, request):
363 394
        return self.model.all_objects.all()
364 395

  
396

  
365 397
admin.site.register(models.Attribute, AttributeAdmin)
366 398

  
367 399

  
......
370 402
    date_hierarchy = 'deleted'
371 403
    search_fields = ['=old_user_id', '^old_uuid', 'old_email']
372 404

  
405

  
373 406
admin.site.register(DeletedUser, DeletedUserAdmin)
374 407

  
375 408

  
......
377 410
def login(request, extra_context=None):
378 411
    return utils.redirect_to_login(request, login_url=utils.get_manager_login_url())
379 412

  
413

  
380 414
admin.site.login = login
381 415

  
382 416

  
......
384 418
def logout(request, extra_context=None):
385 419
    return utils.redirect_to_login(request, login_url='auth_logout')
386 420

  
421

  
387 422
admin.site.logout = logout
388 423

  
389 424
admin.site.register(models.PasswordReset)
src/authentic2/api_mixins.py
55 55
        except qs.model.DoesNotExist:
56 56
            return None
57 57
        except qs.model.MultipleObjectsReturned:
58
            raise Conflict('retrieved several instances of model %s for key attributes %s' % (
59
                qs.model.__name__, kwargs))
58
            raise Conflict(
59
                'retrieved several instances of model %s for key attributes %s' % (qs.model.__name__, kwargs)
60
            )
60 61

  
61 62
    def _validate_get_keys(self, keys):
62 63
        # Remove many-to-many relationships from validated_data.
src/authentic2/api_urls.py
22 22
    url(r'^register/$', api_views.register, name='a2-api-register'),
23 23
    url(r'^password-change/$', api_views.password_change, name='a2-api-password-change'),
24 24
    url(r'^user/$', api_views.user, name='a2-api-user'),
25
    url(r'^roles/(?P<role_uuid>[\w+]*)/members/(?P<member_uuid>[^/]+)/$', api_views.role_membership,
26
        name='a2-api-role-member'),
27
    url(r'^roles/(?P<role_uuid>[\w+]*)/relationships/members/$', api_views.role_memberships,
28
        name='a2-api-role-members'),
25
    url(
26
        r'^roles/(?P<role_uuid>[\w+]*)/members/(?P<member_uuid>[^/]+)/$',
27
        api_views.role_membership,
28
        name='a2-api-role-member',
29
    ),
30
    url(
31
        r'^roles/(?P<role_uuid>[\w+]*)/relationships/members/$',
32
        api_views.role_memberships,
33
        name='a2-api-role-members',
34
    ),
29 35
    url(r'^check-password/$', api_views.check_password, name='a2-api-check-password'),
30 36
    url(r'^validate-password/$', api_views.validate_password, name='a2-api-validate-password'),
31 37
    url(r'^address-autocomplete/$', api_views.address_autocomplete, name='a2-api-address-autocomplete'),
src/authentic2/api_views.py
46 46
from rest_framework.generics import GenericAPIView
47 47
from rest_framework.response import Response
48 48
from rest_framework import permissions, status, authentication
49
from rest_framework.exceptions import (PermissionDenied, AuthenticationFailed,
50
    ValidationError, NotFound)
49
from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ValidationError, NotFound
51 50
from rest_framework.fields import CreateOnlyDefault
52 51
from authentic2.compat.drf import action
53 52
from rest_framework.authentication import SessionAuthentication
......
61 60

  
62 61
from .passwords import get_password_checker
63 62
from .custom_user.models import User
64
from . import (utils, decorators, attribute_kinds, app_settings, hooks,
65
               api_mixins)
63
from . import utils, decorators, attribute_kinds, app_settings, hooks, api_mixins
66 64
from .models import Attribute, PasswordReset, Service
67 65
from .a2_rbac.utils import get_default_ou
68 66
from .journal_event_types import UserLogin, UserRegistration
......
73 71
if django.VERSION < (2,):
74 72
    import rest_framework.fields
75 73
    from . import validators
74

  
76 75
    rest_framework.fields.ProhibitNullCharactersValidator = validators.ProhibitNullCharactersValidator
77 76
if django.VERSION < (1, 11):
78 77
    authentication.authenticate = utils.authenticate
......
128 127

  
129 128
class RegistrationSerializer(serializers.Serializer):
130 129
    '''Register RPC payload'''
131
    email = serializers.EmailField(
132
        required=False, allow_blank=True)
130

  
131
    email = serializers.EmailField(required=False, allow_blank=True)
133 132
    ou = serializers.SlugRelatedField(
134 133
        queryset=get_ou_model().objects.all(),
135 134
        slug_field='slug',
136 135
        default=get_default_ou,
137
        required=False, allow_null=True)
138
    username = serializers.CharField(
139
        required=False, allow_blank=True)
140
    first_name = serializers.CharField(
141
        required=False, allow_blank=True, default='')
142
    last_name = serializers.CharField(
143
        required=False, allow_blank=True, default='')
144
    password = serializers.CharField(
145
        required=False, allow_null=True)
146
    no_email_validation = serializers.BooleanField(
147
        required=False)
136
        required=False,
137
        allow_null=True,
138
    )
139
    username = serializers.CharField(required=False, allow_blank=True)
140
    first_name = serializers.CharField(required=False, allow_blank=True, default='')
141
    last_name = serializers.CharField(required=False, allow_blank=True, default='')
142
    password = serializers.CharField(required=False, allow_null=True)
143
    no_email_validation = serializers.BooleanField(required=False)
148 144
    return_url = serializers.URLField(required=False, allow_blank=True)
149 145

  
150 146
    def validate(self, data):
......
157 153
            else:
158 154
                authorized = request.user.has_perm(perm)
159 155
            if not authorized:
160
                raise serializers.ValidationError(_('you are not authorized '
161
                                                    'to create users in '
162
                                                    'this ou'))
156
                raise serializers.ValidationError(
157
                    _('you are not authorized ' 'to create users in ' 'this ou')
158
                )
163 159
        User = get_user_model()
164 160
        if ou:
165
            if (app_settings.A2_EMAIL_IS_UNIQUE or
166
                    app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE):
161
            if app_settings.A2_EMAIL_IS_UNIQUE or app_settings.A2_REGISTRATION_EMAIL_IS_UNIQUE:
167 162
                if 'email' not in data:
168
                    raise serializers.ValidationError(
169
                        _('Email is required'))
170
                if User.objects.filter(
171
                        email__iexact=data['email']).exists():
172
                    raise serializers.ValidationError(
173
                        _('Account already exists'))
163
                    raise serializers.ValidationError(_('Email is required'))
164
                if User.objects.filter(email__iexact=data['email']).exists():
165
                    raise serializers.ValidationError(_('Account already exists'))
174 166

  
175 167
            if ou.email_is_unique:
176 168
                if 'email' not in data:
177
                    raise serializers.ValidationError(
178
                        _('Email is required in this ou'))
179
                if User.objects.filter(
180
                        ou=ou, email__iexact=data['email']).exists():
181
                    raise serializers.ValidationError(
182
                        _('Account already exists in this ou'))
183

  
184
            if (app_settings.A2_USERNAME_IS_UNIQUE or
185
                    app_settings.A2_REGISTRATION_USERNAME_IS_UNIQUE):
169
                    raise serializers.ValidationError(_('Email is required in this ou'))
170
                if User.objects.filter(ou=ou, email__iexact=data['email']).exists():
171
                    raise serializers.ValidationError(_('Account already exists in this ou'))
172

  
173
            if app_settings.A2_USERNAME_IS_UNIQUE or app_settings.A2_REGISTRATION_USERNAME_IS_UNIQUE:
186 174
                if 'username' not in data:
187
                    raise serializers.ValidationError(
188
                        _('Username is required'))
189
                if User.objects.filter(
190
                        username=data['username']).exists():
191
                    raise serializers.ValidationError(
192
                        _('Account already exists'))
175
                    raise serializers.ValidationError(_('Username is required'))
176
                if User.objects.filter(username=data['username']).exists():
177
                    raise serializers.ValidationError(_('Account already exists'))
193 178

  
194 179
            if ou.username_is_unique:
195 180
                if 'username' not in data:
196
                    raise serializers.ValidationError(
197
                        _('Username is required in this ou'))
198
                if User.objects.filter(
199
                        ou=ou, username=data['username']).exists():
200
                    raise serializers.ValidationError(
201
                        _('Account already exists in this ou'))
181
                    raise serializers.ValidationError(_('Username is required in this ou'))
182
                if User.objects.filter(ou=ou, username=data['username']).exists():
183
                    raise serializers.ValidationError(_('Account already exists in this ou'))
202 184
        return data
203 185

  
204 186

  
......
218 200

  
219 201

  
220 202
class Register(BaseRpcView):
221
    '''Register the given email, send a mail to the user and return a
222
       validation token.
223

  
224
       A mail will be sent to the user to validate its email. On
225
       validation of the mail the user will be logged and redirected to
226
       `{return_url}?token={token}`. It's the durty of the requesting
227
       service to finish the registration process on its side.
228

  
229
       If email is unique and an account already exist the requesting
230
       must enter in a process of registration through SSO, i.e. ask for
231
       authentication of the user and then finish the registration
232
       process for the received identity.
233
    '''
203
    """Register the given email, send a mail to the user and return a
204
    validation token.
205

  
206
    A mail will be sent to the user to validate its email. On
207
    validation of the mail the user will be logged and redirected to
208
    `{return_url}?token={token}`. It's the durty of the requesting
209
    service to finish the registration process on its side.
210

  
211
    If email is unique and an account already exist the requesting
212
    must enter in a process of registration through SSO, i.e. ask for
213
    authentication of the user and then finish the registration
214
    process for the received identity.
215
    """
216

  
234 217
    permission_classes = (permissions.IsAuthenticated,)
235 218
    serializer_class = RegistrationSerializer
236 219

  
237 220
    def rpc(self, request, serializer):
238 221
        validated_data = serializer.validated_data
239 222
        if not request.user.has_ou_perm('custom_user.add_user', validated_data['ou']):
240
            raise PermissionDenied('You do not have permission to create users in ou %s' %
241
                                   validated_data['ou'].slug)
223
            raise PermissionDenied(
224
                'You do not have permission to create users in ou %s' % validated_data['ou'].slug
225
            )
242 226
        email = validated_data.get('email')
243 227
        registration_data = {}
244 228
        for field in ('first_name', 'last_name', 'password', 'username'):
......
255 239
        final_return_url = None
256 240
        if validated_data.get('return_url'):
257 241
            token = utils.get_hex_uuid()[:16]
258
            final_return_url = utils.make_url(validated_data['return_url'],
259
                                              params={'token': token})
242
            final_return_url = utils.make_url(validated_data['return_url'], params={'token': token})
260 243
        if email and not validated_data.get('no_email_validation'):
261 244

  
262 245
            registration_template = ['authentic2/activation_email']
263 246
            if validated_data['ou']:
264
                registration_template.insert(0, 'authentic2/activation_email_%s' %
265
                                             validated_data['ou'].slug)
247
                registration_template.insert(0, 'authentic2/activation_email_%s' % validated_data['ou'].slug)
266 248

  
267 249
            try:
268
                utils.send_registration_mail(self.request, email,
269
                                             template_names=registration_template,
270
                                             next_url=final_return_url,
271
                                             ou=validated_data['ou'],
272
                                             context=ctx,
273
                                             **registration_data)
250
                utils.send_registration_mail(
251
                    self.request,
252
                    email,
253
                    template_names=registration_template,
254
                    next_url=final_return_url,
255
                    ou=validated_data['ou'],
256
                    context=ctx,
257
                    **registration_data,
258
                )
274 259
            except smtplib.SMTPException as e:
275 260
                response = {
276 261
                    'result': 0,
277
                    'errors': {
278
                        '__all__': ['Mail sending failed']
279
                    },
262
                    'errors': {'__all__': ['Mail sending failed']},
280 263
                    'exception': force_text(e),
281 264
                }
282 265
                response_status = status.HTTP_503_SERVICE_UNAVAILABLE
......
293 276
            last_name = validated_data.get('last_name')
294 277
            password = validated_data.get('password')
295 278
            ou = validated_data.get('ou')
296
            if not email and \
297
               not username and \
298
               not (first_name and last_name):
279
            if not email and not username and not (first_name and last_name):
299 280
                response = {
300 281
                    'result': 0,
301 282
                    'errors': {
302
                        '__all__': ['You must set at least a username, an email or '
303
                                    'a first name and a last name']
283
                        '__all__': [
284
                            'You must set at least a username, an email or ' 'a first name and a last name'
285
                        ]
304 286
                    },
305 287
                }
306 288
                response_status = status.HTTP_400_BAD_REQUEST
307 289
            else:
308
                new_user = User(email=email, username=username, ou=ou, first_name=first_name,
309
                                last_name=last_name)
290
                new_user = User(
291
                    email=email, username=username, ou=ou, first_name=first_name, last_name=last_name
292
                )
310 293
                if password:
311 294
                    new_user.set_password(password)
312 295
                new_user.save()
......
318 301
                }
319 302
                if email:
320 303
                    response['validation_url'] = utils.build_activation_url(
321
                        request, email, next_url=final_return_url, ou=ou, **registration_data)
304
                        request, email, next_url=final_return_url, ou=ou, **registration_data
305
                    )
322 306
                if token:
323 307
                    response['token'] = token
324 308
                response_status = status.HTTP_201_CREATED
325 309
        return response, response_status
326 310

  
311

  
327 312
register = Register.as_view()
328 313

  
329 314

  
330 315
class PasswordChangeSerializer(serializers.Serializer):
331 316
    '''Register RPC payload'''
317

  
332 318
    email = serializers.EmailField()
333 319
    ou = serializers.SlugRelatedField(
334
        queryset=get_ou_model().objects.all(),
335
        slug_field='slug',
336
        required=False, allow_null=True)
337
    old_password = serializers.CharField(
338
        required=True, allow_null=True)
339
    new_password = serializers.CharField(
340
        required=True, allow_null=True)
320
        queryset=get_ou_model().objects.all(), slug_field='slug', required=False, allow_null=True
321
    )
322
    old_password = serializers.CharField(required=True, allow_null=True)
323
    new_password = serializers.CharField(required=True, allow_null=True)
341 324

  
342 325
    def validate(self, data):
343 326
        User = get_user_model()
......
364 347
        serializer.user.save()
365 348
        return {'result': 1}, status.HTTP_200_OK
366 349

  
350

  
367 351
password_change = PasswordChange.as_view()
368 352

  
369 353

  
......
378 362

  
379 363
class BaseUserSerializer(serializers.ModelSerializer):
380 364
    ou = serializers.SlugRelatedField(
381
        queryset=get_ou_model().objects.all(),
382
        slug_field='slug',
383
        required=False, default=get_default_ou)
365
        queryset=get_ou_model().objects.all(), slug_field='slug', required=False, default=get_default_ou
366
    )
384 367
    date_joined = serializers.DateTimeField(read_only=True)
385 368
    last_login = serializers.DateTimeField(read_only=True)
386 369
    dist = serializers.FloatField(read_only=True)
387
    send_registration_email = serializers.BooleanField(write_only=True, required=False,
388
                                                       default=False)
370
    send_registration_email = serializers.BooleanField(write_only=True, required=False, default=False)
389 371
    send_registration_email_next_url = serializers.URLField(write_only=True, required=False)
390 372
    password = serializers.CharField(max_length=128, required=False)
391 373
    force_password_reset = serializers.BooleanField(write_only=True, required=False, default=False)
......
402 384
            else:
403 385
                self.fields[at.name] = at.get_drf_field()
404 386
            self.fields[at.name + '_verified'] = serializers.BooleanField(
405
                source='is_verified.%s' % at.name, required=False)
387
                source='is_verified.%s' % at.name, required=False
388
            )
406 389
        for key in self.fields:
407 390
            if key in app_settings.A2_REQUIRED_FIELDS:
408 391
                self.fields[key].required = True
......
418 401
    def create(self, validated_data):
419 402
        original_data = validated_data.copy()
420 403
        send_registration_email = validated_data.pop('send_registration_email', False)
421
        send_registration_email_next_url = validated_data.pop('send_registration_email_next_url',
422
                                                              None)
404
        send_registration_email_next_url = validated_data.pop('send_registration_email_next_url', None)
423 405
        force_password_reset = validated_data.pop('force_password_reset', False)
424 406

  
425 407
        attributes = validated_data.pop('attributes', {})
......
454 436
            try:
455 437
                utils.send_password_reset_mail(
456 438
                    instance,
457
                    template_names=['authentic2/api_user_create_registration_email',
458
                                    'authentic2/password_reset'],
439
                    template_names=[
440
                        'authentic2/api_user_create_registration_email',
441
                        'authentic2/password_reset',
442
                    ],
459 443
                    request=self.context['request'],
460 444
                    next_url=send_registration_email_next_url,
461 445
                    context={
462 446
                        'data': original_data,
463
                    })
447
                    },
448
                )
464 449
            except smtplib.SMTPException as e:
465
                logging.getLogger(__name__).error(u'registration mail could not be sent to user %s '
466
                                                  'created through API: %s', instance, e)
450
                logging.getLogger(__name__).error(
451
                    u'registration mail could not be sent to user %s ' 'created through API: %s', instance, e
452
                )
467 453
        return instance
468 454

  
469 455
    def update(self, instance, validated_data):
......
479 465
        self.check_perm('custom_user.change_user', instance.ou)
480 466
        if 'ou' in validated_data:
481 467
            self.check_perm('custom_user.change_user', validated_data.get('ou'))
482
        if validated_data.get('email') != instance.email and \
483
                not validated_data.get('email_verified'):
468
        if validated_data.get('email') != instance.email and not validated_data.get('email_verified'):
484 469
            instance.email_verified = False
485 470
        super(BaseUserSerializer, self).update(instance, validated_data)
486 471
        for key, value in attributes.items():
......
523 508
        update_or_create_fields = self.context['view'].request.GET.getlist('update_or_create')
524 509

  
525 510
        already_used = False
526
        if ('email' not in get_or_create_fields
527
                and 'email' not in update_or_create_fields
528
                and data.get('email')
529
                and (not self.instance or data.get('email') != self.instance.email)):
530
            if app_settings.A2_EMAIL_IS_UNIQUE and qs.filter(
531
                    email=data['email']).exists():
511
        if (
512
            'email' not in get_or_create_fields
513
            and 'email' not in update_or_create_fields
514
            and data.get('email')
515
            and (not self.instance or data.get('email') != self.instance.email)
516
        ):
517
            if app_settings.A2_EMAIL_IS_UNIQUE and qs.filter(email=data['email']).exists():
532 518
                already_used = True
533
            if ou and ou.email_is_unique and qs.filter(
534
                    ou=ou, email=data['email']).exists():
519
            if ou and ou.email_is_unique and qs.filter(ou=ou, email=data['email']).exists():
535 520
                already_used = True
536 521

  
537 522
        errors = {}
......
587 572
        required=False,
588 573
        default=CreateOnlyDefault(get_default_ou),
589 574
        queryset=get_ou_model().objects.all(),
590
        slug_field='slug')
575
        slug_field='slug',
576
    )
591 577
    slug = serializers.SlugField(
592
        required=False,
593
        allow_blank=False,
594
        max_length=256,
595
        default=SlugFromNameDefault())
578
        required=False, allow_blank=False, max_length=256, default=SlugFromNameDefault()
579
    )
596 580

  
597 581
    @property
598 582
    def user(self):
......
626 610

  
627 611
    class Meta:
628 612
        model = get_role_model()
629
        fields = ('uuid', 'name', 'slug', 'ou',)
613
        fields = (
614
            'uuid',
615
            'name',
616
            'slug',
617
            'ou',
618
        )
630 619
        extra_kwargs = {'uuid': {'read_only': True}}
631 620
        validators = [
632
            UniqueTogetherValidator(
633
                queryset=get_role_model().objects.all(),
634
                fields=['name', 'ou']
635
            ),
636
            UniqueTogetherValidator(
637
                queryset=get_role_model().objects.all(),
638
                fields=['slug', 'ou']
639
            )
621
            UniqueTogetherValidator(queryset=get_role_model().objects.all(), fields=['name', 'ou']),
622
            UniqueTogetherValidator(queryset=get_role_model().objects.all(), fields=['slug', 'ou']),
640 623
        ]
641 624

  
642 625

  
......
652 635
            return super(IsoDateTimeField, self).strptime(value, format)
653 636
        except AmbiguousTimeError:
654 637
            parsed = parse_datetime(value)
655
            possible = sorted([
656
                handle_timezone(parsed, is_dst=True),
657
                handle_timezone(parsed, is_dst=False),
658
            ])
638
            possible = sorted(
639
                [
640
                    handle_timezone(parsed, is_dst=True),
641
                    handle_timezone(parsed, is_dst=False),
642
                ]
643
            )
659 644
            if self.bound == 'lesser':
660 645
                return possible[0]
661 646
            elif self.bound == 'upper':
......
680 665
    class Meta:
681 666
        model = get_user_model()
682 667
        fields = {
683
            'username': [
684
                'exact',
685
                'iexact'
686
            ],
668
            'username': ['exact', 'iexact'],
687 669
            'first_name': [
688 670
                'exact',
689 671
                'iexact',
......
728 710

  
729 711

  
730 712
class FreeTextSearchFilter(BaseFilterBackend):
731
    """
732
    """
713
    """"""
714

  
733 715
    def filter_queryset(self, request, queryset, view):
734 716
        if 'q' in request.GET:
735 717
            queryset = queryset.free_text_search(request.GET['q'])
......
753 735

  
754 736
    @property
755 737
    def ordering(self):
756
       if 'q' in self.request.GET:
757
           return ['dist', Unaccent('last_name'), Unaccent('first_name')]
758
       return User._meta.ordering
738
        if 'q' in self.request.GET:
739
            return ['dist', Unaccent('last_name'), Unaccent('first_name')]
740
        return User._meta.ordering
759 741

  
760 742
    def get_queryset(self):
761 743
        qs = super().get_queryset()
......
773 755
        if 'service-slug' in self.request.GET:
774 756
            service_slug = self.request.GET['service-slug']
775 757
            service_ou = self.request.GET.get('service-ou', '')
776
            service = Service.objects.filter(
777
                slug=service_slug,
778
                ou__slug=service_ou
779
            ).prefetch_related('authorized_roles').first()
758
            service = (
759
                Service.objects.filter(slug=service_slug, ou__slug=service_ou)
760
                .prefetch_related('authorized_roles')
761
                .first()
762
            )
780 763
            if service:
781 764
                if service.authorized_roles.all():
782 765
                    qs = qs.filter(roles__in=service.authorized_roles.children())
......
809 792
        known_uuids = User.objects.filter(uuid__in=uuids).values_list('uuid', flat=True)
810 793
        return set(uuids) - set(known_uuids)
811 794

  
812
    @action(detail=False, methods=['post'],
813
            permission_classes=(DjangoPermission('custom_user.search_user'),))
795
    @action(detail=False, methods=['post'], permission_classes=(DjangoPermission('custom_user.search_user'),))
814 796
    def synchronization(self, request):
815 797
        serializer = self.SynchronizationSerializer(data=request.data)
816 798
        if not serializer.is_valid():
817
            response = {
818
                'result': 0,
819
                'errors': serializer.errors
820
            }
799
            response = {'result': 0, 'errors': serializer.errors}
821 800
            return Response(response, status.HTTP_400_BAD_REQUEST)
822 801
        hooks.call_hooks('api_modify_serializer_after_validation', self, serializer)
823 802
        unknown_uuids = self.check_uuids(serializer.validated_data.get('known_uuids', []))
......
828 807
        hooks.call_hooks('api_modify_response', self, 'synchronization', data)
829 808
        return Response(data)
830 809

  
831
    @action(detail=True, methods=['post'], url_path='password-reset',
832
                  permission_classes=(DjangoPermission('custom_user.reset_password_user'),))
810
    @action(
811
        detail=True,
812
        methods=['post'],
813
        url_path='password-reset',
814
        permission_classes=(DjangoPermission('custom_user.reset_password_user'),),
815
    )
833 816
    def password_reset(self, request, uuid):
834 817
        user = self.get_object()
835 818
        # An user without email cannot receive the token
836 819
        if not user.email:
837
            return Response({'result': 0, 'reason': 'User has no mail'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
820
            return Response(
821
                {'result': 0, 'reason': 'User has no mail'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
822
            )
838 823

  
839 824
        utils.send_password_reset_mail(user, request=request)
840 825
        return Response(status=status.HTTP_204_NO_CONTENT)
841 826

  
842
    @action(detail=True, methods=['post'],
843
                  permission_classes=(DjangoPermission('custom_user.change_user'),))
827
    @action(detail=True, methods=['post'], permission_classes=(DjangoPermission('custom_user.change_user'),))
844 828
    def email(self, request, uuid):
845 829
        user = self.get_object()
846 830
        serializer = ChangeEmailSerializer(data=request.data)
847 831
        if not serializer.is_valid():
848
            response = {
849
                'result': 0,
850
                'errors': serializer.errors
851
            }
832
            response = {'result': 0, 'errors': serializer.errors}
852 833
            return Response(response, status.HTTP_400_BAD_REQUEST)
853 834
        user.email_verified = False
854 835
        user.save()
855 836
        utils.send_email_change_email(user, serializer.validated_data['email'], request=request)
856 837
        return Response({'result': 1})
857 838

  
858
    @action(detail=False, methods=['get'],
859
            permission_classes=(DjangoPermission('custom_user.search_user'),))
839
    @action(detail=False, methods=['get'], permission_classes=(DjangoPermission('custom_user.search_user'),))
860 840
    def find_duplicates(self, request):
861 841
        serializer = self.get_serializer(data=request.query_params, partial=True)
862 842
        if not serializer.is_valid():
863
            response = {
864
                'data': [],
865
                'err': 1,
866
                'err_desc': serializer.errors
867
            }
843
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
868 844
            return Response(response, status.HTTP_400_BAD_REQUEST)
869 845
        data = serializer.validated_data
870 846

  
......
882 858
        birthdate = attributes.get('birthdate')
883 859
        qs = User.objects.find_duplicates(first_name, last_name, birthdate=birthdate)
884 860

  
885
        return Response({
886
            'data': DuplicateUserSerializer(qs, many=True).data,
887
            'err': 0,
888
        })
861
        return Response(
862
            {
863
                'data': DuplicateUserSerializer(qs, many=True).data,
864
                'err': 0,
865
            }
866
        )
889 867

  
890 868

  
891 869
class RolesAPI(api_mixins.GetOrCreateMixinView, ExceptionHandlerMixin, ModelViewSet):
......
919 897

  
920 898
    def post(self, request, *args, **kwargs):
921 899
        self.role.members.add(self.member)
922
        return Response({'result': 1, 'detail': _('User successfully added to role')},
923
                        status=status.HTTP_201_CREATED)
900
        return Response(
901
            {'result': 1, 'detail': _('User successfully added to role')}, status=status.HTTP_201_CREATED
902
        )
924 903

  
925 904
    def delete(self, request, *args, **kwargs):
926 905
        self.role.members.remove(self.member)
927
        return Response({'result': 1, 'detail': _('User successfully removed from role')},
928
                        status=status.HTTP_200_OK)
906
        return Response(
907
            {'result': 1, 'detail': _('User successfully removed from role')}, status=status.HTTP_200_OK
908
        )
909

  
929 910

  
930 911
role_membership = RoleMembershipAPI.as_view()
931 912

  
......
956 937
            try:
957 938
                uuid = entry['uuid']
958 939
            except TypeError:
959
                raise ValidationError(_("List elements of the 'data' dict "
960
                        "entry must be dictionaries"))
940
                raise ValidationError(_("List elements of the 'data' dict " "entry must be dictionaries"))
961 941
            except KeyError:
962
                raise ValidationError(_("Missing 'uuid' key for dict entry %s "
963
                        "of the 'data' payload") % entry)
942
                raise ValidationError(
943
                    _("Missing 'uuid' key for dict entry %s " "of the 'data' payload") % entry
944
                )
964 945
            try:
965 946
                self.members.append(User.objects.get(uuid=uuid))
966 947
            except User.DoesNotExist:
967
                raise ValidationError(
968
                        _('No known user for UUID %s') % entry['uuid'])
948
                raise ValidationError(_('No known user for UUID %s') % entry['uuid'])
969 949

  
970 950
        if not len(self.members) and request.method in ('POST', 'DELETE'):
971 951
            raise ValidationError(_('No valid user UUID'))
......
973 953
    def post(self, request, *args, **kwargs):
974 954
        self.role.members.add(*self.members)
975 955
        return Response(
976
                {
977
                    'result': 1,
978
                    'detail': _('Users successfully added to role')
979
                },
980
                status=status.HTTP_201_CREATED)
956
            {'result': 1, 'detail': _('Users successfully added to role')}, status=status.HTTP_201_CREATED
957
        )
981 958

  
982 959
    def delete(self, request, *args, **kwargs):
983 960
        self.role.members.remove(*self.members)
984 961
        return Response(
985
                {
986
                    'result': 1,
987
                    'detail': _('Users successfully removed from role')
988
                },
989
                status=status.HTTP_200_OK)
962
            {'result': 1, 'detail': _('Users successfully removed from role')}, status=status.HTTP_200_OK
963
        )
990 964

  
991 965
    def patch(self, request, *args, **kwargs):
992 966
        self.role.members.set(self.members)
993 967
        return Response(
994
                {
995
                    'result': 1,
996
                    'detail': _('Users successfully assigned to role')
997
                },
998
                status=status.HTTP_200_OK)
968
            {'result': 1, 'detail': _('Users successfully assigned to role')}, status=status.HTTP_200_OK
969
        )
999 970

  
1000 971
    def put(self, request, *args, **kwargs):
1001 972
        return self.patch(request, *args, **kwargs)
1002 973

  
974

  
1003 975
role_memberships = RoleMembershipsAPI.as_view()
1004 976

  
1005 977

  
1006 978
class BaseOrganizationalUnitSerializer(serializers.ModelSerializer):
1007 979
    slug = serializers.SlugField(
1008
            required=False,
1009
            allow_blank=False,
1010
            max_length=256,
1011
            default=SlugFromNameDefault(),
1012
        )
980
        required=False,
981
        allow_blank=False,
982
        max_length=256,
983
        default=SlugFromNameDefault(),
984
    )
985

  
1013 986
    class Meta:
1014 987
        model = get_ou_model()
1015 988
        fields = '__all__'
......
1023 996
    def get_queryset(self):
1024 997
        return get_ou_model().objects.all()
1025 998

  
999

  
1026 1000
router = SimpleRouter()
1027 1001
router.register(r'users', UsersAPI, base_name='a2-api-users')
1028 1002
router.register(r'ous', OrganizationalUnitAPI, base_name='a2-api-ous')
......
1046 1020
            if hasattr(authenticator, 'authenticate_credentials'):
1047 1021
                try:
1048 1022
                    user, oidc_client = authenticator.authenticate_credentials(
1049
                        username, password, request=request)
1023
                        username, password, request=request
1024
                    )
1050 1025
                    result['result'] = 1
1051 1026
                    if hasattr(user, 'oidc_client'):
1052 1027
                        result['oidc_client'] = True
......
1056 1031
                    result['errors'] = [exc.detail]
1057 1032
        return result, status.HTTP_200_OK
1058 1033

  
1034

  
1059 1035
check_password = CheckPasswordAPI.as_view()
1060 1036

  
1061 1037

  
......
1080 1056
        ok = True
1081 1057
        for check in password_checker(serializer.validated_data['password']):
1082 1058
            ok = ok and check.result
1083
            checks.append({
1084
                'result': check.result,
1085
                'label': check.label,
1086
            })
1059
            checks.append(
1060
                {
1061
                    'result': check.result,
1062
                    'label': check.label,
1063
                }
1064
            )
1087 1065
        result['ok'] = ok
1088 1066
        return result, status.HTTP_200_OK
1089 1067

  
1068

  
1090 1069
validate_password = ValidatePasswordAPI.as_view()
1091 1070

  
1092 1071

  
......
1097 1076
        if not getattr(settings, 'ADDRESS_AUTOCOMPLETE_URL', None):
1098 1077
            return Response({})
1099 1078
        try:
1100
            response = requests.get(
1101
                settings.ADDRESS_AUTOCOMPLETE_URL,
1102
                params=request.GET
1103
            )
1079
            response = requests.get(settings.ADDRESS_AUTOCOMPLETE_URL, params=request.GET)
1104 1080
            response.raise_for_status()
1105 1081
            return Response(response.json())
1106 1082
        except RequestException:
......
1119 1095

  
1120 1096

  
1121 1097
class StatisticsSerializer(serializers.Serializer):
1122
    TIME_INTERVAL_CHOICES = [
1123
        ('day', _('Day')),
1124
        ('month', _('Month')),
1125
        ('year', _('Year'))
1126
    ]
1098
    TIME_INTERVAL_CHOICES = [('day', _('Day')), ('month', _('Month')), ('year', _('Year'))]
1127 1099

  
1128 1100
    time_interval = serializers.ChoiceField(choices=TIME_INTERVAL_CHOICES, default='month')
1129 1101
    service = ServiceOUField(child=serializers.SlugField(max_length=256), required=False)
......
1143 1115
    def wraps(func):
1144 1116
        func.filters = filters
1145 1117
        return decorator(func)
1118

  
1146 1119
    return wraps
1147 1120

  
1148 1121

  
......
1169 1142
            {
1170 1143
                'id': 'time_interval',
1171 1144
                'label': _('Time interval'),
1172
                'options': [{'id': key, 'label': label} for key, label in time_interval_field.choices.items()],
1145
                'options': [
1146
                    {'id': key, 'label': label} for key, label in time_interval_field.choices.items()
1147
                ],
1173 1148
                'required': True,
1174 1149
                'default': time_interval_field.default,
1175 1150
            }
......
1195 1170
            }
1196 1171
            statistics.append(data)
1197 1172

  
1198
        return Response({
1199
            'data': statistics,
1200
            'err': 0,
1201
        })
1173
        return Response(
1174
            {
1175
                'data': statistics,
1176
                'err': 0,
1177
            }
1178
        )
1202 1179

  
1203 1180
    def get_statistics(self, request, klass, method):
1204 1181
        serializer = StatisticsSerializer(data=request.query_params)
1205 1182
        if not serializer.is_valid():
1206
            response = {
1207
                'data': [],
1208
                'err': 1,
1209
                'err_desc': serializer.errors
1210
            }
1183
            response = {'data': [], 'err': 1, 'err_desc': serializer.errors}
1211 1184
            return Response(response, status.HTTP_400_BAD_REQUEST)
1212 1185
        data = serializer.validated_data
1213 1186

  
......
1231 1204
        if users_ou and 'users_ou' in allowed_filters:
1232 1205
            kwargs['users_ou'] = get_object_or_404(get_ou_model(), slug=users_ou)
1233 1206

  
1234
        return Response({
1235
            'data': getattr(klass, method)(**kwargs),
1236
            'err': 0,
1237
        })
1207
        return Response(
1208
            {
1209
                'data': getattr(klass, method)(**kwargs),
1210
                'err': 0,
1211
            }
1212
        )
1238 1213

  
1239 1214
    @stat(name=_('Login count by authentication type'), filters=('services_ou', 'users_ou', 'service'))
1240 1215
    def login(self, request):
src/authentic2/app.py
20 20

  
21 21
from . import plugins
22 22

  
23

  
23 24
class Authentic2Config(AppConfig):
24 25
    name = 'authentic2'
25 26
    verbose_name = 'Authentic2'
26 27

  
27 28
    def ready(self):
28 29
        plugins.init()
29
        debug.HIDDEN_SETTINGS = re.compile(
30
            'API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE|LDAP')
30
        debug.HIDDEN_SETTINGS = re.compile('API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE|LDAP')
src/authentic2/app_settings.py
44 44
    def settings(self):
45 45
        if not hasattr(self, '_settings'):
46 46
            from django.conf import settings
47

  
47 48
            self._settings = settings
48 49
        return self._settings
49 50

  
......
59 60
                    realms[realm] = realm
60 61
                else:
61 62
                    realms[realm[0]] = realm[1]
63

  
62 64
        from django.contrib.auth import get_backends
65

  
63 66
        for backend in get_backends():
64 67
            if hasattr(backend, 'get_realms'):
65 68
                add_realms(backend.get_realms())
......
87 90
        if self.defaults[key].has_default():
88 91
            return self.defaults[key].default
89 92
        raise ImproperlyConfigured(
90
            'missing setting %s(%s) is mandatory' % (key, self.defaults[key].description))
93
            'missing setting %s(%s) is mandatory' % (key, self.defaults[key].description)
94
        )
95

  
91 96

  
92 97
default_settings = dict(
93 98
    ATTRIBUTE_BACKENDS=Setting(
......
104 109
    CAFILE=Setting(
105 110
        names=('AUTHENTIC2_CAFILE', 'CAFILE'),
106 111
        default=None,
107
        definition='File containing certificate chains as PEM certificates'),
112
        definition='File containing certificate chains as PEM certificates',
113
    ),
108 114
    A2_REGISTRATION_CAN_DELETE_ACCOUNT=Setting(
109
        default=True,
110
        definition='Can user self delete their account and all their data'),
115
        default=True, definition='Can user self delete their account and all their data'
116
    ),
111 117
    A2_REGISTRATION_CAN_CHANGE_PASSWORD=Setting(
112
        default=True,
113
        definition='Allow user to change its own password'),
118
        default=True, definition='Allow user to change its own password'
119
    ),
114 120
    A2_REGISTRATION_EMAIL_BLACKLIST=Setting(
115
        default=[],
116
        definition='List of forbidden email wildcards, ex.: ^.*@ville.fr$'),
121
        default=[], definition='List of forbidden email wildcards, ex.: ^.*@ville.fr$'
122
    ),
117 123
    A2_REGISTRATION_REDIRECT=Setting(
118 124
        default=None,
119 125
        definition='Forced redirection after each redirect, NEXT_URL substring is replaced'
120
        ' by the original next_url passed to /accounts/register/'),
121
    A2_PROFILE_CAN_CHANGE_EMAIL=Setting(
122
        default=True,
123
        definition='Can user self change their email'),
124
    A2_PROFILE_CAN_EDIT_PROFILE=Setting(
125
        default=True,
126
        definition='Can user self edit their profile'),
127
    A2_PROFILE_CAN_MANAGE_FEDERATION=Setting(
128
        default=True,
129
        definition='Can user manage its federations'),
126
        ' by the original next_url passed to /accounts/register/',
127
    ),
128
    A2_PROFILE_CAN_CHANGE_EMAIL=Setting(default=True, definition='Can user self change their email'),
129
    A2_PROFILE_CAN_EDIT_PROFILE=Setting(default=True, definition='Can user self edit their profile'),
130
    A2_PROFILE_CAN_MANAGE_FEDERATION=Setting(default=True, definition='Can user manage its federations'),
130 131
    A2_PROFILE_CAN_MANAGE_SERVICE_AUTHORIZATIONS=Setting(
131
        default=True,
132
        definition='Allow user to revoke granted services access to its account profile data'),
133
    A2_PROFILE_DISPLAY_EMPTY_FIELDS=Setting(
134
        default=False,
135
        definition='Include empty fields in profile view'),
136
    A2_HOMEPAGE_URL=Setting(
137
        default=None,
138
        definition='IdP has no homepage, redirect to this one.'),
139
    A2_USER_CAN_RESET_PASSWORD=Setting(
140
        default=None,
141
        definition='Allow online reset of passwords'),
132
        default=True, definition='Allow user to revoke granted services access to its account profile data'
133
    ),
134
    A2_PROFILE_DISPLAY_EMPTY_FIELDS=Setting(default=False, definition='Include empty fields in profile view'),
135
    A2_HOMEPAGE_URL=Setting(default=None, definition='IdP has no homepage, redirect to this one.'),
136
    A2_USER_CAN_RESET_PASSWORD=Setting(default=None, definition='Allow online reset of passwords'),
142 137
    A2_RESET_PASSWORD_ID_LABEL=Setting(
143
        default=None,
144
        definition='Alternate ID label for the password reset form'),
145
    A2_EMAIL_IS_UNIQUE=Setting(
146
        default=False,
147
        definition='Email of users must be unique'),
138
        default=None, definition='Alternate ID label for the password reset form'
139
    ),
140
    A2_EMAIL_IS_UNIQUE=Setting(default=False, definition='Email of users must be unique'),
148 141
    A2_REGISTRATION_EMAIL_IS_UNIQUE=Setting(
149
        default=False,
150
        definition='Email of registered accounts must be unique'),
142
        default=False, definition='Email of registered accounts must be unique'
143
    ),
151 144
    A2_REGISTRATION_FORM_USERNAME_REGEX=Setting(
152
        default=r'^[\w.@+-]+$',
153
        definition='Regex to validate usernames'),
145
        default=r'^[\w.@+-]+$', definition='Regex to validate usernames'
146
    ),
154 147
    A2_REGISTRATION_FORM_USERNAME_HELP_TEXT=Setting(
155
        default=_('Required. At most 30 characters. Letters, digits, and @/./+/-/_ only.')),
156
    A2_REGISTRATION_FORM_USERNAME_LABEL=Setting(
157
        default=_('Username')),
148
        default=_('Required. At most 30 characters. Letters, digits, and @/./+/-/_ only.')
149
    ),
150
    A2_REGISTRATION_FORM_USERNAME_LABEL=Setting(default=_('Username')),
158 151
    A2_REGISTRATION_REALM=Setting(
159
        default=None,
160
        definition='Default realm to assign to self-registrated users'),
161
    A2_REGISTRATION_GROUPS=Setting(
162
        default=(),
163
        definition='Default groups for self-registered users'),
164
    A2_PROFILE_FIELDS=Setting(
165
        default=(),
166
        definition='Fields to show to the user in the profile page'),
152
        default=None, definition='Default realm to assign to self-registrated users'
153
    ),
154
    A2_REGISTRATION_GROUPS=Setting(default=(), definition='Default groups for self-registered users'),
155
    A2_PROFILE_FIELDS=Setting(default=(), definition='Fields to show to the user in the profile page'),
167 156
    A2_REGISTRATION_FIELDS=Setting(
168
        default=(),
169
        definition='Fields from the user model that must appear on the registration form'),
170
    A2_REQUIRED_FIELDS=Setting(
171
        default=(),
172
        definition='User fields that are required'),
157
        default=(), definition='Fields from the user model that must appear on the registration form'
158
    ),
159
    A2_REQUIRED_FIELDS=Setting(default=(), definition='User fields that are required'),
173 160
    A2_REGISTRATION_REQUIRED_FIELDS=Setting(
174
        default=(),
175
        definition='Fields from the registration form that must be required'),
176
    A2_PRE_REGISTRATION_FIELDS=Setting(
177
        default=(),
178
        definition='User fields to ask with email'),
179
    A2_REALMS=Setting(
180
        default=(),
181
        definition='List of realms to search user accounts'),
182
    A2_USERNAME_REGEX=Setting(
183
        default=None,
184
        definition='Regex that username must validate'),
185
    A2_USERNAME_LABEL=Setting(
186
        default=None,
187
        definition='Alternate username label for the login form'),
161
        default=(), definition='Fields from the registration form that must be required'
162
    ),
163
    A2_PRE_REGISTRATION_FIELDS=Setting(default=(), definition='User fields to ask with email'),
164
    A2_REALMS=Setting(default=(), definition='List of realms to search user accounts'),
165
    A2_USERNAME_REGEX=Setting(default=None, definition='Regex that username must validate'),
166
    A2_USERNAME_LABEL=Setting(default=None, definition='Alternate username label for the login form'),
188 167
    A2_USERNAME_HELP_TEXT=Setting(
189
        default=None,
190
        definition='Help text to explain validation rules of usernames'),
191
    A2_USERNAME_IS_UNIQUE=Setting(
192
        default=True,
193
        definition='Check username uniqueness'),
168
        default=None, definition='Help text to explain validation rules of usernames'
169
    ),
170
    A2_USERNAME_IS_UNIQUE=Setting(default=True, definition='Check username uniqueness'),
194 171
    A2_LOGIN_FORM_OU_SELECTOR=Setting(
195
        default=False,
196
        definition='Whether to add an OU selector to the login form'),
197
    A2_LOGIN_FORM_OU_SELECTOR_LABEL=Setting(
198
        default=None,
199
        definition='Label of OU field on login page'),
172
        default=False, definition='Whether to add an OU selector to the login form'
173
    ),
174
    A2_LOGIN_FORM_OU_SELECTOR_LABEL=Setting(default=None, definition='Label of OU field on login page'),
200 175
    A2_REGISTRATION_USERNAME_IS_UNIQUE=Setting(
201
        default=True,
202
        definition='Check username uniqueness on registration'),
176
        default=True, definition='Check username uniqueness on registration'
177
    ),
203 178
    IDP_BACKENDS=(),
204 179
    AUTH_FRONTENDS=(),
205 180
    AUTH_FRONTENDS_KWARGS={},
206
    VALID_REFERERS=Setting(
207
        default=(),
208
        definition='List of prefix to match referers'),
209
    A2_OPENED_SESSION_COOKIE_NAME=Setting(
210
        default='A2_OPENED_SESSION',
211
        definition='Authentic session open'),
212
    A2_OPENED_SESSION_COOKIE_DOMAIN=Setting(
213
        default=None),
214
    A2_OPENED_SESSION_COOKIE_SECURE=Setting(
215
        default=False),
216
    A2_ATTRIBUTE_KINDS=Setting(
217
        default=(),
218
        definition='List of other attribute kinds'),
181
    VALID_REFERERS=Setting(default=(), definition='List of prefix to match referers'),
182
    A2_OPENED_SESSION_COOKIE_NAME=Setting(default='A2_OPENED_SESSION', definition='Authentic session open'),
183
    A2_OPENED_SESSION_COOKIE_DOMAIN=Setting(default=None),
184
    A2_OPENED_SESSION_COOKIE_SECURE=Setting(default=False),
185
    A2_ATTRIBUTE_KINDS=Setting(default=(), definition='List of other attribute kinds'),
219 186
    A2_ATTRIBUTE_KIND_PROFILE_IMAGE_SIZE=Setting(
220
        default=200,
221
        definition='Width and height for a profile image'),
187
        default=200, definition='Width and height for a profile image'
188
    ),
222 189
    A2_VALIDATE_EMAIL=Setting(
223
        default=False,
224
        definition='Validate user email server by doing an RCPT command'),
225
    A2_VALIDATE_EMAIL_DOMAIN=Setting(
226
        default=True,
227
        definition='Validate user email domain'),
190
        default=False, definition='Validate user email server by doing an RCPT command'
191
    ),
192
    A2_VALIDATE_EMAIL_DOMAIN=Setting(default=True, definition='Validate user email domain'),
228 193
    A2_PASSWORD_POLICY_MIN_CLASSES=Setting(
229
        default=3,
230
        definition='Minimum number of characters classes to be present in passwords'),
231
    A2_PASSWORD_POLICY_MIN_LENGTH=Setting(
232
        default=8,
233
        definition='Minimum number of characters in a password'),
234
    A2_PASSWORD_POLICY_REGEX=Setting(
235
        default=None,
236
        definition='Regular expression for validating passwords'),
194
        default=3, definition='Minimum number of characters classes to be present in passwords'
195
    ),
196
    A2_PASSWORD_POLICY_MIN_LENGTH=Setting(default=8, definition='Minimum number of characters in a password'),
197
    A2_PASSWORD_POLICY_REGEX=Setting(default=None, definition='Regular expression for validating passwords'),
237 198
    A2_PASSWORD_POLICY_REGEX_ERROR_MSG=Setting(
238 199
        default=None,
239
        definition='Error message to show when the password do not validate the regular expression'),
200
        definition='Error message to show when the password do not validate the regular expression',
201
    ),
240 202
    A2_PASSWORD_POLICY_CLASS=Setting(
241 203
        default='authentic2.passwords.DefaultPasswordChecker',
242
        definition='path of a class to validate passwords'),
204
        definition='path of a class to validate passwords',
205
    ),
243 206
    A2_PASSWORD_POLICY_SHOW_LAST_CHAR=Setting(
244
        default=False,
245
        definition='Show last character in password fields'),
207
        default=False, definition='Show last character in password fields'
208
    ),
246 209
    A2_AUTH_PASSWORD_ENABLE=Setting(
247
        default=True,
248
        definition='Activate login/password authentication', names=('AUTH_PASSWORD',)),
210
        default=True, definition='Activate login/password authentication', names=('AUTH_PASSWORD',)
211
    ),
249 212
    A2_SUGGESTED_EMAIL_DOMAINS=Setting(
250
        default=['gmail.com', 'msn.com', 'hotmail.com', 'hotmail.fr',
251
                 'wanadoo.fr', 'yahoo.fr', 'yahoo.com', 'laposte.net',
252
                 'free.fr', 'orange.fr', 'numericable.fr'],
253
        definition='List of suggested email domains'),
213
        default=[
214
            'gmail.com',
215
            'msn.com',
216
            'hotmail.com',
217
            'hotmail.fr',
218
            'wanadoo.fr',
219
            'yahoo.fr',
220
            'yahoo.com',
221
            'laposte.net',
222
            'free.fr',
223
            'orange.fr',
224
            'numericable.fr',
225
        ],
226
        definition='List of suggested email domains',
227
    ),
254 228
    A2_LOGIN_FAILURE_COUNT_BEFORE_WARNING=Setting(
255 229
        default=0,
256 230
        definition='Failure count before logging a warning to '
257 231
        'authentic2.user_login_failure. No warning will be send if value is '
258
        '0.'),
259
    PUSH_PROFILE_UPDATES=Setting(
260
        default=False,
261
        definition='Push profile update to linked services'),
262
    TEMPLATE_VARS=Setting(
263
        default={},
264
        definition='Variable to pass to templates'),
232
        '0.',
233
    ),
234
    PUSH_PROFILE_UPDATES=Setting(default=False, definition='Push profile update to linked services'),
235
    TEMPLATE_VARS=Setting(default={}, definition='Variable to pass to templates'),
265 236
    A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_FACTOR=Setting(
266 237
        default=1.8,
267
        definition='exponential backoff factor duration as seconds until '
268
        'next try after a login failure'),
238
        definition='exponential backoff factor duration as seconds until ' 'next try after a login failure',
239
    ),
269 240
    A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_DURATION=Setting(
270 241
        default=1,
271 242
        definition='exponential backoff base factor duration as seconds '
272
        'until next try after a login failure'),
243
        'until next try after a login failure',
244
    ),
273 245
    A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MAX_DURATION=Setting(
274 246
        default=3600,
275 247
        definition='maximum exponential backoff maximum duration as seconds until '
276
        'next try after a login failure'),
248
        'next try after a login failure',
249
    ),
277 250
    A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION=Setting(
278 251
        default=10,
279 252
        definition='minimum exponential backoff maximum duration as seconds until '
280
        'next try after a login failure'),
281
    A2_VERIFY_SSL=Setting(
282
        default=True,
283
        definition='Verify SSL certificate in HTTP requests'),
284
    A2_ATTRIBUTE_KIND_TITLE_CHOICES=Setting(
285
        default=(),
286
        definition='Choices for the title attribute kind'),
253
        'next try after a login failure',
254
    ),
255
    A2_VERIFY_SSL=Setting(default=True, definition='Verify SSL certificate in HTTP requests'),
256
    A2_ATTRIBUTE_KIND_TITLE_CHOICES=Setting(default=(), definition='Choices for the title attribute kind'),
287 257
    A2_CORS_WHITELIST=Setting(
288
        default=(),
289
        definition='List of origin URL to whitelist, must be scheme://netloc[:port]'),
258
        default=(), definition='List of origin URL to whitelist, must be scheme://netloc[:port]'
259
    ),
290 260
    A2_EMAIL_CHANGE_TOKEN_LIFETIME=Setting(
291
        default=7200,
292
        definition='Lifetime in seconds of the token sent to verify email adresses'),
261
        default=7200, definition='Lifetime in seconds of the token sent to verify email adresses'
262
    ),
293 263
    A2_DELETION_REQUEST_LIFETIME=Setting(
294
        default=48*3600,
295
        definition='Lifetime in seconds of the user account deletion request'),
264
        default=48 * 3600, definition='Lifetime in seconds of the user account deletion request'
265
    ),
296 266
    A2_REDIRECT_WHITELIST=Setting(
297
        default=(),
298
        definition='List of origins which are authorized to ask for redirection.'),
267
        default=(), definition='List of origins which are authorized to ask for redirection.'
268
    ),
299 269
    A2_API_USERS_REQUIRED_FIELDS=Setting(
300
        default=(),
301
        definition='List of fields to require on user\'s API, override other settings'),
270
        default=(), definition='List of fields to require on user\'s API, override other settings'
271
    ),
302 272
    A2_USER_FILTER=Setting(
303 273
        default={},
304
        definition='Filters (as in QuerySet.filter() to apply to User queryset before '
305
                   'authentication'),
274
        definition='Filters (as in QuerySet.filter() to apply to User queryset before ' 'authentication',
275
    ),
306 276
    A2_USER_EXCLUDE=Setting(
307 277
        default={},
308 278
        definition='Exclusion filter (as in QuerySet.exclude() to apply to User queryset before '
309
                   'authentication'),
279
        'authentication',
280
    ),
310 281
    A2_USER_REMEMBER_ME=Setting(
311 282
        default=None,
312 283
        definition='Session duration as seconds when using the remember me '
313
        'checkbox. Truthiness activates the checkbox.'),
284
        'checkbox. Truthiness activates the checkbox.',
285
    ),
314 286
    A2_LOGIN_REDIRECT_AUTHENTICATED_USERS_TO_HOMEPAGE=Setting(
315
        default=False,
316
        definition='Redirect authenticated users to homepage'),
287
        default=False, definition='Redirect authenticated users to homepage'
288
    ),
317 289
    A2_LOGIN_DISPLAY_A_CANCEL_BUTTON=Setting(
318 290
        default=False,
319
        definition='Display a cancel button.'
320
        'This is only applicable for Liberty single sign on requests'),
291
        definition='Display a cancel button.' 'This is only applicable for Liberty single sign on requests',
292
    ),
321 293
    A2_SET_RANDOM_PASSWORD_ON_RESET=Setting(
322 294
        default=True,
323
        definition='Set a random password on request to reset the password from the front-office'),
324
    A2_ACCOUNTS_URL=Setting(
325
        default=None,
326
        definition='IdP has no account page, redirect to this one.'),
327
    A2_CACHE_ENABLED=Setting(
328
        default=True,
329
        definition='Disable all cache decorators for testing purpose.'),
330
    A2_ACCEPT_EMAIL_AUTHENTICATION=Setting(
331
        default=True,
332
        definition='Enable authentication by email'),
295
        definition='Set a random password on request to reset the password from the front-office',
296
    ),
297
    A2_ACCOUNTS_URL=Setting(default=None, definition='IdP has no account page, redirect to this one.'),
298
    A2_CACHE_ENABLED=Setting(default=True, definition='Disable all cache decorators for testing purpose.'),
299
    A2_ACCEPT_EMAIL_AUTHENTICATION=Setting(default=True, definition='Enable authentication by email'),
333 300
    A2_EMAILS_IP_RATELIMIT=Setting(
334
        default='10/h',
335
        definition='Maximum rate of email sendings triggered by the same IP address.'),
301
        default='10/h', definition='Maximum rate of email sendings triggered by the same IP address.'
302
    ),
336 303
    A2_EMAILS_ADDRESS_RATELIMIT=Setting(
337
        default='3/d',
338
        definition='Maximum rate of emails sent to the same email address.'),
304
        default='3/d', definition='Maximum rate of emails sent to the same email address.'
305
    ),
339 306
    A2_USER_DELETED_KEEP_DATA=Setting(
340
        default=['email', 'uuid'],
341
        definition='User data to keep after deletion'),
307
        default=['email', 'uuid'], definition='User data to keep after deletion'
308
    ),
342 309
    A2_USER_DELETED_KEEP_DATA_DAYS=Setting(
343
        default=365,
344
        definition='Number of days to keep data on deleted users'),
310
        default=365, definition='Number of days to keep data on deleted users'
311
    ),
345 312
    A2_TOKEN_EXISTS_WARNING=Setting(
346
        default=True,
347
        definition='If an active token exists, warn user before generating a new one.'),
313
        default=True, definition='If an active token exists, warn user before generating a new one.'
314
    ),
348 315
    A2_DUPLICATES_THRESHOLD=Setting(
349
        default=0.7,
350
        definition='Trigram similarity threshold for considering user as duplicate.'),
351
    A2_FTS_THRESHOLD=Setting(
352
        default=0.2,
353
        definition='Trigram similarity threshold for free text search.'),
316
        default=0.7, definition='Trigram similarity threshold for considering user as duplicate.'
317
    ),
318
    A2_FTS_THRESHOLD=Setting(default=0.2, definition='Trigram similarity threshold for free text search.'),
354 319
    A2_DUPLICATES_BIRTHDATE_BONUS=Setting(
355
        default=0.3,
356
        definition='Bonus in case of birthdate match (no bonus is 0, max is 1).'),
320
        default=0.3, definition='Bonus in case of birthdate match (no bonus is 0, max is 1).'
321
    ),
357 322
    A2_EMAIL_FORMAT=Setting(
358 323
        default='multipart/alternative',
359
        definition='Send email as "multiplart/alternative" or limit to "text/plain" or "text/html".'),
324
        definition='Send email as "multiplart/alternative" or limit to "text/plain" or "text/html".',
325
    ),
360 326
)
361 327

  
362 328
app_settings = AppSettings(default_settings)
src/authentic2/apps/journal/migrations/0001_initial.py
113 113
                'DROP INDEX journal_event_reference_ct_ids_idx;',
114 114
                'DROP INDEX journal_event_reference_ids_idx;',
115 115
                'DROP INDEX journal_event_timestamp_id_idx;',
116
            ]
116
            ],
117 117
        ),
118 118
    ]
src/authentic2/apps/journal/models.py
307 307
    type = models.ForeignKey(verbose_name=_('type'), to=EventType, on_delete=models.PROTECT)
308 308

  
309 309
    reference_ids = ArrayField(
310
        verbose_name=_('reference ids'), base_field=models.BigIntegerField(), null=True,
310
        verbose_name=_('reference ids'),
311
        base_field=models.BigIntegerField(),
312
        null=True,
311 313
    )
312 314

  
313 315
    reference_ct_ids = ArrayField(
314
        verbose_name=_('reference ct ids'), base_field=models.IntegerField(), null=True,
316
        verbose_name=_('reference ct ids'),
317
        base_field=models.IntegerField(),
318
        null=True,
315 319
    )
316 320

  
317 321
    data = JSONField(verbose_name=_('data'), null=True)
......
361 365

  
362 366
    @classmethod
363 367
    def cleanup(cls):
364
        '''Expire old events by default retention days or customized at the
365
           EventTypeDefinition level.'''
368
        """Expire old events by default retention days or customized at the
369
        EventTypeDefinition level."""
366 370
        event_types_by_retention_days = defaultdict(set)
367 371
        default_retention_days = getattr(settings, 'JOURNAL_DEFAULT_RETENTION_DAYS', 365 * 2)
368 372
        for event_type in EventType.objects.all():
src/authentic2/apps/journal/search_engine.py
80 80
        if not hasattr(self, method_name):
81 81
            return
82 82

  
83
        yield from getattr(self, method_name)(lexem[len(prefix) + 1:])
83
        yield from getattr(self, method_name)(lexem[len(prefix) + 1 :])
84 84

  
85 85
    @classmethod
86 86
    def documentation(cls):
87
        yield _('You can use colon terminated prefixes to make special searches, '
88
                'and you can use quote around the suffix to preserve spaces.')
87
        yield _(
88
            'You can use colon terminated prefixes to make special searches, '
89
            'and you can use quote around the suffix to preserve spaces.'
90
        )
89 91
        for name in dir(cls):
90 92
            documentation = getattr(getattr(cls, name), 'documentation', None)
91 93
            if documentation:
src/authentic2/attribute_aggregator/migrations/0001_initial.py
15 15
        migrations.CreateModel(
16 16
            name='AttributeItem',
17 17
            fields=[
18
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19
                ('attribute_name', models.CharField(default=('OpenLDAProotDSE', 'OpenLDAProotDSE'), max_length=100, verbose_name='Attribute name', choices=[('OpenLDAProotDSE', 'OpenLDAProotDSE'), ('aRecord', 'aRecord'), ('administrativeRole', 'administrativeRole'), ('alias', 'alias'), ('aliasedObjectName', 'aliasedObjectName'), ('altServer', 'altServer'), ('associatedDomain', 'associatedDomain'), ('associatedName', 'associatedName'), ('attributeTypes', 'attributeTypes'), ('audio', 'audio'), ('authPassword', 'authPassword'), ('authorityRevocationList', 'authorityRevocationList'), ('authzFrom', 'authzFrom'), ('authzTo', 'authzTo'), ('bootFile', 'bootFile'), ('bootParameter', 'bootParameter'), ('buildingName', 'buildingName'), ('businessCategory', 'businessCategory'), ('c', 'c'), ('cACertificate', 'cACertificate'), ('cNAMERecord', 'cNAMERecord'), ('carLicense', 'carLicense'), ('certificateRevocationList', 'certificateRevocationList'), ('children', 'children'), ('cn', 'cn'), ('co', 'co'), ('collectiveAttributeSubentries', 'collectiveAttributeSubentries'), ('collectiveAttributeSubentry', 'collectiveAttributeSubentry'), ('collectiveExclusions', 'collectiveExclusions'), ('configContext', 'configContext'), ('contextCSN', 'contextCSN'), ('createTimestamp', 'createTimestamp'), ('creatorsName', 'creatorsName'), ('crossCertificatePair', 'crossCertificatePair'), ('dITContentRules', 'dITContentRules'), ('dITRedirect', 'dITRedirect'), ('dITStructureRules', 'dITStructureRules'), ('dSAQuality', 'dSAQuality'), ('dc', 'dc'), ('deltaRevocationList', 'deltaRevocationList'), ('departmentNumber', 'departmentNumber'), ('description', 'description'), ('destinationIndicator', 'destinationIndicator'), ('displayName', 'displayName'), ('distinguishedName', 'distinguishedName'), ('dmdName', 'dmdName'), ('dnQualifier', 'dnQualifier'), ('documentAuthor', 'documentAuthor'), ('documentIdentifier', 'documentIdentifier'), ('documentLocation', 'documentLocation'), ('documentPublisher', 'documentPublisher'), ('documentTitle', 'documentTitle'), ('documentVersion', 'documentVersion'), ('drink', 'drink'), ('dynamicObject', 'dynamicObject'), ('dynamicSubtrees', 'dynamicSubtrees'), ('eduOrgHomePageURI', 'eduOrgHomePageURI'), ('eduOrgIdentityAuthNPolicyURI', 'eduOrgIdentityAuthNPolicyURI'), ('eduOrgLegalName', 'eduOrgLegalName'), ('eduOrgSuperiorURI', 'eduOrgSuperiorURI'), ('eduOrgWhitePagesURI', 'eduOrgWhitePagesURI'), ('eduPersonAffiliation', 'eduPersonAffiliation'), ('eduPersonAssurance', 'eduPersonAssurance'), ('eduPersonEntitlement', 'eduPersonEntitlement'), ('eduPersonNickname', 'eduPersonNickname'), ('eduPersonOrgDN', 'eduPersonOrgDN'), ('eduPersonOrgUnitDN', 'eduPersonOrgUnitDN'), ('eduPersonPrimaryAffiliation', 'eduPersonPrimaryAffiliation'), ('eduPersonPrimaryOrgUnitDN', 'eduPersonPrimaryOrgUnitDN'), ('eduPersonPrincipalName', 'eduPersonPrincipalName'), ('eduPersonScopedAffiliation', 'eduPersonScopedAffiliation'), ('eduPersonTargetedID', 'eduPersonTargetedID'), ('email', 'email'), ('employeeNumber', 'employeeNumber'), ('employeeType', 'employeeType'), ('enhancedSearchGuide', 'enhancedSearchGuide'), ('entry', 'entry'), ('entryCSN', 'entryCSN'), ('entryDN', 'entryDN'), ('entryTtl', 'entryTtl'), ('entryUUID', 'entryUUID'), ('extensibleObject', 'extensibleObject'), ('fax', 'fax'), ('gecos', 'gecos'), ('generationQualifier', 'generationQualifier'), ('gidNumber', 'gidNumber'), ('givenName', 'givenName'), ('glue', 'glue'), ('hasSubordinates', 'hasSubordinates'), ('homeDirectory', 'homeDirectory'), ('homePhone', 'homePhone'), ('homePostalAddress', 'homePostalAddress'), ('host', 'host'), ('houseIdentifier', 'houseIdentifier'), ('info', 'info'), ('initials', 'initials'), ('internationaliSDNNumber', 'internationaliSDNNumber'), ('ipHostNumber', 'ipHostNumber'), ('ipNetmaskNumber', 'ipNetmaskNumber'), ('ipNetworkNumber', 'ipNetworkNumber'), ('ipProtocolNumber', 'ipProtocolNumber'), ('ipServicePort', 'ipServicePort'), ('ipServiceProtocolSUPname', 'ipServiceProtocolSUPname'), ('janetMailbox', 'janetMailbox'), ('jpegPhoto', 'jpegPhoto'), ('knowledgeInformation', 'knowledgeInformation'), ('l', 'l'), ('labeledURI', 'labeledURI'), ('ldapSyntaxes', 'ldapSyntaxes'), ('loginShell', 'loginShell'), ('mDRecord', 'mDRecord'), ('mXRecord', 'mXRecord'), ('macAddress', 'macAddress'), ('mail', 'mail'), ('mailForwardingAddress', 'mailForwardingAddress'), ('mailHost', 'mailHost'), ('mailLocalAddress', 'mailLocalAddress'), ('mailPreferenceOption', 'mailPreferenceOption'), ('mailRoutingAddress', 'mailRoutingAddress'), ('manager', 'manager'), ('matchingRuleUse', 'matchingRuleUse'), ('matchingRules', 'matchingRules'), ('member', 'member'), ('memberNisNetgroup', 'memberNisNetgroup'), ('memberUid', 'memberUid'), ('mobile', 'mobile'), ('modifiersName', 'modifiersName'), ('modifyTimestamp', 'modifyTimestamp'), ('monitorContext', 'monitorContext'), ('nSRecord', 'nSRecord'), ('name', 'name'), ('nameForms', 'nameForms'), ('namingCSN', 'namingCSN'), ('namingContexts', 'namingContexts'), ('nisMapEntry', 'nisMapEntry'), ('nisMapNameSUPname', 'nisMapNameSUPname'), ('nisNetgroupTriple', 'nisNetgroupTriple'), ('o', 'o'), ('objectClass', 'objectClass'), ('objectClasses', 'objectClasses'), ('oncRpcNumber', 'oncRpcNumber'), ('organizationalStatus', 'organizationalStatus'), ('otherMailbox', 'otherMailbox'), ('ou', 'ou'), ('owner', 'owner'), ('pager', 'pager'), ('personalSignature', 'personalSignature'), ('personalTitle', 'personalTitle'), ('photo', 'photo'), ('physicalDeliveryOfficeName', 'physicalDeliveryOfficeName'), ('postOfficeBox', 'postOfficeBox'), ('postalAddress', 'postalAddress'), ('postalCode', 'postalCode'), ('preferredDeliveryMethod', 'preferredDeliveryMethod'), ('preferredLanguage', 'preferredLanguage'), ('presentationAddress', 'presentationAddress'), ('protocolInformation', 'protocolInformation'), ('pseudonym', 'pseudonym'), ('ref', 'ref'), ('referral', 'referral'), ('registeredAddress', 'registeredAddress'), ('rfc822MailMember', 'rfc822MailMember'), ('role', 'role'), ('roleOccupant', 'roleOccupant'), ('roomNumber', 'roomNumber'), ('sOARecord', 'sOARecord'), ('schacHomeOrganization', 'schacHomeOrganization'), ('schacHomeOrganizationType', 'schacHomeOrganizationType'), ('searchGuide', 'searchGuide'), ('secretary', 'secretary'), ('seeAlso', 'seeAlso'), ('serialNumber', 'serialNumber'), ('shadowExpire', 'shadowExpire'), ('shadowFlag', 'shadowFlag'), ('shadowInactive', 'shadowInactive'), ('shadowLastChange', 'shadowLastChange'), ('shadowMax', 'shadowMax'), ('shadowMin', 'shadowMin'), ('shadowWarning', 'shadowWarning'), ('singleLevelQuality', 'singleLevelQuality'), ('sn', 'sn'), ('st', 'st'), ('street', 'street'), ('structuralObjectClass', 'structuralObjectClass'), ('subentry', 'subentry'), ('subschema', 'subschema'), ('subschemaSubentry', 'subschemaSubentry'), ('subtreeMaximumQuality', 'subtreeMaximumQuality'), ('subtreeMinimumQuality', 'subtreeMinimumQuality'), ('subtreeSpecification', 'subtreeSpecification'), ('supannActivite', 'supannActivite'), ('supannAffectation', 'supannAffectation'), ('supannAliasLogin', 'supannAliasLogin'), ('supannAutreMail', 'supannAutreMail'), ('supannAutreTelephone', 'supannAutreTelephone'), ('supannCivilite', 'supannCivilite'), ('supannCodeEntite', 'supannCodeEntite'), ('supannCodeEntiteParent', 'supannCodeEntiteParent'), ('supannCodeINE', 'supannCodeINE'), ('supannEmpCorps', 'supannEmpCorps'), ('supannEmpId', 'supannEmpId'), ('supannEntiteAffectation', 'supannEntiteAffectation'), ('supannEntiteAffectationPrincipale', 'supannEntiteAffectationPrincipale'), ('supannEtablissement', 'supannEtablissement'), ('supannEtuAnneeInscription', 'supannEtuAnneeInscription'), ('supannEtuCursusAnnee', 'supannEtuCursusAnnee'), ('supannEtuDiplome', 'supannEtuDiplome'), ('supannEtuElementPedagogique', 'supannEtuElementPedagogique'), ('supannEtuEtape', 'supannEtuEtape'), ('supannEtuId', 'supannEtuId'), ('supannEtuInscription', 'supannEtuInscription'), ('supannEtuRegimeInscription', 'supannEtuRegimeInscription'), ('supannEtuSecteurDisciplinaire', 'supannEtuSecteurDisciplinaire'), ('supannEtuTypeDiplome', 'supannEtuTypeDiplome'), ('supannGroupeAdminDN', 'supannGroupeAdminDN'), ('supannGroupeDateFin', 'supannGroupeDateFin'), ('supannGroupeLecteurDN', 'supannGroupeLecteurDN'), ('supannListeRouge', 'supannListeRouge'), ('supannMailPerso', 'supannMailPerso'), ('supannOrganisme', 'supannOrganisme'), ('supannParrainDN', 'supannParrainDN'), ('supannRefId', 'supannRefId'), ('supannRole', 'supannRole'), ('supannRoleEntite', 'supannRoleEntite'), ('supannRoleGenerique', 'supannRoleGenerique'), ('supannTypeEntite', 'supannTypeEntite'), ('supannTypeEntiteAffectation', 'supannTypeEntiteAffectation'), ('superiorUUID', 'superiorUUID'), ('supportedAlgorithms', 'supportedAlgorithms'), ('supportedApplicationContext', 'supportedApplicationContext'), ('supportedAuthPasswordSchemes', 'supportedAuthPasswordSchemes'), ('supportedControl', 'supportedControl'), ('supportedExtension', 'supportedExtension'), ('supportedFeatures', 'supportedFeatures'), ('supportedLDAPVersion', 'supportedLDAPVersion'), ('supportedSASLMechanisms', 'supportedSASLMechanisms'), ('syncConsumerSubentry', 'syncConsumerSubentry'), ('syncProviderSubentry', 'syncProviderSubentry'), ('syncTimestamp', 'syncTimestamp'), ('syncreplCookie', 'syncreplCookie'), ('telephoneNumber', 'telephoneNumber'), ('teletexTerminalIdentifier', 'teletexTerminalIdentifier'), ('telexNumber', 'telexNumber'), ('textEncodedORAddress', 'textEncodedORAddress'), ('title', 'title'), ('top', 'top'), ('uid', 'uid'), ('uidNumber', 'uidNumber'), ('uniqueIdentifier', 'uniqueIdentifier'), ('uniqueMember', 'uniqueMember'), ('userCertificate', 'userCertificate'), ('userClass', 'userClass'), ('userPKCS12', 'userPKCS12'), ('userPassword', 'userPassword'), ('userSMIMECertificate', 'userSMIMECertificate'), ('vendorName', 'vendorName'), ('vendorVersion', 'vendorVersion'), ('x121Address', 'x121Address'), ('x500UniqueIdentifier', 'x500UniqueIdentifier')])),
20
                ('output_name_format', models.CharField(default=('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'), max_length=100, verbose_name='Output name format', choices=[('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'), ('urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'SAMLv2 BASIC')])),
21
                ('output_namespace', models.CharField(default=('Default', 'Default'), max_length=100, verbose_name='Output namespace', choices=[('Default', 'Default'), ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims')])),
18
                (
19
                    'id',
20
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
21
                ),
22
                (
23
                    'attribute_name',
24
                    models.CharField(
25
                        default=('OpenLDAProotDSE', 'OpenLDAProotDSE'),
26
                        max_length=100,
27
                        verbose_name='Attribute name',
28
                        choices=[
29
                            ('OpenLDAProotDSE', 'OpenLDAProotDSE'),
30
                            ('aRecord', 'aRecord'),
31
                            ('administrativeRole', 'administrativeRole'),
32
                            ('alias', 'alias'),
33
                            ('aliasedObjectName', 'aliasedObjectName'),
34
                            ('altServer', 'altServer'),
35
                            ('associatedDomain', 'associatedDomain'),
36
                            ('associatedName', 'associatedName'),
37
                            ('attributeTypes', 'attributeTypes'),
38
                            ('audio', 'audio'),
39
                            ('authPassword', 'authPassword'),
40
                            ('authorityRevocationList', 'authorityRevocationList'),
41
                            ('authzFrom', 'authzFrom'),
42
                            ('authzTo', 'authzTo'),
43
                            ('bootFile', 'bootFile'),
44
                            ('bootParameter', 'bootParameter'),
45
                            ('buildingName', 'buildingName'),
46
                            ('businessCategory', 'businessCategory'),
47
                            ('c', 'c'),
48
                            ('cACertificate', 'cACertificate'),
49
                            ('cNAMERecord', 'cNAMERecord'),
50
                            ('carLicense', 'carLicense'),
51
                            ('certificateRevocationList', 'certificateRevocationList'),
52
                            ('children', 'children'),
53
                            ('cn', 'cn'),
54
                            ('co', 'co'),
55
                            ('collectiveAttributeSubentries', 'collectiveAttributeSubentries'),
56
                            ('collectiveAttributeSubentry', 'collectiveAttributeSubentry'),
57
                            ('collectiveExclusions', 'collectiveExclusions'),
58
                            ('configContext', 'configContext'),
59
                            ('contextCSN', 'contextCSN'),
60
                            ('createTimestamp', 'createTimestamp'),
61
                            ('creatorsName', 'creatorsName'),
62
                            ('crossCertificatePair', 'crossCertificatePair'),
63
                            ('dITContentRules', 'dITContentRules'),
64
                            ('dITRedirect', 'dITRedirect'),
65
                            ('dITStructureRules', 'dITStructureRules'),
66
                            ('dSAQuality', 'dSAQuality'),
67
                            ('dc', 'dc'),
68
                            ('deltaRevocationList', 'deltaRevocationList'),
69
                            ('departmentNumber', 'departmentNumber'),
70
                            ('description', 'description'),
71
                            ('destinationIndicator', 'destinationIndicator'),
72
                            ('displayName', 'displayName'),
73
                            ('distinguishedName', 'distinguishedName'),
74
                            ('dmdName', 'dmdName'),
75
                            ('dnQualifier', 'dnQualifier'),
76
                            ('documentAuthor', 'documentAuthor'),
77
                            ('documentIdentifier', 'documentIdentifier'),
78
                            ('documentLocation', 'documentLocation'),
79
                            ('documentPublisher', 'documentPublisher'),
80
                            ('documentTitle', 'documentTitle'),
81
                            ('documentVersion', 'documentVersion'),
82
                            ('drink', 'drink'),
83
                            ('dynamicObject', 'dynamicObject'),
84
                            ('dynamicSubtrees', 'dynamicSubtrees'),
85
                            ('eduOrgHomePageURI', 'eduOrgHomePageURI'),
86
                            ('eduOrgIdentityAuthNPolicyURI', 'eduOrgIdentityAuthNPolicyURI'),
87
                            ('eduOrgLegalName', 'eduOrgLegalName'),
88
                            ('eduOrgSuperiorURI', 'eduOrgSuperiorURI'),
89
                            ('eduOrgWhitePagesURI', 'eduOrgWhitePagesURI'),
90
                            ('eduPersonAffiliation', 'eduPersonAffiliation'),
91
                            ('eduPersonAssurance', 'eduPersonAssurance'),
92
                            ('eduPersonEntitlement', 'eduPersonEntitlement'),
93
                            ('eduPersonNickname', 'eduPersonNickname'),
94
                            ('eduPersonOrgDN', 'eduPersonOrgDN'),
95
                            ('eduPersonOrgUnitDN', 'eduPersonOrgUnitDN'),
96
                            ('eduPersonPrimaryAffiliation', 'eduPersonPrimaryAffiliation'),
97
                            ('eduPersonPrimaryOrgUnitDN', 'eduPersonPrimaryOrgUnitDN'),
98
                            ('eduPersonPrincipalName', 'eduPersonPrincipalName'),
99
                            ('eduPersonScopedAffiliation', 'eduPersonScopedAffiliation'),
100
                            ('eduPersonTargetedID', 'eduPersonTargetedID'),
101
                            ('email', 'email'),
102
                            ('employeeNumber', 'employeeNumber'),
103
                            ('employeeType', 'employeeType'),
104
                            ('enhancedSearchGuide', 'enhancedSearchGuide'),
105
                            ('entry', 'entry'),
106
                            ('entryCSN', 'entryCSN'),
107
                            ('entryDN', 'entryDN'),
108
                            ('entryTtl', 'entryTtl'),
109
                            ('entryUUID', 'entryUUID'),
110
                            ('extensibleObject', 'extensibleObject'),
111
                            ('fax', 'fax'),
112
                            ('gecos', 'gecos'),
113
                            ('generationQualifier', 'generationQualifier'),
114
                            ('gidNumber', 'gidNumber'),
115
                            ('givenName', 'givenName'),
116
                            ('glue', 'glue'),
117
                            ('hasSubordinates', 'hasSubordinates'),
118
                            ('homeDirectory', 'homeDirectory'),
119
                            ('homePhone', 'homePhone'),
120
                            ('homePostalAddress', 'homePostalAddress'),
121
                            ('host', 'host'),
122
                            ('houseIdentifier', 'houseIdentifier'),
123
                            ('info', 'info'),
124
                            ('initials', 'initials'),
125
                            ('internationaliSDNNumber', 'internationaliSDNNumber'),
126
                            ('ipHostNumber', 'ipHostNumber'),
127
                            ('ipNetmaskNumber', 'ipNetmaskNumber'),
128
                            ('ipNetworkNumber', 'ipNetworkNumber'),
129
                            ('ipProtocolNumber', 'ipProtocolNumber'),
130
                            ('ipServicePort', 'ipServicePort'),
131
                            ('ipServiceProtocolSUPname', 'ipServiceProtocolSUPname'),
132
                            ('janetMailbox', 'janetMailbox'),
133
                            ('jpegPhoto', 'jpegPhoto'),
134
                            ('knowledgeInformation', 'knowledgeInformation'),
135
                            ('l', 'l'),
136
                            ('labeledURI', 'labeledURI'),
137
                            ('ldapSyntaxes', 'ldapSyntaxes'),
138
                            ('loginShell', 'loginShell'),
139
                            ('mDRecord', 'mDRecord'),
140
                            ('mXRecord', 'mXRecord'),
141
                            ('macAddress', 'macAddress'),
142
                            ('mail', 'mail'),
143
                            ('mailForwardingAddress', 'mailForwardingAddress'),
144
                            ('mailHost', 'mailHost'),
145
                            ('mailLocalAddress', 'mailLocalAddress'),
146
                            ('mailPreferenceOption', 'mailPreferenceOption'),
147
                            ('mailRoutingAddress', 'mailRoutingAddress'),
148
                            ('manager', 'manager'),
149
                            ('matchingRuleUse', 'matchingRuleUse'),
150
                            ('matchingRules', 'matchingRules'),
151
                            ('member', 'member'),
152
                            ('memberNisNetgroup', 'memberNisNetgroup'),
153
                            ('memberUid', 'memberUid'),
154
                            ('mobile', 'mobile'),
155
                            ('modifiersName', 'modifiersName'),
156
                            ('modifyTimestamp', 'modifyTimestamp'),
157
                            ('monitorContext', 'monitorContext'),
158
                            ('nSRecord', 'nSRecord'),
159
                            ('name', 'name'),
160
                            ('nameForms', 'nameForms'),
161
                            ('namingCSN', 'namingCSN'),
162
                            ('namingContexts', 'namingContexts'),
163
                            ('nisMapEntry', 'nisMapEntry'),
164
                            ('nisMapNameSUPname', 'nisMapNameSUPname'),
165
                            ('nisNetgroupTriple', 'nisNetgroupTriple'),
166
                            ('o', 'o'),
167
                            ('objectClass', 'objectClass'),
168
                            ('objectClasses', 'objectClasses'),
169
                            ('oncRpcNumber', 'oncRpcNumber'),
170
                            ('organizationalStatus', 'organizationalStatus'),
171
                            ('otherMailbox', 'otherMailbox'),
172
                            ('ou', 'ou'),
173
                            ('owner', 'owner'),
174
                            ('pager', 'pager'),
175
                            ('personalSignature', 'personalSignature'),
176
                            ('personalTitle', 'personalTitle'),
177
                            ('photo', 'photo'),
178
                            ('physicalDeliveryOfficeName', 'physicalDeliveryOfficeName'),
179
                            ('postOfficeBox', 'postOfficeBox'),
180
                            ('postalAddress', 'postalAddress'),
181
                            ('postalCode', 'postalCode'),
182
                            ('preferredDeliveryMethod', 'preferredDeliveryMethod'),
183
                            ('preferredLanguage', 'preferredLanguage'),
184
                            ('presentationAddress', 'presentationAddress'),
185
                            ('protocolInformation', 'protocolInformation'),
186
                            ('pseudonym', 'pseudonym'),
187
                            ('ref', 'ref'),
188
                            ('referral', 'referral'),
189
                            ('registeredAddress', 'registeredAddress'),
190
                            ('rfc822MailMember', 'rfc822MailMember'),
191
                            ('role', 'role'),
192
                            ('roleOccupant', 'roleOccupant'),
193
                            ('roomNumber', 'roomNumber'),
194
                            ('sOARecord', 'sOARecord'),
195
                            ('schacHomeOrganization', 'schacHomeOrganization'),
196
                            ('schacHomeOrganizationType', 'schacHomeOrganizationType'),
197
                            ('searchGuide', 'searchGuide'),
198
                            ('secretary', 'secretary'),
199
                            ('seeAlso', 'seeAlso'),
200
                            ('serialNumber', 'serialNumber'),
201
                            ('shadowExpire', 'shadowExpire'),
202
                            ('shadowFlag', 'shadowFlag'),
203
                            ('shadowInactive', 'shadowInactive'),
204
                            ('shadowLastChange', 'shadowLastChange'),
205
                            ('shadowMax', 'shadowMax'),
206
                            ('shadowMin', 'shadowMin'),
207
                            ('shadowWarning', 'shadowWarning'),
208
                            ('singleLevelQuality', 'singleLevelQuality'),
209
                            ('sn', 'sn'),
210
                            ('st', 'st'),
211
                            ('street', 'street'),
212
                            ('structuralObjectClass', 'structuralObjectClass'),
213
                            ('subentry', 'subentry'),
214
                            ('subschema', 'subschema'),
215
                            ('subschemaSubentry', 'subschemaSubentry'),
216
                            ('subtreeMaximumQuality', 'subtreeMaximumQuality'),
217
                            ('subtreeMinimumQuality', 'subtreeMinimumQuality'),
218
                            ('subtreeSpecification', 'subtreeSpecification'),
219
                            ('supannActivite', 'supannActivite'),
220
                            ('supannAffectation', 'supannAffectation'),
221
                            ('supannAliasLogin', 'supannAliasLogin'),
222
                            ('supannAutreMail', 'supannAutreMail'),
223
                            ('supannAutreTelephone', 'supannAutreTelephone'),
224
                            ('supannCivilite', 'supannCivilite'),
225
                            ('supannCodeEntite', 'supannCodeEntite'),
226
                            ('supannCodeEntiteParent', 'supannCodeEntiteParent'),
227
                            ('supannCodeINE', 'supannCodeINE'),
228
                            ('supannEmpCorps', 'supannEmpCorps'),
229
                            ('supannEmpId', 'supannEmpId'),
230
                            ('supannEntiteAffectation', 'supannEntiteAffectation'),
231
                            ('supannEntiteAffectationPrincipale', 'supannEntiteAffectationPrincipale'),
232
                            ('supannEtablissement', 'supannEtablissement'),
233
                            ('supannEtuAnneeInscription', 'supannEtuAnneeInscription'),
234
                            ('supannEtuCursusAnnee', 'supannEtuCursusAnnee'),
235
                            ('supannEtuDiplome', 'supannEtuDiplome'),
236
                            ('supannEtuElementPedagogique', 'supannEtuElementPedagogique'),
237
                            ('supannEtuEtape', 'supannEtuEtape'),
238
                            ('supannEtuId', 'supannEtuId'),
239
                            ('supannEtuInscription', 'supannEtuInscription'),
240
                            ('supannEtuRegimeInscription', 'supannEtuRegimeInscription'),
241
                            ('supannEtuSecteurDisciplinaire', 'supannEtuSecteurDisciplinaire'),
242
                            ('supannEtuTypeDiplome', 'supannEtuTypeDiplome'),
243
                            ('supannGroupeAdminDN', 'supannGroupeAdminDN'),
244
                            ('supannGroupeDateFin', 'supannGroupeDateFin'),
245
                            ('supannGroupeLecteurDN', 'supannGroupeLecteurDN'),
246
                            ('supannListeRouge', 'supannListeRouge'),
247
                            ('supannMailPerso', 'supannMailPerso'),
248
                            ('supannOrganisme', 'supannOrganisme'),
249
                            ('supannParrainDN', 'supannParrainDN'),
250
                            ('supannRefId', 'supannRefId'),
251
                            ('supannRole', 'supannRole'),
252
                            ('supannRoleEntite', 'supannRoleEntite'),
253
                            ('supannRoleGenerique', 'supannRoleGenerique'),
254
                            ('supannTypeEntite', 'supannTypeEntite'),
255
                            ('supannTypeEntiteAffectation', 'supannTypeEntiteAffectation'),
256
                            ('superiorUUID', 'superiorUUID'),
257
                            ('supportedAlgorithms', 'supportedAlgorithms'),
258
                            ('supportedApplicationContext', 'supportedApplicationContext'),
259
                            ('supportedAuthPasswordSchemes', 'supportedAuthPasswordSchemes'),
260
                            ('supportedControl', 'supportedControl'),
261
                            ('supportedExtension', 'supportedExtension'),
262
                            ('supportedFeatures', 'supportedFeatures'),
263
                            ('supportedLDAPVersion', 'supportedLDAPVersion'),
264
                            ('supportedSASLMechanisms', 'supportedSASLMechanisms'),
265
                            ('syncConsumerSubentry', 'syncConsumerSubentry'),
266
                            ('syncProviderSubentry', 'syncProviderSubentry'),
267
                            ('syncTimestamp', 'syncTimestamp'),
268
                            ('syncreplCookie', 'syncreplCookie'),
269
                            ('telephoneNumber', 'telephoneNumber'),
270
                            ('teletexTerminalIdentifier', 'teletexTerminalIdentifier'),
271
                            ('telexNumber', 'telexNumber'),
272
                            ('textEncodedORAddress', 'textEncodedORAddress'),
273
                            ('title', 'title'),
274
                            ('top', 'top'),
275
                            ('uid', 'uid'),
276
                            ('uidNumber', 'uidNumber'),
277
                            ('uniqueIdentifier', 'uniqueIdentifier'),
278
                            ('uniqueMember', 'uniqueMember'),
279
                            ('userCertificate', 'userCertificate'),
280
                            ('userClass', 'userClass'),
281
                            ('userPKCS12', 'userPKCS12'),
282
                            ('userPassword', 'userPassword'),
283
                            ('userSMIMECertificate', 'userSMIMECertificate'),
284
                            ('vendorName', 'vendorName'),
285
                            ('vendorVersion', 'vendorVersion'),
286
                            ('x121Address', 'x121Address'),
287
                            ('x500UniqueIdentifier', 'x500UniqueIdentifier'),
288
                        ],
289
                    ),
290
                ),
291
                (
292
                    'output_name_format',
293
                    models.CharField(
294
                        default=('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'),
295
                        max_length=100,
296
                        verbose_name='Output name format',
297
                        choices=[
298
                            ('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'),
299
                            ('urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'SAMLv2 BASIC'),
300
                        ],
301
                    ),
302
                ),
303
                (
304
                    'output_namespace',
305
                    models.CharField(
306
                        default=('Default', 'Default'),
307
                        max_length=100,
308
                        verbose_name='Output namespace',
309
                        choices=[
310
                            ('Default', 'Default'),
311
                            (
312
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
313
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
314
                            ),
315
                        ],
316
                    ),
317
                ),
22 318
                ('required', models.BooleanField(default=False, verbose_name='Required')),
23 319
            ],
24 320
            options={
......
30 326
        migrations.CreateModel(
31 327
            name='AttributeList',
32 328
            fields=[
33
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
329
                (
330
                    'id',
331
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
332
                ),
34 333
                ('name', models.CharField(unique=True, max_length=100, verbose_name='Name')),
35
                ('attributes', models.ManyToManyField(related_name='attributes of the list', null=True, verbose_name='Attributes', to='attribute_aggregator.AttributeItem', blank=True)),
334
                (
335
                    'attributes',
336
                    models.ManyToManyField(
337
                        related_name='attributes of the list',
338
                        null=True,
339
                        verbose_name='Attributes',
340
                        to='attribute_aggregator.AttributeItem',
341
                        blank=True,
342
                    ),
343
                ),
36 344
            ],
37 345
            options={
38 346
                'verbose_name': 'attribute list',
......
43 351
        migrations.CreateModel(
44 352
            name='AttributeSource',
45 353
            fields=[
46
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
354
                (
355
                    'id',
356
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
357
                ),
47 358
                ('name', models.CharField(unique=True, max_length=200, verbose_name='Name')),
48
                ('namespace', models.CharField(default=('Default', 'Default'), max_length=100, verbose_name='Namespace', choices=[('Default', 'Default'), ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims')])),
359
                (
360
                    'namespace',
361
                    models.CharField(
362
                        default=('Default', 'Default'),
363
                        max_length=100,
364
                        verbose_name='Namespace',
365
                        choices=[
366
                            ('Default', 'Default'),
367
                            (
368
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
369
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
370
                            ),
371
                        ],
372
                    ),
373
                ),
49 374
            ],
50 375
            options={
51 376
                'verbose_name': 'attribute source',
......
56 381
        migrations.CreateModel(
57 382
            name='LdapSource',
58 383
            fields=[
59
                ('attributesource_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='attribute_aggregator.AttributeSource', on_delete=models.CASCADE)),
384
                (
385
                    'attributesource_ptr',
386
                    models.OneToOneField(
387
                        parent_link=True,
388
                        auto_created=True,
389
                        primary_key=True,
390
                        serialize=False,
391
                        to='attribute_aggregator.AttributeSource',
392
                        on_delete=models.CASCADE,
393
                    ),
394
                ),
60 395
                ('server', models.CharField(unique=True, max_length=200, verbose_name='Server')),
61 396
                ('user', models.CharField(max_length=200, null=True, verbose_name='User', blank=True)),
62
                ('password', models.CharField(max_length=200, null=True, verbose_name='Password', blank=True)),
397
                (
398
                    'password',
399
                    models.CharField(max_length=200, null=True, verbose_name='Password', blank=True),
400
                ),
63 401
                ('base', models.CharField(max_length=200, verbose_name='Base')),
64 402
                ('port', models.IntegerField(default=389, verbose_name='Port')),
65 403
                ('ldaps', models.BooleanField(default=False, verbose_name='LDAPS')),
66 404
                ('certificate', models.TextField(verbose_name='Certificate', blank=True)),
67
                ('is_auth_backend', models.BooleanField(default=False, verbose_name='Is it used for authentication?')),
405
                (
406
                    'is_auth_backend',
407
                    models.BooleanField(default=False, verbose_name='Is it used for authentication?'),
408
                ),
68 409
            ],
69 410
            options={
70 411
                'verbose_name': 'ldap attribute source',
......
75 416
        migrations.CreateModel(
76 417
            name='UserAliasInSource',
77 418
            fields=[
78
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
419
                (
420
                    'id',
421
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
422
                ),
79 423
                ('name', models.CharField(max_length=200, verbose_name='Name')),
80
                ('source', models.ForeignKey(verbose_name='attribute source', to='attribute_aggregator.AttributeSource', on_delete=models.CASCADE)),
81
                ('user', models.ForeignKey(related_name='user_alias_in_source', verbose_name='user', to='auth.User', on_delete=models.CASCADE)),
424
                (
425
                    'source',
426
                    models.ForeignKey(
427
                        verbose_name='attribute source',
428
                        to='attribute_aggregator.AttributeSource',
429
                        on_delete=models.CASCADE,
430
                    ),
431
                ),
432
                (
433
                    'user',
434
                    models.ForeignKey(
435
                        related_name='user_alias_in_source',
436
                        verbose_name='user',
437
                        to='auth.User',
438
                        on_delete=models.CASCADE,
439
                    ),
440
                ),
82 441
            ],
83 442
            options={
84 443
                'verbose_name': 'user alias from source',
......
89 448
        migrations.CreateModel(
90 449
            name='UserAttributeProfile',
91 450
            fields=[
92
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
451
                (
452
                    'id',
453
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
454
                ),
93 455
                ('data', models.TextField(null=True, blank=True)),
94
                ('user', models.OneToOneField(related_name='user_attribute_profile', null=True, blank=True, to='auth.User', on_delete=models.CASCADE)),
456
                (
457
                    'user',
458
                    models.OneToOneField(
459
                        related_name='user_attribute_profile',
460
                        null=True,
461
                        blank=True,
462
                        to='auth.User',
463
                        on_delete=models.CASCADE,
464
                    ),
465
                ),
95 466
            ],
96 467
            options={
97 468
                'verbose_name': 'user attribute profile',
......
106 477
        migrations.AddField(
107 478
            model_name='attributeitem',
108 479
            name='source',
109
            field=models.ForeignKey(verbose_name='Attribute source', blank=True, to='attribute_aggregator.AttributeSource', null=True, on_delete=models.CASCADE),
480
            field=models.ForeignKey(
481
                verbose_name='Attribute source',
482
                blank=True,
483
                to='attribute_aggregator.AttributeSource',
484
                null=True,
485
                on_delete=models.CASCADE,
486
            ),
110 487
            preserve_default=True,
111 488
        ),
112 489
    ]
src/authentic2/attribute_aggregator/migrations/0002_auto_20150409_1840.py
16 16
        migrations.AlterField(
17 17
            model_name='useraliasinsource',
18 18
            name='user',
19
            field=models.ForeignKey(related_name='user_alias_in_source', verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
19
            field=models.ForeignKey(
20
                related_name='user_alias_in_source',
21
                verbose_name='user',
22
                to=settings.AUTH_USER_MODEL,
23
                on_delete=models.CASCADE,
24
            ),
20 25
            preserve_default=True,
21 26
        ),
22 27
        migrations.AlterField(
23 28
            model_name='userattributeprofile',
24 29
            name='user',
25
            field=models.OneToOneField(related_name='user_attribute_profile', null=True, blank=True, to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
30
            field=models.OneToOneField(
31
                related_name='user_attribute_profile',
32
                null=True,
33
                blank=True,
34
                to=settings.AUTH_USER_MODEL,
35
                on_delete=models.CASCADE,
36
            ),
26 37
            preserve_default=True,
27 38
        ),
28 39
    ]
src/authentic2/attribute_aggregator/migrations/0003_auto_20150526_2239.py
15 15
        migrations.AlterField(
16 16
            model_name='attributelist',
17 17
            name='attributes',
18
            field=models.ManyToManyField(to='attribute_aggregator.AttributeItem', verbose_name='Attributes', blank=True),
18
            field=models.ManyToManyField(
19
                to='attribute_aggregator.AttributeItem', verbose_name='Attributes', blank=True
20
            ),
19 21
        ),
20 22
        migrations.AlterField(
21 23
            model_name='useraliasinsource',
22 24
            name='user',
23
            field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
25
            field=models.ForeignKey(
26
                verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
27
            ),
24 28
        ),
25 29
    ]
src/authentic2/attribute_kinds.py
49 49
def capfirst(value):
50 50
    return value and value[0].upper() + value[1:]
51 51

  
52

  
52 53
DEFAULT_TITLE_CHOICES = (
53 54
    (pgettext_lazy('title', 'Mrs'), pgettext_lazy('title', 'Mrs')),
54 55
    (pgettext_lazy('title', 'Mr'), pgettext_lazy('title', 'Mr')),
......
140 141
def get_title_choices():
141 142
    return app_settings.A2_ATTRIBUTE_KIND_TITLE_CHOICES or DEFAULT_TITLE_CHOICES
142 143

  
144

  
143 145
validate_phone_number = RegexValidator(
144
    r'^\+?\d{,20}$',
145
    message=_('Phone number can start with a + and must contain only digits.'))
146
    r'^\+?\d{,20}$', message=_('Phone number can start with a + and must contain only digits.')
147
)
146 148

  
147 149

  
148 150
def clean_number(number):
......
172 174
        return clean_number(super().to_internal_value(data))
173 175

  
174 176

  
175
validate_fr_postcode = RegexValidator(
176
    r'^\d{5}$',
177
    message=_('The value must be a valid french postcode'))
177
validate_fr_postcode = RegexValidator(r'^\d{5}$', message=_('The value must be a valid french postcode'))
178 178

  
179 179

  
180 180
class FrPostcodeField(forms.CharField):
......
215 215
    for chunk in uploadedfile.chunks():
216 216
        h_computation.update(chunk)
217 217
    hexdigest = h_computation.hexdigest()
218
    stored_file = default_storage.save(
219
        os.path.join('profile-image', hexdigest + '.jpeg'),
220
        uploadedfile)
218
    stored_file = default_storage.save(os.path.join('profile-image', hexdigest + '.jpeg'), uploadedfile)
221 219
    return stored_file
222 220

  
223 221

  
......
229 227

  
230 228
def profile_image_html_value(attribute, value):
231 229
    if value:
232
        fragment = u'<a href="%s"><img class="%s" src="%s"/></a>' % (
233
            value.url, attribute.name, value.url)
230
        fragment = u'<a href="%s"><img class="%s" src="%s"/></a>' % (value.url, attribute.name, value.url)
234 231
        return html.mark_safe(fragment)
235 232
    return ''
236 233

  
......
276 273
        'kwargs': {
277 274
            'choices': get_title_choices(),
278 275
            'widget': forms.RadioSelect,
279
        }
276
        },
280 277
    },
281 278
    {
282 279
        'label': _('boolean'),
src/authentic2/attributes_ng/engine.py
25 25

  
26 26

  
27 27
class UnsortableError(Exception):
28
    '''
28
    """
29 29
    Raise when topological_sort is unable to sort instance topologically.
30 30
    sorted_list contains the instances that could be sorted unsorted contains
31 31
    the instances that couldn't.
32
    '''
32
    """
33

  
33 34
    def __init__(self, sorted_list, unsortable_instances):
34 35
        self.sorted_list = sorted_list
35 36
        self.unsortable_instances = unsortable_instances
......
39 40

  
40 41

  
41 42
def topological_sort(source_and_instances, ctx, raise_on_unsortable=False):
42
    '''
43
    """
43 44
    Sort instances topologically based on their dependency declarations.
44
    '''
45
    """
45 46
    sorted_list = []
46 47
    variables = set(ctx.keys())
47 48
    unsorted = list(source_and_instances)
......
66 67
                for source, instance in unsorted:
67 68
                    dependencies = set(source.get_dependencies(instance, ctx))
68 69
                    sorted_list.append((source, instance))
69
                    logger.debug('missing dependencies for instance %r of %r: %s', instance, source,
70
                                 list(dependencies - variables))
70
                    logger.debug(
71
                        'missing dependencies for instance %r of %r: %s',
72
                        instance,
73
                        source,
74
                        list(dependencies - variables),
75
                    )
71 76
                break
72 77
    return sorted_list
73 78

  
74 79

  
75 80
@to_list
76 81
def get_sources():
77
    '''
82
    """
78 83
    List all known sources
79
    '''
84
    """
80 85
    for path in app_settings.ATTRIBUTE_BACKENDS:
81 86
        yield utils.import_module_or_class(path)
82 87
    for plugin in plugins.get_plugins():
......
87 92

  
88 93
@to_list
89 94
def get_attribute_names(ctx):
90
    '''
95
    """
91 96
    Return attribute names from all sources
92
    '''
97
    """
93 98
    for source in get_sources():
94 99
        for instance in source.get_instances(ctx):
95 100
            for attribute_name, attribute_description in source.get_attribute_names(instance, ctx):
......
97 102

  
98 103

  
99 104
def get_attributes(ctx):
100
    '''
105
    """
101 106
    Traverse and sources instances and aggregate produced attributes.
102 107

  
103 108
    Traversal is done by respecting a topological sort of instances based on
104 109
    their declared dependencies
105
    '''
110
    """
106 111
    source_and_instances = []
107 112
    for source in get_sources():
108 113
        source_and_instances.extend(((source, instance) for instance in source.get_instances(ctx)))
......
116 121
@to_iter
117 122
def get_service_attributes(service):
118 123
    ctx = {'request': None, 'user': None, 'service': service}
119
    return ([('', _('None'))] + get_attribute_names(ctx)
120
            + [('@verified_attributes@', _('List of verified attributes'))])
124
    return (
125
        [('', _('None'))]
126
        + get_attribute_names(ctx)
127
        + [('@verified_attributes@', _('List of verified attributes'))]
128
    )
src/authentic2/attributes_ng/sources/__init__.py
21 21

  
22 22
@six.add_metaclass(abc.ABCMeta)
23 23
class BaseAttributeSource(object):
24
    '''
24
    """
25 25
    Base class for attribute sources
26
    '''
26
    """
27

  
27 28
    @abc.abstractmethod
28 29
    def get_instances(self, ctx):
29 30
        pass
src/authentic2/attributes_ng/sources/django_user.py
27 27

  
28 28
@to_list
29 29
def get_instances(ctx):
30
    '''
30
    """
31 31
    Retrieve instances from settings
32
    '''
32
    """
33 33
    return [None]
34 34

  
35 35

  
src/authentic2/attributes_ng/sources/format.py
25 25

  
26 26
@to_list
27 27
def get_field_refs(format_string):
28
    '''
28
    """
29 29
    Extract the base references from format_string
30
    '''
30
    """
31 31
    from string import Formatter
32

  
32 33
    l = Formatter().parse(format_string)  # noqa: E741
33 34
    for p in l:
34 35
        field_ref = p[1].split('[', 1)[0]
35 36
        field_ref = field_ref.split('.', 1)[0]
36 37
    yield field_ref
37 38

  
39

  
38 40
UNEXPECTED_KEYS_ERROR = '{0}: unexpected ' 'key(s) {1} in configuration'
39 41
FORMAT_STRING_ERROR = '{0}: template string must contain only keyword references: {1}'
40 42
BAD_CONFIG_ERROR = 'template attribute source must contain a name and at least a template'
......
47 49

  
48 50
@to_list
49 51
def get_instances(ctx):
50
    '''
52
    """
51 53
    Retrieve instances from settings
52
    '''
54
    """
53 55
    from django.conf import settings
56

  
54 57
    for kind, d in getattr(settings, 'ATTRIBUTE_SOURCES', []):
55 58
        if kind != 'template':
56 59
            continue
src/authentic2/attributes_ng/sources/function.py
35 35

  
36 36
@to_list
37 37
def get_instances(ctx):
38
    '''
38
    """
39 39
    Retrieve instances from settings
40
    '''
40
    """
41 41
    from django.conf import settings
42

  
42 43
    for kind, d in getattr(settings, 'ATTRIBUTE_SOURCES', []):
43 44
        if kind != 'function':
44 45
            continue
......
50 51
            missing = REQUIRED_KEYS - keys
51 52
            config_error(MISSING_KEYS_ERROR, missing)
52 53
        dependencies = d['dependencies']
53
        if not isinstance(dependencies, (list, tuple)) or \
54
                not all(isinstance(dep, str) for dep in dependencies):
54
        if not isinstance(dependencies, (list, tuple)) or not all(
55
            isinstance(dep, str) for dep in dependencies
56
        ):
55 57
            config_error(DEPENDENCY_TYPE_ERROR)
56 58

  
57 59
        if not callable(d['function']):
src/authentic2/attributes_ng/sources/ldap.py
21 21

  
22 22
@to_list
23 23
def get_instances(ctx):
24
    '''
24
    """
25 25
    Retrieve instances from settings
26
    '''
26
    """
27 27
    return [None]
28 28

  
29 29

  
src/authentic2/attributes_ng/sources/service_roles.py
33 33
    if not isinstance(service, Service):
34 34
        return
35 35
    names = []
36
    for service_role in Role.objects.filter(service=service) \
37
            .prefetch_related('attributes'):
36
    for service_role in Role.objects.filter(service=service).prefetch_related('attributes'):
38 37
        for service_role_attribute in service_role.attributes.all():
39 38
            if service_role_attribute.name in names:
40 39
                continue
......
45 44

  
46 45

  
47 46
def get_dependencies(instance, ctx):
48
    return ('user', 'service',)
47
    return (
48
        'user',
49
        'service',
50
    )
49 51

  
50 52

  
51 53
def get_attributes(instance, ctx):
......
54 56
    if not user or not service:
55 57
        return ctx
56 58
    ctx = ctx.copy()
57
    roles = Role.objects.for_user(user) \
58
        .filter(service=service) \
59
        .prefetch_related('attributes')
59
    roles = Role.objects.for_user(user).filter(service=service).prefetch_related('attributes')
60 60
    for service_role in roles:
61 61
        for service_role_attribute in service_role.attributes.all():
62 62
            name = service_role_attribute.name
src/authentic2/auth2_auth/auth2_ssl/migrations/0001_initial.py
7 7
class Migration(migrations.Migration):
8 8

  
9 9
    dependencies = [
10
            ('auth', '0002_auto_20150323_1720'),
10
        ('auth', '0002_auto_20150323_1720'),
11 11
    ]
12 12

  
13 13
    operations = [
14 14
        migrations.CreateModel(
15 15
            name='ClientCertificate',
16 16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17
                (
18
                    'id',
19
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
20
                ),
18 21
                ('serial', models.CharField(max_length=255, blank=True)),
19 22
                ('subject_dn', models.CharField(max_length=255)),
20 23
                ('issuer_dn', models.CharField(max_length=255)),
21 24
                ('cert', models.TextField()),
22 25
                ('user', models.ForeignKey(to='auth.User')),
23 26
            ],
24
            options={
25
            },
27
            options={},
26 28
            bases=(models.Model,),
27 29
        ),
28 30
    ]
src/authentic2/auth_migrations_18/0001_initial.py
16 16
        migrations.CreateModel(
17 17
            name='Permission',
18 18
            fields=[
19
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19
                (
20
                    'id',
21
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
22
                ),
20 23
                ('name', models.CharField(max_length=50, verbose_name='name')),
21
                ('content_type', models.ForeignKey(to='contenttypes.ContentType', to_field='id', on_delete=models.CASCADE)),
24
                (
25
                    'content_type',
26
                    models.ForeignKey(to='contenttypes.ContentType', to_field='id', on_delete=models.CASCADE),
27
                ),
22 28
                ('codename', models.CharField(max_length=100, verbose_name='codename')),
23 29
            ],
24 30
            options={
......
30 36
        migrations.CreateModel(
31 37
            name='Group',
32 38
            fields=[
33
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
39
                (
40
                    'id',
41
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
42
                ),
34 43
                ('name', models.CharField(unique=True, max_length=80, verbose_name='name')),
35
                ('permissions', models.ManyToManyField(to='auth.Permission', verbose_name='permissions', blank=True)),
44
                (
45
                    'permissions',
46
                    models.ManyToManyField(to='auth.Permission', verbose_name='permissions', blank=True),
47
                ),
36 48
            ],
37 49
            options={
38 50
                'verbose_name': 'group',
......
42 54
        migrations.CreateModel(
43 55
            name='User',
44 56
            fields=[
45
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
57
                (
58
                    'id',
59
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
60
                ),
46 61
                ('password', models.CharField(max_length=128, verbose_name='password')),
47 62
                ('last_login', models.DateTimeField(default=timezone.now, verbose_name='last login')),
48
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
49
                ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
63
                (
64
                    'is_superuser',
65
                    models.BooleanField(
66
                        default=False,
67
                        help_text='Designates that this user has all permissions without explicitly assigning them.',
68
                        verbose_name='superuser status',
69
                    ),
70
                ),
71
                (
72
                    'username',
73
                    models.CharField(
74
                        help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
75
                        unique=True,
76
                        max_length=30,
77
                        verbose_name='username',
78
                        validators=[
79
                            validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')
80
                        ],
81
                    ),
82
                ),
50 83
                ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
51 84
                ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
52 85
                ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
53
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
54
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
86
                (
87
                    'is_staff',
88
                    models.BooleanField(
89
                        default=False,
90
                        help_text='Designates whether the user can log into this admin site.',
91
                        verbose_name='staff status',
92
                    ),
93
                ),
94
                (
95
                    'is_active',
96
                    models.BooleanField(
97
                        default=True,
98
                        help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
99
                        verbose_name='active',
100
                    ),
101
                ),
55 102
                ('date_joined', models.DateTimeField(default=timezone.now, verbose_name='date joined')),
56
                ('groups', models.ManyToManyField(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.', related_name='+', related_query_name='+')),
57
                ('user_permissions', models.ManyToManyField(to='auth.Permission', verbose_name='user permissions', blank=True, help_text='Specific permissions for this user.', related_name='+', related_query_name='+')),
103
                (
104
                    'groups',
105
                    models.ManyToManyField(
106
                        to='auth.Group',
107
                        verbose_name='groups',
108
                        blank=True,
109
                        help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
110
                        related_name='+',
111
                        related_query_name='+',
112
                    ),
113
                ),
114
                (
115
                    'user_permissions',
116
                    models.ManyToManyField(
117
                        to='auth.Permission',
118
                        verbose_name='user permissions',
119
                        blank=True,
120
                        help_text='Specific permissions for this user.',
121
                        related_name='+',
122
                        related_query_name='+',
123
                    ),
124
                ),
58 125
            ],
59 126
            options={
60 127
                'verbose_name': 'user',
src/authentic2/auth_migrations_18/0002_auto_20150323_1720.py
15 15
        migrations.AlterField(
16 16
            model_name='user',
17 17
            name='username',
18
            field=models.CharField(help_text='Required, 255 characters or fewer. Only letters, numbers, and @, ., +, -, or _ characters.', unique=True, max_length=255, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')]),
18
            field=models.CharField(
19
                help_text='Required, 255 characters or fewer. Only letters, numbers, and @, ., +, -, or _ characters.',
20
                unique=True,
21
                max_length=255,
22
                verbose_name='username',
23
                validators=[
24
                    django.core.validators.RegexValidator(
25
                        '^[\\w.@+-]+$', 'Enter a valid username.', 'invalid'
26
                    )
27
                ],
28
            ),
19 29
            preserve_default=True,
20 30
        ),
21 31
    ]
src/authentic2/auth_migrations_18/0003_auto_20150410_1657.py
13 13
    ]
14 14

  
15 15
    operations = [
16
            migrations.DeleteModel('User'),
16
        migrations.DeleteModel('User'),
17 17
    ]
src/authentic2/auth_migrations_18/0004_user.py
19 19
        migrations.CreateModel(
20 20
            name='User',
21 21
            fields=[
22
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
22
                (
23
                    'id',
24
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
25
                ),
23 26
                ('password', models.CharField(max_length=128, verbose_name='password')),
24
                ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
25
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
26
                ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
27
                (
28
                    'last_login',
29
                    models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login'),
30
                ),
31
                (
32
                    'is_superuser',
33
                    models.BooleanField(
34
                        default=False,
35
                        help_text='Designates that this user has all permissions without explicitly assigning them.',
36
                        verbose_name='superuser status',
37
                    ),
38
                ),
39
                (
40
                    'username',
41
                    models.CharField(
42
                        help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
43
                        unique=True,
44
                        max_length=30,
45
                        verbose_name='username',
46
                        validators=[
47
                            django.core.validators.RegexValidator(
48
                                '^[\\w.@+-]+$', 'Enter a valid username.', 'invalid'
49
                            )
50
                        ],
51
                    ),
52
                ),
27 53
                ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
28 54
                ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
29 55
                ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
30
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
31
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
32
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
33
                ('groups', models.ManyToManyField(related_query_name='user', 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.', verbose_name='groups')),
34
                ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
56
                (
57
                    'is_staff',
58
                    models.BooleanField(
59
                        default=False,
60
                        help_text='Designates whether the user can log into this admin site.',
61
                        verbose_name='staff status',
62
                    ),
63
                ),
64
                (
65
                    'is_active',
66
                    models.BooleanField(
67
                        default=True,
68
                        help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
69
                        verbose_name='active',
70
                    ),
71
                ),
72
                (
73
                    'date_joined',
74
                    models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'),
75
                ),
76
                (
77
                    'groups',
78
                    models.ManyToManyField(
79
                        related_query_name='user',
80
                        related_name='user_set',
81
                        to='auth.Group',
82
                        blank=True,
83
                        help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
84
                        verbose_name='groups',
85
                    ),
86
                ),
87
                (
88
                    'user_permissions',
89
                    models.ManyToManyField(
90
                        related_query_name='user',
91
                        related_name='user_set',
92
                        to='auth.Permission',
93
                        blank=True,
94
                        help_text='Specific permissions for this user.',
95
                        verbose_name='user permissions',
96
                    ),
97
                ),
35 98
            ],
36 99
            options={
37 100
                'abstract': False,
src/authentic2/auth_migrations_18/0005_auto_20150526_2303.py
44 44
        migrations.AlterField(
45 45
            model_name='user',
46 46
            name='groups',
47
            field=models.ManyToManyField(related_query_name='user', 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.', verbose_name='groups'),
47
            field=models.ManyToManyField(
48
                related_query_name='user',
49
                related_name='user_set',
50
                to='auth.Group',
51
                blank=True,
52
                help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
53
                verbose_name='groups',
54
            ),
48 55
        ),
49 56
        migrations.AlterField(
50 57
            model_name='user',
......
54 61
        migrations.AlterField(
55 62
            model_name='user',
56 63
            name='username',
57
            field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
64
            field=models.CharField(
65
                error_messages={'unique': 'A user with that username already exists.'},
66
                max_length=30,
67
                validators=[
68
                    django.core.validators.RegexValidator(
69
                        '^[\\w.@+-]+$',
70
                        'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.',
71
                        'invalid',
72
                    )
73
                ],
74
                help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
75
                unique=True,
76
                verbose_name='username',
77
            ),
58 78
        ),
59 79
    ]
src/authentic2/authentication.py
17 17
import inspect
18 18

  
19 19
from django.utils import six
20

  
20 21
try:
21 22
    from django.utils.deprecation import CallableTrue
22 23
except ImportError:
......
29 30

  
30 31

  
31 32
class OIDCUser(object):
32
    """ Fake user class to return in case OIDC authentication
33
    """
33
    """Fake user class to return in case OIDC authentication"""
34 34

  
35 35
    def __init__(self, oidc_client):
36 36
        self.oidc_client = oidc_client
......
54 54

  
55 55

  
56 56
class Authentic2Authentication(BasicAuthentication):
57

  
58 57
    def authenticate_credentials(self, userid, password, request=None):
59 58
        # try Simple OIDC Authentication
60 59
        try:
61 60
            client = OIDCClient.objects.get(client_id=userid, client_secret=password)
62 61
            if not client.has_api_access:
63 62
                raise AuthenticationFailed('OIDC client does not have access to the API')
64
            if client.identifier_policy not in (client.POLICY_UUID,
65
                                                client.POLICY_PAIRWISE_REVERSIBLE):
66
                raise AuthenticationFailed('OIDC Client identifier policy does not allow access to '
67
                                           'the API')
63
            if client.identifier_policy not in (client.POLICY_UUID, client.POLICY_PAIRWISE_REVERSIBLE):
64
                raise AuthenticationFailed(
65
                    'OIDC Client identifier policy does not allow access to ' 'the API'
66
                )
68 67
            user = OIDCUser(client)
69 68
            user.authenticated = True
70 69
            return (user, True)
71 70
        except OIDCClient.DoesNotExist:
72 71
            pass
73 72
        # try BasicAuthentication
74
        if (six.PY3
75
                and 'request' in inspect.signature(
76
                    super(Authentic2Authentication, self).authenticate_credentials).parameters):
73
        if (
74
            six.PY3
75
            and 'request'
76
            in inspect.signature(super(Authentic2Authentication, self).authenticate_credentials).parameters
77
        ):
77 78
            # compatibility with DRF 3.4
78
            return super(Authentic2Authentication, self).authenticate_credentials(userid, password, request=request)
79
            return super(Authentic2Authentication, self).authenticate_credentials(
80
                userid, password, request=request
81
            )
79 82
        return super(Authentic2Authentication, self).authenticate_credentials(userid, password)
src/authentic2/authenticators.py
32 32

  
33 33

  
34 34
class BaseAuthenticator(object):
35

  
36 35
    def __init__(self, show_condition=None, **kwargs):
37 36
        self.show_condition = show_condition
38 37

  
......
71 70
        if not roles:
72 71
            return []
73 72
        service_ou_ids = []
74
        qs = User.objects.filter(roles__in=roles).values_list('ou').annotate(count=Count('ou')).order_by('-count')
73
        qs = (
74
            User.objects.filter(roles__in=roles)
75
            .values_list('ou')
76
            .annotate(count=Count('ou'))
77
            .order_by('-count')
78
        )
75 79
        for ou_id, count in qs:
76 80
            if not ou_id:
77 81
                continue
......
109 113
                initial['ou'] = preferred_ous[0]
110 114

  
111 115
        form = authentication_forms.AuthenticationForm(
112
            request=request,
113
            data=data,
114
            initial=initial,
115
            preferred_ous=preferred_ous)
116
            request=request, data=data, initial=initial, preferred_ous=preferred_ous
117
        )
116 118
        if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION:
117 119
            form.fields['username'].label = _('Username or email')
118 120
        if app_settings.A2_USERNAME_LABEL:
......
131 133
                    request.session.set_expiry(app_settings.A2_USER_REMEMBER_ME)
132 134
                response = utils.login(request, form.get_user(), how, service=service)
133 135
                if 'ou' in form.fields:
134
                    utils.prepend_remember_cookie(request, response, 'preferred-ous', form.cleaned_data['ou'].pk)
136
                    utils.prepend_remember_cookie(
137
                        request, response, 'preferred-ous', form.cleaned_data['ou'].pk
138
                    )
135 139

  
136 140
                if hasattr(request, 'needs_password_change'):
137 141
                    del request.needs_password_change
138
                    return utils.redirect(request, 'password_change', params={'next': response.url}, resolve=True)
142
                    return utils.redirect(
143
                        request, 'password_change', params={'next': response.url}, resolve=True
144
                    )
139 145

  
140 146
                return response
141 147
            else:
src/authentic2/backends/ldap_backend.py
26 26
    from ldap.controls import SimplePagedResultsControl, DecodeControlTuples
27 27
    from ldap.controls import ppolicy
28 28
    from pyasn1.codec.der import decoder
29

  
29 30
    PYTHON_LDAP3 = [int(x) for x in ldap.__version__.split('.')] >= [3]
30 31
    LDAPObject = NativeLDAPObject
31 32
except ImportError:
......
97 98

  
98 99

  
99 100
if PYTHON_LDAP3 is True:
101

  
100 102
    class LDAPObject(NativeLDAPObject):
101
        def __init__(self, uri, trace_level=0, trace_file=None,
102
                     trace_stack_limit=5, bytes_mode=False,
103
                     bytes_strictness=None, retry_max=1, retry_delay=60.0):
104
            NativeLDAPObject.__init__(self, uri=uri, trace_level=trace_level,
105
                                      trace_file=trace_file,
106
                                      trace_stack_limit=trace_stack_limit,
107
                                      bytes_mode=bytes_mode, bytes_strictness=bytes_strictness,
108
                                      retry_max=retry_max,
109
                                      retry_delay=retry_delay)
103
        def __init__(
104
            self,
105
            uri,
106
            trace_level=0,
107
            trace_file=None,
108
            trace_stack_limit=5,
109
            bytes_mode=False,
110
            bytes_strictness=None,
111
            retry_max=1,
112
            retry_delay=60.0,
113
        ):
114
            NativeLDAPObject.__init__(
115
                self,
116
                uri=uri,
117
                trace_level=trace_level,
118
                trace_file=trace_file,
119
                trace_stack_limit=trace_stack_limit,
120
                bytes_mode=bytes_mode,
121
                bytes_strictness=bytes_strictness,
122
                retry_max=retry_max,
123
                retry_delay=retry_delay,
124
            )
110 125

  
111 126
        @to_list
112 127
        def _convert_results_to_unicode(self, result_list):
......
119 134
        def modify_s(self, dn, modlist):
120 135
            new_modlist = []
121 136
            for mod_op, mod_typ, mod_vals in modlist:
137

  
122 138
                def convert(v):
123 139
                    if hasattr(v, 'isnumeric'):
124 140
                        # unicode case
125 141
                        v = v.encode('utf-8')
126 142
                    return v
143

  
127 144
                if mod_vals is None:
128 145
                    pass
129 146
                elif isinstance(mod_vals, list):
......
133 150
                new_modlist.append((mod_op, mod_typ, mod_vals))
134 151
            return NativeLDAPObject.modify_s(self, dn, new_modlist)
135 152

  
136
        def result4(self, msgid=ldap.RES_ANY, all=1, timeout=None, add_ctrls=0,
137
                    add_intermediates=0, add_extop=0, resp_ctrl_classes=None):
138
            resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value = NativeLDAPObject.result4(
153
        def result4(
154
            self,
155
            msgid=ldap.RES_ANY,
156
            all=1,
157
            timeout=None,
158
            add_ctrls=0,
159
            add_intermediates=0,
160
            add_extop=0,
161
            resp_ctrl_classes=None,
162
        ):
163
            (
164
                resp_type,
165
                resp_data,
166
                resp_msgid,
167
                decoded_resp_ctrls,
168
                resp_name,
169
                resp_value,
170
            ) = NativeLDAPObject.result4(
139 171
                self,
140 172
                msgid=msgid,
141 173
                all=all,
......
143 175
                add_ctrls=add_ctrls,
144 176
                add_intermediates=add_intermediates,
145 177
                add_extop=add_extop,
146
                resp_ctrl_classes=resp_ctrl_classes)
178
                resp_ctrl_classes=resp_ctrl_classes,
179
            )
147 180
            if resp_data:
148 181
                resp_data = self._convert_results_to_unicode(resp_data)
149 182
            return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
150 183

  
184

  
151 185
elif PYTHON_LDAP3 is False:
186

  
152 187
    class LDAPObject(NativeLDAPObject):
153 188
        def simple_bind_s(self, who='', cred='', serverctrls=None, clientctrls=None):
154 189
            who = force_bytes(who)
155 190
            cred = force_bytes(cred)
156
            return NativeLDAPObject.simple_bind_s(self, who=who, cred=cred,
157
                                                  serverctrls=serverctrls,
158
                                                  clientctrls=clientctrls)
191
            return NativeLDAPObject.simple_bind_s(
192
                self, who=who, cred=cred, serverctrls=serverctrls, clientctrls=clientctrls
193
            )
159 194

  
160 195
        def passwd_s(self, dn, oldpw, newpw, serverctrls=None, clientctrls=None):
161 196
            dn = force_bytes(dn)
162 197
            oldpw = force_bytes(oldpw)
163 198
            newpw = force_bytes(newpw)
164
            return NativeLDAPObject.passwd_s(self, dn, oldpw, newpw,
165
                                             serverctrls=serverctrls,
166
                                             clientctrls=clientctrls)
199
            return NativeLDAPObject.passwd_s(
200
                self, dn, oldpw, newpw, serverctrls=serverctrls, clientctrls=clientctrls
201
            )
167 202

  
168 203
        @to_list
169 204
        def _convert_results_to_unicode(self, result_list):
......
173 208
                    attrs = {attribute: filter_non_unicode_values(attrs[attribute]) for attribute in attrs}
174 209
                    yield force_text(dn), attrs
175 210

  
176
        def search_ext(self, base, scope, filterstr='(objectclass=*)',
177
                       attrlist=None, attrsonly=0, serverctrls=None,
178
                       clientctrls=None, timeout=-1, sizelimit=0):
211
        def search_ext(
212
            self,
213
            base,
214
            scope,
215
            filterstr='(objectclass=*)',
216
            attrlist=None,
217
            attrsonly=0,
218
            serverctrls=None,
219
            clientctrls=None,
220
            timeout=-1,
221
            sizelimit=0,
222
        ):
179 223
            base = force_bytes(base)
180 224
            filterstr = force_bytes(filterstr)
181 225
            if attrlist:
182 226
                attrlist = [force_bytes(attr) for attr in attrlist]
183
            return NativeLDAPObject.search_ext(self, base, scope,
184
                                               filterstr=filterstr,
185
                                               attrlist=attrlist,
186
                                               attrsonly=attrsonly,
187
                                               serverctrls=serverctrls,
188
                                               clientctrls=clientctrls,
189
                                               timeout=timeout,
190
                                               sizelimit=sizelimit)
227
            return NativeLDAPObject.search_ext(
228
                self,
229
                base,
230
                scope,
231
                filterstr=filterstr,
232
                attrlist=attrlist,
233
                attrsonly=attrsonly,
234
                serverctrls=serverctrls,
235
                clientctrls=clientctrls,
236
                timeout=timeout,
237
                sizelimit=sizelimit,
238
            )
191 239

  
192 240
        def modify_s(self, dn, modlist):
193 241
            dn = force_bytes(dn)
......
200 248
                        # unicode case
201 249
                        v = force_bytes(v)
202 250
                    return v
251

  
203 252
                if mod_vals is None:
204 253
                    pass
205 254
                elif isinstance(mod_vals, list):
......
209 258
                new_modlist.append((mod_op, mod_typ, mod_vals))
210 259
            return NativeLDAPObject.modify_s(self, dn, new_modlist)
211 260

  
212
        def result4(self, msgid=ldap.RES_ANY, all=1, timeout=None, add_ctrls=0,
213
                    add_intermediates=0, add_extop=0, resp_ctrl_classes=None):
214
            resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value = NativeLDAPObject.result4(
261
        def result4(
262
            self,
263
            msgid=ldap.RES_ANY,
264
            all=1,
265
            timeout=None,
266
            add_ctrls=0,
267
            add_intermediates=0,
268
            add_extop=0,
269
            resp_ctrl_classes=None,
270
        ):
271
            (
272
                resp_type,
273
                resp_data,
274
                resp_msgid,
275
                decoded_resp_ctrls,
276
                resp_name,
277
                resp_value,
278
            ) = NativeLDAPObject.result4(
215 279
                self,
216 280
                msgid=msgid,
217 281
                all=all,
......
219 283
                add_ctrls=add_ctrls,
220 284
                add_intermediates=add_intermediates,
221 285
                add_extop=add_extop,
222
                resp_ctrl_classes=resp_ctrl_classes)
286
                resp_ctrl_classes=resp_ctrl_classes,
287
            )
223 288
            if resp_data:
224 289
                resp_data = self._convert_results_to_unicode(resp_data)
225 290
            return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
......
258 323

  
259 324
    if ctrl.timeBeforeExpiration:
260 325
        expiration_date = time.asctime(time.localtime(time.time() + ctrl.timeBeforeExpiration))
261
        messages.append(_('The password will expire at {expiration_date}.').format(
262
            expiration_date=expiration_date))
326
        messages.append(
327
            _('The password will expire at {expiration_date}.').format(expiration_date=expiration_date)
328
        )
263 329
    if ctrl.graceAuthNsRemaining:
264
        messages.append(ngettext(
265
            'This password expired: this is the last time it can be used.',
266
            'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
267
            ctrl.graceAuthNsRemaining).format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining))
330
        messages.append(
331
            ngettext(
332
                'This password expired: this is the last time it can be used.',
333
                'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
334
                ctrl.graceAuthNsRemaining,
335
            ).format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining)
336
        )
268 337
    return messages
269 338

  
339

  
270 340
class LDAPUser(User):
271 341
    SESSION_LDAP_DATA_KEY = 'ldap-data'
272 342
    _changed = False
......
295 365
            # update dn case, can be removed in the future
296 366
            self.ldap_data['dn'] = self.ldap_data['dn'].lower()
297 367
            if self.ldap_data.get('password'):
298
                self.ldap_data['password'] = {key.lower(): value for key, value in self.ldap_data['password'].items()}
368
                self.ldap_data['password'] = {
369
                    key.lower(): value for key, value in self.ldap_data['password'].items()
370
                }
299 371

  
300 372
            # retrieve encrypted bind pw if necessary
301 373
            encrypted_bindpw = self.ldap_data.get('block', {}).get('encrypted_bindpw')
302 374
            if encrypted_bindpw:
303
                decrypted = crypto.aes_base64_decrypt(settings.SECRET_KEY, encrypted_bindpw,
304
                                                      raise_on_error=False)
375
                decrypted = crypto.aes_base64_decrypt(
376
                    settings.SECRET_KEY, encrypted_bindpw, raise_on_error=False
377
                )
305 378
                if decrypted:
306 379
                    decrypted = force_text(decrypted)
307 380
                    self.ldap_data['block']['bindpw'] = decrypted
......
312 385
        data = dict(self.ldap_data)
313 386
        data['block'] = dict(data['block'])
314 387
        if data['block'].get('bindpw'):
315
            data['block']['encrypted_bindpw'] = force_text(crypto.aes_base64_encrypt(
316
                settings.SECRET_KEY, force_bytes(data['block']['bindpw'])))
388
            data['block']['encrypted_bindpw'] = force_text(
389
                crypto.aes_base64_encrypt(settings.SECRET_KEY, force_bytes(data['block']['bindpw']))
390
            )
317 391
            del data['block']['bindpw']
318 392
        session[self.SESSION_LDAP_DATA_KEY] = data
319 393

  
......
346 420
        cache = self.ldap_data.setdefault('password', {})
347 421
        if password is not None:
348 422
            # Prevent eavesdropping of the password through the session storage
349
            password = force_text(crypto.aes_base64_encrypt(
350
                    settings.SECRET_KEY, force_bytes(password)))
423
            password = force_text(crypto.aes_base64_encrypt(settings.SECRET_KEY, force_bytes(password)))
351 424
        cache[self.dn] = password
352 425
        # ensure session is marked dirty
353 426
        self.update_request()
......
420 493
        return self.ldap_backend.get_connection(self.block, credentials=credentials)
421 494

  
422 495
    def get_attributes(self, attribute_source, ctx):
423
        cache_key = hashlib.md5((force_text(str(self.pk)) + ';' + force_text(self.dn)).encode('utf-8')).hexdigest()
496
        cache_key = hashlib.md5(
497
            (force_text(str(self.pk)) + ';' + force_text(self.dn)).encode('utf-8')
498
        ).hexdigest()
424 499
        conn = self.get_connection()
425 500
        # prevents blocking on temporary LDAP failures
426 501
        if conn is not None:
......
506 581
        'update_username': False,
507 582
        # lookup existing user with an external id build with attributes
508 583
        'lookups': ('external_id', 'username'),
509
        'external_id_tuples': (('uid',), ('dn:noquote',),),
584
        'external_id_tuples': (
585
            ('uid',),
586
            ('dn:noquote',),
587
        ),
510 588
        # clean all other existing external id for an user after linking the user
511 589
        # to an external id.
512 590
        'clean_external_id_on_update': True,
......
532 610
        'certfile': '',
533 611
        'keyfile': '',
534 612
        # LDAP library options
535
        'ldap_options': {
536
        },
537
        'global_ldap_options': {
538
        },
613
        'ldap_options': {},
614
        'global_ldap_options': {},
539 615
        # Use Password Modify extended operation
540 616
        'use_password_modify': True,
541 617
        # Target OU
......
551 627
    }
552 628
    _REQUIRED = ('url', 'basedn')
553 629
    _TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive')
554
    _TO_LOWERCASE = ('fname_field', 'lname_field', 'email_field', 'attributes',
555
                     'mandatory_attributes_values', 'member_of_attribute',
556
                     'group_to_role_mapping', 'group_mapping',
557
                     'attribute_mappings', 'external_id_tuples')
630
    _TO_LOWERCASE = (
631
        'fname_field',
632
        'lname_field',
633
        'email_field',
634
        'attributes',
635
        'mandatory_attributes_values',
636
        'member_of_attribute',
637
        'group_to_role_mapping',
638
        'group_mapping',
639
        'attribute_mappings',
640
        'external_id_tuples',
641
    )
558 642
    _VALID_CONFIG_KEYS = list(set(_REQUIRED).union(set(_DEFAULTS)))
559 643

  
560 644
    @classmethod
......
607 691
    def get_groups_dns(cls, conn, block):
608 692
        group_base_dn = block['group_basedn'] or block['basedn']
609 693
        # 1.1 is special attribute meaning, "no attribute requested"
610
        results = conn.search_s(group_base_dn, ldap.SCOPE_SUBTREE,
611
                                block['group_filter'], ['1.1'])
694
        results = conn.search_s(group_base_dn, ldap.SCOPE_SUBTREE, block['group_filter'], ['1.1'])
612 695
        results = cls.normalize_ldap_results(results)
613 696
        return set([group_dn for group_dn, attrs in results])
614 697

  
......
638 721
                if realm and block.get('realm') != realm:
639 722
                    continue
640 723
            if '%s' not in block['user_filter']:
641
                log.error(
642
                    "account name authentication filter doesn't contain '%s'")
724
                log.error("account name authentication filter doesn't contain '%s'")
643 725
                continue
644 726
            user = self.authenticate_block(request, block, uid, password)
645 727
            if user is not None:
......
667 749
                            try:
668 750
                                query = filter_format(user_filter, (username,) * n)
669 751
                            except TypeError as e:
670
                                log.error('user_filter syntax error %r: %s', block['user_filter'],
671
                                          e)
752
                                log.error('user_filter syntax error %r: %s', block['user_filter'], e)
672 753
                                return
673
                            log.debug('[%s] looking up dn for username %r using query %r', ldap_uri,
674
                                      username, query)
754
                            log.debug(
755
                                '[%s] looking up dn for username %r using query %r', ldap_uri, username, query
756
                            )
675 757
                            results = conn.search_s(user_basedn, ldap.SCOPE_SUBTREE, query, [u'1.1'])
676 758
                            results = self.normalize_ldap_results(results)
677 759
                            # remove search references
......
680 762
                            if len(results) == 0:
681 763
                                log.debug('[%s] user lookup failed: no entry found, %s', ldap_uri, query)
682 764
                            elif not block['multimatch'] and len(results) > 1:
683
                                log.error('[%s] user lookup failed: too many (%d) entries found: %s',
684
                                          ldap_uri, len(results), query)
765
                                log.error(
766
                                    '[%s] user lookup failed: too many (%d) entries found: %s',
767
                                    ldap_uri,
768
                                    len(results),
769
                                    query,
770
                                )
685 771
                            else:
686 772
                                authz_ids.extend(result[0] for result in results)
687 773
                        else:
......
719 805
                            break
720 806
                        except ldap.INVALID_CREDENTIALS as e:
721 807
                            if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]:
722
                                self.process_controls(request, authz_id, DecodeControlTuples(e.args[0]['ctrls']))
808
                                self.process_controls(
809
                                    request, authz_id, DecodeControlTuples(e.args[0]['ctrls'])
810
                                )
723 811
                            attributes = self.get_ldap_attributes(block, conn, authz_id)
724 812
                            user = self.lookup_existing_user(authz_id, block, attributes)
725 813
                            if user and hasattr(request, 'failed_logins'):
......
738 826
                        break
739 827
                return self._return_user(authz_id, password, conn, block)
740 828
            except ldap.CONNECT_ERROR:
741
                log.error('connection to %r failed, did you forget to declare the TLS certificate '
742
                          'in /etc/ldap/ldap.conf ?', block['url'])
829
                log.error(
830
                    'connection to %r failed, did you forget to declare the TLS certificate '
831
                    'in /etc/ldap/ldap.conf ?',
832
                    block['url'],
833
                )
743 834
            except ldap.TIMEOUT:
744 835
                log.error('connection to %r timed out', block['url'])
745 836
            except ldap.SERVER_DOWN:
......
767 858
    @classmethod
768 859
    def _parse_simple_config(self):
769 860
        if len(settings.LDAP_AUTH_SETTINGS) < 2:
770
            raise ImproperlyConfigured('In a minimal configuration, you must at least specify '
771
                                       'url and user DN')
861
            raise ImproperlyConfigured(
862
                'In a minimal configuration, you must at least specify ' 'url and user DN'
863
            )
772 864
        return {'url': settings.LDAP_AUTH_SETTINGS[0], 'basedn': settings.LDAP_AUTH_SETTINGS[1]}
773 865

  
774 866
    def backend_name(self):
......
786 878

  
787 879
    def populate_user_attributes(self, user, block, attributes):
788 880
        # map legacy attributes (columns from Django user model)
789
        for legacy_attribute, legacy_field in (('email', 'email_field'),
790
                                               ('first_name', 'fname_field'),
791
                                               ('last_name', 'lname_field')):
881
        for legacy_attribute, legacy_field in (
882
            ('email', 'email_field'),
883
            ('first_name', 'fname_field'),
884
            ('last_name', 'lname_field'),
885
        ):
792 886
            ldap_attribute = block[legacy_field]
793 887
            if not ldap_attribute:
794 888
                break
......
820 914
                user._changed = True
821 915

  
822 916
    def populate_admin_flags_by_group(self, user, block, group_dns):
823
        '''Attribute admin flags based on groups.
824

  
825
           It supersedes is_staff, is_superuser and is_active.'''
826
        for g, attr in (('groupsu', 'is_superuser'),
827
                        ('groupstaff', 'is_staff'),
828
                        ('groupactive', 'is_active')):
917
        """Attribute admin flags based on groups.
918

  
919
        It supersedes is_staff, is_superuser and is_active."""
920
        for g, attr in (
921
            ('groupsu', 'is_superuser'),
922
            ('groupstaff', 'is_staff'),
923
            ('groupactive', 'is_active'),
924
        ):
829 925
            group_dns_to_match = block[g]
830 926
            if not group_dns_to_match:
831 927
                continue
......
887 983
                    role.save()
888 984

  
889 985
    def get_ldap_group_dns(self, user, dn, conn, block, attributes):
890
        '''Retrieve group DNs from the LDAP by attributes (memberOf) or by
891
           filter.
892
        '''
986
        """Retrieve group DNs from the LDAP by attributes (memberOf) or by
987
        filter.
988
        """
893 989
        group_base_dn = block['group_basedn'] or block['basedn']
894 990
        member_of_attribute = block['member_of_attribute']
895 991
        group_filter = block['group_filter']
......
957 1053
                try:
958 1054
                    return Role.objects.get(name=slug, **kwargs), None
959 1055
                except Role.DoesNotExist:
960
                    error = ('role %r does not exist' % role_id)
1056
                    error = 'role %r does not exist' % role_id
961 1057
                except Role.MultipleObjectsReturned:
962 1058
                    error = 'multiple objects returned, identifier is imprecise'
963 1059
            except Role.MultipleObjectsReturned:
964 1060
                error = 'multiple objects returned, identifier is imprecise'
965 1061
        else:
966
            error = 'invalid role identifier must be slug, (slug, ou__slug) or (slug, ou__slug, service__slug)'
1062
            error = (
1063
                'invalid role identifier must be slug, (slug, ou__slug) or (slug, ou__slug, service__slug)'
1064
            )
967 1065
        return None, error
968 1066

  
969 1067
    def populate_mandatory_groups(self, user, block):
......
1018 1116
        self.populate_user_roles(user, dn, conn, block, attributes)
1019 1117

  
1020 1118
    def populate_user_ou(self, user, dn, conn, block, attributes):
1021
        '''Assign LDAP user to an ou, the default one if ou_slug setting is
1022
           None'''
1119
        """Assign LDAP user to an ou, the default one if ou_slug setting is
1120
        None"""
1023 1121

  
1024 1122
        ou_slug = block['ou_slug']
1025 1123
        OU = get_ou_model()
......
1052 1150
    def get_ldap_attributes_names(cls, block):
1053 1151
        attributes = set()
1054 1152
        attributes.update(map_text(block['attributes']))
1055
        for field in ('email_field', 'fname_field', 'lname_field',
1056
                      'member_of_attribute'):
1153
        for field in ('email_field', 'fname_field', 'lname_field', 'member_of_attribute'):
1057 1154
            if block[field]:
1058 1155
                attributes.add(block[field])
1059 1156
        for external_id_tuple in map_text(block['external_id_tuples']):
1060
            attributes.update(cls.attribute_name_from_external_id_tuple(
1061
                external_id_tuple))
1157
            attributes.update(cls.attribute_name_from_external_id_tuple(external_id_tuple))
1062 1158
        for from_at, to_at in map_text(block['attribute_mappings']):
1063 1159
            attributes.add(to_at)
1064 1160
        for mapping in block['user_attributes']:
......
1076 1172

  
1077 1173
    @classmethod
1078 1174
    def get_ldap_attributes(cls, block, conn, dn):
1079
        '''Retrieve some attributes from LDAP, add mandatory values then apply
1080
           defined mappings between atrribute names'''
1175
        """Retrieve some attributes from LDAP, add mandatory values then apply
1176
        defined mappings between atrribute names"""
1081 1177
        attributes = cls.get_ldap_attributes_names(block)
1082 1178
        attribute_mappings = map_text(block['attribute_mappings'])
1083 1179
        mandatory_attributes_values = map_text(block['mandatory_attributes_values'])
......
1125 1221
            extra_attribute_config = block['extra_attributes'][extra_attribute_name]
1126 1222
            extra_attribute_values = []
1127 1223
            if 'loop_over_attribute' in extra_attribute_config:
1128
                extra_attribute_config['loop_over_attribute'] = extra_attribute_config['loop_over_attribute'].lower()
1224
                extra_attribute_config['loop_over_attribute'] = extra_attribute_config[
1225
                    'loop_over_attribute'
1226
                ].lower()
1129 1227
                if extra_attribute_config['loop_over_attribute'] not in attribute_map:
1130
                    log.debug('loop_over_attribute %s not present (or empty) in user object attributes retreived. Pass.' % extra_attribute_config['loop_over_attribute'])
1228
                    log.debug(
1229
                        'loop_over_attribute %s not present (or empty) in user object attributes retreived. Pass.'
1230
                        % extra_attribute_config['loop_over_attribute']
1231
                    )
1131 1232
                    continue
1132 1233
                if 'filter' not in extra_attribute_config and 'basedn' not in extra_attribute_config:
1133
                    log.warning('Extra attribute %s not correctly configured : you need to defined at least one of filter or basedn parameters' % extra_attribute_name)
1234
                    log.warning(
1235
                        'Extra attribute %s not correctly configured : you need to defined at least one of filter or basedn parameters'
1236
                        % extra_attribute_name
1237
                    )
1134 1238
                for item in attribute_map[extra_attribute_config['loop_over_attribute']]:
1135
                    ldap_filter = extra_attribute_config.get('filter', 'objectClass=*').format(item=item, **attribute_map)
1136
                    ldap_basedn = extra_attribute_config.get('basedn', block.get('basedn')).format(item=item, **attribute_map)
1137
                    ldap_scope = ldap_scopes.get(extra_attribute_config.get('scope', 'sub'), ldap.SCOPE_SUBTREE)
1239
                    ldap_filter = extra_attribute_config.get('filter', 'objectClass=*').format(
1240
                        item=item, **attribute_map
1241
                    )
1242
                    ldap_basedn = extra_attribute_config.get('basedn', block.get('basedn')).format(
1243
                        item=item, **attribute_map
1244
                    )
1245
                    ldap_scope = ldap_scopes.get(
1246
                        extra_attribute_config.get('scope', 'sub'), ldap.SCOPE_SUBTREE
1247
                    )
1138 1248
                    ldap_attributes_mapping = extra_attribute_config.get('mapping', {})
1139
                    ldap_attributes_names = list(filter(lambda a: a != 'dn', ldap_attributes_mapping.values()))
1249
                    ldap_attributes_names = list(
1250
                        filter(lambda a: a != 'dn', ldap_attributes_mapping.values())
1251
                    )
1140 1252
                    try:
1141 1253
                        results = conn.search_s(ldap_basedn, ldap_scope, ldap_filter, ldap_attributes_names)
1142 1254
                    except ldap.LDAPError:
1143
                        log.exception('unable to retrieve extra attribute %s for item %s' % (extra_attribute_name, item))
1255
                        log.exception(
1256
                            'unable to retrieve extra attribute %s for item %s' % (extra_attribute_name, item)
1257
                        )
1144 1258
                        continue
1145 1259
                    else:
1146 1260
                        results = cls.normalize_ldap_results(results)
1147 1261
                    item_value = {}
1148 1262
                    for dn, attrs in results:
1149
                        log.debug(u'Object retrieved for extra attr %s with item %s : %s %s' % (
1150
                            extra_attribute_name, item, dn, attrs))
1263
                        log.debug(
1264
                            u'Object retrieved for extra attr %s with item %s : %s %s'
1265
                            % (extra_attribute_name, item, dn, attrs)
1266
                        )
1151 1267
                        for key in ldap_attributes_mapping:
1152 1268
                            item_value[key] = attrs.get(ldap_attributes_mapping[key].lower())
1153
                            log.debug('Object attribute %s value retrieved for extra attr %s with item %s : %s' % (
1154
                                ldap_attributes_mapping[key], extra_attribute_name, item, item_value[key]))
1269
                            log.debug(
1270
                                'Object attribute %s value retrieved for extra attr %s with item %s : %s'
1271
                                % (ldap_attributes_mapping[key], extra_attribute_name, item, item_value[key])
1272
                            )
1155 1273
                            if not item_value[key]:
1156 1274
                                del item_value[key]
1157 1275
                            elif len(item_value[key]) == 1:
......
1165 1283
            elif extra_attribute_serialization == 'json':
1166 1284
                attribute_map[extra_attribute_name] = json.dumps(extra_attribute_values)
1167 1285
            else:
1168
                log.warning('Invalid serialization type "%s" for extra attribute %s' % (extra_attribute_serialization, extra_attribute_name))
1286
                log.warning(
1287
                    'Invalid serialization type "%s" for extra attribute %s'
1288
                    % (extra_attribute_serialization, extra_attribute_name)
1289
                )
1169 1290
        return attribute_map
1170 1291

  
1171 1292
    @classmethod
1172 1293
    def external_id_to_filter(cls, external_id, external_id_tuple):
1173
        '''Split the external id, decode it and build an LDAP filter from it
1174
           and the external_id_tuple.
1175
        '''
1294
        """Split the external id, decode it and build an LDAP filter from it
1295
        and the external_id_tuple.
1296
        """
1176 1297
        splitted = external_id.split()
1177 1298
        if len(splitted) != len(external_id_tuple):
1178 1299
            return
......
1191 1312
        return u'(&{0})'.format(''.join(filters))
1192 1313

  
1193 1314
    def build_external_id(self, external_id_tuple, attributes):
1194
        '''Build the exernal id for the user, use attribute that eventually
1195
           never change like GUID or UUID.
1196
        '''
1315
        """Build the exernal id for the user, use attribute that eventually
1316
        never change like GUID or UUID.
1317
        """
1197 1318
        parts = []
1198 1319
        for attribute in external_id_tuple:
1199 1320
            quote = True
......
1221 1342
            if not external_id:
1222 1343
                continue
1223 1344
            log.debug('lookup using external_id %r: %r', eid_tuple, external_id)
1224
            users = LDAPUser.objects.prefetch_related('groups').filter(
1225
                userexternalid__external_id__iexact=external_id,
1226
                userexternalid__source=force_text(block['realm'])).order_by('-last_login')
1345
            users = (
1346
                LDAPUser.objects.prefetch_related('groups')
1347
                .filter(
1348
                    userexternalid__external_id__iexact=external_id,
1349
                    userexternalid__source=force_text(block['realm']),
1350
                )
1351
                .order_by('-last_login')
1352
            )
1227 1353
            # ordering of NULLs cannot be done through the ORM
1228 1354
            users = sorted(users, reverse=True, key=lambda u: (u.last_login is not None, u.last_login))
1229 1355
            if users:
1230 1356
                user = users[0]
1231 1357
                if len(users) > 1:
1232
                    log.info('found %d users, collectings roles into the first one and deleting the other ones.',
1233
                             len(users))
1358
                    log.info(
1359
                        'found %d users, collectings roles into the first one and deleting the other ones.',
1360
                        len(users),
1361
                    )
1234 1362
                    for other in users[1:]:
1235 1363
                        for r in other.roles.all():
1236 1364
                            user.roles.add(r)
......
1255 1383
                log_msg = 'updating username from %r to %r'
1256 1384
                log.debug(log_msg, old_username, user.username)
1257 1385
        # if external_id lookup is used, update it
1258
        if 'external_id' in block['lookups'] \
1259
           and block.get('external_id_tuples') \
1260
           and block['external_id_tuples'][0]:
1386
        if (
1387
            'external_id' in block['lookups']
1388
            and block.get('external_id_tuples')
1389
            and block['external_id_tuples'][0]
1390
        ):
1261 1391
            if not user.pk:
1262 1392
                user.save()
1263 1393
                user._changed = False
1264
            external_id = self.build_external_id(
1265
                map_text(block['external_id_tuples'][0]),
1266
                attributes)
1394
            external_id = self.build_external_id(map_text(block['external_id_tuples'][0]), attributes)
1267 1395
            if external_id:
1268 1396
                new, created = UserExternalId.objects.get_or_create(
1269
                    user=user, external_id=external_id, source=force_text(block['realm']))
1397
                    user=user, external_id=external_id, source=force_text(block['realm'])
1398
                )
1270 1399
                if block['clean_external_id_on_update']:
1271
                    UserExternalId.objects \
1272
                        .exclude(id=new.id) \
1273
                        .filter(user=user, source=force_text(block['realm'])) \
1274
                        .delete()
1400
                    UserExternalId.objects.exclude(id=new.id).filter(
1401
                        user=user, source=force_text(block['realm'])
1402
                    ).delete()
1275 1403

  
1276 1404
    def _return_user(self, dn, password, conn, block, attributes=None):
1277 1405
        attributes = attributes or self.get_ldap_attributes(block, conn, dn)
......
1345 1473
            user_basedn = force_text(block.get('user_basedn') or block['basedn'])
1346 1474
            user_filter = cls.get_sync_ldap_user_filter(block)
1347 1475
            attribute_names = cls.get_ldap_attributes_names(block)
1348
            results = cls.paged_search(conn, user_basedn, ldap.SCOPE_SUBTREE, user_filter, attrlist=attribute_names)
1476
            results = cls.paged_search(
1477
                conn, user_basedn, ldap.SCOPE_SUBTREE, user_filter, attrlist=attribute_names
1478
            )
1349 1479
            backend = cls()
1350 1480
            for dn, attrs in results:
1351 1481
                yield backend._return_user(dn, None, conn, block, attrs)
1352 1482

  
1353

  
1354 1483
    @classmethod
1355 1484
    def deactivate_orphaned_users(cls):
1356 1485
        for block in cls.get_config():
1357 1486
            conn = cls.get_connection(block)
1358 1487
            if conn is None:
1359 1488
                continue
1360
            eids = list(UserExternalId.objects.filter(user__is_active=True,
1361
                                                      source=block['realm']).values_list('external_id', flat=True))
1489
            eids = list(
1490
                UserExternalId.objects.filter(user__is_active=True, source=block['realm']).values_list(
1491
                    'external_id', flat=True
1492
                )
1493
            )
1362 1494
            basedn = force_text(block.get('user_basedn') or block['basedn'])
1363
            attribute_names = [a[0] for a in cls.attribute_name_from_external_id_tuple(block['external_id_tuples'])]
1495
            attribute_names = [
1496
                a[0] for a in cls.attribute_name_from_external_id_tuple(block['external_id_tuples'])
1497
            ]
1364 1498
            user_filter = cls.get_sync_ldap_user_filter(block)
1365
            results = cls.paged_search(conn, basedn, ldap.SCOPE_SUBTREE,
1366
                                       user_filter,
1367
                                       attrlist=attribute_names)
1499
            results = cls.paged_search(
1500
                conn, basedn, ldap.SCOPE_SUBTREE, user_filter, attrlist=attribute_names
1501
            )
1368 1502
            for dn, attrs in results:
1369 1503
                data = attrs.copy()
1370 1504
                data['dn'] = dn
......
1379 1513
            for eid in UserExternalId.objects.filter(external_id__in=eids):
1380 1514
                eid.user.mark_as_inactive()
1381 1515

  
1382

  
1383 1516
    @classmethod
1384 1517
    def ad_encoding(cls, s):
1385 1518
        '''Encode a string for AD consumption as a password'''
......
1398 1531
                if old_password:
1399 1532
                    modlist = [
1400 1533
                        (ldap.MOD_DELETE, key, [cls.ad_encoding(old_password)]),
1401
                        (ldap.MOD_ADD, key, [value])
1534
                        (ldap.MOD_ADD, key, [value]),
1402 1535
                    ]
1403 1536
                else:
1404 1537
                    modlist = [(ldap.MOD_REPLACE, key, [value])]
......
1438 1571
            if block['timeout'] > 0:
1439 1572
                conn.set_option(ldap.OPT_NETWORK_TIMEOUT, block['timeout'])
1440 1573
                conn.set_option(ldap.OPT_TIMEOUT, block['timeout'])
1441
            conn.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,
1442
                            getattr(ldap, 'OPT_X_TLS_' + block['require_cert'].upper()))
1574
            conn.set_option(
1575
                ldap.OPT_X_TLS_REQUIRE_CERT, getattr(ldap, 'OPT_X_TLS_' + block['require_cert'].upper())
1576
            )
1443 1577
            if block['cacertfile']:
1444 1578
                conn.set_option(ldap.OPT_X_TLS_CACERTFILE, block['cacertfile'])
1445 1579
            if block['cacertdir']:
......
1458 1592
                    try:
1459 1593
                        conn.start_tls_s()
1460 1594
                    except ldap.CONNECT_ERROR:
1461
                        log.error('connection to %r failed when activating TLS, did you forget '
1462
                                  'to declare the TLS certificate in /etc/ldap/ldap.conf ?', url)
1595
                        log.error(
1596
                            'connection to %r failed when activating TLS, did you forget '
1597
                            'to declare the TLS certificate in /etc/ldap/ldap.conf ?',
1598
                            url,
1599
                        )
1463 1600
                        continue
1464 1601
            except ldap.TIMEOUT:
1465 1602
                log.error('connection to %r timed out', url)
1466 1603
                continue
1467 1604
            except ldap.CONNECT_ERROR:
1468
                log.error('connection to %r failed when activating TLS, did you forget to '
1469
                          'declare the TLS certificate in /etc/ldap/ldap.conf ?', url)
1605
                log.error(
1606
                    'connection to %r failed when activating TLS, did you forget to '
1607
                    'declare the TLS certificate in /etc/ldap/ldap.conf ?',
1608
                    url,
1609
                )
1470 1610
                continue
1471 1611
            except ldap.SERVER_DOWN:
1472 1612
                if block['replicas']:
......
1529 1669
            if key not in cls._VALID_CONFIG_KEYS and validate:
1530 1670
                raise ImproperlyConfigured(
1531 1671
                    '"{}" : invalid LDAP_AUTH_SETTINGS key, available are {}'.format(
1532
                        key, cls._VALID_CONFIG_KEYS))
1672
                        key, cls._VALID_CONFIG_KEYS
1673
                    )
1674
                )
1533 1675

  
1534 1676
        for r in cls._REQUIRED:
1535 1677
            if r not in block:
1536
                raise ImproperlyConfigured(
1537
                    'LDAP_AUTH_SETTINGS: missing required configuration option %r' % r)
1678
                raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: missing required configuration option %r' % r)
1538 1679

  
1539 1680
        # convert string to list of strings for settings accepting it
1540 1681
        for i in cls._TO_ITERABLE:
......
1549 1690
            else:
1550 1691
                if isinstance(cls._DEFAULTS[d], six.string_types):
1551 1692
                    if not isinstance(block[d], six.string_types):
1552
                        raise ImproperlyConfigured(
1553
                            'LDAP_AUTH_SETTINGS: attribute %r must be a string' % d)
1693
                        raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: attribute %r must be a string' % d)
1554 1694
                    try:
1555 1695
                        block[d] = force_text(block[d])
1556 1696
                    except UnicodeEncodeError:
1557
                        raise ImproperlyConfigured(
1558
                            'LDAP_AUTH_SETTINGS: attribute %r must be a string' % d)
1697
                        raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: attribute %r must be a string' % d)
1559 1698
                if isinstance(cls._DEFAULTS[d], bool) and not isinstance(block[d], bool):
1699
                    raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: attribute %r must be a boolean' % d)
1700
                if isinstance(cls._DEFAULTS[d], (list, tuple)) and not isinstance(block[d], (list, tuple)):
1560 1701
                    raise ImproperlyConfigured(
1561
                        'LDAP_AUTH_SETTINGS: attribute %r must be a boolean' % d)
1562
                if (isinstance(cls._DEFAULTS[d], (list, tuple))
1563
                        and not isinstance(block[d], (list, tuple))):
1564
                    raise ImproperlyConfigured(
1565
                        'LDAP_AUTH_SETTINGS: attribute %r must be a list or a tuple' % d)
1702
                        'LDAP_AUTH_SETTINGS: attribute %r must be a list or a tuple' % d
1703
                    )
1566 1704
                if isinstance(cls._DEFAULTS[d], dict) and not isinstance(block[d], dict):
1567
                    raise ImproperlyConfigured(
1568
                        'LDAP_AUTH_SETTINGS: attribute %r must be a dictionary' % d)
1705
                    raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: attribute %r must be a dictionary' % d)
1569 1706
                if not isinstance(cls._DEFAULTS[d], bool) and d in cls._REQUIRED and not block[d]:
1570
                    raise ImproperlyConfigured(
1571
                        'LDAP_AUTH_SETTINGS: attribute %r is required but is empty')
1707
                    raise ImproperlyConfigured('LDAP_AUTH_SETTINGS: attribute %r is required but is empty')
1572 1708
                # force_bytes all strings in iterable or dict
1573 1709
                if isinstance(block[d], (list, tuple, dict)):
1574 1710
                    block[d] = map_text(block[d])
......
1600 1736
            else:
1601 1737
                raise NotImplementedError(
1602 1738
                    'LDAP setting %r cannot be converted to lowercase setting, its type is %r'
1603
                    % (key, type(block[key])))
1739
                    % (key, type(block[key]))
1740
                )
1604 1741
        # special case user_attributes
1605 1742
        user_attributes = []
1606 1743
        for mapping in block['user_attributes']:
......
1641 1778
                            results = conn.search_s(dn, ldap.SCOPE_BASE)
1642 1779
                        else:
1643 1780
                            ldap_filter = self.external_id_to_filter(external_id, external_id_tuple)
1644
                            results = conn.search_s(block['basedn'],
1645
                                                    ldap.SCOPE_SUBTREE, ldap_filter)
1781
                            results = conn.search_s(block['basedn'], ldap.SCOPE_SUBTREE, ldap_filter)
1646 1782
                            results = self.normalize_ldap_results(results)
1647 1783
                            if not results:
1648 1784
                                log.warning(
1649
                                    u'unable to find user %r based on external id %s',
1650
                                    user, external_id)
1785
                                    u'unable to find user %r based on external id %s', user, external_id
1786
                                )
1651 1787
                                continue
1652 1788
                            dn = results[0][0]
1653 1789
                    except ldap.LDAPError as e:
1654 1790
                        log.warning(
1655
                            u'unable to find user %r based on external id %s: %r',
1656
                            user,
1657
                            external_id,
1658
                            e)
1791
                            u'unable to find user %r based on external id %s: %r', user, external_id, e
1792
                        )
1659 1793
                        continue
1660 1794
                    return self._return_user(dn, None, conn, block)
1661 1795

  
1796

  
1662 1797
LDAPUser.ldap_backend = LDAPBackend
1663 1798
LDAPBackendPasswordLost.ldap_backend = LDAPBackend
src/authentic2/backends/models_backend.py
31 31
    '''Build an UPN from a username and a realm'''
32 32
    return u'{0}@{1}'.format(username, realm)
33 33

  
34

  
34 35
PROXY_USER_MODEL = None
35 36

  
36 37

  
......
44 45
        username_field = 'username'
45 46
        queries = []
46 47
        try:
47
            if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION \
48
                    and UserModel._meta.get_field('email'):
48
            if app_settings.A2_ACCEPT_EMAIL_AUTHENTICATION and UserModel._meta.get_field('email'):
49 49
                queries.append(models.Q(**{'email__iexact': username}))
50 50
        except models.FieldDoesNotExist:
51 51
            pass
......
55 55
            if '@' not in username:
56 56
                if app_settings.REALMS:
57 57
                    for realm, desc in app_settings.REALMS:
58
                        queries.append(models.Q(
59
                            **{username_field: upn(username, realm)}))
58
                        queries.append(models.Q(**{username_field: upn(username, realm)}))
60 59
        else:
61 60
            queries.append(models.Q(**{username_field: upn(username, realm)}))
62 61
        queries = six.moves.reduce(models.Q.__or__, queries)
......
66 65

  
67 66
    def must_reset_password(self, user):
68 67
        from .. import models
68

  
69 69
        return bool(models.PasswordReset.filter(user=user).count())
70 70

  
71 71
    def authenticate(self, request, username=None, password=None, realm=None, ou=None):
......
96 96

  
97 97
    def get_saml2_authn_context(self):
98 98
        import lasso
99

  
99 100
        return lasso.SAML2_AUTHN_CONTEXT_PASSWORD
100 101

  
101 102

  
src/authentic2/cbv.py
26 26

  
27 27

  
28 28
class ValidateCSRFMixin(object):
29
    '''Move CSRF token validation inside the form validation.
29
    """Move CSRF token validation inside the form validation.
30

  
31
    This mixin must always be the leftest one and if your class override
32
    form_valid() dispatch() you should move those overrides in a base
33
    class.
34
    """
30 35

  
31
       This mixin must always be the leftest one and if your class override
32
       form_valid() dispatch() you should move those overrides in a base
33
       class.
34
    '''
35 36
    @method_decorator(csrf_exempt)
36 37
    @method_decorator(ensure_csrf_cookie)
37 38
    def dispatch(self, *args, **kwargs):
......
54 55

  
55 56

  
56 57
class NextURLViewMixin(RedirectToNextURLViewMixin):
57
    '''Make a view handle a next parameter, if it's not present it is
58
       automatically generated from the Referrer or from the value
59
       returned by the method get_next_url_default().
60
    '''
58
    """Make a view handle a next parameter, if it's not present it is
59
    automatically generated from the Referrer or from the value
60
    returned by the method get_next_url_default().
61
    """
62

  
61 63
    next_url_default = '..'
62 64

  
63 65
    def get_next_url_default(self):
......
67 69
        if REDIRECT_FIELD_NAME in request.GET:
68 70
            pass
69 71
        else:
70
            next_url = request.META.get('HTTP_REFERER') or \
71
                self.next_url_default
72
            return utils.redirect(request, request.path, keep_params=True,
73
                                  params={
74
                                      REDIRECT_FIELD_NAME: next_url,
75
                                  },
76
                                  status=303)
77
        return super(NextURLViewMixin, self).dispatch(request, *args,
78
                                                      **kwargs)
72
            next_url = request.META.get('HTTP_REFERER') or self.next_url_default
73
            return utils.redirect(
74
                request,
75
                request.path,
76
                keep_params=True,
77
                params={
78
                    REDIRECT_FIELD_NAME: next_url,
79
                },
80
                status=303,
81
            )
82
        return super(NextURLViewMixin, self).dispatch(request, *args, **kwargs)
79 83

  
80 84

  
81 85
class TemplateNamesMixin(object):
src/authentic2/compat/cookies.py
19 19
if django.VERSION < (2, 1):
20 20
    # Copied from Django >=2.1 / django.http.cookies
21 21
    from http import cookies
22

  
22 23
    cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
src/authentic2/compat/misc.py
24 24
    from django.contrib.auth import get_user_model
25 25
except ImportError:
26 26
    from django.contrib.auth.models import User
27

  
27 28
    get_user_model = lambda: User
28 29

  
29 30
try:
30 31
    from django.db.transaction import atomic
32

  
31 33
    commit_on_success = atomic
32 34
except ImportError:
33 35
    from django.db.transaction import commit_on_success
......
40 42
    from binascii import Error as Base64Error
41 43

  
42 44
if hasattr(inspect, 'signature'):
45

  
43 46
    def signature_parameters(func):
44 47
        return inspect.signature(func).parameters.keys()
48

  
49

  
45 50
else:
51

  
46 52
    def signature_parameters(func):
47 53
        return inspect.getargspec(func)[0]
src/authentic2/compat_lasso.py
17 17
try:
18 18
    import lasso
19 19
except ImportError:
20

  
20 21
    class MockLasso(object):
21 22
        def __getattr__(self, key):
22 23
            if key[0].isupper():
23 24
                return ''
24 25
            return AttributeError('Please install lasso')
26

  
25 27
    lasso = MockLasso()
src/authentic2/context_processors.py
23 23

  
24 24
class UserFederations(object):
25 25
    '''Provide access to all federations of the current user'''
26

  
26 27
    def __init__(self, request):
27 28
        self.request = request
28 29

  
29 30
    def __getattr__(self, name):
30
        d = {'provider': None, 'links': [] }
31
        d = {'provider': None, 'links': []}
31 32
        if name.startswith('service_'):
32 33
            try:
33 34
                provider_id = int(name.split('_', 1)[1])
......
43 44
            return d
44 45
        return super(UserFederations, self).__getattr__(name)
45 46

  
47

  
46 48
__AUTHENTIC2_DISTRIBUTION = None
47 49

  
48 50

  
src/authentic2/cors.py
63 63
            if plugin.check_origin(request, origin):
64 64
                return True
65 65
    return False
66

  
67

  
src/authentic2/crypto.py
54 54

  
55 55

  
56 56
def aes_base64_encrypt(key, data):
57
    '''Generate an AES key from any key material using PBKDF2, and encrypt data using CFB mode. A
58
       new IV is generated each time, the IV is also used as salt for PBKDF2.
59
    '''
57
    """Generate an AES key from any key material using PBKDF2, and encrypt data using CFB mode. A
58
    new IV is generated each time, the IV is also used as salt for PBKDF2.
59
    """
60 60
    iv = Random.get_random_bytes(16)
61 61
    aes_key = PBKDF2(key, iv)
62 62
    aes = AES.new(aes_key, AES.MODE_CFB, iv=iv)
......
100 100
def remove_padding(msg, block_size):
101 101
    '''Ignore padded zero bytes'''
102 102
    try:
103
        msg_length, = struct.unpack('<h', msg[:2])
103
        (msg_length,) = struct.unpack('<h', msg[:2])
104 104
    except struct.error:
105 105
        raise DecryptionError('wrong padding')
106 106
    if len(msg) % block_size != 0:
107
        raise DecryptionError('message length is not a multiple of block size', len(msg),
108
                              block_size)
109
    unpadded = msg[2:2 + msg_length]
107
        raise DecryptionError('message length is not a multiple of block size', len(msg), block_size)
108
    unpadded = msg[2 : 2 + msg_length]
110 109
    if msg_length > len(msg) - 2:
111 110
        raise DecryptionError('wrong padding')
112
    if len(msg[2 + msg_length:].strip(force_bytes('\0'))):
111
    if len(msg[2 + msg_length :].strip(force_bytes('\0'))):
113 112
        raise DecryptionError('padding is not all zero')
114 113
    if len(unpadded) != msg_length:
115 114
        raise DecryptionError('wrong padding')
......
117 116

  
118 117

  
119 118
def aes_base64url_deterministic_encrypt(key, data, salt, hash_name='sha256', count=1):
120
    '''Encrypt using AES-128 and sign using HMAC-SHA256 shortened to 64 bits.
119
    """Encrypt using AES-128 and sign using HMAC-SHA256 shortened to 64 bits.
121 120

  
122
       Count and algorithm are encoded in the final string for future evolution.
121
    Count and algorithm are encoded in the final string for future evolution.
123 122

  
124
    '''
123
    """
125 124
    mode = 1  # AES128-SHA256
126 125
    hashmod = SHA256
127 126
    key_size = 16
......
200 199
        key = key.encode('utf-8')
201 200
    if hasattr(url, 'isnumeric'):
202 201
        url = url.encode('utf-8', 'replace')
203
    return base64.b32encode(hmac.HMAC(key=key, msg=url, digestmod=hashlib.sha256).digest()).decode('ascii').strip('=')
202
    return (
203
        base64.b32encode(hmac.HMAC(key=key, msg=url, digestmod=hashlib.sha256).digest())
204
        .decode('ascii')
205
        .strip('=')
206
    )
204 207

  
205 208

  
206 209
def check_hmac_url(key, url, signature):
src/authentic2/csv_import.py
257 257

  
258 258

  
259 259
class ImportUserForm(BaseUserForm):
260
    locals()[ROLE_NAME] = forms.CharField(
261
        label=_('Role name'),
262
        required=False)
263
    locals()[ROLE_SLUG] = forms.CharField(
264
        label=_('Role slug'),
265
        required=False)
260
    locals()[ROLE_NAME] = forms.CharField(label=_('Role name'), required=False)
261
    locals()[ROLE_SLUG] = forms.CharField(label=_('Role slug'), required=False)
266 262
    choices = [
267 263
        (REGISTRATION_RESET_EMAIL, _('Email user so they can set a password')),
268 264
    ]
269 265
    locals()[REGISTRATION] = forms.ChoiceField(
270
        choices=choices,
271
        label=_('Registration option'),
272
        required=False)
273
    locals()[PASSWORD_HASH] = forms.CharField(
274
        label=_('Password hash'),
275
        required=False)
276

  
266
        choices=choices, label=_('Registration option'), required=False
267
    )
268
    locals()[PASSWORD_HASH] = forms.CharField(label=_('Password hash'), required=False)
277 269

  
278 270
    def clean(self):
279 271
        super(BaseUserForm, self).clean()
......
296 288
            RegexValidator(
297 289
                r'^[a-zA-Z0-9_-]+$',
298 290
                _('_source_name must contain no spaces and only letters, digits, - and _'),
299
                'invalid')])
300
    locals()[SOURCE_ID] = forms.CharField(
301
        label=_('Source external id'))
291
                'invalid',
292
            )
293
        ],
294
    )
295
    locals()[SOURCE_ID] = forms.CharField(label=_('Source external id'))
302 296

  
303 297

  
304 298
@attrs
......
410 404
            except Simulate:
411 405
                pass
412 406

  
413
        for action in [
414
                parse_csv,
415
                self.parse_header_row,
416
                self.parse_rows,
417
                do_import]:
407
        for action in [parse_csv, self.parse_header_row, self.parse_rows, do_import]:
418 408
            action()
419 409
            if self.errors:
420 410
                break
......
454 444
        header_names = set(self.headers_by_name)
455 445
        if header_names & SOURCE_COLUMNS and not SOURCE_COLUMNS.issubset(header_names):
456 446
            self.add_error(
457
                Error('invalid-external-id-pair',
458
                      _('You must have a _source_name and a _source_id column')))
447
                Error('invalid-external-id-pair', _('You must have a _source_name and a _source_id column'))
448
            )
459 449
        if ROLE_NAME in header_names and ROLE_SLUG in header_names:
460 450
            self.add_error(
461
                Error('invalid-role-column',
462
                      _('Either specify role names or role slugs, not both')))
451
                Error('invalid-role-column', _('Either specify role names or role slugs, not both'))
452
            )
463 453

  
464 454
    def parse_header(self, head, column):
465 455
        splitted = head.split()
466 456
        try:
467 457
            header = CsvHeader(column, splitted[0])
468 458
            if header.name in self.headers_by_name:
469
                self.add_error(
470
                    Error('duplicate-header', _('Header "%s" is duplicated') % header.name))
459
                self.add_error(Error('duplicate-header', _('Header "%s" is duplicated') % header.name))
471 460
                return
472 461
            self.headers_by_name[header.name] = header
473 462
        except IndexError:
......
503 492

  
504 493
        self.headers.append(header)
505 494

  
506
        if (not (header.field or header.attribute)
507
                and header.name not in SPECIAL_COLUMNS):
508
            self.add_error(LineError('unknown-or-missing-attribute',
509
                                     _('unknown or missing attribute "%s"') % head,
510
                                     line=1, column=column))
495
        if not (header.field or header.attribute) and header.name not in SPECIAL_COLUMNS:
496
            self.add_error(
497
                LineError(
498
                    'unknown-or-missing-attribute',
499
                    _('unknown or missing attribute "%s"') % head,
500
                    line=1,
501
                    column=column,
502
                )
503
            )
511 504
            return
512 505

  
513 506
        for flag in splitted[1:]:
514 507
            if header.name in SOURCE_COLUMNS:
515
                self.add_error(LineError(
516
                    'flag-forbidden-on-source-columns',
517
                    _('You cannot set flags on _source_name and _source_id columns'),
518
                    line=1))
508
                self.add_error(
509
                    LineError(
510
                        'flag-forbidden-on-source-columns',
511
                        _('You cannot set flags on _source_name and _source_id columns'),
512
                        line=1,
513
                    )
514
                )
519 515
                break
520 516
            value = True
521 517
            if flag.startswith('no-'):
......
537 533
        rows = self.rows = []
538 534
        for i, row in enumerate(self.csv_importer.rows[1:]):
539 535
            csv_row = self.parse_row(form_class, row, line=i + 2)
540
            self.has_errors = self.has_errors or not(csv_row.is_valid)
536
            self.has_errors = self.has_errors or not (csv_row.is_valid)
541 537
            rows.append(csv_row)
542 538

  
543 539
    def parse_row(self, form_class, row, line):
......
561 557
                header=header,
562 558
                value=form.cleaned_data.get(header.name),
563 559
                missing=header.name not in data,
564
                errors=get_form_errors(form, header.name))
565
            for header in self.headers]
560
                errors=get_form_errors(form, header.name),
561
            )
562
            for header in self.headers
563
        ]
566 564
        cell_errors = any(bool(cell.errors) for cell in cells)
567 565
        errors = get_form_errors(form, '__all__')
568
        return CsvRow(
569
            line=line,
570
            cells=cells,
571
            errors=errors,
572
            is_valid=not bool(cell_errors or errors))
566
        return CsvRow(line=line, cells=cells, errors=errors, is_valid=not bool(cell_errors or errors))
573 567

  
574 568
    @property
575 569
    def email_is_unique(self):
......
611 605
                    row.user_first_seen = False
612 606
                else:
613 607
                    errors.append(
614
                        Error('unique-constraint-failed',
615
                              _('Unique constraint on column "%(column)s" failed: '
616
                                'value already appear on line %(line)d') % {
617
                                    'column': header.name,
618
                                    'line': unique_map[unique_key]}))
608
                        Error(
609
                            'unique-constraint-failed',
610
                            _(
611
                                'Unique constraint on column "%(column)s" failed: '
612
                                'value already appear on line %(line)d'
613
                            )
614
                            % {'column': header.name, 'line': unique_map[unique_key]},
615
                        )
616
                    )
619 617
            else:
620 618
                unique_map[unique_key] = row.line
621 619

  
622 620
        for cell in row:
623
            if (not cell.header.globally_unique and not cell.header.unique) or (user and not cell.header.update):
621
            if (not cell.header.globally_unique and not cell.header.unique) or (
622
                user and not cell.header.update
623
            ):
624 624
                continue
625 625
            if not cell.value:
626 626
                continue
......
637 637
                    row.user_first_seen = False
638 638
                else:
639 639
                    errors.append(
640
                        Error('unique-constraint-failed',
641
                              _('Unique constraint on column "%s" failed') % cell.header.name))
640
                        Error(
641
                            'unique-constraint-failed',
642
                            _('Unique constraint on column "%s" failed') % cell.header.name,
643
                        )
644
                    )
642 645
        row.errors.extend(errors)
643 646
        row.is_valid = row.is_valid and not bool(errors)
644 647
        return not bool(errors)
......
680 683

  
681 684
        if len(users) > 1:
682 685
            row.errors.append(
683
                Error('key-matches-too-many-users',
684
                      _('Key value "%s" matches too many users') % key_value))
686
                Error('key-matches-too-many-users', _('Key value "%s" matches too many users') % key_value)
687
            )
685 688
            return False
686 689

  
687 690
        user = None
......
701 704
        for cell in row.cells:
702 705
            if not cell.header.field:
703 706
                continue
704
            if (row.action == 'create' and cell.header.create) or (row.action == 'update' and cell.header.update):
707
            if (row.action == 'create' and cell.header.create) or (
708
                row.action == 'update' and cell.header.update
709
            ):
705 710
                if getattr(user, cell.header.name) != cell.value:
706 711
                    setattr(user, cell.header.name, cell.value)
707 712
                    if cell.header.name == 'email' and cell.header.verified:
......
714 719

  
715 720
        if header_key.name == SOURCE_ID and row.action == 'create':
716 721
            try:
717
                UserExternalId.objects.create(user=user,
718
                                              source=source_name,
719
                                              external_id=source_id)
722
                UserExternalId.objects.create(user=user, source=source_name, external_id=source_id)
720 723
            except IntegrityError:
721 724
                # should never happen since we have a unique index...
722 725
                source_full_id = '%s.%s' % (source_name, source_id)
723 726
                row.errors.append(
724
                    Error('external-id-already-exist',
725
                          _('External id "%s" already exists') % source_full_id))
727
                    Error('external-id-already-exist', _('External id "%s" already exists') % source_full_id)
728
                )
726 729
                raise CancelImport
727 730

  
728 731
        for cell in row.cells:
729 732
            if cell.header.field or not cell.header.attribute:
730 733
                continue
731
            if (row.action == 'create' and cell.header.create) or (row.action == 'update' and cell.header.update):
734
            if (row.action == 'create' and cell.header.create) or (
735
                row.action == 'update' and cell.header.update
736
            ):
732 737
                attributes = user.attributes
733 738
                if cell.header.verified:
734 739
                    attributes = user.verified_attributes
......
762 767
                role = Role.objects.get(slug=cell.value, ou=self.ou)
763 768
        except Role.DoesNotExist:
764 769
            self._missing_roles.add(cell.value)
765
            cell.errors.append(
766
                Error('role-not-found',
767
                      _('Role "%s" does not exist') % cell.value))
770
            cell.errors.append(Error('role-not-found', _('Role "%s" does not exist') % cell.value))
768 771
            return False
769 772
        if cell.header.delete:
770 773
            user.roles.remove(role)
......
781 784
        if cell.value == REGISTRATION_RESET_EMAIL:
782 785
            send_password_reset_mail(
783 786
                user,
784
                template_names=['authentic2/manager/user_create_registration_email',
785
                                'authentic2/password_reset'],
787
                template_names=[
788
                    'authentic2/manager/user_create_registration_email',
789
                    'authentic2/password_reset',
790
                ],
786 791
                next_url='/accounts/',
787
                context={'user': user})
792
                context={'user': user},
793
            )
788 794
        return True
src/authentic2/custom_user/apps.py
25 25
    def ready(self):
26 26
        from django.db.models.signals import post_migrate
27 27

  
28
        post_migrate.connect(
29
            self.create_first_name_last_name_attributes,
30
            sender=self)
28
        post_migrate.connect(self.create_first_name_last_name_attributes, sender=self)
31 29

  
32
    def create_first_name_last_name_attributes(self, app_config, verbosity=2, interactive=True,
33
                                               using=DEFAULT_DB_ALIAS, **kwargs):
30
    def create_first_name_last_name_attributes(
31
        self, app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs
32
    ):
34 33
        from django.utils import translation
35 34
        from django.utils.translation import ugettext_lazy as _
36 35
        from django.conf import settings
......
52 51
        attrs = {}
53 52
        attrs['first_name'], created = Attribute.objects.get_or_create(
54 53
            name='first_name',
55
            defaults={'kind': 'string',
56
                      'label': _('First name'),
57
                      'required': True,
58
                      'asked_on_registration': True,
59
                      'user_editable': True,
60
                      'user_visible': True})
54
            defaults={
55
                'kind': 'string',
56
                'label': _('First name'),
57
                'required': True,
58
                'asked_on_registration': True,
59
                'user_editable': True,
60
                'user_visible': True,
61
            },
62
        )
61 63
        attrs['last_name'], created = Attribute.objects.get_or_create(
62 64
            name='last_name',
63
            defaults={'kind': 'string',
64
                      'label': _('Last name'),
65
                      'required': True,
66
                      'asked_on_registration': True,
67
                      'user_editable': True,
68
                      'user_visible': True})
65
            defaults={
66
                'kind': 'string',
67
                'label': _('Last name'),
68
                'required': True,
69
                'asked_on_registration': True,
70
                'user_editable': True,
71
                'user_visible': True,
72
            },
73
        )
69 74

  
70 75
        serialize = get_kind('string').get('serialize')
71 76
        for user in User.objects.all():
......
77 82
                    defaults={
78 83
                        'multiple': False,
79 84
                        'verified': False,
80
                        'content': serialize(getattr(user, attr_name, None))
81
                    })
85
                        'content': serialize(getattr(user, attr_name, None)),
86
                    },
87
                )
src/authentic2/custom_user/management/commands/changepassword.py
34 34
    def add_arguments(self, parser):
35 35
        parser.add_argument('username', nargs='?', type=str)
36 36
        parser.add_argument(
37
            '--database', action='store', dest='database',
38
            default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".')
37
            '--database',
38
            action='store',
39
            dest='database',
40
            default=DEFAULT_DB_ALIAS,
41
            help='Specifies the database to use. Default is "default".',
42
        )
39 43

  
40 44
    def _get_pass(self, prompt="Password: "):
41 45
        p = getpass.getpass(prompt=force_str(prompt))
src/authentic2/custom_user/management/commands/fix-attributes.py
31 31

  
32 32
        i = 0
33 33
        while True:
34
            batch = user_ids[i * 100:i * 100 + 100]
34
            batch = user_ids[i * 100 : i * 100 + 100]
35 35
            if not batch:
36 36
                break
37
            users = User.objects.prefetch_related('attribute_values__attribute').filter(
38
                id__in=batch)
37
            users = User.objects.prefetch_related('attribute_values__attribute').filter(id__in=batch)
39 38
            count = 0
40 39
            for user in users:
41 40
                try:
......
70 69
                    count += 1
71 70
            i += 1
72 71
            print('Fixed %d users.' % count)
73

  
74

  
src/authentic2/custom_user/managers.py
46 46

  
47 47
        if '@' in search and len(search.split()) == 1:
48 48
            with connection.cursor() as cursor:
49
                cursor.execute(
50
                    "SET pg_trgm.similarity_threshold = %f" % app_settings.A2_FTS_THRESHOLD
51
                )
49
                cursor.execute("SET pg_trgm.similarity_threshold = %f" % app_settings.A2_FTS_THRESHOLD)
52 50
            qs = self.filter(email__icontains=search).order_by(Unaccent('last_name'), Unaccent('first_name'))
53 51
            if qs.exists():
54 52
                return wrap_qs(qs)
......
74 72
            pass
75 73
        else:
76 74
            attribute_values = AttributeValue.objects.filter(
77
                search_vector=SearchQuery(phone_number), attribute__kind='phone_number')
75
                search_vector=SearchQuery(phone_number), attribute__kind='phone_number'
76
            )
78 77
            qs = self.filter(attribute_values__in=attribute_values).order_by('last_name', 'first_name')
79 78
            if qs.exists():
80 79
                return wrap_qs(qs)
......
85 84
            pass
86 85
        else:
87 86
            attribute_values = AttributeValue.objects.filter(
88
                search_vector=SearchQuery(date.isoformat()), attribute__kind='birthdate')
87
                search_vector=SearchQuery(date.isoformat()), attribute__kind='birthdate'
88
            )
89 89
            qs = self.filter(attribute_values__in=attribute_values).order_by('last_name', 'first_name')
90 90
            if qs.exists():
91 91
                return wrap_qs(qs)
92 92

  
93 93
        qs = self.find_duplicates(fullname=search, limit=None, threshold=app_settings.A2_FTS_THRESHOLD)
94 94
        extra_user_ids = set()
95
        attribute_values = AttributeValue.objects.filter(search_vector=SearchQuery(search), attribute__searchable=True)
95
        attribute_values = AttributeValue.objects.filter(
96
            search_vector=SearchQuery(search), attribute__searchable=True
97
        )
96 98
        extra_user_ids.update(self.filter(attribute_values__in=attribute_values).values_list('id', flat=True))
97 99
        if len(search.split()) == 1:
98 100
            extra_user_ids.update(
99
                self.filter(
100
                    Q(username__istartswith=search)
101
                    | Q(email__istartswith=search)
102
                ).values_list('id', flat=True))
101
                self.filter(Q(username__istartswith=search) | Q(email__istartswith=search)).values_list(
102
                    'id', flat=True
103
                )
104
            )
103 105
        if extra_user_ids:
104 106
            qs = qs | self.filter(id__in=extra_user_ids)
105 107
        qs = qs.order_by('dist', Unaccent('last_name'), Unaccent('first_name'))
106 108
        return qs
107 109

  
108
    def find_duplicates(self, first_name=None, last_name=None, fullname=None, birthdate=None, limit=5, threshold=None):
110
    def find_duplicates(
111
        self, first_name=None, last_name=None, fullname=None, birthdate=None, limit=5, threshold=None
112
    ):
109 113
        with connection.cursor() as cursor:
110 114
            cursor.execute(
111 115
                "SET pg_trgm.similarity_threshold = %f" % (threshold or app_settings.A2_DUPLICATES_THRESHOLD)
......
133 137
                object_id=OuterRef('pk'),
134 138
                content_type=content_type,
135 139
                attribute__kind='birthdate',
136
                content=birthdate
140
                content=birthdate,
137 141
            ).annotate(bonus=Value(1 - bonus, output_field=FloatField()))
138
            qs = qs.annotate(dist=Coalesce(
139
                Subquery(same_birthdate.values('bonus'), output_field=FloatField()) * F('dist'),
140
                F('dist')
141
            ))
142
            qs = qs.annotate(
143
                dist=Coalesce(
144
                    Subquery(same_birthdate.values('bonus'), output_field=FloatField()) * F('dist'), F('dist')
145
                )
146
            )
142 147

  
143 148
        return qs
144 149

  
145 150

  
146 151
class UserManager(BaseUserManager):
147

  
148
    def _create_user(self, username, email, password,
149
                     is_staff, is_superuser, **extra_fields):
152
    def _create_user(self, username, email, password, is_staff, is_superuser, **extra_fields):
150 153
        """
151 154
        Creates and saves a User with the given username, email and password.
152 155
        """
......
154 157
        if not username:
155 158
            raise ValueError('The given username must be set')
156 159
        email = self.normalize_email(email)
157
        user = self.model(username=username, email=email,
158
                          is_staff=is_staff, is_active=True,
159
                          is_superuser=is_superuser, last_login=now,
160
                          date_joined=now, **extra_fields)
160
        user = self.model(
161
            username=username,
162
            email=email,
163
            is_staff=is_staff,
164
            is_active=True,
165
            is_superuser=is_superuser,
166
            last_login=now,
167
            date_joined=now,
168
            **extra_fields,
169
        )
161 170
        user.set_password(password)
162 171
        user.save(using=self._db)
163 172
        return user
164 173

  
165 174
    def create_user(self, username, email=None, password=None, **extra_fields):
166
        return self._create_user(username, email, password, False, False,
167
                                 **extra_fields)
175
        return self._create_user(username, email, password, False, False, **extra_fields)
168 176

  
169 177
    def create_superuser(self, username, email, password, **extra_fields):
170
        return self._create_user(username, email, password, True, True,
171
                                 **extra_fields)
178
        return self._create_user(username, email, password, True, True, **extra_fields)
172 179

  
173 180
    def get_by_natural_key(self, uuid):
174 181
        return self.get(uuid=uuid)
src/authentic2/custom_user/migrations/0001_initial.py
6 6
import authentic2.utils
7 7
import authentic2.validators
8 8

  
9

  
9 10
def noop(apps, schema_editor):
10 11
    pass
11 12

  
13

  
12 14
def copy_old_users_to_custom_user_model(apps, schema_editor):
13 15
    OldUser = apps.get_model('auth', 'User')
14 16
    NewUser = apps.get_model('custom_user', 'User')
15
    fields = ['id', 'username', 'email', 'first_name', 'last_name',
16
            'is_staff', 'is_active', 'date_joined', 'is_superuser',
17
            'last_login', 'password']
17
    fields = [
18
        'id',
19
        'username',
20
        'email',
21
        'first_name',
22
        'last_name',
23
        'is_staff',
24
        'is_active',
25
        'date_joined',
26
        'is_superuser',
27
        'last_login',
28
        'password',
29
    ]
18 30
    old_users = OldUser.objects.prefetch_related('groups', 'user_permissions').order_by('id')
19 31
    new_users = []
20 32
    for old_user in old_users:
......
40 52
    PermissionThrough.objects.bulk_create(new_permissions)
41 53
    # Reset sequences
42 54
    if schema_editor.connection.vendor == 'postgresql':
43
        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";')
44
        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";')
45
        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";')
55
        schema_editor.execute(
56
            '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";'
57
        )
58
        schema_editor.execute(
59
            '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";'
60
        )
61
        schema_editor.execute(
62
            'SELECT setval(pg_get_serial_sequence(\'"custom_user_user"\',\'id\'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "custom_user_user";'
63
        )
46 64
    elif schema_editor.connection.vendor == 'sqlite':
47
        schema_editor.execute('UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user) WHERE name = "custom_user_user";')
48
        schema_editor.execute('UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_groups) WHERE name = "custom_user_user_groups";')
49
        schema_editor.execute('UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_user_permissions) WHERE name = "custom_user_user_permissions";')
65
        schema_editor.execute(
66
            'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user) WHERE name = "custom_user_user";'
67
        )
68
        schema_editor.execute(
69
            'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_groups) WHERE name = "custom_user_user_groups";'
70
        )
71
        schema_editor.execute(
72
            'UPDATE sqlite_sequence SET seq = (SELECT MAX(id) FROM custom_user_user_user_permissions) WHERE name = "custom_user_user_permissions";'
73
        )
50 74
    else:
51 75
        raise NotImplementedError()
52 76

  
53 77

  
54

  
55 78
class Migration(migrations.Migration):
56 79

  
57 80
    dependencies = [
58
            ('auth', '__first__'),
81
        ('auth', '__first__'),
59 82
    ]
60 83

  
61 84
    operations = [
62 85
        migrations.CreateModel(
63 86
            name='User',
64 87
            fields=[
65
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
88
                (
89
                    'id',
90
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
91
                ),
66 92
                ('password', models.CharField(max_length=128, verbose_name='password')),
67
                ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
68
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
69
                ('uuid', models.CharField(default=authentic2.utils.get_hex_uuid, verbose_name='uuid', unique=True, max_length=32, editable=False)),
70
                ('username', models.CharField(max_length=256, null=True, verbose_name='username', blank=True)),
93
                (
94
                    'last_login',
95
                    models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login'),
96
                ),
97
                (
98
                    'is_superuser',
99
                    models.BooleanField(
100
                        default=False,
101
                        help_text='Designates that this user has all permissions without explicitly assigning them.',
102
                        verbose_name='superuser status',
103
                    ),
104
                ),
105
                (
106
                    'uuid',
107
                    models.CharField(
108
                        default=authentic2.utils.get_hex_uuid,
109
                        verbose_name='uuid',
110
                        unique=True,
111
                        max_length=32,
112
                        editable=False,
113
                    ),
114
                ),
115
                (
116
                    'username',
117
                    models.CharField(max_length=256, null=True, verbose_name='username', blank=True),
118
                ),
71 119
                ('first_name', models.CharField(max_length=64, verbose_name='first name', blank=True)),
72 120
                ('last_name', models.CharField(max_length=64, verbose_name='last name', blank=True)),
73
                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address', validators=[authentic2.validators.EmailValidator])),
74
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
75
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
76
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
77
                ('groups', models.ManyToManyField(related_query_name='user', 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.', verbose_name='groups')),
78
                ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
121
                (
122
                    'email',
123
                    models.EmailField(
124
                        blank=True,
125
                        max_length=254,
126
                        verbose_name='email address',
127
                        validators=[authentic2.validators.EmailValidator],
128
                    ),
129
                ),
130
                (
131
                    'is_staff',
132
                    models.BooleanField(
133
                        default=False,
134
                        help_text='Designates whether the user can log into this admin site.',
135
                        verbose_name='staff status',
136
                    ),
137
                ),
138
                (
139
                    'is_active',
140
                    models.BooleanField(
141
                        default=True,
142
                        help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
143
                        verbose_name='active',
144
                    ),
145
                ),
146
                (
147
                    'date_joined',
148
                    models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'),
149
                ),
150
                (
151
                    'groups',
152
                    models.ManyToManyField(
153
                        related_query_name='user',
154
                        related_name='user_set',
155
                        to='auth.Group',
156
                        blank=True,
157
                        help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.',
158
                        verbose_name='groups',
159
                    ),
160
                ),
161
                (
162
                    'user_permissions',
163
                    models.ManyToManyField(
164
                        related_query_name='user',
165
                        related_name='user_set',
166
                        to='auth.Permission',
167
                        blank=True,
168
                        help_text='Specific permissions for this user.',
169
                        verbose_name='user permissions',
170
                    ),
171
                ),
79 172
            ],
80 173
            options={
81 174
                'verbose_name': 'user',
src/authentic2/custom_user/migrations/0002_auto_20150410_1823.py
4 4
from django.conf import settings
5 5
from django.db import models, migrations
6 6

  
7

  
7 8
class ThirdPartyAlterField(migrations.AlterField):
8 9
    def __init__(self, *args, **kwargs):
9 10
        self.app_label = kwargs.pop('app_label')
......
15 16
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
16 17
        if hasattr(from_state, 'clear_delayed_apps_cache'):
17 18
            from_state.clear_delayed_apps_cache()
18
        super(ThirdPartyAlterField, self).database_forwards(self.app_label,
19
                schema_editor, from_state, to_state)
19
        super(ThirdPartyAlterField, self).database_forwards(
20
            self.app_label, schema_editor, from_state, to_state
21
        )
20 22

  
21 23
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
22 24
        self.database_forwards(app_label, schema_editor, from_state, to_state)
23 25

  
24 26
    def __eq__(self, other):
25 27
        return (
26
            (self.__class__ == other.__class__) and
27
            (self.app_label == other.app_label) and
28
            (self.name == other.name) and
29
            (self.model_name.lower() == other.model_name.lower()) and
30
            (self.field.deconstruct()[1:] == other.field.deconstruct()[1:])
28
            (self.__class__ == other.__class__)
29
            and (self.app_label == other.app_label)
30
            and (self.name == other.name)
31
            and (self.model_name.lower() == other.model_name.lower())
32
            and (self.field.deconstruct()[1:] == other.field.deconstruct()[1:])
31 33
        )
32 34

  
33 35
    def references_model(self, *args, **kwargs):
......
48 50
    ]
49 51

  
50 52
    operations = [
51
            # Django admin log
52
            ThirdPartyAlterField(
53
                app_label='admin',
54
                model_name='logentry',
55
                name='user',
56
                field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
57
                preserve_default=True
58
            ),
53
        # Django admin log
54
        ThirdPartyAlterField(
55
            app_label='admin',
56
            model_name='logentry',
57
            name='user',
58
            field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
59
            preserve_default=True,
60
        ),
59 61
    ]
src/authentic2/custom_user/migrations/0003_auto_20150504_1410.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='user',
16
            options={'verbose_name': 'user', 'verbose_name_plural': 'users',},
16
            options={
17
                'verbose_name': 'user',
18
                'verbose_name_plural': 'users',
19
            },
17 20
        ),
18 21
    ]
src/authentic2/custom_user/migrations/0004_user_ou.py
16 16
        migrations.AddField(
17 17
            model_name='user',
18 18
            name='ou',
19
            field=models.ForeignKey(blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE),
19
            field=models.ForeignKey(
20
                blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE
21
            ),
20 22
            preserve_default=True,
21 23
        ),
22 24
    ]
src/authentic2/custom_user/migrations/0005_auto_20150522_1527.py
15 15
        migrations.AlterField(
16 16
            model_name='user',
17 17
            name='ou',
18
            field=models.ForeignKey(verbose_name='organizational unit', blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE),
18
            field=models.ForeignKey(
19
                verbose_name='organizational unit',
20
                blank=True,
21
                to=settings.RBAC_OU_MODEL,
22
                null=True,
23
                on_delete=models.CASCADE,
24
            ),
19 25
            preserve_default=True,
20 26
        ),
21 27
    ]
src/authentic2/custom_user/migrations/0006_auto_20150527_1212.py
3 3

  
4 4
from django.db import models, migrations
5 5

  
6

  
6 7
def noop(apps, schema_editor):
7 8
    pass
8 9

  
10

  
9 11
def set_last_login(apps, schema_editor):
10 12
    User = apps.get_model('custom_user', 'User')
11
    User.objects.filter(last_login__isnull=True) \
12
        .update(last_login=models.F('date_joined'))
13
    User.objects.filter(last_login__isnull=True).update(last_login=models.F('date_joined'))
14

  
13 15

  
14 16
class Migration(migrations.Migration):
15 17

  
src/authentic2/custom_user/migrations/0007_auto_20150610_1527.py
3 3

  
4 4
from django.db import models, migrations
5 5

  
6

  
6 7
def noop(apps, schema_editor):
7 8
    pass
8 9

  
10

  
9 11
def set_last_login(apps, schema_editor):
10 12
    User = apps.get_model('custom_user', 'User')
11
    User.objects.filter(last_login__isnull=True) \
12
        .update(last_login=models.F('date_joined'))
13
    User.objects.filter(last_login__isnull=True).update(last_login=models.F('date_joined'))
14

  
13 15

  
14 16
class Migration(migrations.Migration):
15 17

  
src/authentic2/custom_user/migrations/0009_auto_20150810_1953.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='user',
16
            options={'ordering': ('first_name', 'last_name', 'email', 'username'), 'verbose_name': 'user', 'verbose_name_plural': 'users',},
16
            options={
17
                'ordering': ('first_name', 'last_name', 'email', 'username'),
18
                'verbose_name': 'user',
19
                'verbose_name_plural': 'users',
20
            },
17 21
        ),
18 22
    ]
src/authentic2/custom_user/migrations/0012_user_modified.py
16 16
        migrations.AddField(
17 17
            model_name='user',
18 18
            name='modified',
19
            field=models.DateTimeField(default=datetime.datetime(2017, 3, 13, 14, 41, 7, 593150, tzinfo=utc), auto_now=True, verbose_name='Last modification time', db_index=True),
19
            field=models.DateTimeField(
20
                default=datetime.datetime(2017, 3, 13, 14, 41, 7, 593150, tzinfo=utc),
21
                auto_now=True,
22
                verbose_name='Last modification time',
23
                db_index=True,
24
            ),
20 25
            preserve_default=False,
21 26
        ),
22 27
    ]
src/authentic2/custom_user/migrations/0015_auto_20170707_1653.py
13 13
    operations = [
14 14
        migrations.AlterModelOptions(
15 15
            name='user',
16
            options={'ordering': ('last_name', 'first_name', 'email', 'username'), 'verbose_name': 'user', 'verbose_name_plural': 'users',},
16
            options={
17
                'ordering': ('last_name', 'first_name', 'email', 'username'),
18
                'verbose_name': 'user',
19
                'verbose_name_plural': 'users',
20
            },
17 21
        ),
18 22
    ]
src/authentic2/custom_user/migrations/0017_auto_20200305_1645.py
17 17
        migrations.AlterField(
18 18
            model_name='user',
19 19
            name='email',
20
            field=models.EmailField(blank=True, max_length=254, verbose_name='email address', validators=[authentic2.validators.email_validator]),
20
            field=models.EmailField(
21
                blank=True,
22
                max_length=254,
23
                verbose_name='email address',
24
                validators=[authentic2.validators.email_validator],
25
            ),
21 26
        ),
22 27
    ]
src/authentic2/custom_user/migrations/0020_deleteduser.py
14 14
        migrations.CreateModel(
15 15
            name='DeletedUser',
16 16
            fields=[
17
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17
                (
18
                    'id',
19
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
20
                ),
18 21
                ('deleted', models.DateTimeField(verbose_name='Deletion date', auto_now_add=True)),
19 22
                ('old_uuid', models.TextField(blank=True, null=True, verbose_name='Old UUID')),
20
                ('old_user_id', models.PositiveIntegerField(blank=True, null=True, verbose_name='Old user id')),
21
                ('old_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Old email adress')),
22
                ('old_data', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True, verbose_name='Old data')),
23
                (
24
                    'old_user_id',
25
                    models.PositiveIntegerField(blank=True, null=True, verbose_name='Old user id'),
26
                ),
27
                (
28
                    'old_email',
29
                    models.EmailField(blank=True, max_length=254, null=True, verbose_name='Old email adress'),
30
                ),
31
                (
32
                    'old_data',
33
                    django.contrib.postgres.fields.jsonb.JSONField(
34
                        blank=True, null=True, verbose_name='Old data'
35
                    ),
36
                ),
23 37
            ],
24 38
            options={
25 39
                'verbose_name': 'deleted user',
src/authentic2/custom_user/migrations/0022_index_email.py
9 9
    operations = [
10 10
        migrations.RunSQL(
11 11
            sql=r'CREATE INDEX "custom_user_user_email_idx" ON "custom_user_user" (UPPER("email") text_pattern_ops);',
12
            reverse_sql=r'DROP INDEX "custom_user_user_email_idx";'
12
            reverse_sql=r'DROP INDEX "custom_user_user_email_idx";',
13 13
        ),
14 14
    ]
src/authentic2/custom_user/migrations/0023_index_username.py
9 9
    operations = [
10 10
        migrations.RunSQL(
11 11
            sql=r'CREATE INDEX "custom_user_user_username_idx" ON "custom_user_user" (UPPER("username") text_pattern_ops);',
12
            reverse_sql=r'DROP INDEX "custom_user_user_username_idx";'
12
            reverse_sql=r'DROP INDEX "custom_user_user_username_idx";',
13 13
        ),
14 14
    ]
src/authentic2/custom_user/migrations/0024_index_email_by_trigrams.py
59 59
    operations = [
60 60
        TrigramExtension(),
61 61
        RunSQLIfExtension(
62
            sql=["CREATE INDEX IF NOT EXISTS custom_user_user_email_trgm_idx ON custom_user_user USING gist "
63
                 "(LOWER(email) public.gist_trgm_ops)"],
62
            sql=[
63
                "CREATE INDEX IF NOT EXISTS custom_user_user_email_trgm_idx ON custom_user_user USING gist "
64
                "(LOWER(email) public.gist_trgm_ops)"
65
            ],
64 66
            reverse_sql=['DROP INDEX custom_user_user_email_trgm_idx'],
65 67
        ),
66 68
    ]
src/authentic2/custom_user/migrations/0026_remove_user_deleted.py
13 13
    DeletedUser = apps.get_model('custom_user', 'DeletedUser')
14 14

  
15 15
    def delete_user(self):
16
        deleted_user = DeletedUser(
17
            old_user_id=self.id)
16
        deleted_user = DeletedUser(old_user_id=self.id)
18 17
        if 'email' in app_settings.A2_USER_DELETED_KEEP_DATA:
19 18
            deleted_user.old_email = self.email.rsplit('#', 1)[0]
20 19
        if 'uuid' in app_settings.A2_USER_DELETED_KEEP_DATA:
src/authentic2/custom_user/models.py
28 28
from django.utils import six
29 29
from django.utils.translation import ugettext_lazy as _
30 30
from django.core.exceptions import ValidationError, MultipleObjectsReturned
31

  
31 32
try:
32 33
    from django.contrib.contenttypes.fields import GenericRelation
33 34
except ImportError:
......
88 89
            else:
89 90
                atv = self.values.get(name)
90 91
                self.values[name] = attribute.set_value(
91
                    self.owner, value,
92
                    verified=bool(self.verified),
93
                    attribute_value=atv)
92
                    self.owner, value, verified=bool(self.verified), attribute_value=atv
93
                )
94 94

  
95 95
            update_fields = ['modified']
96 96
            if name in ['first_name', 'last_name']:
......
128 128

  
129 129
    def __getattr__(self, name):
130 130
        v = getattr(self.user.attributes, name, None)
131
        return (
132
            v is not None
133
            and v == getattr(self.user.verified_attributes, name, None)
134
        )
131
        return v is not None and v == getattr(self.user.verified_attributes, name, None)
135 132

  
136 133

  
137 134
class IsVerifiedDescriptor(object):
......
146 143

  
147 144
    Username, password and email are required. Other fields are optional.
148 145
    """
149
    uuid = models.CharField(
150
        _('uuid'),
151
        max_length=32,
152
        default=utils.get_hex_uuid, editable=False, unique=True)
146

  
147
    uuid = models.CharField(_('uuid'), max_length=32, default=utils.get_hex_uuid, editable=False, unique=True)
153 148
    username = models.CharField(_('username'), max_length=256, null=True, blank=True)
154 149
    first_name = models.CharField(_('first name'), max_length=128, blank=True)
155 150
    last_name = models.CharField(_('last name'), max_length=128, blank=True)
156
    email = models.EmailField(
157
        _('email address'),
158
        blank=True,
159
        max_length=254,
160
        validators=[email_validator])
161
    email_verified = models.BooleanField(
162
        default=False,
163
        verbose_name=_('email verified'))
151
    email = models.EmailField(_('email address'), blank=True, max_length=254, validators=[email_validator])
152
    email_verified = models.BooleanField(default=False, verbose_name=_('email verified'))
164 153
    is_staff = models.BooleanField(
165 154
        _('staff status'),
166 155
        default=False,
167
        help_text=_('Designates whether the user can log into this admin '
168
                    'site.'))
156
        help_text=_('Designates whether the user can log into this admin ' 'site.'),
157
    )
169 158
    is_active = models.BooleanField(
170 159
        _('active'),
171 160
        default=True,
172
        help_text=_('Designates whether this user should be treated as '
173
                    'active. Unselect this instead of deleting accounts.'))
161
        help_text=_(
162
            'Designates whether this user should be treated as '
163
            'active. Unselect this instead of deleting accounts.'
164
        ),
165
    )
174 166
    ou = models.ForeignKey(
175 167
        verbose_name=_('organizational unit'),
176 168
        to='a2_rbac.OrganizationalUnit',
177 169
        blank=True,
178 170
        null=True,
179 171
        swappable=False,
180
        on_delete=models.CASCADE)
172
        on_delete=models.CASCADE,
173
    )
181 174

  
182 175
    # events dates
183 176
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
184
    modified = models.DateTimeField(
185
        verbose_name=_('Last modification time'),
186
        db_index=True,
187
        auto_now=True)
177
    modified = models.DateTimeField(verbose_name=_('Last modification time'), db_index=True, auto_now=True)
188 178
    last_account_deletion_alert = models.DateTimeField(
189
        verbose_name=_('Last account deletion alert'),
190
        null=True,
191
        blank=True)
192
    deactivation = models.DateTimeField(
193
        verbose_name=_('Deactivation datetime'),
194
        null=True,
195
        blank=True)
179
        verbose_name=_('Last account deletion alert'), null=True, blank=True
180
    )
181
    deactivation = models.DateTimeField(verbose_name=_('Deactivation datetime'), null=True, blank=True)
196 182

  
197 183
    objects = UserManager.from_queryset(UserQuerySet)()
198 184
    attributes = AttributesDescriptor()
......
237 223
        qs = (qs1 | qs2).order_by('name').distinct()
238 224
        RoleParenting = get_role_parenting_model()
239 225
        rp_qs = RoleParenting.objects.filter(child__in=qs1)
240
        qs = qs.prefetch_related(models.Prefetch(
241
            'child_relation', queryset=rp_qs), 'child_relation__parent')
242
        qs = qs.prefetch_related(models.Prefetch(
243
            'members', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='member'))
226
        qs = qs.prefetch_related(models.Prefetch('child_relation', queryset=rp_qs), 'child_relation__parent')
227
        qs = qs.prefetch_related(
228
            models.Prefetch('members', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='member')
229
        )
244 230
        return qs
245 231

  
246 232
    def __str__(self):
......
252 238
        return '<User: %r>' % six.text_type(self)
253 239

  
254 240
    def clean(self):
255
        if not (self.username
256
                or self.email
257
                or (self.first_name and self.last_name)):
258
            raise ValidationError(_('An account needs at least one identifier: '
259
                                    'username, email or a full name (first and last name).'))
241
        if not (self.username or self.email or (self.first_name and self.last_name)):
242
            raise ValidationError(
243
                _(
244
                    'An account needs at least one identifier: '
245
                    'username, email or a full name (first and last name).'
246
                )
247
            )
260 248

  
261 249
    def validate_unique(self, exclude=None):
262 250
        errors = {}
......
271 259
        if self.pk:
272 260
            qs = qs.exclude(pk=self.pk)
273 261

  
274
        if 'username' not in exclude and self.username and (app_settings.A2_USERNAME_IS_UNIQUE
275
                                                            or (self.ou and self.ou.username_is_unique)):
262
        if (
263
            'username' not in exclude
264
            and self.username
265
            and (app_settings.A2_USERNAME_IS_UNIQUE or (self.ou and self.ou.username_is_unique))
266
        ):
276 267
            username_qs = qs
277 268
            if not app_settings.A2_USERNAME_IS_UNIQUE:
278 269
                username_qs = qs.filter(ou=self.ou)
......
285 276
                pass
286 277
            else:
287 278
                errors.setdefault('username', []).append(
288
                    _('This username is already in use. Please supply a different username.'))
279
                    _('This username is already in use. Please supply a different username.')
280
                )
289 281

  
290
        if 'email' not in exclude and self.email and (app_settings.A2_EMAIL_IS_UNIQUE
291
                                                      or (self.ou and self.ou.email_is_unique)):
282
        if (
283
            'email' not in exclude
284
            and self.email
285
            and (app_settings.A2_EMAIL_IS_UNIQUE or (self.ou and self.ou.email_is_unique))
286
        ):
292 287
            email_qs = qs
293 288
            if not app_settings.A2_EMAIL_IS_UNIQUE:
294 289
                email_qs = qs.filter(ou=self.ou)
......
301 296
                pass
302 297
            else:
303 298
                errors.setdefault('email', []).append(
304
                    _('This email address is already in use. Please supply a different email '
305
                      'address.'))
299
                    _('This email address is already in use. Please supply a different email ' 'address.')
300
                )
306 301
        if errors:
307 302
            raise ValidationError(errors)
308 303

  
......
319 314
            attribute = attributes_map[av.attribute_id]
320 315
            drf_field = attribute.get_drf_field()
321 316
            d[str(attribute.name)] = drf_field.to_representation(av.to_python())
322
        d.update({
323
            'uuid': self.uuid,
324
            'username': self.username,
325
            'email': self.email,
326
            'ou': self.ou.name if self.ou else None,
327
            'ou__uuid': self.ou.uuid if self.ou else None,
328
            'ou__slug': self.ou.slug if self.ou else None,
329
            'ou__name': self.ou.name if self.ou else None,
330
            'first_name': self.first_name,
331
            'last_name': self.last_name,
332
            'is_superuser': self.is_superuser,
333
            'roles': [role.to_json() for role in self.roles_and_parents()],
334
            'services': [service.to_json(roles=self.roles_and_parents()) for service in Service.objects.all()],
335
        })
317
        d.update(
318
            {
319
                'uuid': self.uuid,
320
                'username': self.username,
321
                'email': self.email,
322
                'ou': self.ou.name if self.ou else None,
323
                'ou__uuid': self.ou.uuid if self.ou else None,
324
                'ou__slug': self.ou.slug if self.ou else None,
325
                'ou__name': self.ou.name if self.ou else None,
326
                'first_name': self.first_name,
327
                'last_name': self.last_name,
328
                'is_superuser': self.is_superuser,
329
                'roles': [role.to_json() for role in self.roles_and_parents()],
330
                'services': [
331
                    service.to_json(roles=self.roles_and_parents()) for service in Service.objects.all()
332
                ],
333
            }
334
        )
336 335
        return d
337 336

  
338 337
    def save(self, *args, **kwargs):
......
373 372

  
374 373
    @transaction.atomic
375 374
    def delete(self, **kwargs):
376
        deleted_user = DeletedUser(
377
            old_user_id=self.id)
375
        deleted_user = DeletedUser(old_user_id=self.id)
378 376
        if 'email' in app_settings.A2_USER_DELETED_KEEP_DATA:
379 377
            deleted_user.old_email = self.email.rsplit('#', 1)[0]
380 378
        if 'uuid' in app_settings.A2_USER_DELETED_KEEP_DATA:
......
397 395

  
398 396

  
399 397
class DeletedUser(models.Model):
400
    deleted = models.DateTimeField(
401
        verbose_name=_('Deletion date'),
402
        auto_now_add=True)
403
    old_uuid = models.TextField(
404
        verbose_name=_('Old UUID'),
405
        null=True,
406
        blank=True)
407
    old_user_id = models.PositiveIntegerField(
408
        verbose_name=_('Old user id'),
409
        null=True,
410
        blank=True)
411
    old_email = models.EmailField(
412
        verbose_name=_('Old email adress'),
413
        null=True,
414
        blank=True)
415
    old_data = JSONField(
416
        verbose_name=_('Old data'),
417
        null=True,
418
        blank=True)
398
    deleted = models.DateTimeField(verbose_name=_('Deletion date'), auto_now_add=True)
399
    old_uuid = models.TextField(verbose_name=_('Old UUID'), null=True, blank=True)
400
    old_user_id = models.PositiveIntegerField(verbose_name=_('Old user id'), null=True, blank=True)
401
    old_email = models.EmailField(verbose_name=_('Old email adress'), null=True, blank=True)
402
    old_data = JSONField(verbose_name=_('Old data'), null=True, blank=True)
419 403

  
420 404
    @classmethod
421 405
    def cleanup(cls, threshold=None, timestamp=None):
422
        threshold = threshold or (timezone.now() - datetime.timedelta(days=app_settings.A2_USER_DELETED_KEEP_DATA_DAYS))
406
        threshold = threshold or (
407
            timezone.now() - datetime.timedelta(days=app_settings.A2_USER_DELETED_KEEP_DATA_DAYS)
408
        )
423 409
        cls.objects.filter(deleted__lt=threshold).delete()
424 410

  
425 411
    def __str__(self):
426 412
        return 'DeletedUser(old_id=%s, old_uuid=%s…, old_email=%s)' % (
427 413
            self.old_user_id or '-',
428 414
            (self.old_uuid or '')[:6],
429
            self.old_email or '-')
415
            self.old_email or '-',
416
        )
430 417

  
431 418
    class Meta:
432 419
        verbose_name = _('deleted user')
src/authentic2/data_transfer.py
24 24
from django.utils.text import format_lazy
25 25

  
26 26
from django_rbac.models import Operation
27
from django_rbac.utils import (
28
    get_ou_model, get_role_model, get_role_parenting_model, get_permission_model)
27
from django_rbac.utils import get_ou_model, get_role_model, get_role_parenting_model, get_permission_model
29 28

  
30 29
from authentic2.decorators import errorcollector
31 30
from authentic2.a2_rbac.models import RoleAttribute
......
57 56
                            yield message.message
58 57
                        else:
59 58
                            yield message
59

  
60 60
                for message in error_list(messages):
61
                    errorlist.append(format_lazy(u'{}="{}": {}', obj.__class__._meta.get_field(key).verbose_name, value, message))
61
                    errorlist.append(
62
                        format_lazy(
63
                            u'{}="{}": {}', obj.__class__._meta.get_field(key).verbose_name, value, message
64
                        )
65
                    )
62 66
        raise ValidationError(errorlist)
63 67
    obj.save()
64 68

  
......
99 103

  
100 104

  
101 105
def export_roles(context):
102
    """ Serialize roles in role_queryset
103
    """
104
    return [
105
        role.export_json(attributes=True, parents=True, permissions=True)
106
        for role in context.role_qs
107
    ]
106
    """Serialize roles in role_queryset"""
107
    return [role.export_json(attributes=True, parents=True, permissions=True) for role in context.role_qs]
108 108

  
109 109

  
110 110
def search_ou(ou_d):
......
129 129
        return role
130 130

  
131 131

  
132

  
133 132
class ImportContext(object):
134
    """ Holds information on how to perform the import.
133
    """Holds information on how to perform the import.
135 134

  
136 135
    ou_delete_orphans: if True any existing ou that is not found in the export will
137 136
                       be deleted
......
152 151
    """
153 152

  
154 153
    def __init__(
155
            self,
156
            import_roles=True,
157
            import_ous=True,
158
            role_delete_orphans=False,
159
            role_parentings_update=True,
160
            role_permissions_update=True,
161
            role_attributes_update=True,
162
            ou_delete_orphans=False,
163
            set_ou=None):
154
        self,
155
        import_roles=True,
156
        import_ous=True,
157
        role_delete_orphans=False,
158
        role_parentings_update=True,
159
        role_permissions_update=True,
160
        role_attributes_update=True,
161
        ou_delete_orphans=False,
162
        set_ou=None,
163
    ):
164 164
        self.import_roles = import_roles
165 165
        self.import_ous = import_ous
166 166
        self.role_delete_orphans = role_delete_orphans
......
196 196
            try:
197 197
                return func(self, *args, **kwargs)
198 198
            except ValidationError as e:
199
                raise ValidationError(_('Role "%(name)s": %(errors)s') % {
200
                    'name': self._role_d.get('name', self._role_d.get('slug')),
201
                    'errors': lazy_join(', ', [v.message for v in e.error_list]),
202
                })
199
                raise ValidationError(
200
                    _('Role "%(name)s": %(errors)s')
201
                    % {
202
                        'name': self._role_d.get('name', self._role_d.get('slug')),
203
                        'errors': lazy_join(', ', [v.message for v in e.error_list]),
204
                    }
205
                )
206

  
203 207
        return f
204 208

  
205 209
    @wraps_validationerror
......
241 245

  
242 246
    @wraps_validationerror
243 247
    def attributes(self):
244
        """ Update attributes (delete everything then create)
245
        """
248
        """Update attributes (delete everything then create)"""
246 249
        created, deleted = [], []
247 250
        for attr in self._obj.attributes.all():
248 251
            attr.delete()
......
257 260

  
258 261
    @wraps_validationerror
259 262
    def parentings(self):
260
        """ Update parentings (delete everything then create)
261
        """
263
        """Update parentings (delete everything then create)"""
262 264
        created, deleted = [], []
263 265
        Parenting = get_role_parenting_model()
264 266
        for parenting in Parenting.objects.filter(child=self._obj, direct=True):
......
270 272
                parent = search_role(parent_d)
271 273
                if not parent:
272 274
                    raise ValidationError(_("Could not find parent role: %s") % parent_d)
273
                created.append(Parenting.objects.create(
274
                    child=self._obj, direct=True, parent=parent))
275
                created.append(Parenting.objects.create(child=self._obj, direct=True, parent=parent))
275 276

  
276 277
        return created, deleted
277 278

  
278 279
    @wraps_validationerror
279 280
    def permissions(self):
280
        """ Update permissions (delete everything then create)
281
        """
281
        """Update permissions (delete everything then create)"""
282 282
        created, deleted = [], []
283 283
        for perm in self._obj.permissions.all():
284 284
            perm.delete()
......
287 287
        if self._permissions:
288 288
            for perm in self._permissions:
289 289
                op = Operation.objects.get_by_natural_key_json(perm['operation'])
290
                ou = get_ou_model().objects.get_by_natural_key_json(
291
                    perm['ou']) if perm['ou'] else None
290
                ou = get_ou_model().objects.get_by_natural_key_json(perm['ou']) if perm['ou'] else None
292 291
                ct = ContentType.objects.get_by_natural_key_json(perm['target_ct'])
293 292
                target = ct.model_class().objects.get_by_natural_key_json(perm['target'])
294 293
                perm = get_permission_model().objects.create(
295
                    operation=op, ou=ou, target_ct=ct, target_id=target.pk)
294
                    operation=op, ou=ou, target_ct=ct, target_id=target.pk
295
                )
296 296
                self._obj.permissions.add(perm)
297 297
                created.append(perm)
298 298

  
......
300 300

  
301 301

  
302 302
class ImportResult(object):
303

  
304 303
    def __init__(self):
305 304
        self.roles = {'created': [], 'updated': []}
306 305
        self.ous = {'created': [], 'updated': []}
......
388 387
                result.update_permissions(*ds.permissions())
389 388

  
390 389
        if import_context.ou_delete_orphans:
391
            raise ValidationError(_("Unsupported context value for ou_delete_orphans : %s") % (
392
                import_context.ou_delete_orphans))
390
            raise ValidationError(
391
                _("Unsupported context value for ou_delete_orphans : %s") % (import_context.ou_delete_orphans)
392
            )
393 393

  
394 394
        if import_context.role_delete_orphans:
395 395
            # FIXME : delete each role that is in DB but not in the export
396
            raise ValidationError(_("Unsupported context value for role_delete_orphans : %s") % (
397
                import_context.role_delete_orphans))
396
            raise ValidationError(
397
                _("Unsupported context value for role_delete_orphans : %s")
398
                % (import_context.role_delete_orphans)
399
            )
398 400

  
399 401
    return result
src/authentic2/decorators.py
29 29
from django.utils import six
30 30

  
31 31
from . import app_settings, middleware
32

  
32 33
# XXX: import to_list for retrocompaibility
33 34
from .utils import to_list, to_iter  # noqa: F401
34 35

  
......
39 40

  
40 41
def unless(test, message):
41 42
    '''Decorator returning a 404 status code if some condition is not met'''
43

  
42 44
    def decorator(func):
43 45
        @wraps(func)
44 46
        def f(request, *args, **kwargs):
45 47
            if not test():
46 48
                return technical_404_response(request, Http404(message))
47 49
            return func(request, *args, **kwargs)
50

  
48 51
        return f
52

  
49 53
    return decorator
50 54

  
51 55

  
......
55 59

  
56 60
    def test():
57 61
        return getattr(settings, name, False)
62

  
58 63
    return unless(test, 'please enable %s' % full_name)
59 64

  
60 65

  
......
62 67
    def test():
63 68
        try:
64 69
            import lasso  # noqa: F401
70

  
65 71
            return True
66 72
        except ImportError:
67 73
            return False
74

  
68 75
    return unless(test, 'please install lasso')
69 76

  
70 77

  
71 78
def required(wrapping_functions, patterns_rslt):
72
    '''
79
    """
73 80
    Used to require 1..n decorators in any view returned by a url tree
74 81

  
75 82
    Usage:
......
77 84
      urlpatterns = required((func,func,func),patterns(...))
78 85

  
79 86
    Note:
80
      Use functools.partial to pass keyword params to the required 
81
      decorators. If you need to pass args you will have to write a 
87
      Use functools.partial to pass keyword params to the required
88
      decorators. If you need to pass args you will have to write a
82 89
      wrapper function.
83 90

  
84 91
    Example:
......
88 95
          partial(login_required,login_url='/accounts/login/'),
89 96
          patterns(...)
90 97
      )
91
    '''
98
    """
92 99
    if not hasattr(wrapping_functions, '__iter__'):
93 100
        wrapping_functions = (wrapping_functions,)
94 101

  
95
    return [
96
        _wrap_instance__resolve(wrapping_functions, instance)
97
        for instance in patterns_rslt
98
    ]
102
    return [_wrap_instance__resolve(wrapping_functions, instance) for instance in patterns_rslt]
99 103

  
100 104

  
101 105
def _wrap_instance__resolve(wrapping_functions, instance):
......
123 127

  
124 128

  
125 129
class CacheDecoratorBase(object):
126
    '''Base class to build cache decorators.
130
    """Base class to build cache decorators.
131

  
132
    It helps for building keys from function arguments.
133
    """
127 134

  
128
       It helps for building keys from function arguments.
129
    '''
130 135
    def __new__(cls, *args, **kwargs):
131 136
        if len(args) > 1:
132 137
            raise TypeError(
133
                '%s got unexpected arguments, only one argument must be given, the function to decorate' % cls.__name__)
138
                '%s got unexpected arguments, only one argument must be given, the function to decorate'
139
                % cls.__name__
140
            )
134 141
        if args:
135 142
            # Case of a decorator used directly
136 143
            return cls(**kwargs)(args[0])
137 144
        return super(CacheDecoratorBase, cls).__new__(cls)
138 145

  
139
    def __init__(self, timeout=None, hostname_vary=True, args=None,
140
                 kwargs=None):
146
    def __init__(self, timeout=None, hostname_vary=True, args=None, kwargs=None):
141 147
        self.timeout = timeout
142 148
        self.hostname_vary = hostname_vary
143 149
        self.args = args
......
162 168
                key = self.key(*args, **kwargs)
163 169
                value, tstamp = self.get(key)
164 170
                if tstamp is not None:
165
                    if (self.timeout is None
166
                            or tstamp + self.timeout > now):
171
                    if self.timeout is None or tstamp + self.timeout > now:
167 172
                        return value
168 173
                    if hasattr(self, 'delete'):
169 174
                        self.delete(key, (key, tstamp))
......
172 177
                return value
173 178
            except CacheUnusable:  # fallback when cache cannot be used
174 179
                return func(*args, **kwargs)
180

  
175 181
        f.cache = self
176 182
        return f
177 183

  
......
199 205

  
200 206

  
201 207
class SimpleDictionnaryCacheMixin(object):
202
    '''Default implementations of set, get and delete for a cache implemented
203
       using a dictionary. The dictionnary must be returned by a property named
204
       'cache'.
205
    '''
208
    """Default implementations of set, get and delete for a cache implemented
209
    using a dictionary. The dictionnary must be returned by a property named
210
    'cache'.
211
    """
212

  
206 213
    def set(self, key, value):
207 214
        self.cache[key] = value
208 215

  
......
264 271
        return value
265 272

  
266 273

  
267
class SessionCache(PickleCacheMixin, SimpleDictionnaryCacheMixin,
268
                   CacheDecoratorBase):
274
class SessionCache(PickleCacheMixin, SimpleDictionnaryCacheMixin, CacheDecoratorBase):
269 275
    @property
270 276
    def cache(self):
271 277
        request = middleware.StoreRequestMiddleware.get_request()
......
308 314
                if variable in request.GET:
309 315
                    identifier = request.GET[variable]
310 316
                    if not re.match(r'^[$a-zA-Z_][0-9a-zA-Z_$]*$', identifier):
311
                        return HttpResponseBadRequest('invalid JSONP callback name', content_type='text/plain')
317
                        return HttpResponseBadRequest(
318
                            'invalid JSONP callback name', content_type='text/plain'
319
                        )
312 320
                    jsonp = True
313 321
                    break
314 322
        # 1. check origin
......
336 344
            response['Access-Control-Allow-Headers'] = 'x-requested-with'
337 345
        response.write(json_str)
338 346
        return response
347

  
339 348
    return f
src/authentic2/disco_service/disco_responder.py
82 82
        logger.warn('get_disco_return_url_from_metadata: unknown service provider %s', entity_id)
83 83
        return None
84 84
    dom = parseString(liberty_provider.metadata.encode('utf8'))
85
    endpoints = dom.getElementsByTagNameNS('urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol',
86
                                           'DiscoveryResponse')
85
    endpoints = dom.getElementsByTagNameNS(
86
        'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol', 'DiscoveryResponse'
87
    )
87 88
    if not endpoints:
88 89
        logger.warn('get_disco_return_url_from_metadata: no discovery service endpoint for %s', entity_id)
89 90
        return None
......
141 142

  
142 143
    entityID = None
143 144
    _return = None
144
    policy = \
145
        "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single",
145
    policy = ("urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single",)
146 146
    returnIDParam = None
147 147
    isPassive = False
148 148

  
......
167 167
        # Discovery request parameters
168 168
        entityID = request.GET.get('entityID', '')
169 169
        _return = request.GET.get('return', '')
170
        policy = request.GET.get('idp_selected', 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single')
170
        policy = request.GET.get(
171
            'idp_selected', 'urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol:single'
172
        )
171 173
        returnIDParam = request.GET.get('returnIDParam', 'entityID')
172 174
        # XXX: isPassive is unused
173 175
        isPassive = request.GET.get('isPassive', '')
......
199 201
    # equal to returnIDParam. Else, it is an unconformant SP.
200 202
    if is_param_id_in_return_url(return_url, returnIDParam):
201 203
        message = _('invalid return url %(return_url)s for %(entity_id)s') % dict(
202
            return_url=return_url, entity_id=entityID)
204
            return_url=return_url, entity_id=entityID
205
        )
203 206
        return error_page(request, message, logger=logger)
204 207

  
205 208
    # not back from selection interface
......
227 230
    idp_selected = urlquote('http://www.identity-hub.com/idp/saml2/metadata')
228 231
    return HttpResponseRedirect('%s?idp_selected=%s' % (reverse(disco), idp_selected))
229 232

  
233

  
230 234
urlpatterns = [
231 235
    url(r'^disco$', disco),
232 236
    url(r'^idp_selection$', idp_selection),
src/authentic2/exponential_retry_timeout.py
29 29
    KEY_PREFIX = 'exp-backoff-'
30 30
    CACHE_DURATION = 86400
31 31

  
32
    def __init__(self,
33
                 factor=FACTOR,
34
                 duration=DURATION,
35
                 max_duration=MAX_DURATION,
36
                 key_prefix=None,
37
                 cache_duration=CACHE_DURATION):
32
    def __init__(
33
        self,
34
        factor=FACTOR,
35
        duration=DURATION,
36
        max_duration=MAX_DURATION,
37
        key_prefix=None,
38
        cache_duration=CACHE_DURATION,
39
    ):
38 40
        self.factor = factor
39 41
        self.duration = duration
40 42
        self.max_duration = max_duration
......
48 50
        return '%s%s' % (self.key_prefix or self.KEY_PREFIX, hashlib.md5(key).hexdigest())
49 51

  
50 52
    def seconds_to_wait(self, *keys):
51
        '''Return the duration in seconds until the next time when an action can be
52
           done.
53
        '''
53
        """Return the duration in seconds until the next time when an action can be
54
        done.
55
        """
54 56
        key = self.key(keys)
55 57
        if self.duration:
56 58
            now = time.time()
......
60 62
        return 0
61 63

  
62 64
    def success(self, *keys):
63
        '''Signal an action success, delete exponential backoff cache.
64
        '''
65
        """Signal an action success, delete exponential backoff cache."""
65 66
        key = self.key(keys)
66 67
        if not self.duration:
67 68
            return
......
69 70
        self.logger.debug(u'success for %s', keys)
70 71

  
71 72
    def failure(self, *keys):
72
        '''Signal an action failure, augment the exponential backoff one level.
73
        '''
73
        """Signal an action failure, augment the exponential backoff one level."""
74 74
        key = self.key(keys)
75 75
        if not self.duration:
76 76
            return
src/authentic2/forms/authentication.py
39 39
        initial=False,
40 40
        required=False,
41 41
        label=_('Remember me'),
42
        help_text=_('Do not ask for authentication next time'))
42
        help_text=_('Do not ask for authentication next time'),
43
    )
43 44
    ou = forms.ModelChoiceField(
44 45
        label=lazy_label(_('Organizational unit'), lambda: app_settings.A2_LOGIN_FORM_OU_SELECTOR_LABEL),
45 46
        required=True,
46
        queryset=OU.objects.all())
47
        queryset=OU.objects.all(),
48
    )
47 49

  
48 50
    def __init__(self, *args, **kwargs):
49 51
        preferred_ous = kwargs.pop('preferred_ous', [])
......
53 55
        self.exponential_backoff = ExponentialRetryTimeout(
54 56
            key_prefix='login-exp-backoff-',
55 57
            duration=app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_DURATION,
56
            factor=app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_FACTOR)
58
            factor=app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_FACTOR,
59
        )
57 60

  
58 61
        if not app_settings.A2_USER_REMEMBER_ME:
59 62
            del self.fields['remember_me']
......
64 67
            if preferred_ous:
65 68
                choices = self.fields['ou'].choices
66 69
                new_choices = list(choices)[:1] + [
67
                    (ugettext('Preferred organizational units'), [
68
                        (ou.pk, ou.name) for ou in preferred_ous]),
70
                    (ugettext('Preferred organizational units'), [(ou.pk, ou.name) for ou in preferred_ous]),
69 71
                    (ugettext('All organizational units'), list(choices)[1:]),
70 72
                ]
71 73
                self.fields['ou'].choices = new_choices
......
88 90
            seconds_to_wait = self.exponential_backoff.seconds_to_wait(*keys)
89 91
            if seconds_to_wait > app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION:
90 92
                seconds_to_wait -= app_settings.A2_LOGIN_EXPONENTIAL_RETRY_TIMEOUT_MIN_DURATION
91
                msg = _('You made too many login errors recently, you must '
92
                        'wait <span class="js-seconds-until">%s</span> seconds '
93
                        'to try again.')
93
                msg = _(
94
                    'You made too many login errors recently, you must '
95
                    'wait <span class="js-seconds-until">%s</span> seconds '
96
                    'to try again.'
97
                )
94 98
                msg = msg % int(math.ceil(seconds_to_wait))
95 99
                msg = html.mark_safe(msg)
96 100
                raise forms.ValidationError(msg)
......
140 144
        if app_settings.A2_USERNAME_LABEL:
141 145
            username_label = app_settings.A2_USERNAME_LABEL
142 146
        invalid_login_message = [
143
                _('Incorrect %(username_label)s or password.') % {'username_label': username_label},
147
            _('Incorrect %(username_label)s or password.') % {'username_label': username_label},
144 148
        ]
145
        if app_settings.A2_USER_CAN_RESET_PASSWORD is not False and getattr(settings, 'REGISTRATION_OPEN', True):
149
        if app_settings.A2_USER_CAN_RESET_PASSWORD is not False and getattr(
150
            settings, 'REGISTRATION_OPEN', True
151
        ):
146 152
            invalid_login_message.append(
147
                    _('Try again, use the forgotten password link below, or create an account.'))
153
                _('Try again, use the forgotten password link below, or create an account.')
154
            )
148 155
        elif app_settings.A2_USER_CAN_RESET_PASSWORD is not False:
149
            invalid_login_message.append(
150
                    _('Try again or use the forgotten password link below.'))
156
            invalid_login_message.append(_('Try again or use the forgotten password link below.'))
151 157
        elif getattr(settings, 'REGISTRATION_OPEN', True):
152
            invalid_login_message.append(
153
                    _('Try again or create an account.'))
158
            invalid_login_message.append(_('Try again or create an account.'))
154 159
        error_messages['invalid_login'] = ' '.join([force_text(x) for x in invalid_login_message])
155 160
        return error_messages
src/authentic2/forms/fields.py
24 24

  
25 25
from authentic2 import app_settings
26 26
from authentic2.passwords import password_help_text, validate_password
27
from authentic2.forms.widgets import (PasswordInput, NewPasswordInput,
28
                                      CheckPasswordInput, ProfileImageInput,
29
                                      EmailInput)
27
from authentic2.forms.widgets import (
28
    PasswordInput,
29
    NewPasswordInput,
30
    CheckPasswordInput,
31
    ProfileImageInput,
32
    EmailInput,
33
)
30 34
from authentic2.validators import email_validator
31 35

  
32 36
import PIL.Image
......
49 53
    widget = CheckPasswordInput
50 54

  
51 55
    def __init__(self, *args, **kwargs):
52
        kwargs['help_text'] = u'''
56
        kwargs[
57
            'help_text'
58
        ] = u'''
53 59
    <span class="a2-password-check-equality-default">%(default)s</span>
54 60
    <span class="a2-password-check-equality-matched">%(match)s</span>
55 61
    <span class="a2-password-check-equality-unmatched">%(nomatch)s</span>
......
85 91
        output = io.BytesIO()
86 92
        if image.mode != 'RGB':
87 93
            image = image.convert('RGB')
88
        image.save(
89
            output,
90
            format='JPEG',
91
            quality=99,
92
            optimize=1)
94
        image.save(output, format='JPEG', quality=99, optimize=1)
93 95
        output.seek(0)
94 96
        return File(output, name=name)
95 97

  
src/authentic2/forms/mixins.py
61 61
                help_text=field.help_text,
62 62
                initial=initial,
63 63
                required=False,
64
                widget=forms.TextInput(attrs={'readonly': ''}))
64
                widget=forms.TextInput(attrs={'readonly': ''}),
65
            )
65 66
        if not locked_fields:
66 67
            return
67 68

  
src/authentic2/forms/passwords.py
37 37
class PasswordResetForm(forms.Form):
38 38
    next_url = forms.CharField(widget=forms.HiddenInput, required=False)
39 39

  
40
    email = forms.CharField(
41
        label=_("Email"), max_length=254)
40
    email = forms.CharField(label=_("Email"), max_length=254)
42 41

  
43 42
    def save(self):
44 43
        """
......
51 50
        for user in active_users:
52 51
            # we don't set the password to a random string, as some users should not have
53 52
            # a password
54
            set_random_password = (user.has_usable_password()
55
                                   and app_settings.A2_SET_RANDOM_PASSWORD_ON_RESET)
53
            set_random_password = user.has_usable_password() and app_settings.A2_SET_RANDOM_PASSWORD_ON_RESET
56 54
            utils.send_password_reset_mail(
57
                user,
58
                set_random_password=set_random_password,
59
                next_url=self.cleaned_data.get('next_url'))
55
                user, set_random_password=set_random_password, next_url=self.cleaned_data.get('next_url')
56
            )
60 57
        for user in users.filter(is_active=False):
61 58
            logger.info('password reset failed for user "%r": account is disabled', user)
62 59
            utils.send_templated_mail(user, ['authentic2/password_reset_refused'])
......
68 65

  
69 66

  
70 67
class PasswordResetMixin(Form):
71
    '''Remove all password reset object for the current user when password is
72
       successfully changed.'''
68
    """Remove all password reset object for the current user when password is
69
    successfully changed."""
73 70

  
74 71
    def save(self, commit=True):
75 72
        ret = super(PasswordResetMixin, self).save(commit=commit)
......
82 79
                ret = old_save(*args, **kwargs)
83 80
                models.PasswordReset.objects.filter(user=self.user).delete()
84 81
                return ret
82

  
85 83
            self.user.save = save
86 84
        return ret
87 85

  
......
109 107
        return new_password1
110 108

  
111 109

  
112
class PasswordChangeForm(NotifyOfPasswordChange, NextUrlFormMixin, PasswordResetMixin,
113
                         auth_forms.PasswordChangeForm):
110
class PasswordChangeForm(
111
    NotifyOfPasswordChange, NextUrlFormMixin, PasswordResetMixin, auth_forms.PasswordChangeForm
112
):
114 113
    old_password = PasswordField(label=_('Old password'))
115 114
    new_password1 = NewPasswordField(label=_('New password'))
116 115
    new_password2 = CheckPasswordField(label=_("New password confirmation"))
......
122 121
            raise ValidationError(_('New password must differ from old password'))
123 122
        return new_password1
124 123

  
124

  
125 125
# make old_password the first field
126 126
new_base_fields = OrderedDict()
127 127

  
src/authentic2/forms/profile.py
50 50

  
51 51

  
52 52
class EmailChangeForm(EmailChangeFormNoPassword):
53
    password = forms.CharField(label=_("Password"),
54
                               widget=forms.PasswordInput)
53
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
55 54

  
56 55
    def clean_email(self):
57 56
        email = self.cleaned_data['email']
......
120 119
            def save_m2m(*args, **kwargs):
121 120
                old(*args, **kwargs)
122 121
                self.save_attributes()
122

  
123 123
            self.save_m2m = save_m2m
124 124
        return result
125 125

  
......
129 129

  
130 130

  
131 131
def modelform_factory(model, **kwargs):
132
    '''Build a modelform for the given model,
132
    """Build a modelform for the given model,
133 133

  
134
       For the user model also add attribute based fields.
135
    '''
134
    For the user model also add attribute based fields.
135
    """
136 136

  
137 137
    form = kwargs.pop('form', None)
138 138
    fields = kwargs.get('fields') or []
......
159 159
        form = forms.ModelForm
160 160
    modelform = None
161 161
    if required:
162

  
162 163
        def __init__(self, *args, **kwargs):
163 164
            super(modelform, self).__init__(*args, **kwargs)
164 165
            for field in required:
165 166
                if field in self.fields:
166 167
                    self.fields[field].required = True
168

  
167 169
        d['__init__'] = __init__
168 170
    modelform = type(model.__name__ + 'ModelForm', (form,), d)
169 171
    kwargs['form'] = modelform
170 172
    modelform.required_css_class = 'form-field-required'
171 173
    return dj_modelform_factory(model, **kwargs)
172

  
173

  
src/authentic2/forms/registration.py
100 100
                else:
101 101
                    exist = True
102 102
                if exist:
103
                    raise ValidationError(_('This username is already in '
104
                                            'use. Please supply a different username.'))
103
                    raise ValidationError(
104
                        _('This username is already in ' 'use. Please supply a different username.')
105
                    )
105 106
            return username
106 107

  
107 108
    def clean_email(self):
......
118 119
                else:
119 120
                    exist = True
120 121
                if exist:
121
                    raise ValidationError(_('This email address is already in '
122
                                            'use. Please supply a different email address.'))
122
                    raise ValidationError(
123
                        _('This email address is already in ' 'use. Please supply a different email address.')
124
                    )
123 125
            return BaseUserManager.normalize_email(email)
124 126

  
125 127
    def save(self, commit=True):
src/authentic2/forms/widgets.py
66 66
    '%Y': 'yyyy',
67 67
    '%y': 'yy',
68 68
    '%p': 'P',
69
    '%S': 'ss'
69
    '%S': 'ss',
70 70
}
71 71

  
72 72
DATE_FORMAT_TO_JS_REGEX = re.compile(r'(?<!\w)(' + '|'.join(DATE_FORMAT_PY_JS_MAPPING.keys()) + r')\b')
......
131 131
        # with a default, and convert it to a Python data format for later string parsing
132 132
        date_format = self.options['format']
133 133
        self.format = DATE_FORMAT_TO_PYTHON_REGEX.sub(
134
            lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()],
135
            date_format)
134
            lambda x: DATE_FORMAT_JS_PY_MAPPING[x.group()], date_format
135
        )
136 136

  
137 137
        super(PickerWidgetMixin, self).__init__(attrs, format=self.format)
138 138

  
......
147 147
        final_attrs = self.build_attrs(attrs)
148 148
        final_attrs['class'] = "controls input-append date"
149 149
        rendered_widget = super(PickerWidgetMixin, self).render(
150
                name, value, attrs=final_attrs, renderer=renderer)
150
            name, value, attrs=final_attrs, renderer=renderer
151
        )
151 152

  
152 153
        # if not set, autoclose have to be true.
153 154
        self.options.setdefault('autoclose', True)
......
166 167
        if not help_text:
167 168
            help_text = u'%s %s' % (_('Format:'), self.options['format'])
168 169

  
169
        return mark_safe(self.render_template % dict(
170
            id=id,
171
            rendered_widget=rendered_widget,
172
            clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
173
            glyphicon=self.glyphicon,
174
            language=get_language(),
175
            options=js_options,
176
            help_text=help_text))
170
        return mark_safe(
171
            self.render_template
172
            % dict(
173
                id=id,
174
                rendered_widget=rendered_widget,
175
                clear_button=CLEAR_BTN_TEMPLATE if self.options.get('clearBtn') else '',
176
                glyphicon=self.glyphicon,
177
                language=get_language(),
178
                options=js_options,
179
                help_text=help_text,
180
            )
181
        )
177 182

  
178 183

  
179 184
class DateTimeWidget(PickerWidgetMixin, DateTimeInput):
......
255 260
            xstatic('jquery', 'jquery.min.js'),
256 261
            'authentic2/js/password.js',
257 262
        )
258
        css = {
259
            'all': ('authentic2/css/password.css',)
260
        }
263
        css = {'all': ('authentic2/css/password.css',)}
261 264

  
262 265
    def render(self, name, value, attrs=None, renderer=None):
263
        output = super(PasswordInput, self).render(
264
                name, value, attrs=attrs, renderer=renderer)
266
        output = super(PasswordInput, self).render(name, value, attrs=attrs, renderer=renderer)
265 267
        if attrs and app_settings.A2_PASSWORD_POLICY_SHOW_LAST_CHAR:
266 268
            _id = attrs.get('id')
267 269
            if _id:
......
274 276
        if attrs is None:
275 277
            attrs = {}
276 278
        attrs['autocomplete'] = 'new-password'
277
        output = super(NewPasswordInput, self).render(
278
                name, value, attrs=attrs, renderer=renderer)
279
        output = super(NewPasswordInput, self).render(name, value, attrs=attrs, renderer=renderer)
279 280
        if attrs:
280 281
            _id = attrs.get('id')
281 282
            if _id:
......
290 291
        if attrs is None:
291 292
            attrs = {}
292 293
        attrs['autocomplete'] = 'new-password'
293
        output = super(CheckPasswordInput, self).render(
294
                name, value, attrs=attrs, renderer=renderer)
294
        output = super(CheckPasswordInput, self).render(name, value, attrs=attrs, renderer=renderer)
295 295
        if attrs:
296 296
            _id = attrs.get('id')
297 297
            if _id and _id.endswith('2'):
......
326 326
        self.attrs.update({'list': self.name})
327 327

  
328 328
    def render(self, name, value, attrs=None, renderer=None):
329
        output = super(DatalistTextInput, self).render(
330
                name, value, attrs=attrs, renderer=renderer)
329
        output = super(DatalistTextInput, self).render(name, value, attrs=attrs, renderer=renderer)
331 330
        datalist = '<datalist id="%s">' % self.name
332 331
        for element in self.data:
333 332
            datalist += '<option value="%s">' % element
......
348 347
    def media(self):
349 348
        if app_settings.A2_SUGGESTED_EMAIL_DOMAINS:
350 349
            return forms.Media(
351
                js = (
350
                js=(
352 351
                    xstatic('jquery', 'jquery.min.js'),
353 352
                    'authentic2/js/email_domains_suggestions.js',
354 353
                )
355 354
            )
355

  
356 356
    def get_context(self, *args, **kwargs):
357 357
        context = super(EmailInput, self).get_context(*args, **kwargs)
358 358
        if app_settings.A2_SUGGESTED_EMAIL_DOMAINS:
359
            context['widget']['attrs']['data-suggested-domains'] = ':'.join(app_settings.A2_SUGGESTED_EMAIL_DOMAINS)
359
            context['widget']['attrs']['data-suggested-domains'] = ':'.join(
360
                app_settings.A2_SUGGESTED_EMAIL_DOMAINS
361
            )
360 362
            context['domains_suggested'] = True
361 363
        return context
src/authentic2/hashers.py
31 31
    """
32 32
    Secure password hashing using the algorithm used by Drupal 7 (recommended)
33 33
    """
34

  
34 35
    algorithm = "drupal7_sha512"
35 36
    iterations = 10000
36 37
    digest = hashlib.sha512
......
49 50
        while i < count:
50 51
            value = v[i]
51 52
            i += 1
52
            out += self.i64toa(value & 0x3f)
53
            out += self.i64toa(value & 0x3F)
53 54
            if i < count:
54 55
                value |= v[i] << 8
55
            out += self.i64toa((value >> 6) & 0x3f)
56
            out += self.i64toa((value >> 6) & 0x3F)
56 57
            if i == count:
57 58
                break
58 59
            i += 1
59 60
            if i < count:
60 61
                value |= v[i] << 16
61
            out += self.i64toa((value >> 12) & 0x3f)
62
            out += self.i64toa((value >> 12) & 0x3F)
62 63
            if i == count:
63 64
                break
64 65
            i += 1
65
            out += self.i64toa((value >> 18) & 0x3f)
66
            out += self.i64toa((value >> 18) & 0x3F)
66 67
        return out
67 68

  
68 69
    def from_drupal(self, encoded):
......
95 96
    def safe_summary(self, encoded):
96 97
        algorithm, iterations, salt, hash = encoded.split('$', 3)
97 98
        assert algorithm == self.algorithm
98
        return OrderedDict([
99
            (_('algorithm'), algorithm),
100
            (_('iterations'), iterations),
101
            (_('salt'), hashers.mask_hash(salt)),
102
            (_('hash'), hashers.mask_hash(hash)),
103
        ])
99
        return OrderedDict(
100
            [
101
                (_('algorithm'), algorithm),
102
                (_('iterations'), iterations),
103
                (_('salt'), hashers.mask_hash(salt)),
104
                (_('hash'), hashers.mask_hash(hash)),
105
            ]
106
        )
104 107

  
105 108

  
106 109
class CommonPasswordHasher(hashers.BasePasswordHasher):
107 110
    """
108 111
    The Salted MD5 password hashing algorithm (not recommended)
109 112
    """
113

  
110 114
    algorithm = None
111 115
    digest = None
112 116

  
......
125 129
    def safe_summary(self, encoded):
126 130
        algorithm, salt, hash = encoded.split('$', 2)
127 131
        assert algorithm == self.algorithm
128
        return OrderedDict([
129
            (_('algorithm'), algorithm),
130
            (_('salt'), hashers.mask_hash(salt, show=2)),
131
            (_('hash'), hashers.mask_hash(hash)),
132
        ])
132
        return OrderedDict(
133
            [
134
                (_('algorithm'), algorithm),
135
                (_('salt'), hashers.mask_hash(salt, show=2)),
136
                (_('hash'), hashers.mask_hash(hash)),
137
            ]
138
        )
133 139

  
134 140

  
135 141
OPENLDAP_ALGO_MAPPING = {
136
    'SHA': (
137
        'sha-oldap',
138
        0,
139
        True
140
    ),
141
    'SSHA': (
142
        'ssha-oldap',
143
        20,
144
        True
145
    ),
146
    'MD5': (
147
        'md5-oldap',
148
        0,
149
        True
150
    ),
151
    'SMD5': (
152
        'md5-oldap',
153
        16,
154
        True
155
    ),
142
    'SHA': ('sha-oldap', 0, True),
143
    'SSHA': ('ssha-oldap', 20, True),
144
    'MD5': ('md5-oldap', 0, True),
145
    'SMD5': ('md5-oldap', 16, True),
156 146
}
157 147

  
158 148

  
......
301 291

  
302 292
        # throw away the prefix
303 293
        if data.startswith(self._prefix):
304
            data = data[len(self._prefix):]
294
            data = data[len(self._prefix) :]
305 295

  
306 296
        # extract salt from encoded data
307 297
        intermediate = base64.b64decode(data)
......
313 303
    def safe_summary(self, encoded):
314 304
        algorithm, hash = encoded.split('$', 1)
315 305
        assert algorithm == self.algorithm
316
        return OrderedDict([
317
            (_('algorithm'), algorithm),
318
            (_('hash'), hashers.mask_hash(hash)),
319
        ])
306
        return OrderedDict(
307
            [
308
                (_('algorithm'), algorithm),
309
                (_('hash'), hashers.mask_hash(hash)),
310
            ]
311
        )
src/authentic2/hooks.py
24 24

  
25 25
@decorators.GlobalCache
26 26
def get_hooks(hook_name):
27
    '''Return a list of defined hook named a2_hook<hook_name> on AppConfig classes of installed
28
       Django applications.
27
    """Return a list of defined hook named a2_hook<hook_name> on AppConfig classes of installed
28
    Django applications.
29 29

  
30
       Ordering of hooks can be defined using an orer field on the hook method.
31
    '''
30
    Ordering of hooks can be defined using an orer field on the hook method.
31
    """
32 32
    hooks = []
33 33
    for app in apps.get_app_configs():
34 34
        name = 'a2_hook_' + hook_name
src/authentic2/idp/interactions.py
21 21

  
22 22
@login_required
23 23
def consent_federation(request, nonce='', provider_id=None):
24
    '''On a GET produce a form asking for consentment,
25
       On a POST handle the form and redirect to next'''
24
    """On a GET produce a form asking for consentment,
25
    On a POST handle the form and redirect to next"""
26 26
    if request.method == "GET":
27 27
        return render(
28
            request, 'interaction/consent_federation.html',
28
            request,
29
            'interaction/consent_federation.html',
29 30
            {
30 31
                'provider_id': request.GET.get('provider_id', ''),
31 32
                'nonce': request.GET.get('nonce', ''),
32
                'next': request.GET.get('next', '')
33
            })
33
                'next': request.GET.get('next', ''),
34
            },
35
        )
34 36
    else:
35 37
        next_url = '/'
36 38
        if 'next' in request.POST:
src/authentic2/idp/migrations/0001_initial.py
14 14
        migrations.CreateModel(
15 15
            name='AttributePolicy',
16 16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17
                (
18
                    'id',
19
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
20
                ),
18 21
                ('name', models.CharField(unique=True, max_length=100)),
19 22
                ('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
20
                ('ask_consent_attributes', models.BooleanField(default=True, verbose_name='Ask the user consent before forwarding attributes')),
21
                ('allow_attributes_selection', models.BooleanField(default=True, verbose_name='Allow the user to select the forwarding attributes')),
22
                ('forward_attributes_from_push_sources', models.BooleanField(default=False, verbose_name='Forward pushed attributes')),
23
                ('map_attributes_from_push_sources', models.BooleanField(default=False, verbose_name='Map forwarded pushed attributes')),
24
                ('output_name_format', models.CharField(default=('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'), max_length=100, verbose_name='Output name format', choices=[('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'), ('urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'SAMLv2 BASIC')])),
25
                ('output_namespace', models.CharField(default=('Default', 'Default'), max_length=100, verbose_name='Output namespace', choices=[('Default', 'Default'), ('http://schemas.xmlsoap.org/ws/2005/05/identity/claims', 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims')])),
26
                ('filter_source_of_filtered_attributes', models.BooleanField(default=False, verbose_name='Filter by source and per attribute the forwarded pushed attributes')),
27
                ('map_attributes_of_filtered_attributes', models.BooleanField(default=False, verbose_name='Map filtered attributes')),
28
                ('send_error_and_no_attrs_if_missing_required_attrs', models.BooleanField(default=False, verbose_name='Send an error when a required attribute is missing')),
29
                ('attribute_filter_for_sso_from_push_sources', models.ForeignKey(related_name='filter attributes of push sources with list', verbose_name='Filter by attribute names the forwarded pushed attributes', blank=True, to='attribute_aggregator.AttributeList', null=True, on_delete=models.CASCADE)),
30
                ('attribute_list_for_sso_from_pull_sources', models.ForeignKey(related_name='attributes from pull sources', verbose_name='Pull attributes list', blank=True, to='attribute_aggregator.AttributeList', null=True, on_delete=models.CASCADE)),
31
                ('source_filter_for_sso_from_push_sources', models.ManyToManyField(related_name='filter attributes of push sources with sources', null=True, verbose_name='Filter by source the forwarded pushed attributes', to='attribute_aggregator.AttributeSource', blank=True)),
23
                (
24
                    'ask_consent_attributes',
25
                    models.BooleanField(
26
                        default=True, verbose_name='Ask the user consent before forwarding attributes'
27
                    ),
28
                ),
29
                (
30
                    'allow_attributes_selection',
31
                    models.BooleanField(
32
                        default=True, verbose_name='Allow the user to select the forwarding attributes'
33
                    ),
34
                ),
35
                (
36
                    'forward_attributes_from_push_sources',
37
                    models.BooleanField(default=False, verbose_name='Forward pushed attributes'),
38
                ),
39
                (
40
                    'map_attributes_from_push_sources',
41
                    models.BooleanField(default=False, verbose_name='Map forwarded pushed attributes'),
42
                ),
43
                (
44
                    'output_name_format',
45
                    models.CharField(
46
                        default=('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'),
47
                        max_length=100,
48
                        verbose_name='Output name format',
49
                        choices=[
50
                            ('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI'),
51
                            ('urn:oasis:names:tc:SAML:2.0:attrname-format:basic', 'SAMLv2 BASIC'),
52
                        ],
53
                    ),
54
                ),
55
                (
56
                    'output_namespace',
57
                    models.CharField(
58
                        default=('Default', 'Default'),
59
                        max_length=100,
60
                        verbose_name='Output namespace',
61
                        choices=[
62
                            ('Default', 'Default'),
63
                            (
64
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
65
                                'http://schemas.xmlsoap.org/ws/2005/05/identity/claims',
66
                            ),
67
                        ],
68
                    ),
69
                ),
70
                (
71
                    'filter_source_of_filtered_attributes',
72
                    models.BooleanField(
73
                        default=False,
74
                        verbose_name='Filter by source and per attribute the forwarded pushed attributes',
75
                    ),
76
                ),
77
                (
78
                    'map_attributes_of_filtered_attributes',
79
                    models.BooleanField(default=False, verbose_name='Map filtered attributes'),
80
                ),
81
                (
82
                    'send_error_and_no_attrs_if_missing_required_attrs',
83
                    models.BooleanField(
84
                        default=False, verbose_name='Send an error when a required attribute is missing'
85
                    ),
86
                ),
87
                (
88
                    'attribute_filter_for_sso_from_push_sources',
89
                    models.ForeignKey(
90
                        related_name='filter attributes of push sources with list',
91
                        verbose_name='Filter by attribute names the forwarded pushed attributes',
92
                        blank=True,
93
                        to='attribute_aggregator.AttributeList',
94
                        null=True,
95
                        on_delete=models.CASCADE,
96
                    ),
97
                ),
98
                (
99
                    'attribute_list_for_sso_from_pull_sources',
100
                    models.ForeignKey(
101
                        related_name='attributes from pull sources',
102
                        verbose_name='Pull attributes list',
103
                        blank=True,
104
                        to='attribute_aggregator.AttributeList',
105
                        null=True,
106
                        on_delete=models.CASCADE,
107
                    ),
108
                ),
109
                (
110
                    'source_filter_for_sso_from_push_sources',
111
                    models.ManyToManyField(
112
                        related_name='filter attributes of push sources with sources',
113
                        null=True,
114
                        verbose_name='Filter by source the forwarded pushed attributes',
115
                        to='attribute_aggregator.AttributeSource',
116
                        blank=True,
117
                    ),
118
                ),
32 119
            ],
33 120
            options={
34 121
                'verbose_name': 'attribute policy',
src/authentic2/idp/migrations/0002_auto_20150526_2239.py
14 14
        migrations.AlterField(
15 15
            model_name='attributepolicy',
16 16
            name='attribute_filter_for_sso_from_push_sources',
17
            field=models.ForeignKey(related_name='+', verbose_name='Filter by attribute names the forwarded pushed attributes', blank=True, to='attribute_aggregator.AttributeList', null=True, on_delete=models.CASCADE),
17
            field=models.ForeignKey(
18
                related_name='+',
19
                verbose_name='Filter by attribute names the forwarded pushed attributes',
20
                blank=True,
21
                to='attribute_aggregator.AttributeList',
22
                null=True,
23
                on_delete=models.CASCADE,
24
            ),
18 25
        ),
19 26
        migrations.AlterField(
20 27
            model_name='attributepolicy',
21 28
            name='attribute_list_for_sso_from_pull_sources',
22
            field=models.ForeignKey(related_name='+', verbose_name='Pull attributes list', blank=True, to='attribute_aggregator.AttributeList', null=True, on_delete=models.CASCADE),
29
            field=models.ForeignKey(
30
                related_name='+',
31
                verbose_name='Pull attributes list',
32
                blank=True,
33
                to='attribute_aggregator.AttributeList',
34
                null=True,
35
                on_delete=models.CASCADE,
36
            ),
23 37
        ),
24 38
        migrations.AlterField(
25 39
            model_name='attributepolicy',
26 40
            name='source_filter_for_sso_from_push_sources',
27
            field=models.ManyToManyField(to='attribute_aggregator.AttributeSource', verbose_name='Filter by source the forwarded pushed attributes', blank=True),
41
            field=models.ManyToManyField(
42
                to='attribute_aggregator.AttributeSource',
43
                verbose_name='Filter by source the forwarded pushed attributes',
44
                blank=True,
45
            ),
28 46
        ),
29 47
    ]
src/authentic2/idp/saml/__init__.py
20 20

  
21 21

  
22 22
class Plugin(object):
23

  
24 23
    def check_origin(self, request, origin):
25 24
        from authentic2.cors import make_origin
26 25
        from authentic2.saml.models import LibertySession
27
        for session in LibertySession.objects.filter(
28
                django_session_key=request.session.session_key):
26

  
27
        for session in LibertySession.objects.filter(django_session_key=request.session.session_key):
29 28
            provider_origin = make_origin(session.provider_id)
30 29
            if origin == provider_origin:
31 30
                return True
......
44 43

  
45 44
def check_authentic2_config(app_configs, **kwargs):
46 45
    from . import app_settings
46

  
47 47
    errors = []
48 48

  
49
    if (not settings.DEBUG
50
            and app_settings.ENABLE
51
            and (app_settings.is_default('SIGNATURE_PUBLIC_KEY')
52
                 or app_settings.is_default('SIGNATURE_PRIVATE_KEY'))):
49
    if (
50
        not settings.DEBUG
51
        and app_settings.ENABLE
52
        and (
53
            app_settings.is_default('SIGNATURE_PUBLIC_KEY')
54
            or app_settings.is_default('SIGNATURE_PRIVATE_KEY')
55
        )
56
    ):
53 57
        errors.append(
54 58
            Warning(
55 59
                'You should not use default SAML keys in production',
56 60
                hint='Generate new RSA keys and change the value of '
57
                     'A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY and '
58
                     'A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY in your setting file',
61
                'A2_IDP_SAML2_SIGNATURE_PUBLIC_KEY and '
62
                'A2_IDP_SAML2_SIGNATURE_PRIVATE_KEY in your setting file',
59 63
            )
60 64
        )
61 65
    return errors
62 66

  
67

  
63 68
check_authentic2_config = register(Tags.security, deploy=True)(check_authentic2_config)
src/authentic2/idp/saml/app_settings.py
80 80

  
81 81
    def _setting(self, name, dflt):
82 82
        from django.conf import settings
83

  
83 84
        return getattr(settings, name, dflt)
84 85

  
85 86
    def _setting_with_prefix(self, name, dflt):
......
87 88

  
88 89
    @property
89 90
    def ENABLE(self):
90
        return self._setting_with_prefix('ENABLE',
91
                                         self._setting('IDP_SAML2',
92
                                                       self.__DEFAULTS['ENABLE']))
91
        return self._setting_with_prefix('ENABLE', self._setting('IDP_SAML2', self.__DEFAULTS['ENABLE']))
93 92

  
94 93
    @property
95 94
    def SIGNATURE_PUBLIC_KEY(self):
96
        return self._setting_with_prefix('SIGNATURE_PUBLIC_KEY',
97
                                         self._setting('SAML_SIGNATURE_PUBLIC_KEY',
98
                                                       self.__DEFAULTS['SIGNATURE_PUBLIC_KEY']))
95
        return self._setting_with_prefix(
96
            'SIGNATURE_PUBLIC_KEY',
97
            self._setting('SAML_SIGNATURE_PUBLIC_KEY', self.__DEFAULTS['SIGNATURE_PUBLIC_KEY']),
98
        )
99 99

  
100 100
    @property
101 101
    def SIGNATURE_PRIVATE_KEY(self):
102
        return self._setting_with_prefix('SIGNATURE_PRIVATE_KEY',
103
                                         self._setting('SAML_SIGNATURE_PRIVATE_KEY',
104
                                                       self.__DEFAULTS['SIGNATURE_PRIVATE_KEY']))
102
        return self._setting_with_prefix(
103
            'SIGNATURE_PRIVATE_KEY',
104
            self._setting('SAML_SIGNATURE_PRIVATE_KEY', self.__DEFAULTS['SIGNATURE_PRIVATE_KEY']),
105
        )
105 106

  
106 107
    @property
107 108
    def AUTHN_CONTEXT_FROM_SESSION(self):
108
        return self._setting_with_prefix('AUTHN_CONTEXT_FROM_SESSION',
109
                                         self._setting('IDP_SAML2_AUTHN_CONTEXT_FROM_SESSION',
110
                                                       self.__DEFAULTS['AUTHN_CONTEXT_FROM_SESSION']))
109
        return self._setting_with_prefix(
110
            'AUTHN_CONTEXT_FROM_SESSION',
111
            self._setting(
112
                'IDP_SAML2_AUTHN_CONTEXT_FROM_SESSION', self.__DEFAULTS['AUTHN_CONTEXT_FROM_SESSION']
113
            ),
114
        )
111 115

  
112 116
    def is_default(self, name):
113 117
        return getattr(self, name) == self.__DEFAULTS[name]
......
117 121
            raise AttributeError(name)
118 122
        return self._setting_with_prefix(name, self.__DEFAULTS[name])
119 123

  
124

  
120 125
# Ugly? Guido recommends this himself ...
121 126
# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
122 127
app_settings = AppSettings('A2_IDP_SAML2_')
src/authentic2/idp/saml/backend.py
41 41
        q = models.LibertyServiceProvider.objects.all()
42 42
        q = q.filter(
43 43
            Q(liberty_provider__authorized_roles__isnull=True)
44
            | Q(liberty_provider__authorized_roles__in=request.user.roles_and_parents()))
44
            | Q(liberty_provider__authorized_roles__in=request.user.roles_and_parents())
45
        )
45 46
        ls = []
46
        sessions = models.LibertySession.objects.filter(
47
            django_session_key=request.session.session_key)
47
        sessions = models.LibertySession.objects.filter(django_session_key=request.session.session_key)
48 48
        sessions_eids = set(session.provider_id for session in sessions)
49 49
        all_policy = common.get_sp_options_policy_all()
50 50
        default_policy = common.get_sp_options_policy_default()
......
53 53
            queries.append(q)
54 54
            queries.append(q.filter(liberty_provider__entity_id__in=sessions_eids))
55 55
        else:
56
            queries.append(q.filter(
57
                sp_options_policy__enabled=True,
58
                sp_options_policy__idp_initiated_sso=True))
56
            queries.append(
57
                q.filter(sp_options_policy__enabled=True, sp_options_policy__idp_initiated_sso=True)
58
            )
59 59
            queries.append(
60 60
                q.filter(
61 61
                    sp_options_policy__enabled=True,
62 62
                    sp_options_policy__accept_slo=True,
63
                    liberty_provider__entity_id__in=sessions_eids))
63
                    liberty_provider__entity_id__in=sessions_eids,
64
                )
65
            )
64 66
            if default_policy and default_policy.idp_initiated_sso:
65
                queries.append(
66
                    q.filter(
67
                        sp_options_policy__isnull=True))
67
                queries.append(q.filter(sp_options_policy__isnull=True))
68 68
            if default_policy and default_policy.accept_slo:
69 69
                queries.append(
70
                    q.filter(
71
                        sp_options_policy__isnull=True,
72
                        liberty_provider__entity_id__in=sessions_eids))
70
                    q.filter(sp_options_policy__isnull=True, liberty_provider__entity_id__in=sessions_eids)
71
                )
73 72
        qs = six.moves.reduce(operator.__or__, queries)
74 73
        # do some prefetching
75 74
        qs = qs.prefetch_related('liberty_provider')
......
78 77
            liberty_provider = service_provider.liberty_provider
79 78
            if all_policy:
80 79
                policy = all_policy
81
            elif (service_provider.enable_following_sp_options_policy
82
                    and service_provider.sp_options_policy):
80
            elif service_provider.enable_following_sp_options_policy and service_provider.sp_options_policy:
83 81
                policy = service_provider.sp_options_policy
84 82
            else:
85 83
                policy = default_policy
......
89 87
                protocol = 'saml2'
90 88
                if policy.idp_initiated_sso:
91 89
                    actions.append(
92
                        (
93
                            _('Login'), 'POST',
94
                            '/idp/%s/idp_sso/' % protocol,
95
                            (
96
                                ('provider_id', entity_id),
97
                            )
98
                        )
90
                        (_('Login'), 'POST', '/idp/%s/idp_sso/' % protocol, (('provider_id', entity_id),))
99 91
                    )
100 92
                if policy.accept_slo and entity_id in sessions_eids:
101 93
                    actions.append(
102
                        (
103
                            _('Logout'), 'POST',
104
                            '/idp/%s/idp_slo/' % protocol,
105
                            (
106
                                ('provider_id', entity_id),
107
                            )
108
                        )
94
                        (_('Logout'), 'POST', '/idp/%s/idp_slo/' % protocol, (('provider_id', entity_id),))
109 95
                    )
110 96
                if actions:
111
                    ls.append(
112
                        Service(url=None, name=liberty_provider.name, actions=actions))
97
                    ls.append(Service(url=None, name=liberty_provider.name, actions=actions))
113 98
        return ls
114 99

  
115 100
    def logout_list(self, request):
116
        all_sessions = models.LibertySession.objects.filter(
117
            django_session_key=request.session.session_key)
101
        all_sessions = models.LibertySession.objects.filter(django_session_key=request.session.session_key)
118 102
        self.logger.debug("all_sessions %r" % all_sessions)
119 103
        provider_ids = set([s.provider_id for s in all_sessions])
120 104
        self.logger.debug("provider_ids %r" % provider_ids)
......
137 121
                    url = reverse(saml2_endpoints.idp_slo, args=[provider_id])
138 122
                    # add a nonce so this link is never cached
139 123
                    nonce = hex(random.getrandbits(128))
140
                    url = '{0}?provider_id={1}&nonce={2}'.format(
141
                        url, quote(provider_id), nonce)
124
                    url = '{0}?provider_id={1}&nonce={2}'.format(url, quote(provider_id), nonce)
142 125
                    name = name or provider_id
143
                    code = render_to_string('idp/saml/logout_fragment.html', {
144
                        'needs_iframe': policy.needs_iframe_logout,
145
                        'name': name, 'url': url,
146
                        'iframe_timeout': policy.iframe_logout_timeout})
126
                    code = render_to_string(
127
                        'idp/saml/logout_fragment.html',
128
                        {
129
                            'needs_iframe': policy.needs_iframe_logout,
130
                            'name': name,
131
                            'url': url,
132
                            'iframe_timeout': policy.iframe_logout_timeout,
133
                        },
134
                    )
147 135
                    result.append(code)
148 136
        return result
149 137

  
......
152 140
            return ()
153 141
        user = request.user
154 142
        links = []
155
        qs = models.LibertyFederation.objects.filter(
156
            user=user,
157
            sp__isnull=False)
143
        qs = models.LibertyFederation.objects.filter(user=user, sp__isnull=False)
158 144
        qs = qs.select_related('sp')
159 145
        for federation in qs:
160
            links.append(
161
                (federation.sp.liberty_provider, federation.name_id_content)
162
            )
146
            links.append((federation.sp.liberty_provider, federation.name_id_content))
163 147
        return links
164 148

  
165 149
    def can_synchronous_logout(self, django_sessions_keys):
......
179 163
                'hidden_inputs': {
180 164
                    'next': next_url,
181 165
                },
182
                'buttons': (
183
                    ('delete', _('Delete')),
184
                ),
166
                'buttons': (('delete', _('Delete')),),
185 167
                'url': url,
186 168
            }
187 169
        qs = models.LibertyProvider.objects
188 170
        qs = qs.filter(service_provider__users_can_manage_federations=True)
189 171
        qs = qs.exclude(service_provider__libertyfederation__in=federations)
190
        qs = qs.filter(Q(authorized_roles__isnull=True)
191
                       | Q(authorized_roles__in=request.user.roles_and_parents()))
172
        qs = qs.filter(
173
            Q(authorized_roles__isnull=True) | Q(authorized_roles__in=request.user.roles_and_parents())
174
        )
192 175
        qs = qs.select_related()
193 176
        for liberty_provider in qs:
194 177
            url = reverse('a2-idp-saml2-idp-sso')
......
198 181
                    'provider_id': liberty_provider.entity_id,
199 182
                    'next': next_url,
200 183
                },
201
                'buttons': (
202
                    ('create', _('Create')),
203
                ),
184
                'buttons': (('create', _('Create')),),
204 185
                'url': url,
205 186
            }
src/authentic2/idp/saml/saml2_endpoints.py
46 46
from django.contrib.auth import get_user_model
47 47
from django.contrib.auth.decorators import login_required
48 48
from django.core.exceptions import ObjectDoesNotExist
49
from django.http import HttpResponse, HttpResponseRedirect, \
50
    HttpResponseForbidden, HttpResponseBadRequest
49
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseBadRequest
51 50
from django.utils import six
52 51
from django.utils.translation import ugettext as _, ugettext_noop as N_
53 52
from django.views.decorators.csrf import csrf_exempt
......
65 64

  
66 65
import authentic2.views as a2_views
67 66
from authentic2.saml.models import (
68
    LibertyArtifact, LibertySession, LibertyFederation, nameid2kwargs,
69
    saml2_urn_to_nidformat, nidformat_to_saml2_urn, save_key_values,
70
    get_and_delete_key_values, LibertyProvider, LibertyServiceProvider,
71
    SAMLAttribute, NAME_ID_FORMATS)
72
from authentic2.saml.common import redirect_next, asynchronous_bindings, \
73
    soap_bindings, load_provider, get_saml2_request_message, \
74
    error_page, set_saml2_response_responder_status_code, \
75
    AUTHENTIC_STATUS_CODE_MISSING_DESTINATION, \
76
    load_federation, \
77
    return_saml2_response, \
78
    get_soap_message, soap_fault, return_saml_soap_response, \
79
    AUTHENTIC_STATUS_CODE_UNKNOWN_PROVIDER, \
80
    AUTHENTIC_STATUS_CODE_MISSING_NAMEID, \
81
    AUTHENTIC_STATUS_CODE_MISSING_SESSION_INDEX, \
82
    AUTHENTIC_STATUS_CODE_UNKNOWN_SESSION, \
83
    AUTHENTIC_STATUS_CODE_INTERNAL_SERVER_ERROR, \
84
    AUTHENTIC_STATUS_CODE_UNAUTHORIZED, \
85
    send_soap_request, get_saml2_query_request, \
86
    get_saml2_request_message_async_binding, create_saml2_server, \
87
    get_saml2_metadata, get_sp_options_policy, \
88
    get_entity_id, AUTHENTIC_SAME_ID_SENTINEL
67
    LibertyArtifact,
68
    LibertySession,
69
    LibertyFederation,
70
    nameid2kwargs,
71
    saml2_urn_to_nidformat,
72
    nidformat_to_saml2_urn,
73
    save_key_values,
74
    get_and_delete_key_values,
75
    LibertyProvider,
76
    LibertyServiceProvider,
77
    SAMLAttribute,
78
    NAME_ID_FORMATS,
79
)
80
from authentic2.saml.common import (
81
    redirect_next,
82
    asynchronous_bindings,
83
    soap_bindings,
84
    load_provider,
85
    get_saml2_request_message,
86
    error_page,
87
    set_saml2_response_responder_status_code,
88
    AUTHENTIC_STATUS_CODE_MISSING_DESTINATION,
89
    load_federation,
90
    return_saml2_response,
91
    get_soap_message,
92
    soap_fault,
93
    return_saml_soap_response,
94
    AUTHENTIC_STATUS_CODE_UNKNOWN_PROVIDER,
95
    AUTHENTIC_STATUS_CODE_MISSING_NAMEID,
96
    AUTHENTIC_STATUS_CODE_MISSING_SESSION_INDEX,
97
    AUTHENTIC_STATUS_CODE_UNKNOWN_SESSION,
98
    AUTHENTIC_STATUS_CODE_INTERNAL_SERVER_ERROR,
99
    AUTHENTIC_STATUS_CODE_UNAUTHORIZED,
100
    send_soap_request,
101
    get_saml2_query_request,
102
    get_saml2_request_message_async_binding,
103
    create_saml2_server,
104
    get_saml2_metadata,
105
    get_sp_options_policy,
106
    get_entity_id,
107
    AUTHENTIC_SAME_ID_SENTINEL,
108
)
89 109
import authentic2.saml.saml2utils as saml2utils
90 110
from authentic2.idp.saml.common import kill_django_sessions
91 111
from authentic2.constants import NONCE_FIELD_NAME
......
93 113
from authentic2.idp import signals as idp_signals
94 114

  
95 115
from authentic2.utils import (
96
    make_url, get_backends as get_idp_backends, login_require,
97
    find_authentication_event, datetime_to_xs_datetime)
116
    make_url,
117
    get_backends as get_idp_backends,
118
    login_require,
119
    find_authentication_event,
120
    datetime_to_xs_datetime,
121
)
98 122
from authentic2 import utils
99 123
from authentic2.attributes_ng.engine import get_attributes
100 124
from authentic2 import hooks
......
111 135
    alphabet = string.ascii_letters + string.digits
112 136
    return '_' + ''.join(random.SystemRandom().choice(alphabet) for i in range(20))
113 137

  
138

  
114 139
metadata_map = (
115 140
    (saml2utils.Saml2Metadata.SINGLE_SIGN_ON_SERVICE, asynchronous_bindings, '/sso'),
116 141
    (saml2utils.Saml2Metadata.SINGLE_LOGOUT_SERVICE, asynchronous_bindings, '/slo', '/slo_return'),
117 142
    (saml2utils.Saml2Metadata.SINGLE_LOGOUT_SERVICE, soap_bindings, '/slo/soap'),
118
    (saml2utils.Saml2Metadata.ARTIFACT_RESOLUTION_SERVICE, lasso.SAML2_METADATA_BINDING_SOAP, '/artifact')
143
    (saml2utils.Saml2Metadata.ARTIFACT_RESOLUTION_SERVICE, lasso.SAML2_METADATA_BINDING_SOAP, '/artifact'),
119 144
)
120 145

  
121 146

  
......
127 152

  
128 153

  
129 154
def log_assert(func, exception_classes=(AssertionError,)):
130
    '''Convert assertion errors to warning logs and report them to the user
131
       through the messages framework.
155
    """Convert assertion errors to warning logs and report them to the user
156
    through the messages framework.
157

  
158
    Returns a redirect to homepage or the `next` query parameter.
159
    """
132 160

  
133
       Returns a redirect to homepage or the `next` query parameter.
134
    '''
135 161
    @wraps(func)
136 162
    def f(request, *args, **kwargs):
137 163
        try:
138 164
            return func(request, *args, **kwargs)
139 165
        except exception_classes as e:
140 166
            return error_redirect(request, str(e))
167

  
141 168
    return f
142 169

  
170

  
143 171
#####
144 172
# SSO
145 173
#####
......
150 178
    lib_session = LibertySession(
151 179
        provider_id=login.remoteProviderId,
152 180
        saml2_assertion=login.assertion,
153
        django_session_key=request.session.session_key)
181
        django_session_key=request.session.session_key,
182
    )
154 183
    lib_session.save()
155 184

  
156 185

  
157 186
def fill_assertion(request, saml_request, assertion, provider_id, nid_format):
158
    '''Stuff an assertion with information extracted from the user record
187
    """Stuff an assertion with information extracted from the user record
159 188
       and from the session, and eventually from transactions linked to the
160 189
       request, i.e. a login event or a consent event.
161 190

  
......
170 199
    # to the request id
171 200
    # TODO: use information from the consent event to specialize release of
172 201
    # attributes (user only authorized to give its email for email)
173
       '''
202
    """
174 203
    assert nid_format in NAME_ID_FORMATS
175 204

  
176 205
    logger.debug('initializing assertion %s', assertion.id)
......
220 249
    if attribute_value is None:
221 250
        return None
222 251

  
223
    keys = [
224
        force_bytes(salt),
225
        force_bytes(provider.entity_id),
226
        force_bytes(attribute_value)
227
    ]
252
    keys = [force_bytes(salt), force_bytes(provider.entity_id), force_bytes(attribute_value)]
228 253
    return '_' + hashlib.sha256(b''.join(keys)).hexdigest().upper()
229 254

  
230 255

  
......
259 284
        # sequence from lasso are always tuple, so convert to list
260 285
        attributes[(name, name_format)] = attribute, list(attribute.attributeValue)
261 286
        for atv in attribute.attributeValue:
262
            if atv.any and len(atv.any) == 1 and isinstance(atv.any[0], lasso.MiscTextNode) and \
263
                    atv.any[0].textChild:
287
            if (
288
                atv.any
289
                and len(atv.any) == 1
290
                and isinstance(atv.any[0], lasso.MiscTextNode)
291
                and atv.any[0].textChild
292
            ):
264 293
                seen.add((name, name_format, force_text(atv.any[0].content)))
265 294
    definitions = list(qs)
266 295

  
......
273 302
                    name=edu_name,
274 303
                    name_format='uri',
275 304
                    attribute_name='edupersontargetedid',
276
                    friendly_name='eduPersonTargetedID'))
305
                    friendly_name='eduPersonTargetedID',
306
                )
307
            )
277 308

  
278 309
    for definition in definitions:
279 310
        name = definition.name
......
386 417

  
387 418
def build_assertion(request, login, provider, nid_format='transient'):
388 419
    """After a successfully validated authentication request, build an
389
       authentication assertion
420
    authentication assertion
390 421
    """
391 422
    entity_id = get_entity_id(request)
392 423
    now = datetime.datetime.utcnow()
......
414 445
            if how == 'password':
415 446
                authn_context = lasso.SAML2_AUTHN_CONTEXT_PASSWORD
416 447
            elif how == 'password-on-https':
417
                authn_context = \
418
                    lasso.SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT
448
                authn_context = lasso.SAML2_AUTHN_CONTEXT_PASSWORD_PROTECTED_TRANSPORT
419 449
            elif how.startswith('oath-totp'):
420 450
                authn_context = lasso.SAML2_AUTHN_CONTEXT_TIME_SYNC_TOKEN
421 451
            else:
......
429 459
        now.isoformat() + 'Z',
430 460
        'unused',  # reauthenticateOnOrAfter is only for ID-FF 1.2
431 461
        notBefore.isoformat() + 'Z',
432
        notOnOrAfter.isoformat() + 'Z')
462
        notOnOrAfter.isoformat() + 'Z',
463
    )
433 464
    assertion = login.assertion
434 465
    assertion.conditions.notOnOrAfter = notOnOrAfter.isoformat() + 'Z'
435 466
    # Set SessionNotOnOrAfter to expiry date of the current session, so we are sure no session on
......
449 480
        if kwargs.get('name_id_sp_name_qualifier') == login.remoteProviderId:
450 481
            kwargs['name_id_sp_name_qualifier'] = AUTHENTIC_SAME_ID_SENTINEL
451 482
        service_provider = LibertyServiceProvider.objects.get(
452
            liberty_provider__entity_id=login.remoteProviderId)
483
            liberty_provider__entity_id=login.remoteProviderId
484
        )
453 485
        federation, new = LibertyFederation.objects.get_or_create(
454
            sp=service_provider,
455
            user=request.user,
456
            **kwargs)
486
            sp=service_provider, user=request.user, **kwargs
487
        )
457 488
        if new:
458 489
            logger.debug('nameID persistent, new federation')
459 490
            federation.save()
......
465 496
        kwargs = nameid2kwargs(login.assertion.subject.nameID)
466 497
    kwargs['entity_id'] = login.remoteProviderId
467 498
    kwargs['user'] = request.user
468
    logger.debug(u'sending nameID %(name_id_format)r: %(name_id_content)r to '
469
                 u'%(entity_id)s for user %(user)s' % kwargs)
499
    logger.debug(
500
        u'sending nameID %(name_id_format)r: %(name_id_content)r to '
501
        u'%(entity_id)s for user %(user)s' % kwargs
502
    )
470 503
    register_new_saml2_session(request, login)
471 504
    add_attributes(request, entity_id, assertion, provider, nid_format)
472 505
    return kwargs['name_id_content']
......
477 510
@log_assert
478 511
def sso(request):
479 512
    """Endpoint for receiving saml2:AuthnRequests by POST, Redirect or SOAP.
480
       For SOAP a session must be established previously through the login
481
       page. No authentication through the SOAP request is supported.
513
    For SOAP a session must be established previously through the login
514
    page. No authentication through the SOAP request is supported.
482 515
    """
483 516
    if request.method == "GET":
484 517
        logger.debug('called by GET')
......
490 523
    message = get_saml2_request_message(request, login)
491 524
    # 1. Process the request, separate POST and GET treatment
492 525
    if not message:
493
        return HttpResponseForbidden('A SAMLv2 Single Sign On request need a query string',
494
                                     content_type='text/plain')
526
        return HttpResponseForbidden(
527
            'A SAMLv2 Single Sign On request need a query string', content_type='text/plain'
528
        )
495 529
    logger.debug('processing sso request %r', message)
496 530
    policy = None
497 531
    signed = True
......
504 538
            break
505 539
        except (lasso.ProfileInvalidMsgError, lasso.ProfileMissingIssuerError) as e:
506 540
            logger.warning(
507
                'invalid message for WebSSO profile with HTTP-Redirect binding: '
508
                '%r exception: %s', message, e, extra={'request': request})
541
                'invalid message for WebSSO profile with HTTP-Redirect binding: ' '%r exception: %s',
542
                message,
543
                e,
544
                extra={'request': request},
545
            )
509 546
            return HttpResponseBadRequest(
510
                _("SAMLv2 Single Sign On: invalid message for WebSSO profile with HTTP-Redirect "
511
                  "binding: %r") % message, content_type='text/plain')
547
                _(
548
                    "SAMLv2 Single Sign On: invalid message for WebSSO profile with HTTP-Redirect "
549
                    "binding: %r"
550
                )
551
                % message,
552
                content_type='text/plain',
553
            )
512 554
        except lasso.ProfileInvalidProtocolprofileError:
513 555
            log_info_authn_request_details(login)
514 556
            message = _(
515 557
                "SAMLv2 Single Sign On: the request cannot be "
516
                "answered because no valid protocol binding could be found")
517
            logger.warning(
518
                'the request cannot be answered because no valid protocol binding could be found')
558
                "answered because no valid protocol binding could be found"
559
            )
560
            logger.warning('the request cannot be answered because no valid protocol binding could be found')
519 561
            return HttpResponseBadRequest(message, content_type='text/plain')
520 562
        except lasso.ProviderMissingPublicKeyError as e:
521 563
            log_info_authn_request_details(login)
......
527 569
            logger.warning('digital signature treatment error: %s', e)
528 570
            login.response.status.statusMessage = 'Signature validation failed'
529 571
            return return_login_response(request, login)
530
        except (lasso.ServerProviderNotFoundError,
531
                lasso.ProfileUnknownProviderError):
572
        except (lasso.ServerProviderNotFoundError, lasso.ProfileUnknownProviderError):
532 573
            logger.debug('processAuthnRequestMsg not successful')
533 574
            log_info_authn_request_details(login)
534 575
            provider_id = login.remoteProviderId
......
543 584
                    {
544 585
                        'entity_id': provider_id,
545 586
                        'add_url': add_url,
546
                    })
587
                    },
588
                )
547 589
            else:
548 590
                policy = get_sp_options_policy(provider_loaded)
549 591
                if not policy:
550
                    return error_page(
551
                        request,
552
                        _('sso: No SP policy defined'),
553
                        logger=logger, warning=True)
592
                    return error_page(request, _('sso: No SP policy defined'), logger=logger, warning=True)
554 593
                logger.debug('provider %s loaded with success', provider_id)
555 594
            if policy.authn_request_signed:
556 595
                verify_hint = lasso.PROFILE_SIGNATURE_VERIFY_HINT_FORCE
......
561 600

  
562 601
    if signed and not check_destination(request, login.request):
563 602
        logger.warning('wrong or absent destination')
564
        return return_login_error(
565
            request, login,
566
            AUTHENTIC_STATUS_CODE_MISSING_DESTINATION)
603
        return return_login_error(request, login, AUTHENTIC_STATUS_CODE_MISSING_DESTINATION)
567 604
    # Check NameIDPolicy or force the NameIDPolicy
568 605
    name_id_policy = login.request.nameIdPolicy
569
    if (name_id_policy
570
            and name_id_policy.format
571
            and name_id_policy.format != lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED):
606
    if (
607
        name_id_policy
608
        and name_id_policy.format
609
        and name_id_policy.format != lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED
610
    ):
572 611
        logger.debug('nameID policy is %s', force_text(name_id_policy.dump()))
573 612
        nid_format = saml2_urn_to_nidformat(name_id_policy.format, accepted=policy.accepted_name_id_format)
574 613
        logger.debug('nameID format %s', nid_format)
......
576 615
        logger.debug('default nameID format %s', default_nid_format)
577 616
        accepted_nid_format = policy.accepted_name_id_format
578 617
        logger.debug('nameID format accepted %s', accepted_nid_format)
579
        if (not nid_format or nid_format not in accepted_nid_format) and \
580
           default_nid_format != nid_format:
581
            set_saml2_response_responder_status_code(login.response, lasso.SAML2_STATUS_CODE_INVALID_NAME_ID_POLICY)
618
        if (not nid_format or nid_format not in accepted_nid_format) and default_nid_format != nid_format:
619
            set_saml2_response_responder_status_code(
620
                login.response, lasso.SAML2_STATUS_CODE_INVALID_NAME_ID_POLICY
621
            )
582 622
            logger.warning('NameID format required is not accepted')
583 623
            return finish_sso(request, login)
584 624
    else:
......
595 635

  
596 636
def need_login(request, login, nid_format, service):
597 637
    """Redirect to the login page with a nonce parameter to verify later that
598
       the login form was submitted
638
    the login form was submitted
599 639
    """
600 640
    nonce = login.request.id or get_nonce()
601 641
    save_key_values(nonce, force_text(login.dump()), False, nid_format)
602 642
    next_url = make_url(continue_sso, params={NONCE_FIELD_NAME: nonce})
603 643
    logger.debug('redirect to login page with next url %s', next_url)
604
    return login_require(request, next_url=next_url, params={NONCE_FIELD_NAME: nonce},
605
                         service=service, login_hint=get_login_hints_extension(login))
644
    return login_require(
645
        request,
646
        next_url=next_url,
647
        params={NONCE_FIELD_NAME: nonce},
648
        service=service,
649
        login_hint=get_login_hints_extension(login),
650
    )
606 651

  
607 652

  
608 653
def get_url_with_nonce(request, function, nonce):
......
615 660
    save_key_values(nonce, force_text(login.dump()), False, nid_format)
616 661
    display_name = None
617 662
    try:
618
        provider = \
619
            LibertyProvider.objects.get(entity_id=login.request.issuer.content)
663
        provider = LibertyProvider.objects.get(entity_id=login.request.issuer.content)
620 664
        display_name = provider.name
621 665
    except ObjectDoesNotExist:
622 666
        pass
623 667
    if not display_name:
624 668
        display_name = quote(login.request.issuer.content)
625
    url = '%s?%s=%s&next=%s&provider_id=%s' \
626
        % (reverse('a2-consent-federation'), NONCE_FIELD_NAME,
627
            nonce, get_url_with_nonce(request, continue_sso, nonce),
628
            display_name)
669
    url = '%s?%s=%s&next=%s&provider_id=%s' % (
670
        reverse('a2-consent-federation'),
671
        NONCE_FIELD_NAME,
672
        nonce,
673
        get_url_with_nonce(request, continue_sso, nonce),
674
        display_name,
675
    )
629 676
    logger.debug('redirect to url %s', url)
630 677
    return HttpResponseRedirect(url)
631 678

  
......
639 686
        consent_answer = request.GET.get('consent_answer', '')
640 687
        if consent_answer:
641 688
            logger.debug('back from the consent page for federation with answer %s', consent_answer)
642
        consent_attribute_answer = \
643
            request.GET.get('consent_attribute_answer', '')
689
        consent_attribute_answer = request.GET.get('consent_attribute_answer', '')
644 690
        if consent_attribute_answer:
645 691
            logger.debug(u'back from the consent page for attributes %s', consent_attribute_answer)
646 692
    nonce = request.GET.get(NONCE_FIELD_NAME, '')
......
660 706
    if not login:
661 707
        return error_page(request, _('continue_sso: error loading login'), logger=logger)
662 708
    if not load_provider(request, login.remoteProviderId, server=login.server, autoload=True):
663
        return error_page(request, _('continue_sso: unknown provider %s') % login.remoteProviderId, logger=logger)
709
        return error_page(
710
            request, _('continue_sso: unknown provider %s') % login.remoteProviderId, logger=logger
711
        )
664 712
    if 'cancel' in request.GET:
665 713
        logger.debug('login canceled')
666 714
        set_saml2_response_responder_status_code(login.response, lasso.SAML2_STATUS_CODE_REQUEST_DENIED)
......
677 725
        login,
678 726
        consent_obtained=consent_obtained,
679 727
        consent_attribute_answer=consent_attribute_answer,
680
        nid_format=nid_format)
728
        nid_format=nid_format,
729
    )
681 730

  
682 731

  
683 732
def needs_persistence(nid_format):
......
691 740
    element = ctree.fromstring(xml_string)
692 741
    return list(element)
693 742

  
743

  
694 744
EO_NS = 'https://www.entrouvert.com/'
695 745
LOGIN_HINT = '{%s}login-hint' % EO_NS
696 746

  
......
708 758
    return hints
709 759

  
710 760

  
711
def sso_after_process_request(request, login, consent_obtained=False,
712
                              consent_attribute_answer=False, user=None,
713
                              nid_format='transient', return_profile=False):
761
def sso_after_process_request(
762
    request,
763
    login,
764
    consent_obtained=False,
765
    consent_attribute_answer=False,
766
    user=None,
767
    nid_format='transient',
768
    return_profile=False,
769
):
714 770
    """Common path for sso and idp_initiated_sso.
715 771

  
716
       consent_obtained: whether the user has given his consent to this
717
       federation
718
       user: the user which must be federated, if None, current user is the
719
       default.
772
    consent_obtained: whether the user has given his consent to this
773
    federation
774
    user: the user which must be federated, if None, current user is the
775
    default.
720 776
    """
721 777
    nonce = login.request.id
722 778
    user = user or request.user
......
729 785

  
730 786
    # check if user is authorized through this service
731 787
    service = LibertyServiceProvider.objects.get(
732
        liberty_provider__entity_id=login.remoteProviderId).liberty_provider
788
        liberty_provider__entity_id=login.remoteProviderId
789
    ).liberty_provider
733 790

  
734
    if not passive and \
735
            (user.is_anonymous or (force_authn and not did_auth)):
791
    if not passive and (user.is_anonymous or (force_authn and not did_auth)):
736 792
        logger.debug('login required')
737 793
        return need_login(request, login, nid_format, service)
738 794

  
......
752 808
        transient = True
753 809

  
754 810
    decisions = idp_signals.authorize_service.send(
755
        sender=None,
756
        request=request, user=request.user,
757
        audience=login.remoteProviderId,
758
        attributes={})
811
        sender=None, request=request, user=request.user, audience=login.remoteProviderId, attributes={}
812
    )
759 813
    logger.debug('signal authorize_service sent')
760 814

  
761 815
    # You don't dream. By default, access granted.
......
777 831
    if not access_granted:
778 832
        logger.debug('access denied, return answer to the requester')
779 833
        set_saml2_response_responder_status_code(
780
            login.response,
781
            lasso.SAML2_STATUS_CODE_REQUEST_DENIED,
782
            msg=six.text_type(dic['message']))
834
            login.response, lasso.SAML2_STATUS_CODE_REQUEST_DENIED, msg=six.text_type(dic['message'])
835
        )
783 836
        return finish_sso(request, login)
784 837

  
785 838
    provider = load_provider(request, login.remoteProviderId, server=login.server)
786 839
    if not provider:
787
        return error_page(
788
            request,
789
            _('Provider %s is unknown') % login.remoteProviderId,
790
            logger=logger)
840
        return error_page(request, _('Provider %s is unknown') % login.remoteProviderId, logger=logger)
791 841
    saml_policy = get_sp_options_policy(provider)
792 842
    if not saml_policy:
793 843
        return error_page(request, _('No service provider policy defined'), logger=logger)
......
850 900
    if needs_persistence(nid_format):
851 901
        try:
852 902
            LibertyFederation.objects.get(
853
                user=request.user,
854
                sp__liberty_provider__entity_id=login.remoteProviderId)
903
                user=request.user, sp__liberty_provider__entity_id=login.remoteProviderId
904
            )
855 905
            logger.debug('consent already given (existing federation) for %s', login.remoteProviderId)
856 906
            consent_obtained = True
857 907
            '''This is abusive since a federation may exist even if we have
......
862 912

  
863 913
    if not consent_obtained and not transient:
864 914
        logger.debug('signal avoid_consent sent')
865
        avoid_consent = idp_signals.avoid_consent.send(sender=None,
866
                                                       request=request,
867
                                                       user=request.user,
868
                                                       audience=login.remoteProviderId)
915
        avoid_consent = idp_signals.avoid_consent.send(
916
            sender=None, request=request, user=request.user, audience=login.remoteProviderId
917
        )
869 918
        for c in avoid_consent:
870 919
            logger.debug('avoid_consent connected to function %s', c[0].__name__)
871 920
            if c[1] and 'avoid_consent' in c[1] and c[1]['avoid_consent']:
872 921
                logger.debug('avoid consent by signal')
873 922
                consent_obtained = True
874 923
                # The user consent is bypassed by the signal
875
                consent_value = \
876
                    'urn:oasis:names:tc:SAML:2.0:consent:unspecified'
924
                consent_value = 'urn:oasis:names:tc:SAML:2.0:consent:unspecified'
877 925

  
878 926
    if not consent_obtained and not transient:
879 927
        logger.debug('ask the user consent now')
......
939 987
def save_artifact(request, login):
940 988
    '''Remember an artifact message for later retrieving'''
941 989
    LibertyArtifact(
942
        artifact=login.artifact,
943
        content=force_text(login.artifactMessage),
944
        provider_id=login.remoteProviderId).save()
990
        artifact=login.artifact, content=force_text(login.artifactMessage), provider_id=login.remoteProviderId
991
    ).save()
945 992
    logger.debug('artifact saved')
946 993

  
947 994

  
......
960 1007
@never_cache
961 1008
@csrf_exempt
962 1009
def artifact(request):
963
    '''Resolve a SAMLv2 ArtifactResolve request
964
    '''
1010
    """Resolve a SAMLv2 ArtifactResolve request"""
965 1011
    soap_message = get_soap_message(request)
966 1012
    server = create_server(request)
967 1013
    login = lasso.Login(server)
......
984 1030
        logger.debug('resolve response %r', login.msgBody)
985 1031
    except Exception:
986 1032
        logger.exception('resolve error')
987
        return soap_fault(
988
            request,
989
            faultcode='soap:Server',
990
            faultstring='Internal Server Error')
1033
        return soap_fault(request, faultcode='soap:Server', faultstring='Internal Server Error')
991 1034
    logger.debug('treatment ended, return answer')
992 1035
    return return_saml_soap_response(login)
993 1036

  
......
1001 1044
@csrf_exempt
1002 1045
@login_required
1003 1046
def idp_sso(request, provider_id=None, return_profile=False):
1004
    '''Initiate an SSO toward provider_id without a prior AuthnRequest
1005
    '''
1047
    """Initiate an SSO toward provider_id without a prior AuthnRequest"""
1006 1048
    if not provider_id:
1007 1049
        provider_id = request.POST.get('provider_id')
1008 1050
    if not provider_id:
......
1019 1061
            return error_redirect(
1020 1062
                request,
1021 1063
                N_('%r tried to log as %r on %r but was forbidden'),
1022
                request.user, username, provider_id)
1064
                request.user,
1065
                username,
1066
                provider_id,
1067
            )
1023 1068
        try:
1024 1069
            user = User.objects.get_by_natural_key(username=username)
1025 1070
        except User.DoesNotExist:
......
1053 1098
    logger.debug('binding %r', binding)
1054 1099
    logger.debug('authentication request initialized toward provider_id %r', provider_id)
1055 1100

  
1056
    return sso_after_process_request(request, login, consent_obtained=False,
1057
                                     user=user, nid_format=nid_format,
1058
                                     return_profile=return_profile)
1101
    return sso_after_process_request(
1102
        request,
1103
        login,
1104
        consent_obtained=False,
1105
        user=user,
1106
        nid_format=nid_format,
1107
        return_profile=return_profile,
1108
    )
1059 1109

  
1060 1110

  
1061 1111
@never_cache
......
1072 1122
    logout = lasso.Logout.newFromDump(server, force_str(logout_dump))
1073 1123
    load_provider(request, logout.remoteProviderId, server=logout.server)
1074 1124
    # Clean all session
1075
    all_sessions = \
1076
        LibertySession.objects.filter(django_session_key=session_key)
1125
    all_sessions = LibertySession.objects.filter(django_session_key=session_key)
1077 1126
    try:
1078 1127
        logout.buildResponseMsg()
1079 1128
    except lasso.ProfileUnknownProfileUrlError:
......
1085 1134
        return redirect('auth_homepage')
1086 1135
    if all_sessions.exists():
1087 1136
        all_sessions.delete()
1088
        set_saml2_response_responder_status_code(logout.response,
1089
                                                 lasso.SAML2_STATUS_CODE_PARTIAL_LOGOUT)
1137
        set_saml2_response_responder_status_code(logout.response, lasso.SAML2_STATUS_CODE_PARTIAL_LOGOUT)
1090 1138
        logger.warning('partial logout')
1091 1139
        logout.buildResponseMsg()
1092 1140
    provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
......
1114 1162
    try:
1115 1163
        try:
1116 1164
            logout.processRequestMsg(force_str(message))
1117
        except (lasso.ServerProviderNotFoundError,
1118
                lasso.ProfileUnknownProviderError):
1165
        except (lasso.ServerProviderNotFoundError, lasso.ProfileUnknownProviderError):
1119 1166
            logger.debug('loading provider %s', logout.remoteProviderId)
1120 1167
            p = load_provider(request, logout.remoteProviderId, server=logout.server)
1121 1168
            if not p:
......
1169 1216

  
1170 1217
def get_only_last_session(issuer_id, provider_id, name_id, session_indexes):
1171 1218
    """Try to have a decent behaviour when receiving a logout request with
1172
       multiple session indexes.
1219
    multiple session indexes.
1173 1220

  
1174
       Enumerate all emitted assertions for the given session, and for each
1175
       provider only keep the more recent one.
1221
    Enumerate all emitted assertions for the given session, and for each
1222
    provider only keep the more recent one.
1176 1223
    """
1177
    lib_session1 = LibertySession.get_for_nameid_and_session_indexes(issuer_id, provider_id, name_id, session_indexes)
1224
    lib_session1 = LibertySession.get_for_nameid_and_session_indexes(
1225
        issuer_id, provider_id, name_id, session_indexes
1226
    )
1178 1227
    django_session_keys = [s.django_session_key for s in lib_session1]
1179 1228
    lib_session = LibertySession.objects.filter(django_session_key__in=django_session_keys)
1180 1229
    providers = set([s.provider_id for s in lib_session])
......
1190 1239

  
1191 1240

  
1192 1241
def build_session_dump(liberty_sessions):
1193
    '''Build a session dump from a list of pairs
1194
       (provider_id,assertion_content)'''
1242
    """Build a session dump from a list of pairs
1243
    (provider_id,assertion_content)"""
1195 1244
    session = [
1196 1245
        u'<Session xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"'
1197 1246
        u' xmlns="http://www.entrouvert.org/namespaces/lasso/0.0" Version="2">',
1198 1247
    ]
1199 1248
    for liberty_session in liberty_sessions:
1200
        session.append(u'<NidAndSessionIndex ProviderID="{0.provider_id}" '
1201
                       u'AssertionID="xxx" '
1202
                       u'SessionIndex="{0.session_index}">'.format(liberty_session))
1249
        session.append(
1250
            u'<NidAndSessionIndex ProviderID="{0.provider_id}" '
1251
            u'AssertionID="xxx" '
1252
            u'SessionIndex="{0.session_index}">'.format(liberty_session)
1253
        )
1203 1254
        session.append(u'<saml:NameID Format="{0.name_id_format}" '.format(liberty_session))
1204 1255
        if liberty_session.name_id_qualifier:
1205 1256
            session.append(u'NameQualifier="{0.name_id_qualifier}" '.format(liberty_session))
......
1214 1265

  
1215 1266

  
1216 1267
def set_session_dump_from_liberty_sessions(profile, lib_sessions):
1217
    '''Extract all assertion from a list of lib_sessions, and create a session
1218
    dump from them'''
1268
    """Extract all assertion from a list of lib_sessions, and create a session
1269
    dump from them"""
1219 1270
    session_dump = build_session_dump(lib_sessions)
1220 1271
    profile.setSessionFromDump(session_dump)
1221 1272

  
......
1237 1288
        return error
1238 1289

  
1239 1290
    try:
1240
        provider = \
1241
            LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1291
        provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1242 1292
    except ObjectDoesNotExist:
1243 1293
        logger.warning('provider %r unknown', logout.remoteProviderId)
1244 1294
        return return_logout_error(request, logout, AUTHENTIC_STATUS_CODE_UNAUTHORIZED)
......
1255 1305
        logout.server.providerId,
1256 1306
        logout.remoteProviderId,
1257 1307
        logout.request.nameId,
1258
        logout.request.sessionIndexes)
1308
        logout.request.sessionIndexes,
1309
    )
1259 1310
    if not found:
1260 1311
        logger.debug('no third SP session found')
1261 1312
    else:
......
1278 1329
        try:
1279 1330
            logout.validateRequest()
1280 1331
        except lasso.LogoutUnsupportedProfileError:
1281
            '''
1282
                If one provider does not support SLO by SOAP,
1283
                continue with others!
1284
            '''
1285
            logger.warning('one provider does not support SOAP among %s', [s.provider_id for s in lib_sessions])
1332
            """
1333
            If one provider does not support SLO by SOAP,
1334
            continue with others!
1335
            """
1336
            logger.warning(
1337
                'one provider does not support SOAP among %s', [s.provider_id for s in lib_sessions]
1338
            )
1286 1339
        except Exception as e:
1287 1340
            logger.warning('slo, unknown error %s', e)
1288 1341
            logout.buildResponseMsg()
1289 1342
            provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1290
            return return_saml2_response(request, logout, title=_('You are being redirected to "%s"') % provider.name)
1343
            return return_saml2_response(
1344
                request, logout, title=_('You are being redirected to "%s"') % provider.name
1345
            )
1291 1346
        for lib_session in lib_sessions:
1292 1347
            try:
1293 1348
                logger.debug('slo, relaying logout to provider %s', lib_session.provider_id)
......
1323 1378
@never_cache
1324 1379
@csrf_exempt
1325 1380
def slo(request):
1326
    """Endpoint for receiving SLO by POST, Redirect.
1327
    """
1381
    """Endpoint for receiving SLO by POST, Redirect."""
1328 1382
    message = get_saml2_request_message_async_binding(request)
1329 1383
    logout, response = process_logout_request(request, message, request.method)
1330 1384
    if response:
1331 1385
        return response
1332 1386

  
1333 1387
    try:
1334
        provider = \
1335
            LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1388
        provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1336 1389
    except ObjectDoesNotExist:
1337 1390
        logger.debug('provider %r unknown', logout.remoteProviderId)
1338 1391
        return return_logout_error(request, logout, AUTHENTIC_STATUS_CODE_UNAUTHORIZED)
......
1354 1407
        logger.warning('signature error %s', e)
1355 1408
        logout.buildResponseMsg()
1356 1409
        provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1357
        return return_saml2_response(request, logout, title=_('You are being redirected to "%s"') % provider.name)
1410
        return return_saml2_response(
1411
            request, logout, title=_('You are being redirected to "%s"') % provider.name
1412
        )
1358 1413
    except (lasso.ProfileInvalidMsgError, lasso.ProfileMissingIssuerError):
1359 1414
        return error_page(request, _('Invalid logout request'), logger=logger, warning=True)
1360 1415
    session_indexes = logout.request.sessionIndexes
1361 1416
    if len(session_indexes) == 0:
1362
        logger.warning('slo received a request from %s without any SessionIndex, it is forbidden',
1363
                       logout.remoteProviderId)
1417
        logger.warning(
1418
            'slo received a request from %s without any SessionIndex, it is forbidden',
1419
            logout.remoteProviderId,
1420
        )
1364 1421
        logout.buildResponseMsg()
1365 1422
        provider = LibertyProvider.objects.get(entity_id=logout.remoteProviderId)
1366
        return return_saml2_response(request, logout, title=_('You are being redirected to "%s"') % provider.name)
1423
        return return_saml2_response(
1424
            request, logout, title=_('You are being redirected to "%s"') % provider.name
1425
        )
1367 1426
    logger.debug('asynchronous slo from %s', logout.remoteProviderId)
1368 1427
    # Filter sessions
1369 1428
    if not logout.request.nameId:
......
1373 1432
        logout.server.providerId,
1374 1433
        logout.remoteProviderId,
1375 1434
        logout.request.nameId,
1376
        logout.request.sessionIndexes)
1435
        logout.request.sessionIndexes,
1436
    )
1377 1437
    if not all_sessions.exists():
1378 1438
        logger.warning('slo refused, since no session exists with the requesting provider')
1379 1439
        return return_logout_error(request, logout, AUTHENTIC_STATUS_CODE_UNKNOWN_SESSION)
......
1390 1450
        return return_logout_error(request, logout, AUTHENTIC_STATUS_CODE_INTERNAL_SERVER_ERROR)
1391 1451
    # Now clean sessions for this provider
1392 1452
    LibertySession.objects.filter(
1393
        provider_id=logout.remoteProviderId,
1394
        django_session_key=request.session.session_key).delete()
1453
        provider_id=logout.remoteProviderId, django_session_key=request.session.session_key
1454
    ).delete()
1395 1455
    # Save some values for cleaning up
1396 1456
    save_key_values(logout.request.id, force_text(logout.dump()), request.session.session_key)
1397 1457

  
......
1451 1511
        return redirect_next(request, next) or ko_icon(request)
1452 1512

  
1453 1513
    lib_sessions = LibertySession.objects.filter(
1454
        django_session_key=request.session.session_key,
1455
        provider_id=provider_id)
1514
        django_session_key=request.session.session_key, provider_id=provider_id
1515
    )
1456 1516
    if lib_sessions:
1457 1517
        logger.debug('%d lib_sessions found', lib_sessions.count())
1458 1518
        set_session_dump_from_liberty_sessions(logout, [lib_sessions[0]])
1459 1519
    try:
1460 1520
        logout.initRequest(provider_id, policy.http_method_for_slo_request)
1461
    except (lasso.ProfileMissingAssertionError,
1462
            lasso.ProfileSessionNotFoundError):
1521
    except (lasso.ProfileMissingAssertionError, lasso.ProfileSessionNotFoundError):
1463 1522
        logger.debug('slo failed because no sessions exists for %r', provider_id)
1464 1523
        return redirect_next(request, next) or ko_icon(request)
1465 1524
    if all is not None:
......
1496 1555
        logger.warning('slo error: %s', e)
1497 1556
    else:
1498 1557
        LibertySession.objects.filter(
1499
            django_session_key=request.session.session_key,
1500
            provider_id=logout.remoteProviderId).delete()
1558
            django_session_key=request.session.session_key, provider_id=logout.remoteProviderId
1559
        ).delete()
1501 1560
        logger.debug('deleted session to %s', logout.remoteProviderId)
1502 1561
    return redirect_next(request, next) or ok_icon(request)
1503 1562

  
......
1509 1568
        return error_redirect(request, N_('slo no relay state in response'), default_url=icon_url('ko'))
1510 1569
    logger.debug('relay_state %r', relay_state)
1511 1570
    try:
1512
        logout_dump, provider_id, next = \
1513
            get_and_delete_key_values(relay_state)
1571
        logout_dump, provider_id, next = get_and_delete_key_values(relay_state)
1514 1572
    except KeyError:
1515 1573
        return error_redirect(request, N_('unknown relay state %r'), relay_state, default_url=icon_url('ko'))
1516 1574
    server = create_server(request)
......
1526 1584
        logger.warning('failed to load provider %s', provider_id)
1527 1585
    return process_logout_response(request, logout, get_saml2_query_request(request), next)
1528 1586

  
1587

  
1529 1588
# Helpers
1530 1589

  
1531 1590
# Mapping to generate the metadata file, must be kept in sync with the url
......
1542 1601

  
1543 1602

  
1544 1603
def create_server(request, provider_id=None):
1545
    '''Build a lasso.Server object using current settings for the IdP
1604
    """Build a lasso.Server object using current settings for the IdP
1546 1605

  
1547 1606
    The built lasso.Server is cached for later use it should work until
1548 1607
    multithreading is used, then thread local storage should be used.
1549
    '''
1608
    """
1550 1609
    options = get_options()
1551 1610
    __cached_server = create_saml2_server(request, idp_map=metadata_map, options=options)
1552 1611
    return __cached_server
......
1581 1640

  
1582 1641

  
1583 1642
def error_redirect(request, msg, *args, **kwargs):
1584
    '''Log a warning message, register it with the messages framework, then
1585
       redirect the user to the homepage.
1643
    """Log a warning message, register it with the messages framework, then
1644
    redirect the user to the homepage.
1586 1645

  
1587
       It will redirect to Authentic2 homepage unless a next query parameter was used.
1588
    '''
1646
    It will redirect to Authentic2 homepage unless a next query parameter was used.
1647
    """
1589 1648
    default_kwargs = {
1590 1649
        'log_level': logging.WARNING,
1591 1650
        'msg_level': messages.WARNING,
src/authentic2/idp/saml/urls.py
17 17
from django.conf.urls import url
18 18

  
19 19
from . import views
20
from authentic2.idp.saml.saml2_endpoints import (metadata, sso, continue_sso,
21
                                                 slo, slo_soap, idp_slo,
22
                                                 slo_return, finish_slo,
23
                                                 artifact, idp_sso)
20
from authentic2.idp.saml.saml2_endpoints import (
21
    metadata,
22
    sso,
23
    continue_sso,
24
    slo,
25
    slo_soap,
26
    idp_slo,
27
    slo_return,
28
    finish_slo,
29
    artifact,
30
    idp_sso,
31
)
24 32

  
25 33
urlpatterns = [
26 34
    url(r'^metadata$', metadata, name='a2-idp-saml-metadata'),
......
33 41
    url(r'^finish_slo$', finish_slo, name='a2-idp-saml-finish-slo'),
34 42
    url(r'^artifact$', artifact, name='a2-idp-saml-artifact'),
35 43
    # legacy endpoint, now it's prefered to pass the entity_id in a parameter
36
    url(r'^idp_sso/(.+)$',
37
        idp_sso, name='a2-idp-saml-idp-sso-named'),
38
    url(r'^idp_sso/$',
39
        idp_sso,
40
        name='a2-idp-saml2-idp-sso'),
41
    url(r'^federations/create/(?P<pk>\d+)/$',
42
        views.create_federation,
43
        name='a2-idp-saml2-federation-create'),
44
    url(r'^federations/(?P<pk>\d+)/delete/$',
45
        views.delete_federation,
46
        name='a2-idp-saml2-federation-delete'),
44
    url(r'^idp_sso/(.+)$', idp_sso, name='a2-idp-saml-idp-sso-named'),
45
    url(r'^idp_sso/$', idp_sso, name='a2-idp-saml2-idp-sso'),
46
    url(r'^federations/create/(?P<pk>\d+)/$', views.create_federation, name='a2-idp-saml2-federation-create'),
47
    url(r'^federations/(?P<pk>\d+)/delete/$', views.delete_federation, name='a2-idp-saml2-federation-delete'),
47 48
]
src/authentic2/idp/saml/views.py
41 41
        self.object = self.get_object()
42 42
        self.object.user = None
43 43
        self.object.save()
44
        messages.info(request, _('Federation to {0} deleted').format(
45
            self.object.sp.liberty_provider.name))
44
        messages.info(request, _('Federation to {0} deleted').format(self.object.sp.liberty_provider.name))
46 45
        return HttpResponseRedirect(self.get_success_url())
47 46

  
48 47
    def get_success_url(self):
src/authentic2/idp/urls.py
18 18
from authentic2.idp.interactions import consent_federation
19 19

  
20 20
urlpatterns = [
21
    url(r'^consent_federation', consent_federation,
22
        name='a2-consent-federation'),
21
    url(r'^consent_federation', consent_federation, name='a2-consent-federation'),
23 22
]
src/authentic2/journal_event_types.py
53 53
        super().record(user=user, session=session, service=service, data={'how': how})
54 54

  
55 55
    @classmethod
56
    def get_method_statistics(cls, group_by_time, service=None, services_ou=None, users_ou=None, start=None, end=None):
56
    def get_method_statistics(
57
        cls, group_by_time, service=None, services_ou=None, users_ou=None, start=None, end=None
58
    ):
57 59
        if services_ou and not service:
58 60
            service = Service.objects.filter(ou=services_ou)
59 61

  
......
63 65
            which_references=service,
64 66
            users_ou=users_ou,
65 67
            start=start,
66
            end=end
68
            end=end,
67 69
        )
68 70
        stats = Statistics(qs, time_interval=group_by_time)
69 71

  
src/authentic2/log_filters.py
24 24
    DEFAULT_REQUEST_ID = '-'
25 25

  
26 26
    def filter(self, record):
27
        '''Add username, ip and request ID to the log record.
27
        """Add username, ip and request ID to the log record.
28 28

  
29
           Inspired by django-log-request-id
30
        '''
29
        Inspired by django-log-request-id
30
        """
31 31
        from . import middleware
32

  
32 33
        request = record.request = getattr(record, 'request', middleware.StoreRequestMiddleware.get_request())
33 34
        if not hasattr(request, 'META'):
34 35
            record.request = None
src/authentic2/logger.py
19 19

  
20 20
class SettingsLogLevel(int):
21 21
    def __new__(cls, default_log_level, debug_setting='DEBUG'):
22
        return super(SettingsLogLevel, cls).__new__(
23
            cls, getattr(logging, default_log_level))
22
        return super(SettingsLogLevel, cls).__new__(cls, getattr(logging, default_log_level))
24 23

  
25 24
    def __init__(self, default_log_level, debug_setting='DEBUG'):
26 25
        self.debug_setting = debug_setting
......
32 31
        level = super(DjangoLogger, self).getEffectiveLevel()
33 32
        if isinstance(level, SettingsLogLevel):
34 33
            from django.conf import settings
34

  
35 35
            debug = getattr(settings, level.debug_setting, False)
36 36
            if debug:
37 37
                return logging.DEBUG
38 38
        return level
39 39

  
40

  
40 41
logging.setLoggerClass(DjangoLogger)
41 42

  
42 43

  
43 44
class DjangoRootLogger(DjangoLogger, logging.RootLogger):
44 45
    pass
45 46

  
47

  
46 48
logging.root.__class__ = DjangoRootLogger
src/authentic2/management/commands/check-and-repair.py
35 35

  
36 36
from authentic2 import app_settings
37 37
from authentic2.a2_rbac.models import OrganizationalUnit as OU, Role, Permission
38

  
38 39
try:
39 40
    from authentic2.a2_rbac.models import MANAGE_MEMBERS_OP
40 41
except ImportError:
......
83 84
    help = 'Check and repair authentic2 datas'
84 85

  
85 86
    def add_arguments(self, parser):
86
        parser.add_argument('--repair', action='store_true',
87
                            help='repair what\'s broken', default=False)
88
        parser.add_argument('--noinput', action='store_true',
89
                            help='do not ask questions', default=False)
90
        parser.add_argument('--fake', action='store_true',
91
                            help='fake repair', default=False)
87
        parser.add_argument('--repair', action='store_true', help='repair what\'s broken', default=False)
88
        parser.add_argument('--noinput', action='store_true', help='do not ask questions', default=False)
89
        parser.add_argument('--fake', action='store_true', help='fake repair', default=False)
92 90
        if MULTITENANT:
93
            parser.add_argument('-d', '--domain', dest='domain_name',
94
                                help='specify tenant domain name')
91
            parser.add_argument('-d', '--domain', dest='domain_name', help='specify tenant domain name')
95 92
        for key in dir(self):
96 93
            if not key.startswith('check_'):
97 94
                continue
......
100 97
            method = getattr(self, key)
101 98
            if callable(method) and method.__name__.startswith('check_'):
102 99
                slug = method.__name__.replace('_', '-')
103
                parser.add_argument('--no-%s' % slug,
104
                                    action='store_false',
105
                                    default=True,
106
                                    dest=method.__name__,
107
                                    help='disable check %s' % slug)
100
                parser.add_argument(
101
                    '--no-%s' % slug,
102
                    action='store_false',
103
                    default=True,
104
                    dest=method.__name__,
105
                    help='disable check %s' % slug,
106
                )
108 107

  
109 108
    def handle(self, verbosity=0, repair=False, noinput=False, fake=False, domain_name=None, **options):
110 109
        self.verbosity = verbosity
......
162 161
    @atomic
163 162
    def check_and_repair(self, options):
164 163
        for method in [
165
                self.check_roles_with_lost_admin_scope,
166
                self.check_duplicate_manage_members_permissions,
167
                self.check_duplicate_permissions,
168
                self.check_unused_permissions,
169
                self.check_instance_permission_ou,
170
                self.check_admin_roles_ou,
171
                self.check_manager_of_roles,
172
                self.check_identifiers_uniqueness,
164
            self.check_roles_with_lost_admin_scope,
165
            self.check_duplicate_manage_members_permissions,
166
            self.check_duplicate_permissions,
167
            self.check_unused_permissions,
168
            self.check_instance_permission_ou,
169
            self.check_admin_roles_ou,
170
            self.check_manager_of_roles,
171
            self.check_identifiers_uniqueness,
173 172
        ]:
174 173
            if not options[method.__name__]:
175 174
                continue
......
203 202

  
204 203
        used_permission_ids = set()
205 204
        used_permission_ids.update(
206
            Role.objects.filter(
207
                admin_scope_ct=permission_ct).values_list(
208
                    'admin_scope_id', flat=True))
209
        used_permission_ids.update(
210
            Role.permissions.through.objects.values_list(
211
                'permission_id', flat=True))
205
            Role.objects.filter(admin_scope_ct=permission_ct).values_list('admin_scope_id', flat=True)
206
        )
207
        used_permission_ids.update(Role.permissions.through.objects.values_list('permission_id', flat=True))
212 208

  
213 209
        qs = Permission.objects.exclude(id__in=used_permission_ids)
214 210
        count = qs.count()
......
231 227
        manage_members_op = get_operation(MANAGE_MEMBERS_OP)
232 228
        permissions = Permission.objects.exclude(target_ct=ct_ct).filter(operation=manage_members_op)
233 229
        targets_with_duplicates = set(
234
            permissions
235
            .values_list('operation_id', 'target_ct_id', 'target_id')
230
            permissions.values_list('operation_id', 'target_ct_id', 'target_id')
236 231
            .annotate(count=Count(('operation_id', 'target_ct_id', 'target_id')))
237 232
            .filter(count__gt=1)
238 233
            .values_list('operation_id', 'target_ct_id', 'target_id')
......
243 238
                for operation_id, target_ct_id, target_id in targets_with_duplicates:
244 239
                    qs = Permission.objects.filter(target_ct_id=target_ct_id, target_id=target_id)
245 240
                    for perm in qs:
246
                        linked_admin_role = Role.objects.get(admin_scope_ct=permission_ct, admin_scope_id=perm.id)
247
                    target = ContentType.objects.get_for_id(target_ct_id).model_class().objects.get(pk=target_id)
241
                        linked_admin_role = Role.objects.get(
242
                            admin_scope_ct=permission_ct, admin_scope_id=perm.id
243
                        )
244
                    target = (
245
                        ContentType.objects.get_for_id(target_ct_id).model_class().objects.get(pk=target_id)
246
                    )
248 247
                    self.notice(' - %s: [%s]', target, '; '.join(map(str, qs)))
249 248
            if self.do_repair():
250 249
                self.notice('Deleting duplicate manage members permissions...', ending=' ')
251 250
                for operation_id, target_ct_id, target_id in targets_with_duplicates:
252
                    qs = Permission.objects.filter(target_ct_id=target_ct_id, target_id=target_id).order_by('id')
251
                    qs = Permission.objects.filter(target_ct_id=target_ct_id, target_id=target_id).order_by(
252
                        'id'
253
                    )
253 254
                    role_perms = []
254 255
                    for perm in qs:
255 256
                        linked_admin_role = Role.objects.filter(
256
                            admin_scope_ct=permission_ct, admin_scope_id=perm.id).first()
257
                            admin_scope_ct=permission_ct, admin_scope_id=perm.id
258
                        ).first()
257 259
                        if linked_admin_role:
258 260
                            role_perms.append((perm, linked_admin_role))
259 261
                        else:
......
276 278
        ct_ct = ContentType.objects.get_for_model(ContentType)
277 279
        permissions = Permission.objects.exclude(target_ct=ct_ct)
278 280
        targets_with_duplicates = set(
279
            permissions
280
            .values_list('operation_id', 'target_ct_id', 'target_id')
281
            permissions.values_list('operation_id', 'target_ct_id', 'target_id')
281 282
            .annotate(count=Count(('operation_id', 'target_ct_id', 'target_id')))
282 283
            .filter(count__gt=1)
283 284
            .values_list('operation_id', 'target_ct_id', 'target_id')
......
287 288
            if self.repair:
288 289
                for operation_id, target_ct_id, target_id in targets_with_duplicates:
289 290
                    qs = Permission.objects.filter(
290
                        operation_id=operation_id, target_ct_id=target_ct_id, target_id=target_id)
291
                    target = ContentType.objects.get_for_id(target_ct_id).model_class().objects.get(pk=target_id)
291
                        operation_id=operation_id, target_ct_id=target_ct_id, target_id=target_id
292
                    )
293
                    target = (
294
                        ContentType.objects.get_for_id(target_ct_id).model_class().objects.get(pk=target_id)
295
                    )
292 296
                    self.notice(' - %s: [%s]', target, '; '.join(map(str, qs)))
293 297
            if self.do_repair():
294 298
                self.notice('Deleting duplicate permissions...', ending=' ')
295 299
                for operation_id, target_ct_id, target_id in targets_with_duplicates:
296
                    qs = list(Permission.objects.filter(
297
                        operation_id=operation_id, target_ct_id=target_ct_id, target_id=target_id).order_by('id'))
300
                    qs = list(
301
                        Permission.objects.filter(
302
                            operation_id=operation_id, target_ct_id=target_ct_id, target_id=target_id
303
                        ).order_by('id')
304
                    )
298 305
                    first_perm = qs[0]
299 306
                    for perm in qs[1:]:
300 307
                        perm.delete()
......
322 329
        for ou in OU.objects.all():
323 330
            roles = Role.objects.exclude(admin_scope_ct=permission_ct).filter(ou=ou)
324 331
            permissions = Permission.objects.filter(
325
                operation=manage_members_op, target_ct=role_ct, target_id__in=roles.values_list('id'))
332
                operation=manage_members_op, target_ct=role_ct, target_id__in=roles.values_list('id')
333
            )
326 334
            wrong_ou_roles = Role.objects.filter(
327
                admin_scope_ct=permission_ct, admin_scope_id__in=permissions.values_list('id')).exclude(ou=ou)
335
                admin_scope_ct=permission_ct, admin_scope_id__in=permissions.values_list('id')
336
            ).exclude(ou=ou)
328 337
            if wrong_ou_roles:
329 338
                self.warning('Found %4d admin role with wrong ou in ou %s', wrong_ou_roles.count(), ou)
330 339
                roles_to_fix[ou] = wrong_ou_roles
......
347 356
        roles = Role.objects.exclude(slug__startswith='_a2-managers-of-role-')
348 357

  
349 358
        for role in roles:
350
            manager_perms = Permission.objects.filter(operation__in=operations, target_ct=role_ct, target_id=role.id)
359
            manager_perms = Permission.objects.filter(
360
                operation__in=operations, target_ct=role_ct, target_id=role.id
361
            )
351 362
            manager_perms_ids = manager_perms.values_list('id', flat=True)
352
            manager_roles = Role.objects.filter(slug__startswith='_a2-managers-of-role-',
353
                                                admin_scope_ct=permission_ct,
354
                                                admin_scope_id__in=manager_perms_ids)
363
            manager_roles = Role.objects.filter(
364
                slug__startswith='_a2-managers-of-role-',
365
                admin_scope_ct=permission_ct,
366
                admin_scope_id__in=manager_perms_ids,
367
            )
355 368
            role_shown = False
356 369
            to_delete = []
357 370
            to_change_ou = []
......
362 375
                    if list(manager_roles)[0].ou != role.ou:
363 376
                        self.warning(
364 377
                            '- "%s" detected wrong ou, should be "%s" and is "%s"',
365
                            admin_role, role.ou, admin_role.ou)
378
                            admin_role,
379
                            role.ou,
380
                            admin_role.ou,
381
                        )
366 382
                        to_change_ou.append((admin_role, role.ou))
367 383
                    continue
368 384
                add_members = set()
......
374 390
                    direct_members = manager_role.members.all()
375 391
                    direct_members_count = direct_members.count()
376 392
                    direct_children = Role.objects.filter(
377
                        parent_relation__parent=manager_role,
378
                        parent_relation__direct=True)
393
                        parent_relation__parent=manager_role, parent_relation__direct=True
394
                    )
379 395
                    direct_children_count = direct_children.count()
380 396
                    show = members_count or self.verbosity > 1
381 397
                    if show:
......
384 400
                            self.notice('- "%s" has problematic manager roles', role)
385 401
                        self.warning('  - %s', manager_role, ending=' ')
386 402
                    direct_parents = Role.objects.filter(
387
                        child_relation__child=manager_role,
388
                        child_relation__direct=True)
403
                        child_relation__child=manager_role, child_relation__direct=True
404
                    )
389 405
                    if show:
390 406
                        self.warning('DELETE', ending=' ')
391 407
                    to_delete.append(manager_role)
......
439 455
            if not admin_role.admin_scope:
440 456
                self.warning('invalid admin role "%s": no admin scope', admin_role)
441 457
            admin_permissions = (
442
                admin_role.permissions
443
                .filter(operation__in=operations, target_ct=role_ct)
458
                admin_role.permissions.filter(operation__in=operations, target_ct=role_ct)
444 459
                .select_related('ou')
445 460
                .prefetch_related('target__ou')
446 461
            )
......
453 468
                    self.notice(' - %s', admin_permission)
454 469
            for admin_permission in admin_permissions:
455 470
                if MANAGE_MEMBERS_OP and admin_permission.operation != manage_members_op:
456
                    self.warning('invalid admin role "%s" invalid permission "%s": not manage_members operation',
457
                                 admin_role, admin_permission)
471
                    self.warning(
472
                        'invalid admin role "%s" invalid permission "%s": not manage_members operation',
473
                        admin_role,
474
                        admin_permission,
475
                    )
458 476
                if not (
459
                        (admin_permission.target != admin_role and admin_permission == admin_role.admin_scope)
460
                        or (admin_permission.target == admin_role)):
477
                    (admin_permission.target != admin_role and admin_permission == admin_role.admin_scope)
478
                    or (admin_permission.target == admin_role)
479
                ):
461 480
                    self.warning(
462 481
                        'invalid admin role "%s" invalid permission "%s": '
463 482
                        'not admin_scope and not self manage permission',
464
                        admin_role, admin_permission)
483
                        admin_role,
484
                        admin_permission,
485
                    )
465 486
                if admin_permission.ou is not None:
466
                    self.warning('invalid admin role "%s" invalid permission "%s": wrong ou "%s"',
467
                                 admin_role, admin_permission, admin_permission.ou)
487
                    self.warning(
488
                        'invalid admin role "%s" invalid permission "%s": wrong ou "%s"',
489
                        admin_role,
490
                        admin_permission,
491
                        admin_permission.ou,
492
                    )
468 493
                    admin_permission.target.get_admin_role()
469 494
                if admin_permission.target.ou != admin_role.ou:
470
                    self.warning('invalid admin role "%s" wrong ou, should be "%s" is "%s"',
471
                                 admin_role, admin_permission.target.ou, admin_role.ou)
495
                    self.warning(
496
                        'invalid admin role "%s" wrong ou, should be "%s" is "%s"',
497
                        admin_role,
498
                        admin_permission.target.ou,
499
                        admin_role.ou,
500
                    )
472 501

  
473 502
    def duplicate_emails(self, user_qs):
474 503
        return (
475
            user_qs
476
            .order_by()
504
            user_qs.order_by()
477 505
            .values(iemail=Lower('email'))
478 506
            .annotate(count_id=Count('id'))
479 507
            .filter(count_id__gt=1)
......
483 511

  
484 512
    def duplicate_username(self, user_qs):
485 513
        return (
486
            user_qs
487
            .order_by()
514
            user_qs.order_by()
488 515
            .values('username')
489 516
            .exclude(Q(username__isnull=True) | Q(username=''))
490 517
            .annotate(count_id=Count('id'))
......
519 546
            self.notice('  * "%s" %s ', user.get_full_name(), user, ending=' ')
520 547
            self.notice('(created %s', localtime(user.date_joined).strftime('%Y-%m-%dT%H:%M:%S'), ending=' ')
521 548
            if user.last_login:
522
                self.notice(', last login %s', localtime(user.last_login).strftime('%Y-%m-%dT%H:%M:%S'), ending='')
549
                self.notice(
550
                    ', last login %s', localtime(user.last_login).strftime('%Y-%m-%dT%H:%M:%S'), ending=''
551
                )
523 552
            else:
524 553
                self.notice(', never logged in', ending='')
525 554
            external_ids = list(user.userexternalid_set.all())
526 555
            if external_ids:
527 556
                self.notice(
528 557
                    ', external-ids: %s',
529
                    ', '.join(external_id.source + '#' + external_id.external_id for external_id in external_ids),
530
                    ending='')
558
                    ', '.join(
559
                        external_id.source + '#' + external_id.external_id for external_id in external_ids
560
                    ),
561
                    ending='',
562
                )
531 563
            self.notice(')')
532 564

  
533 565
    def _check_username_uniqueness(self, qs, msg):
src/authentic2/management/commands/clean-unused-accounts.py
66 66
        # exclude user from LDAP directories based on their source name (or realm)
67 67
        realms = [block['realm'] for block in LDAPBackend.get_config() if block.get('realm')]
68 68
        self.user_qs = (
69
            User.objects.all()
70
            .exclude(oidc_account__isnull=False)
71
            .exclude(userexternalid__source__in=realms)
69
            User.objects.all().exclude(oidc_account__isnull=False).exclude(userexternalid__source__in=realms)
72 70
        )
73 71

  
74 72
        translation.activate(settings.LANGUAGE_CODE)
......
99 97
            inactive_users_to_delete = inactive_users.filter(
100 98
                last_login__lte=self.now - deletion_delay,
101 99
                # ensure respect of alert delay before deletion
102
                last_account_deletion_alert__lte=self.now - (deletion_delay - alert_delay)
100
                last_account_deletion_alert__lte=self.now - (deletion_delay - alert_delay),
103 101
            )
104 102
            for user in inactive_users_to_delete:
105 103
                logger.info(
106
                    '%s last login more than %d days ago, deleting user', user,
107
                    ou.clean_unused_accounts_deletion)
104
                    '%s last login more than %d days ago, deleting user',
105
                    user,
106
                    ou.clean_unused_accounts_deletion,
107
                )
108 108
                self.delete_user(user)
109 109

  
110 110
    def send_alert(self, user, days_to_deletion):
......
128 128

  
129 129
                def send_mail():
130 130
                    send_templated_mail(email, prefix, ctx)
131

  
131 132
                transaction.on_commit(send_mail)
132 133

  
133 134
    def delete_user(self, user):
src/authentic2/management/commands/deactivate-orphaned-ldap-users.py
20 20

  
21 21

  
22 22
class Command(BaseCommand):
23

  
24 23
    def handle(self, *args, **kwargs):
25 24
        LDAPBackend.deactivate_orphaned_users()
src/authentic2/management/commands/export_site.py
26 26
    help = 'Export site'
27 27

  
28 28
    def add_arguments(self, parser):
29
        parser.add_argument('--output', metavar='FILE', default=None,
30
                            help='name of a file to write output to')
29
        parser.add_argument(
30
            '--output', metavar='FILE', default=None, help='name of a file to write output to'
31
        )
31 32

  
32 33
    def handle(self, *args, **options):
33 34
        if options['output']:
src/authentic2/management/commands/import_site.py
47 47
def provision_contextm(dry_run, settings):
48 48
    if dry_run and 'hobo.agent.authentic2' in settings.INSTALLED_APPS:
49 49
        import hobo.agent.authentic2
50

  
50 51
        with hobo.agent.authentic2.provisionning.Provisionning():
51 52
            yield
52 53
    else:
......
57 58
    help = 'Import site'
58 59

  
59 60
    def add_arguments(self, parser):
61
        parser.add_argument('filename', metavar='FILENAME', type=str, help='name of file to import')
60 62
        parser.add_argument(
61
            'filename', metavar='FILENAME', type=str, help='name of file to import')
62
        parser.add_argument(
63
            '--dry-run', action='store_true', dest='dry_run',
64
            help='Do not actually perform the import')
63
            '--dry-run', action='store_true', dest='dry_run', help='Do not actually perform the import'
64
        )
65 65
        parser.add_argument(
66
            '-o', '--option', action='append', help='Import context options',
66
            '-o',
67
            '--option',
68
            action='append',
69
            help='Import context options',
67 70
            choices=[
68
                'role-delete-orphans', 'ou-delete-orphans', 'no-role-permissions-update',
69
                'no-role-attributes-update', 'no-role-parentings-update'])
71
                'role-delete-orphans',
72
                'ou-delete-orphans',
73
                'no-role-permissions-update',
74
                'no-role-attributes-update',
75
                'no-role-parentings-update',
76
            ],
77
        )
70 78

  
71 79
    def handle(self, filename, **options):
72 80
        translation.activate(settings.LANGUAGE_CODE)
src/authentic2/management/commands/load-ldif.py
77 77
        if self.callback:
78 78
            m.extend(self.callback(u, dn, entry, self.options, d))
79 79
        if 'username' not in d:
80
            self.log.warning('cannot load dn %s, username cannot be initialized from the field %s',
81
                             dn, self.options['username'])
80
            self.log.warning(
81
                'cannot load dn %s, username cannot be initialized from the field %s',
82
                dn,
83
                self.options['username'],
84
            )
82 85
            return
83 86
        try:
84 87
            old = User.objects.get(username=d['username'])
......
97 100

  
98 101

  
99 102
class ExtraAttributeAction(argparse.Action):
100

  
101 103
    def __call__(self, parser, namespace, values, option_string=None):
102 104
        ldap_attribute, django_attribute = values
103 105
        try:
......
111 113

  
112 114
class Command(BaseCommand):
113 115
    '''Load LDAP ldif file'''
116

  
114 117
    can_import_django_settings = True
115 118
    requires_system_checks = True
116 119
    help = 'Load/update LDIF files as users'
117 120

  
118 121
    def add_arguments(self, parser):
119 122
        parser.add_argument('ldif_file', nargs='+')
120
        parser.add_argument(
121
            '--first-name', default='givenName', help='attribute used to set the first name')
122
        parser.add_argument(
123
            '--last-name', default='sn', help='attribute used to set the last name')
123
        parser.add_argument('--first-name', default='givenName', help='attribute used to set the first name')
124
        parser.add_argument('--last-name', default='sn', help='attribute used to set the last name')
124 125
        parser.add_argument('--email', default='mail', help='attribute used to set the email')
125 126
        parser.add_argument('--username', default='uid', help='attribute used to set the username')
126 127
        parser.add_argument(
127
            '--password', default='userPassword',
128
            help='attribute to extract the password from, '
129
                 'OpenLDAP hashing algorithm are recognized'
128
            '--password',
129
            default='userPassword',
130
            help='attribute to extract the password from, ' 'OpenLDAP hashing algorithm are recognized',
130 131
        )
132
        parser.add_argument('--object-class', default='inetOrgPerson', help='object class of records to load')
131 133
        parser.add_argument(
132
            '--object-class', default='inetOrgPerson', help='object class of records to load')
133
        parser.add_argument(
134
            '--extra-attribute', default={}, action=ExtraAttributeAction, nargs=2,
135
            help='object class of records to load'
136
        )
137
        parser.add_argument(
138
            '--result', default=None, help='file to store a JSON log of created users')
139
        parser.add_argument(
140
            '--fake', action='store_true', help='file to store a JSON log of created users'
134
            '--extra-attribute',
135
            default={},
136
            action=ExtraAttributeAction,
137
            nargs=2,
138
            help='object class of records to load',
141 139
        )
140
        parser.add_argument('--result', default=None, help='file to store a JSON log of created users')
141
        parser.add_argument('--fake', action='store_true', help='file to store a JSON log of created users')
142 142
        parser.add_argument('--realm', default=None, help='realm for the new users')
143 143
        parser.add_argument(
144
            '--callback', default=None,
144
            '--callback',
145
            default=None,
145 146
            help='python file containing a function callback(user, dn, entry, options, dump)'
146
                 ' it can return models that will be saved'
147
            ' it can return models that will be saved',
147 148
        )
148 149
        parser.add_argument('--callback-arg', action='append', help='arguments for the callback')
149 150

  
src/authentic2/management/commands/resetpassword.py
35 35
    def add_arguments(self, parser):
36 36
        parser.add_argument('username', nargs='?', type=str)
37 37
        parser.add_argument(
38
            '--database', action='store', dest='database',
39
            default=DEFAULT_DB_ALIAS, help='Specifies the database to use. Default is "default".')
38
            '--database',
39
            action='store',
40
            dest='database',
41
            default=DEFAULT_DB_ALIAS,
42
            help='Specifies the database to use. Default is "default".',
43
        )
40 44

  
41 45
    def _get_pass(self, prompt="Password: "):
42 46
        p = getpass.getpass(prompt=prompt)
......
50 54
            username = getpass.getuser()
51 55

  
52 56
        try:
53
            u = User._default_manager.using(options.get('database')).get(**{
54
                User.USERNAME_FIELD: username
55
            })
57
            u = User._default_manager.using(options.get('database')).get(**{User.USERNAME_FIELD: username})
56 58
        except User.DoesNotExist:
57 59
            raise CommandError("user '%s' does not exist" % username)
58 60

  
......
63 65
        PasswordReset.objects.get_or_create(user=u)
64 66
        return (
65 67
            'Password changed successfully for user "%s", on next login he '
66
            'will be forced to change its password.' % u)
67

  
68
            'will be forced to change its password.' % u
69
        )
src/authentic2/management/commands/sync-ldap-users.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
from __future__ import print_function
18

  
18 19
try:
19 20
    import ldap
20 21
    from ldap.filter import filter_format  # noqa: F401
src/authentic2/manager/app_settings.py
33 33

  
34 34
    def __getattr__(self, name):
35 35
        from django.conf import settings
36

  
36 37
        if name not in self.__DEFAULTS:
37 38
            raise AttributeError
38 39
        return getattr(settings, self.__PREFIX + name, self.__DEFAULTS[name])
39 40

  
41

  
40 42
app_settings = AppSettings()
41 43
app_settings.__name__ = __name__
42 44
sys.modules[__name__] = app_settings
src/authentic2/manager/apps.py
25 25
        from django.db.models.signals import post_save
26 26
        from django_rbac.utils import get_ou_model
27 27

  
28
        post_save.connect(
29
            self.post_save_ou,
30
            sender=get_ou_model())
28
        post_save.connect(self.post_save_ou, sender=get_ou_model())
31 29

  
32 30
    def post_save_ou(self, *args, **kwargs):
33 31
        from . import utils
32

  
34 33
        utils.get_ou_count.cache.clear()
src/authentic2/manager/fields.py
44 44
        continue
45 45
    if issubclass(cls, widgets.ModelSelect2MultipleWidget):
46 46
        cls_name = key.replace('Widget', 'Field')
47
        vars()[cls_name] = type(cls_name, (Select2ModelMultipleChoiceField,), {
48
            'widget': cls,
49
        })
47
        vars()[cls_name] = type(
48
            cls_name,
49
            (Select2ModelMultipleChoiceField,),
50
            {
51
                'widget': cls,
52
            },
53
        )
50 54
    elif issubclass(cls, widgets.ModelSelect2Widget):
51 55
        cls_name = key.replace('Widget', 'Field')
52
        vars()[cls_name] = type(cls_name, (Select2ModelChoiceField,), {
53
            'widget': cls,
54
        })
56
        vars()[cls_name] = type(
57
            cls_name,
58
            (Select2ModelChoiceField,),
59
            {
60
                'widget': cls,
61
            },
62
        )
src/authentic2/manager/forms.py
76 76
                instance.slug += str(i)
77 77
                i += 1
78 78
        if len(instance.slug) > 256:
79
            instance.slug = instance.slug[:252] + \
80
                hashlib.md5(instance.slug).hexdigest()[:4]
79
            instance.slug = instance.slug[:252] + hashlib.md5(instance.slug).hexdigest()[:4]
81 80
        return super(SlugMixin, self).save(commit=commit)
82 81

  
83 82

  
......
88 87

  
89 88

  
90 89
class LimitQuerysetFormMixin(FormWithRequest):
91
    '''Limit queryset of all model choice field based on the objects
92
       viewable by the user.
93
    '''
90
    """Limit queryset of all model choice field based on the objects
91
    viewable by the user.
92
    """
93

  
94 94
    def __init__(self, *args, **kwargs):
95 95
        super(LimitQuerysetFormMixin, self).__init__(*args, **kwargs)
96 96
        if self.request and not self.request.user.is_anonymous:
......
155 155

  
156 156

  
157 157
class ChoosePermissionForm(CssClass, forms.Form):
158
    operation = forms.ModelChoiceField(
159
        required=False,
160
        label=_('Operation'),
161
        queryset=Operation.objects)
158
    operation = forms.ModelChoiceField(required=False, label=_('Operation'), queryset=Operation.objects)
162 159
    ou = forms.ModelChoiceField(
163
        label=_('Organizational unit'),
164
        queryset=get_ou_model().objects,
165
        required=False)
166
    target = forms.ModelChoiceField(
167
        label=_('Target object'),
168
        required=False,
169
        queryset=ContentType.objects)
170
    action = forms.CharField(
171
        initial='add',
172
        required=False,
173
        widget=forms.HiddenInput)
160
        label=_('Organizational unit'), queryset=get_ou_model().objects, required=False
161
    )
162
    target = forms.ModelChoiceField(label=_('Target object'), required=False, queryset=ContentType.objects)
163
    action = forms.CharField(initial='add', required=False, widget=forms.HiddenInput)
174 164
    permission = forms.ModelChoiceField(
175
        queryset=get_permission_model().objects,
176
        required=False,
177
        widget=forms.HiddenInput)
165
        queryset=get_permission_model().objects, required=False, widget=forms.HiddenInput
166
    )
178 167

  
179 168

  
180 169
class UserEditForm(LimitQuerysetFormMixin, CssClass, BaseUserForm):
......
207 196

  
208 197
    class Meta:
209 198
        model = User
210
        exclude = ('is_staff', 'groups', 'user_permissions', 'last_login',
211
                   'date_joined', 'password')
199
        exclude = ('is_staff', 'groups', 'user_permissions', 'last_login', 'date_joined', 'password')
212 200

  
213 201

  
214 202
class UserChangePasswordForm(CssClass, forms.ModelForm):
215 203
    error_messages = {
216 204
        'password_mismatch': _("The two password fields didn't match."),
217 205
    }
218
    notification_template_prefix = \
219
        'authentic2/manager/change-password-notification'
206
    notification_template_prefix = 'authentic2/manager/change-password-notification'
220 207
    require_password = True
221 208

  
222 209
    def clean_password2(self):
......
231 218

  
232 219
    def clean(self):
233 220
        super(UserChangePasswordForm, self).clean()
234
        if (self.require_password
235
                and not self.cleaned_data.get('generate_password')
236
                and not self.cleaned_data.get('password1')
237
                and not self.cleaned_data.get('send_password_reset')):
221
        if (
222
            self.require_password
223
            and not self.cleaned_data.get('generate_password')
224
            and not self.cleaned_data.get('password1')
225
            and not self.cleaned_data.get('send_password_reset')
226
        ):
238 227
            raise forms.ValidationError(
239
                _('You must choose password generation or type a new'
240
                  '  one or send a password reset mail'))
241
        if (not self.has_email()
242
                and (self.cleaned_data.get('send_mail')
243
                     or self.cleaned_data.get('generate_password')
244
                     or self.cleaned_data.get('send_password_reset'))):
228
                _('You must choose password generation or type a new' '  one or send a password reset mail')
229
            )
230
        if not self.has_email() and (
231
            self.cleaned_data.get('send_mail')
232
            or self.cleaned_data.get('generate_password')
233
            or self.cleaned_data.get('send_password_reset')
234
        ):
245 235
            raise forms.ValidationError(
246
                _('User does not have a mail, we cannot send the '
247
                  'informations to him.'))
236
                _('User does not have a mail, we cannot send the ' 'informations to him.')
237
            )
248 238

  
249 239
    def has_email(self):
250 240
        return bool(self.instance and self.instance.email)
......
271 261
                send_templated_mail(
272 262
                    user,
273 263
                    self.notification_template_prefix,
274
                    context={'new_password': new_password, 'user': user})
264
                    context={'new_password': new_password, 'user': user},
265
                )
275 266
        return user
276 267

  
277
    generate_password = forms.BooleanField(
278
        initial=False,
279
        label=_('Generate new password'),
280
        required=False)
281
    password1 = NewPasswordField(
282
        label=_("Password"),
283
        required=False)
284
    password2 = CheckPasswordField(
285
        label=_("Confirmation"),
286
        required=False)
287
    send_mail = forms.BooleanField(
288
        initial=False,
289
        label=_('Send informations to user'),
290
        required=False)
268
    generate_password = forms.BooleanField(initial=False, label=_('Generate new password'), required=False)
269
    password1 = NewPasswordField(label=_("Password"), required=False)
270
    password2 = CheckPasswordField(label=_("Confirmation"), required=False)
271
    send_mail = forms.BooleanField(initial=False, label=_('Send informations to user'), required=False)
291 272

  
292 273
    class Meta:
293 274
        model = User
......
299 280
    form_id = "id_user_add_form"
300 281
    require_password = False
301 282

  
302
    notification_template_prefix = \
303
        'authentic2/manager/new-account-notification'
283
    notification_template_prefix = 'authentic2/manager/new-account-notification'
304 284
    reset_password_at_next_login = forms.BooleanField(
305
        initial=False,
306
        label=_('Ask for password reset on next login'),
307
        required=False)
285
        initial=False, label=_('Ask for password reset on next login'), required=False
286
    )
308 287

  
309 288
    send_password_reset = forms.BooleanField(
310
        initial=False,
311
        label=_('Send mail to user to make it choose a password'),
312
        required=False)
289
        initial=False, label=_('Send mail to user to make it choose a password'), required=False
290
    )
313 291

  
314 292
    def __init__(self, *args, **kwargs):
315 293
        self.ou = kwargs.pop('ou', None)
......
323 301
        has_password = (
324 302
            self.cleaned_data.get('new_password1')
325 303
            or self.cleaned_data.get('generate_password')
326
            or self.cleaned_data.get('send_password_reset'))
304
            or self.cleaned_data.get('send_password_reset')
305
        )
327 306

  
328
        if (has_password
329
                and not self.cleaned_data.get('username')
330
                and not self.cleaned_data.get('email')):
307
        if has_password and not self.cleaned_data.get('username') and not self.cleaned_data.get('email'):
331 308
            raise forms.ValidationError(
332
                _('You must set a username or an email to set a password or send an activation link.'))
309
                _('You must set a username or an email to set a password or send an activation link.')
310
            )
333 311

  
334 312
        if not has_password:
335 313
            self.instance.set_random_password()
......
348 326
                def save(*args, **kwargs):
349 327
                    old_save(*args, **kwargs)
350 328
                    PasswordReset.objects.get_or_create(user=user)
329

  
351 330
                user.save = save
352 331
        if self.cleaned_data.get('send_password_reset'):
353 332
            try:
354 333
                send_password_reset_mail(
355 334
                    user,
356
                    template_names=['authentic2/manager/user_create_registration_email',
357
                                    'authentic2/password_reset'],
335
                    template_names=[
336
                        'authentic2/manager/user_create_registration_email',
337
                        'authentic2/password_reset',
338
                    ],
358 339
                    request=self.request,
359 340
                    next_url='/accounts/',
360 341
                    context={
361 342
                        'user': user,
362
                    })
343
                    },
344
                )
363 345
            except smtplib.SMTPException as e:
364
                logger.error(u'registration mail could not be sent to user %s created through '
365
                             u'manager: %s', user, e)
346
                logger.error(
347
                    u'registration mail could not be sent to user %s created through ' u'manager: %s', user, e
348
                )
366 349
        return user
367 350

  
368 351
    class Meta:
......
374 357
class ServiceRoleSearchForm(CssClass, PrefixFormMixin, FormWithRequest):
375 358
    prefix = 'search'
376 359

  
377
    text = forms.CharField(
378
        label=_('Name'),
379
        required=False)
380
    internals = forms.BooleanField(
381
        initial=False,
382
        label=_('Show internal roles'),
383
        required=False)
360
    text = forms.CharField(label=_('Name'), required=False)
361
    internals = forms.BooleanField(initial=False, label=_('Show internal roles'), required=False)
384 362

  
385 363
    def __init__(self, *args, **kwargs):
386 364
        super(ServiceRoleSearchForm, self).__init__(*args, **kwargs)
......
448 426
                # we were passed an explicit list of objects linked to OUs by a field named 'ou',
449 427
                # get possible OUs from this list
450 428
                related_query_name = self.queryset.model._meta.get_field('ou').related_query_name()
451
                objects_ou_qs = get_ou_model().objects.filter(
452
                    **{"%s__in" % related_query_name: self.queryset}).distinct()
429
                objects_ou_qs = (
430
                    get_ou_model().objects.filter(**{"%s__in" % related_query_name: self.queryset}).distinct()
431
                )
453 432
                # to combine queryset with distinct, each queryset must have the distinct flag
454
                self.ou_qs = (self.ou_qs.distinct() | objects_ou_qs)
433
                self.ou_qs = self.ou_qs.distinct() | objects_ou_qs
455 434

  
456 435
            # even if default ordering is by name on the model, we are not sure it's kept after the
457 436
            # ORing in the previous if condition, so we sort it again.
......
574 553
    ou_permission = 'custom_user.search_user'
575 554
    prefix = 'search'
576 555

  
577
    text = forms.CharField(
578
        label=_('Free text'),
579
        required=False)
556
    text = forms.CharField(label=_('Free text'), required=False)
580 557

  
581 558
    def __init__(self, *args, **kwargs):
582 559
        self.minimum_chars = kwargs.pop('minimum_chars', 0)
......
606 583
class NameSearchForm(CssClass, PrefixFormMixin, FormWithRequest):
607 584
    prefix = 'search'
608 585

  
609
    text = forms.CharField(
610
        label=_('Name'),
611
        required=False)
586
    text = forms.CharField(label=_('Name'), required=False)
612 587

  
613 588
    def filter(self, qs):
614 589
        if self.cleaned_data.get('text'):
......
616 591
        return qs
617 592

  
618 593

  
619
class RoleEditForm(SlugMixin, HideOUFieldMixin, LimitQuerysetFormMixin, CssClass,
620
                   forms.ModelForm):
621
    ou = forms.ModelChoiceField(queryset=get_ou_model().objects,
622
                                required=True, label=_('Organizational unit'))
594
class RoleEditForm(SlugMixin, HideOUFieldMixin, LimitQuerysetFormMixin, CssClass, forms.ModelForm):
595
    ou = forms.ModelChoiceField(
596
        queryset=get_ou_model().objects, required=True, label=_('Organizational unit')
597
    )
623 598

  
624 599
    class Meta:
625 600
        model = get_role_model()
......
634 609
    class Meta:
635 610
        model = get_ou_model()
636 611
        fields = (
637
            'name', 'slug', 'default', 'username_is_unique', 'email_is_unique', 'validate_emails',
638
            'show_username', 'user_can_reset_password', 'user_add_password_policy',
639
            'clean_unused_accounts_alert', 'clean_unused_accounts_deletion'
612
            'name',
613
            'slug',
614
            'default',
615
            'username_is_unique',
616
            'email_is_unique',
617
            'validate_emails',
618
            'show_username',
619
            'user_can_reset_password',
620
            'user_add_password_policy',
621
            'clean_unused_accounts_alert',
622
            'clean_unused_accounts_deletion',
640 623
        )
641 624

  
642 625

  
......
664 647
            self.instance,
665 648
            new_email,
666 649
            request=self.request,
667
            template_names=['authentic2/manager/user_change_email_notification'])
650
            template_names=['authentic2/manager/user_change_email_notification'],
651
        )
668 652
        return self.instance
669 653

  
670 654
    class Meta:
......
700 684
            self.fields['ou'].widget = forms.HiddenInput()
701 685

  
702 686
    ou = forms.ModelChoiceField(
703
        label=_('Organizational unit'),
704
        queryset=get_ou_model().objects,
705
        initial=lambda: get_default_ou().pk
687
        label=_('Organizational unit'), queryset=get_ou_model().objects, initial=lambda: get_default_ou().pk
706 688
    )
707 689

  
708 690

  
......
714 696

  
715 697

  
716 698
class UserImportForm(forms.Form):
717
    import_file = forms.FileField(
718
        label=_('Import file'),
719
        help_text=_('A CSV file'))
720
    encoding = forms.ChoiceField(
721
        label=_('Encoding'),
722
        choices=ENCODINGS)
723
    ou = forms.ModelChoiceField(
724
        label=_('Organizational Unit'),
725
        queryset=OU.objects.all())
699
    import_file = forms.FileField(label=_('Import file'), help_text=_('A CSV file'))
700
    encoding = forms.ChoiceField(label=_('Encoding'), choices=ENCODINGS)
701
    ou = forms.ModelChoiceField(label=_('Organizational Unit'), queryset=OU.objects.all())
726 702

  
727 703
    @staticmethod
728 704
    def raise_validation_error(error_message):
......
749 725
        import_file = self.cleaned_data['import_file']
750 726
        import_file.open()
751 727
        new_import = user_import.UserImport.new(
752
            import_file=import_file,
753
            encoding=self.cleaned_data['encoding'])
728
            import_file=import_file, encoding=self.cleaned_data['encoding']
729
        )
754 730
        with new_import.meta_update as meta:
755 731
            meta['filename'] = import_file.name
756 732
            meta['ou'] = self.cleaned_data['ou']
......
769 745

  
770 746
    def clean(self):
771 747
        from authentic2.csv_import import CsvImporter
748

  
772 749
        encoding = self.cleaned_data['encoding']
773 750
        with self.user_import.import_file as fd:
774 751
            importer = CsvImporter()
src/authentic2/manager/journal_event_types.py
249 249
        service_name = cls.get_service_name(event)
250 250
        if context and context == user:
251 251
            return _('deletion of authorization of single sign on with "{service}" by administrator').format(
252
                service=service_name)
252
                service=service_name
253
            )
253 254
        elif user:
254 255
            return _('deletion of authorization of single sign on with "{service}" of user "{user}"').format(
255 256
                service=service_name,
......
264 265
        references = references or []
265 266
        references = [role] + references
266 267
        data = data or {}
267
        data.update(
268
            {'role_name': str(role), 'role_uuid': role.uuid}
269
        )
268
        data.update({'role_name': str(role), 'role_uuid': role.uuid})
270 269
        super().record(
271
            user=user, session=session, references=references, data=data,
270
            user=user,
271
            session=session,
272
            references=references,
273
            data=data,
272 274
        )
273 275

  
274 276

  
src/authentic2/manager/ou_views.py
39 39
    permissions = ['a2_rbac.search_organizationalunit']
40 40
    title = _('Organizational units')
41 41

  
42

  
42 43
listing = OrganizationalUnitView.as_view()
43 44

  
44 45

  
......
50 51
    exclude_fields = ('slug',)
51 52

  
52 53
    def get_fields(self):
53
        return [x for x in self.form_class.base_fields.keys()
54
                if x not in self.exclude_fields]
54
        return [x for x in self.form_class.base_fields.keys() if x not in self.exclude_fields]
55 55

  
56 56
    def get_success_url(self):
57 57
        return '..'
58 58

  
59

  
59 60
add = OrganizationalUnitAddView.as_view()
60 61

  
61 62

  
......
73 74
        super(OrganizationalUnitDetailView, self).authorize(request, *args, **kwargs)
74 75
        self.can_delete = self.can_delete and not self.object.default
75 76

  
77

  
76 78
detail = OrganizationalUnitDetailView.as_view()
77 79

  
78 80

  
......
83 85
    template_name = 'authentic2/manager/ou_edit.html'
84 86
    title = _('Edit organizational unit')
85 87

  
88

  
86 89
edit = OrganizationalUnitEditView.as_view()
87 90

  
88 91

  
......
94 97

  
95 98
    def dispatch(self, request, *args, **kwargs):
96 99
        if self.get_object().default:
97
            messages.warning(request, _('You cannot delete the default '
98
                                        'organizational unit, you must first '
99
                                        'set another default organiational '
100
                                        'unit.'))
101
            return self.return_ajax_response(
102
                request, HttpResponseRedirect(self.get_success_url()))
103
        return super(OrganizationalUnitDeleteView, self).dispatch(request, *args,
104
                                                                  **kwargs)
100
            messages.warning(
101
                request,
102
                _(
103
                    'You cannot delete the default '
104
                    'organizational unit, you must first '
105
                    'set another default organiational '
106
                    'unit.'
107
                ),
108
            )
109
            return self.return_ajax_response(request, HttpResponseRedirect(self.get_success_url()))
110
        return super(OrganizationalUnitDeleteView, self).dispatch(request, *args, **kwargs)
111

  
105 112

  
106 113
delete = OrganizationalUnitDeleteView.as_view()
107 114

  
......
111 118

  
112 119
    def get(self, request, *args, **kwargs):
113 120
        export = data_transfer.export_site(
114
            data_transfer.ExportContext(
115
                ou_qs=self.get_table_data(),
116
                export_roles=False,
117
                export_ous=True))
121
            data_transfer.ExportContext(ou_qs=self.get_table_data(), export_roles=False, export_ous=True)
122
        )
118 123
        return self.export_response(json.dumps(export, indent=4), 'application/json', 'json')
119 124

  
120 125

  
121 126
export = OusExportView.as_view()
122 127

  
123 128

  
124
class OusImportView(views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest,
125
                      FormView):
129
class OusImportView(
130
    views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest, FormView
131
):
126 132
    form_class = forms.OusImportForm
127 133
    model = get_ou_model()
128 134
    template_name = 'authentic2/manager/import_form.html'
src/authentic2/manager/resources.py
47 47

  
48 48
    class Meta:
49 49
        model = User
50
        exclude = ('password', 'user_permissions', 'is_staff',
51
                   'is_superuser', 'groups')
52
        export_order = ('ou', 'uuid', 'id', 'username', 'email',
53
                        'first_name', 'last_name', 'last_login',
54
                        'date_joined', 'roles')
50
        exclude = ('password', 'user_permissions', 'is_staff', 'is_superuser', 'groups')
51
        export_order = (
52
            'ou',
53
            'uuid',
54
            'id',
55
            'username',
56
            'email',
57
            'first_name',
58
            'last_name',
59
            'last_login',
60
            'date_joined',
61
            'roles',
62
        )
55 63
        widgets = {
56 64
            'roles': {
57 65
                'field': 'name',
58 66
            },
59 67
            'ou': {
60 68
                'field': 'name',
61
            }
69
            },
62 70
        }
63 71

  
64 72

  
src/authentic2/manager/role_views.py
55 55
        permission_ct = ContentType.objects.get_for_model(Permission)
56 56
        ct_ct = ContentType.objects.get_for_model(ContentType)
57 57
        ou_ct = ContentType.objects.get_for_model(OU)
58
        permission_qs = Permission.objects.filter(target_ct_id__in=[ct_ct.id, ou_ct.id]) \
59
            .values_list('id', flat=True)
58
        permission_qs = Permission.objects.filter(target_ct_id__in=[ct_ct.id, ou_ct.id]).values_list(
59
            'id', flat=True
60
        )
60 61
        # only non role-admin roles, they are accessed through the
61 62
        # RoleManager views
62 63
        if not self.admin_roles:
63 64
            qs = qs.filter(
64
                Q(admin_scope_ct__isnull=True) | Q(admin_scope_ct=permission_ct, admin_scope_id__in=permission_qs))
65
                Q(admin_scope_ct__isnull=True)
66
                | Q(admin_scope_ct=permission_ct, admin_scope_id__in=permission_qs)
67
            )
65 68
        if not self.service_roles:
66 69
            qs = qs.filter(service__isnull=True)
67 70
        return qs
68 71

  
69 72

  
70
class RolesView(views.SearchOUMixin, views.HideOUColumnMixin, RolesMixin,
71
                views.BaseTableView):
73
class RolesView(views.SearchOUMixin, views.HideOUColumnMixin, RolesMixin, views.BaseTableView):
72 74
    template_name = 'authentic2/manager/roles.html'
73 75
    model = get_role_model()
74 76
    table_class = tables.RoleTable
......
86 88
        kwargs['queryset'] = self.get_queryset()
87 89
        return kwargs
88 90

  
91

  
89 92
listing = RolesView.as_view()
90 93

  
91 94

  
......
110 113

  
111 114
    def form_valid(self, form):
112 115
        response = super(RoleAddView, self).form_valid(form)
113
        hooks.call_hooks('event', name='manager-add-role', user=self.request.user,
114
                         instance=form.instance, form=form)
116
        hooks.call_hooks(
117
            'event', name='manager-add-role', user=self.request.user, instance=form.instance, form=form
118
        )
115 119
        self.request.journal.record('manager.role.creation', role=form.instance)
116 120
        return response
117 121

  
......
128 132
        if export_format == 'json':
129 133
            export = data_transfer.export_site(
130 134
                data_transfer.ExportContext(
131
                    role_qs=self.get_table_data(),
132
                    export_roles=True,
133
                    export_ous=False))
135
                    role_qs=self.get_table_data(), export_roles=True, export_ous=False
136
                )
137
            )
134 138
            return self.export_response(json.dumps(export, indent=4), 'application/json', 'json')
135 139
        return super(RolesExportView, self).get(request, *args, **kwargs)
136 140

  
......
151 155

  
152 156
    def form_valid(self, form):
153 157
        response = super(RoleEditView, self).form_valid(form)
154
        hooks.call_hooks('event', name='manager-edit-role', user=self.request.user,
155
                         instance=form.instance, form=form)
158
        hooks.call_hooks(
159
            'event', name='manager-edit-role', user=self.request.user, instance=form.instance, form=form
160
        )
156 161
        self.request.journal.record('manager.role.edit', role=form.instance, form=form)
157 162
        return response
158 163

  
164

  
159 165
edit = RoleEditView.as_view()
160 166

  
161 167

  
......
193 199
                    messages.warning(self.request, _('User already in this role.'))
194 200
                else:
195 201
                    self.object.members.add(user)
196
                    hooks.call_hooks('event', name='manager-add-role-member',
197
                                     user=self.request.user, role=self.object, member=user)
198
                    self.request.journal.record('manager.role.membership.grant', role=self.object, member=user)
202
                    hooks.call_hooks(
203
                        'event',
204
                        name='manager-add-role-member',
205
                        user=self.request.user,
206
                        role=self.object,
207
                        member=user,
208
                    )
209
                    self.request.journal.record(
210
                        'manager.role.membership.grant', role=self.object, member=user
211
                    )
199 212
            elif action == 'remove':
200 213
                if not self.object.members.filter(pk=user.pk).exists():
201 214
                    messages.warning(self.request, _('User was not in this role.'))
202 215
                else:
203 216
                    self.object.members.remove(user)
204
                    hooks.call_hooks('event', name='manager-remove-role-member',
205
                                     user=self.request.user, role=self.object, member=user)
206
                    self.request.journal.record('manager.role.membership.removal', role=self.object, member=user)
217
                    hooks.call_hooks(
218
                        'event',
219
                        name='manager-remove-role-member',
220
                        user=self.request.user,
221
                        role=self.object,
222
                        member=user,
223
                    )
224
                    self.request.journal.record(
225
                        'manager.role.membership.removal', role=self.object, member=user
226
                    )
207 227
        else:
208 228
            messages.warning(self.request, _('You are not authorized'))
209 229
        return super(RoleMembersView, self).form_valid(form)
......
217 237

  
218 238
    def get_context_data(self, **kwargs):
219 239
        ctx = super(RoleMembersView, self).get_context_data(**kwargs)
220
        ctx['children'] = views.filter_view(self.request,
221
                                            self.object.children(include_self=False, annotate=True))
222
        ctx['parents'] = views.filter_view(self.request, self.object.parents(
223
            include_self=False, annotate=True).order_by(F('ou').asc(nulls_first=True), 'name'))
240
        ctx['children'] = views.filter_view(
241
            self.request, self.object.children(include_self=False, annotate=True)
242
        )
243
        ctx['parents'] = views.filter_view(
244
            self.request,
245
            self.object.parents(include_self=False, annotate=True).order_by(
246
                F('ou').asc(nulls_first=True), 'name'
247
            ),
248
        )
224 249
        ctx['has_multiple_ou'] = OU.objects.count() > 1
225
        ctx['admin_roles'] = views.filter_view(self.request,
226
                                               self.object.get_admin_role().children(include_self=False,
227
                                                                                     annotate=True))
250
        ctx['admin_roles'] = views.filter_view(
251
            self.request, self.object.get_admin_role().children(include_self=False, annotate=True)
252
        )
228 253
        ctx['from_ldap'] = self._can_manage_members and not self.can_manage_members
229 254
        return ctx
230 255

  
231 256
    def is_ou_specified(self):
232
        return self.search_form.is_valid() \
233
            and self.search_form.cleaned_data.get('ou')
257
        return self.search_form.is_valid() and self.search_form.cleaned_data.get('ou')
234 258

  
235 259
    def get_table(self, **kwargs):
236 260
        show_username = has_show_username()
......
265 289
        self.request.journal.record('manager.role.deletion', role=role)
266 290
        return super(RoleDeleteView, self).delete(request, *args, **kwargs)
267 291

  
292

  
268 293
delete = RoleDeleteView.as_view()
269 294

  
270 295

  
......
287 312
            action = form.cleaned_data.get('action')
288 313
            Permission = get_permission_model()
289 314
            if action == 'add' and operation and target:
290
                perm, created = Permission.objects \
291
                    .get_or_create(operation=operation, ou=ou,
292
                                   target_ct=ContentType.objects.get_for_model(
293
                                       target),
294
                                   target_id=target.pk)
315
                perm, created = Permission.objects.get_or_create(
316
                    operation=operation,
317
                    ou=ou,
318
                    target_ct=ContentType.objects.get_for_model(target),
319
                    target_id=target.pk,
320
                )
295 321
                self.object.permissions.add(perm)
296
                hooks.call_hooks('event', name='manager-add-permission', user=self.request.user,
297
                                 role=self.object, permission=perm)
322
                hooks.call_hooks(
323
                    'event',
324
                    name='manager-add-permission',
325
                    user=self.request.user,
326
                    role=self.object,
327
                    permission=perm,
328
                )
298 329
            elif action == 'remove':
299 330
                try:
300 331
                    permission_id = int(self.request.POST.get('permission', ''))
......
304 335
                else:
305 336
                    if self.object.permissions.filter(id=permission_id).exists():
306 337
                        self.object.permissions.remove(perm)
307
                        hooks.call_hooks('event', name='manager-remove-permission',
308
                                         user=self.request.user, role=self.object, permission=perm)
338
                        hooks.call_hooks(
339
                            'event',
340
                            name='manager-remove-permission',
341
                            user=self.request.user,
342
                            role=self.object,
343
                            permission=perm,
344
                        )
309 345
        else:
310 346
            messages.warning(self.request, _('You are not authorized'))
311 347
        return super(RolePermissionsView, self).form_valid(form)
312 348

  
349

  
313 350
permissions = RolePermissionsView.as_view()
314 351

  
315 352

  
......
320 357
    def get_data(self):
321 358
        return self.get_table_data()
322 359

  
360

  
323 361
members_export = RoleMembersExportView.as_view()
324 362

  
325 363

  
326
class RoleAddChildView(views.AjaxFormViewMixin, views.TitleMixin,
327
                       views.PermissionMixin, views.FormNeedsRequest,
328
                       SingleObjectMixin, FormView):
364
class RoleAddChildView(
365
    views.AjaxFormViewMixin,
366
    views.TitleMixin,
367
    views.PermissionMixin,
368
    views.FormNeedsRequest,
369
    SingleObjectMixin,
370
    FormView,
371
):
329 372
    title = _('Add child role')
330 373
    model = get_role_model()
331 374
    form_class = forms.RolesForm
......
341 384
        parent = self.get_object()
342 385
        for role in form.cleaned_data['roles']:
343 386
            parent.add_child(role)
344
            hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user,
345
                             parent=parent, child=role)
387
            hooks.call_hooks(
388
                'event', name='manager-add-child-role', user=self.request.user, parent=parent, child=role
389
            )
346 390
            self.request.journal.record('manager.role.inheritance.addition', parent=parent, child=role)
347 391
        return super(RoleAddChildView, self).form_valid(form)
348 392

  
393

  
349 394
add_child = RoleAddChildView.as_view()
350 395

  
351 396

  
352
class RoleAddParentView(views.AjaxFormViewMixin, views.TitleMixin,
353
                        views.FormNeedsRequest, SingleObjectMixin, FormView):
397
class RoleAddParentView(
398
    views.AjaxFormViewMixin, views.TitleMixin, views.FormNeedsRequest, SingleObjectMixin, FormView
399
):
354 400
    title = _('Add parent role')
355 401
    model = get_role_model()
356 402
    form_class = forms.RoleParentsForm
......
367 413
        child = self.get_object()
368 414
        for role in form.cleaned_data['roles']:
369 415
            child.add_parent(role)
370
            hooks.call_hooks('event', name='manager-add-child-role', user=self.request.user,
371
                             parent=role, child=child)
416
            hooks.call_hooks(
417
                'event', name='manager-add-child-role', user=self.request.user, parent=role, child=child
418
            )
372 419
            self.request.journal.record('manager.role.inheritance.addition', parent=role, child=child)
373 420
        return super(RoleAddParentView, self).form_valid(form)
374 421

  
422

  
375 423
add_parent = RoleAddParentView.as_view()
376 424

  
377 425

  
378
class RoleRemoveChildView(views.AjaxFormViewMixin, SingleObjectMixin,
379
                          views.PermissionMixin, TemplateView):
426
class RoleRemoveChildView(views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView):
380 427
    title = _('Remove child role')
381 428
    model = get_role_model()
382 429
    success_url = '../..'
......
395 442

  
396 443
    def post(self, request, *args, **kwargs):
397 444
        self.object.remove_child(self.child)
398
        hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user,
399
                         parent=self.object, child=self.child)
445
        hooks.call_hooks(
446
            'event',
447
            name='manager-remove-child-role',
448
            user=self.request.user,
449
            parent=self.object,
450
            child=self.child,
451
        )
400 452
        self.request.journal.record('manager.role.inheritance.removal', parent=self.object, child=self.child)
401 453
        return redirect(self.request, self.success_url)
402 454

  
455

  
403 456
remove_child = RoleRemoveChildView.as_view()
404 457

  
405 458

  
406
class RoleRemoveParentView(views.AjaxFormViewMixin, SingleObjectMixin,
407
                           TemplateView):
459
class RoleRemoveParentView(views.AjaxFormViewMixin, SingleObjectMixin, TemplateView):
408 460
    title = _('Remove parent role')
409 461
    model = get_role_model()
410 462
    success_url = '../..'
......
426 478
        if not self.request.user.has_perm('a2_rbac.manage_members_role', self.parent):
427 479
            raise PermissionDenied
428 480
        self.object.remove_parent(self.parent)
429
        hooks.call_hooks('event', name='manager-remove-child-role', user=self.request.user,
430
                         parent=self.parent, child=self.object)
481
        hooks.call_hooks(
482
            'event',
483
            name='manager-remove-child-role',
484
            user=self.request.user,
485
            parent=self.parent,
486
            child=self.object,
487
        )
431 488
        self.request.journal.record('manager.role.inheritance.removal', parent=self.parent, child=self.object)
432 489
        return redirect(self.request, self.success_url)
433 490

  
491

  
434 492
remove_parent = RoleRemoveParentView.as_view()
435 493

  
436 494

  
437
class RoleAddAdminRoleView(views.AjaxFormViewMixin, views.TitleMixin,
438
                           views.PermissionMixin, views.FormNeedsRequest,
439
                           SingleObjectMixin, FormView):
495
class RoleAddAdminRoleView(
496
    views.AjaxFormViewMixin,
497
    views.TitleMixin,
498
    views.PermissionMixin,
499
    views.FormNeedsRequest,
500
    SingleObjectMixin,
501
    FormView,
502
):
440 503
    title = _('Add admin role')
441 504
    model = get_role_model()
442 505
    form_class = forms.RolesForm
......
452 515
        administered_role = self.get_object()
453 516
        for role in form.cleaned_data['roles']:
454 517
            administered_role.get_admin_role().add_child(role)
455
            hooks.call_hooks('event', name='manager-add-admin-role', user=self.request.user,
456
                             role=administered_role, admin_role=role)
457
            self.request.journal.record('manager.role.administrator.role.addition',
458
                                        role=administered_role, admin_role=role)
518
            hooks.call_hooks(
519
                'event',
520
                name='manager-add-admin-role',
521
                user=self.request.user,
522
                role=administered_role,
523
                admin_role=role,
524
            )
525
            self.request.journal.record(
526
                'manager.role.administrator.role.addition', role=administered_role, admin_role=role
527
            )
459 528
        return super(RoleAddAdminRoleView, self).form_valid(form)
460 529

  
530

  
461 531
add_admin_role = RoleAddAdminRoleView.as_view()
462 532

  
463 533

  
464
class RoleRemoveAdminRoleView(views.TitleMixin, views.AjaxFormViewMixin,
465
                              SingleObjectMixin, views.PermissionMixin,
466
                              TemplateView):
534
class RoleRemoveAdminRoleView(
535
    views.TitleMixin, views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView
536
):
467 537
    title = _('Remove admin role')
468 538
    model = get_role_model()
469 539
    success_url = '../..'
......
482 552

  
483 553
    def post(self, request, *args, **kwargs):
484 554
        self.object.get_admin_role().remove_child(self.child)
485
        hooks.call_hooks('event', name='manager-remove-admin-role',
486
                         user=self.request.user, role=self.object, admin_role=self.child)
487
        self.request.journal.record('manager.role.administrator.role.removal',
488
                                    role=self.object, admin_role=self.child)
555
        hooks.call_hooks(
556
            'event',
557
            name='manager-remove-admin-role',
558
            user=self.request.user,
559
            role=self.object,
560
            admin_role=self.child,
561
        )
562
        self.request.journal.record(
563
            'manager.role.administrator.role.removal', role=self.object, admin_role=self.child
564
        )
489 565
        return redirect(self.request, self.success_url)
490 566

  
567

  
491 568
remove_admin_role = RoleRemoveAdminRoleView.as_view()
492 569

  
493 570

  
494
class RoleAddAdminUserView(views.AjaxFormViewMixin, views.TitleMixin,
495
                           views.PermissionMixin, views.FormNeedsRequest,
496
                           SingleObjectMixin, FormView):
571
class RoleAddAdminUserView(
572
    views.AjaxFormViewMixin,
573
    views.TitleMixin,
574
    views.PermissionMixin,
575
    views.FormNeedsRequest,
576
    SingleObjectMixin,
577
    FormView,
578
):
497 579
    title = _('Add admin user')
498 580
    model = get_role_model()
499 581
    form_class = forms.UsersForm
......
509 591
        administered_role = self.get_object()
510 592
        for user in form.cleaned_data['users']:
511 593
            administered_role.get_admin_role().members.add(user)
512
            hooks.call_hooks('event', name='manager-add-admin-role-user', user=self.request.user,
513
                             role=administered_role, admin=user)
514
            self.request.journal.record('manager.role.administrator.user.addition',
515
                                        role=administered_role, admin_user=user)
594
            hooks.call_hooks(
595
                'event',
596
                name='manager-add-admin-role-user',
597
                user=self.request.user,
598
                role=administered_role,
599
                admin=user,
600
            )
601
            self.request.journal.record(
602
                'manager.role.administrator.user.addition', role=administered_role, admin_user=user
603
            )
516 604
        return super(RoleAddAdminUserView, self).form_valid(form)
517 605

  
606

  
518 607
add_admin_user = RoleAddAdminUserView.as_view()
519 608

  
520 609

  
521
class RoleRemoveAdminUserView(views.TitleMixin, views.AjaxFormViewMixin,
522
                              SingleObjectMixin, views.PermissionMixin,
523
                              TemplateView):
610
class RoleRemoveAdminUserView(
611
    views.TitleMixin, views.AjaxFormViewMixin, SingleObjectMixin, views.PermissionMixin, TemplateView
612
):
524 613
    title = _('Remove admin user')
525 614
    model = get_role_model()
526 615
    success_url = '../..'
......
539 628

  
540 629
    def post(self, request, *args, **kwargs):
541 630
        self.object.get_admin_role().members.remove(self.user)
542
        hooks.call_hooks('event', name='remove-remove-admin-role-user', user=self.request.user,
543
                         role=self.object, admin=self.user)
544
        self.request.journal.record('manager.role.administrator.user.removal',
545
                                    role=self.object, admin_user=self.user)
631
        hooks.call_hooks(
632
            'event',
633
            name='remove-remove-admin-role-user',
634
            user=self.request.user,
635
            role=self.object,
636
            admin=self.user,
637
        )
638
        self.request.journal.record(
639
            'manager.role.administrator.user.removal', role=self.object, admin_user=self.user
640
        )
546 641
        return redirect(self.request, self.success_url)
547 642

  
643

  
548 644
remove_admin_user = RoleRemoveAdminUserView.as_view()
549 645

  
550 646

  
551
class RolesImportView(views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest,
552
                      FormView):
647
class RolesImportView(
648
    views.PermissionMixin, views.TitleMixin, views.MediaMixin, views.FormNeedsRequest, FormView
649
):
553 650
    form_class = forms.RolesImportForm
554 651
    model = get_role_model()
555 652
    template_name = 'authentic2/manager/import_form.html'
......
582 679
    def get_success_url(self):
583 680
        messages.success(
584 681
            self.request,
585
            _('Roles have been successfully imported inside "%s" organizational unit.') % self.ou
682
            _('Roles have been successfully imported inside "%s" organizational unit.') % self.ou,
586 683
        )
587 684
        return reverse('a2-manager-roles') + '?search-ou=%s' % self.ou.pk
588 685

  
......
604 701
        ctx['object'] = self.context
605 702
        return ctx
606 703

  
704

  
607 705
journal = RoleJournal.as_view()
608 706

  
609 707

  
src/authentic2/manager/service_views.py
31 31
    permissions = ['authentic2.search_service']
32 32
    title = _('Services')
33 33

  
34

  
34 35
listing = ServicesView.as_view()
35 36

  
36 37

  
37
class ServiceView(views.SimpleSubTableView, role_views.RoleViewMixin,
38
                  views.MediaMixin, views.FormNeedsRequest, views.FormView):
38
class ServiceView(
39
    views.SimpleSubTableView,
40
    role_views.RoleViewMixin,
41
    views.MediaMixin,
42
    views.FormNeedsRequest,
43
    views.FormView,
44
):
39 45
    search_form_class = forms.NameSearchForm
40 46
    model = Service
41 47
    pk_url_kwarg = 'service_pk'
......
63 69
        if self.can_change:
64 70
            if action == 'add':
65 71
                if self.object.authorized_roles.filter(pk=role.pk).exists():
66
                    messages.warning(self.request, _('Role already authorized in this '
67
                                     'service.'))
72
                    messages.warning(self.request, _('Role already authorized in this ' 'service.'))
68 73
                else:
69 74
                    self.object.add_authorized_role(role)
70 75
            elif action == 'remove':
......
92 97
    fields = ['name', 'slug', 'ou', 'unauthorized_url']
93 98
    success_url = '..'
94 99

  
100

  
95 101
edit = ServiceEditView.as_view()
src/authentic2/manager/tables.py
23 23
import django_tables2 as tables
24 24
from django_tables2.utils import A
25 25

  
26
from django_rbac.utils import get_role_model, get_permission_model, \
27
    get_ou_model
26
from django_rbac.utils import get_role_model, get_permission_model, get_ou_model
28 27

  
29 28
from authentic2.models import Service
30 29
from authentic2.middleware import StoreRequestMiddleware
......
52 51
        verified = user.email_verified
53 52
        value = user.email
54 53
        if value and verified:
55
            return html.format_html(
56
                    '<span class="verified">{value}</span>',
57
                    value=value)
54
            return html.format_html('<span class="verified">{value}</span>', value=value)
58 55
        return value
59 56

  
60 57

  
......
64 61
        value = super().render(**kwargs)
65 62
        if not user.is_active:
66 63
            value = html.format_html(
67
                '<span class="disabled">{value} ({disabled})</span>',
68
                value=value, disabled=_('disabled'))
64
                '<span class="disabled">{value} ({disabled})</span>', value=value, disabled=_('disabled')
65
            )
69 66
        return value
70 67

  
71 68

  
72

  
73 69
class UserTable(tables.Table):
74 70
    link = UserLinkColumn(
75 71
        viewname='a2-manager-user-detail',
......
77 73
        verbose_name=_('User'),
78 74
        accessor='get_full_name',
79 75
        order_by=('last_name', 'first_name', 'email', 'username'),
80
        kwargs={'pk': A('pk')})
76
        kwargs={'pk': A('pk')},
77
    )
81 78
    username = tables.Column()
82 79
    email = VerifiableEmailColumn()
83 80
    ou = tables.Column()
......
85 82
    class Meta:
86 83
        model = User
87 84
        attrs = {'class': 'main', 'id': 'user-table'}
88
        fields = ('username', 'email', 'first_name',
89
                  'last_name', 'ou')
85
        fields = ('username', 'email', 'first_name', 'last_name', 'ou')
90 86
        sequence = ('link', '...')
91 87
        empty_text = _('None')
92 88

  
93 89

  
94 90
class RoleMembersTable(UserTable):
95
    direct = tables.BooleanColumn(verbose_name=_('Direct member'),
96
                                  orderable=False)
91
    direct = tables.BooleanColumn(verbose_name=_('Direct member'), orderable=False)
97 92
    via = tables.TemplateColumn(
98 93
        '{% for role in record.via %}'
99 94
        '<a href="{% url "a2-manager-role-members" pk=role.pk %}">{{ role }}</a>{% if not forloop.last %}, {% endif %}'
100 95
        '{% endfor %}',
101
        verbose_name=_('Inherited from'), orderable=False)
96
        verbose_name=_('Inherited from'),
97
        orderable=False,
98
    )
102 99

  
103 100
    class Meta(UserTable.Meta):
104 101
        pass
105 102

  
106 103

  
107 104
class RoleTable(tables.Table):
108
    name = tables.LinkColumn(viewname='a2-manager-role-members',
109
                             kwargs={'pk': A('pk')},
110
                             accessor='name', verbose_name=_('label'))
105
    name = tables.LinkColumn(
106
        viewname='a2-manager-role-members', kwargs={'pk': A('pk')}, accessor='name', verbose_name=_('label')
107
    )
111 108
    ou = tables.Column()
112
    member_count = tables.Column(verbose_name=_('Direct member count'),
113
                                 orderable=False)
109
    member_count = tables.Column(verbose_name=_('Direct member count'), orderable=False)
114 110

  
115
    def render_name (self, record, bound_column):
111
    def render_name(self, record, bound_column):
116 112
        content = bound_column.column.render(record.name, record, bound_column)
117 113
        if not record.can_manage_members:
118 114
            content = SafeText('%s (%s)' % (content, _('LDAP')))
......
148 144

  
149 145

  
150 146
class OuUserRolesTable(tables.Table):
151
    name = tables.LinkColumn(viewname='a2-manager-role-members',
152
                             kwargs={'pk': A('pk')},
153
                             accessor='name', verbose_name=_('label'))
147
    name = tables.LinkColumn(
148
        viewname='a2-manager-role-members', kwargs={'pk': A('pk')}, accessor='name', verbose_name=_('label')
149
    )
154 150
    via = tables.TemplateColumn(
155 151
        '''{% for rel in record.via %}{{ rel.child }} {% if not forloop.last %}, {% endif %}{% endfor %}''',
156
        verbose_name=_('Inherited from'), orderable=False)
152
        verbose_name=_('Inherited from'),
153
        orderable=False,
154
    )
157 155
    member = tables.TemplateColumn(
158 156
        '{%% load i18n %%}<input class="role-member{%% if not record.member and record.via %%} '
159 157
        'indeterminate{%% endif %%}"'
......
161 159
        '{%% if not record.has_perm %%}disabled '
162 160
        'title="{%% trans "%s" %%}"{%% endif %%} '
163 161
        '{%% if not record.can_manage_members %%}disabled '
164
        '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.')),
162
        'title="{%% trans "%s" %%}"{%% endif %%}/>'
163
        % (
164
            ugettext_noop('You are not authorized to manage this role'),
165
            ugettext_noop('This role is synchronised from LDAP, changing members is not allowed.'),
166
        ),
165 167
        verbose_name=_('Member'),
166
        order_by=('member', 'via', 'name'))
168
        order_by=('member', 'via', 'name'),
169
    )
167 170

  
168
    def render_name (self, record, bound_column):
171
    def render_name(self, record, bound_column):
169 172
        content = bound_column.column.render(record.name, record, bound_column)
170 173
        if not record.can_manage_members:
171 174
            content = SafeText('%s (%s)' % (content, _('LDAP')))
......
180 183

  
181 184

  
182 185
class UserRolesTable(tables.Table):
183
    name = tables.LinkColumn(viewname='a2-manager-role-members',
184
                             kwargs={'pk': A('pk')},
185
                             accessor='name', verbose_name=_('label'))
186
    name = tables.LinkColumn(
187
        viewname='a2-manager-role-members', kwargs={'pk': A('pk')}, accessor='name', verbose_name=_('label')
188
    )
186 189
    ou = tables.Column()
187 190
    via = tables.TemplateColumn(
188 191
        '{% if not record.member %}{% for rel in record.child_relation.all %}'
189 192
        '{{ rel.child }} {% if not forloop.last %}, {% endif %}{% endfor %}'
190 193
        '{% endif %}',
191 194
        verbose_name=_('Inherited from'),
192
        orderable=False)
195
        orderable=False,
196
    )
193 197

  
194
    def render_name (self, record, bound_column):
198
    def render_name(self, record, bound_column):
195 199
        content = bound_column.column.render(record.name, record, bound_column)
196 200
        if not record.can_manage_members:
197 201
            content = SafeText('%s (%s)' % (content, _('LDAP')))
src/authentic2/manager/urls.py
29 29

  
30 30

  
31 31
urlpatterns = required(
32
    manager_login_required, [
32
    manager_login_required,
33
    [
33 34
        # homepage
34 35
        url(r'^$', views.homepage, name='a2-manager-homepage'),
35 36
        url(r'^me/$', user_views.me, name='a2-manager-me'),
36

  
37 37
        # Authentic2 users
38 38
        url(r'^users/$', user_views.users, name='a2-manager-users'),
39
        url(r'^users/export/(?P<format>csv)/$',
40
            user_views.users_export, name='a2-manager-users-export'),
41
        url(r'^users/add/$', user_views.user_add_default_ou,
42
            name='a2-manager-user-add-default-ou'),
43
        url(r'^users/add/choose-ou/$', user_views.user_add_choose_ou,
44
            name='a2-manager-user-add-choose-ou'),
45
        url(r'^users/import/$',
46
            user_views.user_imports, name='a2-manager-users-imports'),
47
        url(r'^users/import/(?P<uuid>[a-z0-9]+)/download/(?P<filename>.*)$',
48
            user_views.user_import, name='a2-manager-users-import-download'),
49
        url(r'^users/import/(?P<uuid>[a-z0-9]+)/$',
50
            user_views.user_import, name='a2-manager-users-import'),
51
        url(r'^users/import/(?P<import_uuid>[a-z0-9]+)/(?P<report_uuid>[a-z0-9]+)/$',
52
            user_views.user_import_report, name='a2-manager-users-import-report'),
53
        url(r'^users/(?P<ou_pk>\d+)/add/$', user_views.user_add,
54
            name='a2-manager-user-add'),
55
        url(r'^users/(?P<pk>\d+)/$', user_views.user_detail,
56
            name='a2-manager-user-detail'),
57
        url(r'^users/(?P<pk>\d+)/edit/$', user_views.user_edit,
58
            name='a2-manager-user-edit'),
59
        url(r'^users/(?P<pk>\d+)/delete/$', user_views.user_delete,
60
            name='a2-manager-user-delete'),
61
        url(r'^users/(?P<pk>\d+)/roles/$',
62
            user_views.roles,
63
            name='a2-manager-user-roles'),
64
        url(r'^users/(?P<pk>\d+)/change-password/$',
39
        url(r'^users/export/(?P<format>csv)/$', user_views.users_export, name='a2-manager-users-export'),
40
        url(r'^users/add/$', user_views.user_add_default_ou, name='a2-manager-user-add-default-ou'),
41
        url(r'^users/add/choose-ou/$', user_views.user_add_choose_ou, name='a2-manager-user-add-choose-ou'),
42
        url(r'^users/import/$', user_views.user_imports, name='a2-manager-users-imports'),
43
        url(
44
            r'^users/import/(?P<uuid>[a-z0-9]+)/download/(?P<filename>.*)$',
45
            user_views.user_import,
46
            name='a2-manager-users-import-download',
47
        ),
48
        url(r'^users/import/(?P<uuid>[a-z0-9]+)/$', user_views.user_import, name='a2-manager-users-import'),
49
        url(
50
            r'^users/import/(?P<import_uuid>[a-z0-9]+)/(?P<report_uuid>[a-z0-9]+)/$',
51
            user_views.user_import_report,
52
            name='a2-manager-users-import-report',
53
        ),
54
        url(r'^users/(?P<ou_pk>\d+)/add/$', user_views.user_add, name='a2-manager-user-add'),
55
        url(r'^users/(?P<pk>\d+)/$', user_views.user_detail, name='a2-manager-user-detail'),
56
        url(r'^users/(?P<pk>\d+)/edit/$', user_views.user_edit, name='a2-manager-user-edit'),
57
        url(r'^users/(?P<pk>\d+)/delete/$', user_views.user_delete, name='a2-manager-user-delete'),
58
        url(r'^users/(?P<pk>\d+)/roles/$', user_views.roles, name='a2-manager-user-roles'),
59
        url(
60
            r'^users/(?P<pk>\d+)/change-password/$',
65 61
            user_views.user_change_password,
66
            name='a2-manager-user-change-password'),
67
        url(r'^users/(?P<pk>\d+)/change-email/$',
62
            name='a2-manager-user-change-password',
63
        ),
64
        url(
65
            r'^users/(?P<pk>\d+)/change-email/$',
68 66
            user_views.user_change_email,
69
            name='a2-manager-user-change-email'),
70
        url(r'^users/(?P<pk>\d+)/su/$', user_views.su,
71
            name='a2-manager-user-su'),
72
        url(r'^users/(?P<pk>\d+)/authorizations/$',
67
            name='a2-manager-user-change-email',
68
        ),
69
        url(r'^users/(?P<pk>\d+)/su/$', user_views.su, name='a2-manager-user-su'),
70
        url(
71
            r'^users/(?P<pk>\d+)/authorizations/$',
73 72
            user_views.user_authorizations,
74
            name='a2-manager-user-authorizations'),
75
        url(r'^users/(?P<pk>\d+)/journal/$',
76
            user_views.user_journal,
77
            name='a2-manager-user-journal'),
73
            name='a2-manager-user-authorizations',
74
        ),
75
        url(r'^users/(?P<pk>\d+)/journal/$', user_views.user_journal, name='a2-manager-user-journal'),
78 76
        # by uuid
79
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/$', user_views.user_detail,
80
            name='a2-manager-user-by-uuid-detail'),
81
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/edit/$', user_views.user_edit,
82
            name='a2-manager-user-by-uuid-edit'),
83
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/roles/$',
84
            user_views.roles,
85
            name='a2-manager-user-by-uuid-roles'),
86
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/change-password/$',
77
        url(
78
            r'^users/uuid:(?P<slug>[a-z0-9]+)/$',
79
            user_views.user_detail,
80
            name='a2-manager-user-by-uuid-detail',
81
        ),
82
        url(
83
            r'^users/uuid:(?P<slug>[a-z0-9]+)/edit/$',
84
            user_views.user_edit,
85
            name='a2-manager-user-by-uuid-edit',
86
        ),
87
        url(
88
            r'^users/uuid:(?P<slug>[a-z0-9]+)/roles/$', user_views.roles, name='a2-manager-user-by-uuid-roles'
89
        ),
90
        url(
91
            r'^users/uuid:(?P<slug>[a-z0-9]+)/change-password/$',
87 92
            user_views.user_change_password,
88
            name='a2-manager-user-by-uuid-change-password'),
89
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/change-email/$',
93
            name='a2-manager-user-by-uuid-change-password',
94
        ),
95
        url(
96
            r'^users/uuid:(?P<slug>[a-z0-9]+)/change-email/$',
90 97
            user_views.user_change_email,
91
            name='a2-manager-user-by-uuid-change-email'),
92
        url(r'^users/uuid:(?P<slug>[a-z0-9]+)/journal/$',
98
            name='a2-manager-user-by-uuid-change-email',
99
        ),
100
        url(
101
            r'^users/uuid:(?P<slug>[a-z0-9]+)/journal/$',
93 102
            user_views.user_journal,
94
            name='a2-manager-user-journal'),
95

  
103
            name='a2-manager-user-journal',
104
        ),
96 105
        # Authentic2 roles
97
        url(r'^roles/$', role_views.listing,
98
            name='a2-manager-roles'),
99
        url(r'^roles/import/$', role_views.roles_import,
100
            name='a2-manager-roles-import'),
101
        url(r'^roles/add/$', role_views.add,
102
            name='a2-manager-role-add'),
103
        url(r'^roles/export/(?P<format>csv|json)/$',
104
            role_views.export, name='a2-manager-roles-export'),
105
        url(r'^roles/journal/$', role_views.roles_journal,
106
            name='a2-manager-roles-journal'),
107
        url(r'^roles/(?P<pk>\d+)/$', role_views.members,
108
            name='a2-manager-role-members'),
109
        url(r'^roles/(?P<pk>\d+)/add-child/$', role_views.add_child,
110
            name='a2-manager-role-add-child'),
111
        url(r'^roles/(?P<pk>\d+)/add-parent/$', role_views.add_parent,
112
            name='a2-manager-role-add-parent'),
113
        url(r'^roles/(?P<pk>\d+)/remove-child/(?P<child_pk>\d+)/$',
114
            role_views.remove_child, name='a2-manager-role-remove-child'),
115
        url(r'^roles/(?P<pk>\d+)/remove-parent/(?P<parent_pk>\d+)/$',
116
            role_views.remove_parent, name='a2-manager-role-remove-parent'),
117

  
118
        url(r'^roles/(?P<pk>\d+)/add-admin-user/$', role_views.add_admin_user,
119
            name='a2-manager-role-add-admin-user'),
120
        url(r'^roles/(?P<pk>\d+)/remove-admin-user/(?P<user_pk>\d+)/$',
121
            role_views.remove_admin_user, name='a2-manager-role-remove-admin-user'),
122

  
123
        url(r'^roles/(?P<pk>\d+)/add-admin-role/$', role_views.add_admin_role,
124
            name='a2-manager-role-add-admin-role'),
125
        url(r'^roles/(?P<pk>\d+)/remove-admin-role/(?P<role_pk>\d+)/$',
126
            role_views.remove_admin_role, name='a2-manager-role-remove-admin-role'),
127

  
128
        url(r'^roles/(?P<pk>\d+)/export/(?P<format>csv)/$',
106
        url(r'^roles/$', role_views.listing, name='a2-manager-roles'),
107
        url(r'^roles/import/$', role_views.roles_import, name='a2-manager-roles-import'),
108
        url(r'^roles/add/$', role_views.add, name='a2-manager-role-add'),
109
        url(r'^roles/export/(?P<format>csv|json)/$', role_views.export, name='a2-manager-roles-export'),
110
        url(r'^roles/journal/$', role_views.roles_journal, name='a2-manager-roles-journal'),
111
        url(r'^roles/(?P<pk>\d+)/$', role_views.members, name='a2-manager-role-members'),
112
        url(r'^roles/(?P<pk>\d+)/add-child/$', role_views.add_child, name='a2-manager-role-add-child'),
113
        url(r'^roles/(?P<pk>\d+)/add-parent/$', role_views.add_parent, name='a2-manager-role-add-parent'),
114
        url(
115
            r'^roles/(?P<pk>\d+)/remove-child/(?P<child_pk>\d+)/$',
116
            role_views.remove_child,
117
            name='a2-manager-role-remove-child',
118
        ),
119
        url(
120
            r'^roles/(?P<pk>\d+)/remove-parent/(?P<parent_pk>\d+)/$',
121
            role_views.remove_parent,
122
            name='a2-manager-role-remove-parent',
123
        ),
124
        url(
125
            r'^roles/(?P<pk>\d+)/add-admin-user/$',
126
            role_views.add_admin_user,
127
            name='a2-manager-role-add-admin-user',
128
        ),
129
        url(
130
            r'^roles/(?P<pk>\d+)/remove-admin-user/(?P<user_pk>\d+)/$',
131
            role_views.remove_admin_user,
132
            name='a2-manager-role-remove-admin-user',
133
        ),
134
        url(
135
            r'^roles/(?P<pk>\d+)/add-admin-role/$',
136
            role_views.add_admin_role,
137
            name='a2-manager-role-add-admin-role',
138
        ),
139
        url(
140
            r'^roles/(?P<pk>\d+)/remove-admin-role/(?P<role_pk>\d+)/$',
141
            role_views.remove_admin_role,
142
            name='a2-manager-role-remove-admin-role',
143
        ),
144
        url(
145
            r'^roles/(?P<pk>\d+)/export/(?P<format>csv)/$',
129 146
            role_views.members_export,
130
            name='a2-manager-role-members-export'),
131
        url(r'^roles/(?P<pk>\d+)/delete/$', role_views.delete,
132
            name='a2-manager-role-delete'),
133
        url(r'^roles/(?P<pk>\d+)/edit/$', role_views.edit,
134
            name='a2-manager-role-edit'),
135
        url(r'^roles/(?P<pk>\d+)/permissions/$', role_views.permissions,
136
            name='a2-manager-role-permissions'),
137
        url(r'^roles/(?P<pk>\d+)/journal/$', role_views.journal,
138
            name='a2-manager-role-journal'),
139

  
140

  
147
            name='a2-manager-role-members-export',
148
        ),
149
        url(r'^roles/(?P<pk>\d+)/delete/$', role_views.delete, name='a2-manager-role-delete'),
150
        url(r'^roles/(?P<pk>\d+)/edit/$', role_views.edit, name='a2-manager-role-edit'),
151
        url(r'^roles/(?P<pk>\d+)/permissions/$', role_views.permissions, name='a2-manager-role-permissions'),
152
        url(r'^roles/(?P<pk>\d+)/journal/$', role_views.journal, name='a2-manager-role-journal'),
141 153
        # Authentic2 organizational units
142
        url(r'^organizational-units/$', ou_views.listing,
143
            name='a2-manager-ous'),
144
        url(r'^organizational-units/add/$', ou_views.add,
145
            name='a2-manager-ou-add'),
146
        url(r'^organizational-units/(?P<pk>\d+)/$', ou_views.detail,
147
            name='a2-manager-ou-detail'),
148
        url(r'^organizational-units/(?P<pk>\d+)/edit/$', ou_views.edit,
149
            name='a2-manager-ou-edit'),
150
        url(r'^organizational-units/(?P<pk>\d+)/delete/$', ou_views.delete,
151
            name='a2-manager-ou-delete'),
152
        url(r'^organizational-units/export/(?P<format>json)/$',
153
            ou_views.export,
154
            name='a2-manager-ou-export'),
155
        url(r'^organizational-units/import/$',
156
            ou_views.ous_import,
157
            name='a2-manager-ous-import'),
158

  
154
        url(r'^organizational-units/$', ou_views.listing, name='a2-manager-ous'),
155
        url(r'^organizational-units/add/$', ou_views.add, name='a2-manager-ou-add'),
156
        url(r'^organizational-units/(?P<pk>\d+)/$', ou_views.detail, name='a2-manager-ou-detail'),
157
        url(r'^organizational-units/(?P<pk>\d+)/edit/$', ou_views.edit, name='a2-manager-ou-edit'),
158
        url(r'^organizational-units/(?P<pk>\d+)/delete/$', ou_views.delete, name='a2-manager-ou-delete'),
159
        url(r'^organizational-units/export/(?P<format>json)/$', ou_views.export, name='a2-manager-ou-export'),
160
        url(r'^organizational-units/import/$', ou_views.ous_import, name='a2-manager-ous-import'),
159 161
        # Services
160
        url(r'^services/$', service_views.listing,
161
            name='a2-manager-services'),
162
        url(r'^services/(?P<service_pk>\d+)/$', service_views.roles,
163
            name='a2-manager-service'),
164
        url(r'^services/(?P<service_pk>\d+)/edit/$', service_views.edit,
165
            name='a2-manager-service-edit'),
166

  
162
        url(r'^services/$', service_views.listing, name='a2-manager-services'),
163
        url(r'^services/(?P<service_pk>\d+)/$', service_views.roles, name='a2-manager-service'),
164
        url(r'^services/(?P<service_pk>\d+)/edit/$', service_views.edit, name='a2-manager-service-edit'),
167 165
        # Journal
168
        url(r'^journal/$', journal_views.journal,
169
            name='a2-manager-journal'),
170

  
166
        url(r'^journal/$', journal_views.journal, name='a2-manager-journal'),
171 167
        # backoffice menu as json
172 168
        url(r'^menu.json$', views.menu_json),
173

  
174 169
        # general management
175 170
        url(r'^site-export/$', views.site_export, name='a2-manager-site-export'),
176 171
        url(r'^site-import/$', views.site_import, name='a2-manager-site-import'),
177
    ]
172
    ],
178 173
)
179 174

  
180 175
urlpatterns += [
181
    url(r'^jsi18n/$',
176
    url(
177
        r'^jsi18n/$',
182 178
        JavaScriptCatalog.as_view(packages=['authentic2.manager']),
183
        name='a2-manager-javascript-catalog'),
179
        name='a2-manager-javascript-catalog',
180
    ),
184 181
    url(r'^select2.json$', views.select2, name='django_select2-json'),
185 182
]
src/authentic2/manager/user_import.py
44 44

  
45 45

  
46 46
def new_id():
47
    return (base64.b32encode(uuid.uuid4().bytes)
48
            .strip(b'=')
49
            .lower()
50
            .decode('ascii'))
47
    return base64.b32encode(uuid.uuid4().bytes).strip(b'=').lower().decode('ascii')
51 48

  
52 49

  
53 50
class UserImport(object):
......
224 221
                    data['tid'] = gettid()
225 222
                try:
226 223
                    with publik_provisionning():
227
                        importer.run(fd,
228
                                     encoding=self.data['encoding'],
229
                                     ou=self.data['ou'],
230
                                     simulate=simulate)
224
                        importer.run(
225
                            fd, encoding=self.data['encoding'], ou=self.data['ou'], simulate=simulate
226
                        )
231 227
                except Exception as e:
232 228
                    logger.exception('error during report %s:%s run', self.user_import.uuid, self.uuid)
233 229
                    state = self.STATE_ERROR
......
250 246
                    data['exception'] = exception
251 247
                    data['importer'] = importer
252 248
                    data['duration'] = duration
249

  
253 250
        t = threading.Thread(target=thread_worker)
254 251
        t.daemon = True
255 252
        if start:
......
286 283
        for name in os.listdir(self.user_import.path):
287 284
            if name.startswith(self.PREFIX):
288 285
                try:
289
                    yield self[name[len(self.PREFIX):]]
286
                    yield self[name[len(self.PREFIX) :]]
290 287
                except KeyError:
291 288
                    pass
src/authentic2/manager/user_views.py
47 47

  
48 48
from django_rbac.utils import get_role_model, get_role_parenting_model, get_ou_model
49 49

  
50
from .views import (BaseTableView, BaseAddView, BaseEditView, ActionMixin,
51
                    OtherActionsMixin, Action, ExportMixin, BaseSubTableView,
52
                    HideOUColumnMixin, BaseDeleteView, BaseDetailView,
53
                    TitleMixin, PermissionMixin, MediaMixin, FormNeedsRequest)
50
from .views import (
51
    BaseTableView,
52
    BaseAddView,
53
    BaseEditView,
54
    ActionMixin,
55
    OtherActionsMixin,
56
    Action,
57
    ExportMixin,
58
    BaseSubTableView,
59
    HideOUColumnMixin,
60
    BaseDeleteView,
61
    BaseDetailView,
62
    TitleMixin,
63
    PermissionMixin,
64
    MediaMixin,
65
    FormNeedsRequest,
66
)
54 67
from .tables import UserTable, UserRolesTable, OuUserRolesTable, UserAuthorizationsTable
55
from .forms import (UserSearchForm, UserAddForm, UserEditForm,
56
                    UserChangePasswordForm, ChooseUserRoleForm,
57
                    UserRoleSearchForm, UserChangeEmailForm, UserNewImportForm,
58
                    UserEditImportForm, ChooseUserAuthorizationsForm, UserAddChooseOUForm)
68
from .forms import (
69
    UserSearchForm,
70
    UserAddForm,
71
    UserEditForm,
72
    UserChangePasswordForm,
73
    ChooseUserRoleForm,
74
    UserRoleSearchForm,
75
    UserChangeEmailForm,
76
    UserNewImportForm,
77
    UserEditImportForm,
78
    ChooseUserAuthorizationsForm,
79
    UserAddChooseOUForm,
80
)
59 81
from .resources import UserResource
60 82
from .utils import get_ou_count, has_show_username
61 83
from .journal_views import BaseJournalView
......
74 96
    title = _('Users')
75 97

  
76 98
    def is_ou_specified(self):
77
        return self.search_form.is_valid() \
78
            and self.search_form.cleaned_data.get('ou')
99
        return self.search_form.is_valid() and self.search_form.cleaned_data.get('ou')
79 100

  
80 101
    def get_queryset(self):
81 102
        qs = super(UsersView, self).get_queryset()
......
106 127
        table = super(UsersView, self).get_table(**kwargs)
107 128
        if self.search_form.not_enough_chars():
108 129
            user_qs = self.search_form.filter_by_ou(self.get_queryset())
109
            table.empty_text = _('Enter at least %(limit)d characters '
110
                                 '(%(user_count)d users)') % {
130
            table.empty_text = _('Enter at least %(limit)d characters ' '(%(user_count)d users)') % {
111 131
                'limit': self.search_form.minimum_chars,
112 132
                'user_count': user_qs.count(),
113 133
            }
......
127 147
                self.can_add = False
128 148
        extra_actions = ctx['extra_actions'] = []
129 149
        if self.request.user.has_perm('custom_user.admin_user'):
130
            extra_actions.append({
131
                'url': reverse('a2-manager-users-imports'),
132
                'label': _('Import users'),
133
            })
150
            extra_actions.append(
151
                {
152
                    'url': reverse('a2-manager-users-imports'),
153
                    'label': _('Import users'),
154
                }
155
            )
134 156
        return ctx
135 157

  
136 158

  
......
150 172
        'password1',
151 173
        'password2',
152 174
        'reset_password_at_next_login',
153
        'send_mail']
175
        'send_mail',
176
    ]
154 177
    form_class = UserAddForm
155 178
    permissions = ['custom_user.add_user']
156 179
    template_name = 'authentic2/manager/user_add.html'
......
174 197
        if not self.ou.show_username:
175 198
            fields.remove('username')
176 199
        i = fields.index('generate_password')
177
        if self.request.user.is_superuser and \
178
                'is_superuser' not in self.fields:
200
        if self.request.user.is_superuser and 'is_superuser' not in self.fields:
179 201
            fields.insert(i, 'is_superuser')
180 202
            i += 1
181 203
        for attribute in Attribute.objects.all():
......
190 212
            include_post=True,
191 213
            replace={
192 214
                '$UUID': self.object.uuid,
193
            })
215
            },
216
        )
194 217

  
195 218
    def get_context_data(self, **kwargs):
196 219
        context = super(UserAddView, self).get_context_data(**kwargs)
197
        context['cancel_url'] = select_next_url(
198
            self.request,
199
            default='../..',
200
            field_name='cancel')
220
        context['cancel_url'] = select_next_url(self.request, default='../..', field_name='cancel')
201 221
        context['next'] = select_next_url(self.request, default=None, include_post=True)
202 222
        context['ou'] = self.ou
203 223
        context['duplicate_users'] = self.duplicate_users
......
219 239
                return self.form_invalid(form)
220 240

  
221 241
        response = super(UserAddView, self).form_valid(form)
222
        hooks.call_hooks('event', name='manager-add-user', user=self.request.user,
223
                         instance=form.instance, form=form)
242
        hooks.call_hooks(
243
            'event', name='manager-add-user', user=self.request.user, instance=form.instance, form=form
244
        )
224 245
        self.request.journal.record('manager.user.creation', form=form)
225 246
        return response
226 247

  
......
234 255
        value = ou.user_add_password_policy
235 256
        return ou.USER_ADD_PASSWD_POLICY_VALUES[value]._asdict()
236 257

  
258

  
237 259
user_add = UserAddView.as_view()
238 260

  
239 261

  
......
287 309
    def get_other_actions(self):
288 310
        for action in super(UserDetailView, self).get_other_actions():
289 311
            yield action
290
        yield Action('password_reset', _('Reset password'),
291
                     permission='custom_user.reset_password_user')
312
        yield Action('password_reset', _('Reset password'), permission='custom_user.reset_password_user')
292 313
        if self.object.is_active:
293
            yield Action('deactivate', _('Suspend'),
294
                         permission='custom_user.activate_user')
314
            yield Action('deactivate', _('Suspend'), permission='custom_user.activate_user')
295 315
        else:
296
            yield Action('activate', _('Activate'),
297
                         permission='custom_user.activate_user')
316
            yield Action('activate', _('Activate'), permission='custom_user.activate_user')
298 317
        if PasswordReset.objects.filter(user=self.object).exists():
299
            yield Action('delete_password_reset', _('Do not force password change on next login'),
300
                         permission='custom_user.reset_password_user')
318
            yield Action(
319
                'delete_password_reset',
320
                _('Do not force password change on next login'),
321
                permission='custom_user.reset_password_user',
322
            )
301 323
        else:
302
            yield Action('force_password_change', _('Force password change on '
303
                         'next login'),
304
                         permission='custom_user.reset_password_user')
305
        yield Action('change_password', _('Change user password'),
306
                     url_name='a2-manager-user-change-password',
307
                     permission='custom_user.change_password_user')
324
            yield Action(
325
                'force_password_change',
326
                _('Force password change on ' 'next login'),
327
                permission='custom_user.reset_password_user',
328
            )
329
        yield Action(
330
            'change_password',
331
            _('Change user password'),
332
            url_name='a2-manager-user-change-password',
333
            permission='custom_user.change_password_user',
334
        )
308 335
        if self.request.user.is_superuser:
309
            yield Action('su', _('Impersonate this user'),
310
                         url_name='a2-manager-user-su')
336
            yield Action('su', _('Impersonate this user'), url_name='a2-manager-user-su')
311 337
        if self.object.ou and self.object.ou.validate_emails:
312
            yield Action('change_email', _('Change user email'),
313
                         url_name='a2-manager-user-change-email',
314
                         permission='custom_user.change_email_user')
338
            yield Action(
339
                'change_email',
340
                _('Change user email'),
341
                url_name='a2-manager-user-change-email',
342
                permission='custom_user.change_email_user',
343
            )
315 344

  
316 345
    def action_force_password_change(self, request, *args, **kwargs):
317 346
        PasswordReset.objects.get_or_create(user=self.object)
......
324 353

  
325 354
    def action_deactivate(self, request, *args, **kwargs):
326 355
        if request.user == self.object:
327
            messages.warning(request, _('You cannot desactivate your own '
328
                             'user'))
356
            messages.warning(request, _('You cannot desactivate your own ' 'user'))
329 357
        else:
330 358
            self.object.mark_as_inactive()
331 359
            request.journal.record('manager.user.deactivation', target_user=self.object)
......
333 361
    def action_password_reset(self, request, *args, **kwargs):
334 362
        user = self.object
335 363
        if not user.email:
336
            messages.info(request, _('User has no email, it\'not possible to '
337
                                     'send him am email to reset its '
338
                                     'password'))
364
            messages.info(
365
                request,
366
                _('User has no email, it\'not possible to ' 'send him am email to reset its ' 'password'),
367
            )
339 368
            return
340 369
        send_password_reset_mail(user, request=request)
341 370
        messages.info(request, _('A mail was sent to %s') % self.object.email)
......
346 375
        request.journal.record('manager.user.password.change.unforce', target_user=self.object)
347 376

  
348 377
    def action_su(self, request, *args, **kwargs):
349
        return redirect(request, 'auth_logout',
350
                        params={REDIRECT_FIELD_NAME: switch_user.build_url(self.object)})
378
        return redirect(
379
            request, 'auth_logout', params={REDIRECT_FIELD_NAME: switch_user.build_url(self.object)}
380
        )
351 381

  
352 382
    # Copied from PasswordResetForm implementation
353
    def send_mail(self, subject_template_name, email_template_name,
354
                  context, to_email):
383
    def send_mail(self, subject_template_name, email_template_name, context, to_email):
355 384
        """
356 385
        Sends a django.core.mail.EmailMultiAlternatives to `to_email`.
357 386
        """
......
371 400
            if attribute.name == 'address_autocomplete':
372 401
                continue
373 402
            fields.append(attribute.name)
374
        if self.request.user.is_superuser and \
375
                'is_superuser' not in self.fields:
403
        if self.request.user.is_superuser and 'is_superuser' not in self.fields:
376 404
            fields.append('is_superuser')
377 405
        return fields
378 406

  
......
410 438
        # show modify roles button only if something is possible
411 439
        kwargs['can_change_roles'] = self.has_perm_on_roles(self.request.user, self.object)
412 440
        user_data = []
413
        user_data += [data for datas in hooks.call_hooks('manager_user_data', self, self.object)
414
                      for data in datas]
441
        user_data += [
442
            data for datas in hooks.call_hooks('manager_user_data', self, self.object) for data in datas
443
        ]
415 444
        kwargs['user_data'] = user_data
416 445
        ctx = super(UserDetailView, self).get_context_data(**kwargs)
417 446
        return ctx
418 447

  
448

  
419 449
user_detail = UserDetailView.as_view()
420 450

  
421 451

  
......
437 467
            fields.append('email')
438 468
        for attribute in Attribute.objects.all():
439 469
            fields.append(attribute.name)
440
        if self.request.user.is_superuser and \
441
                'is_superuser' not in self.fields:
470
        if self.request.user.is_superuser and 'is_superuser' not in self.fields:
442 471
            fields.append('is_superuser')
443 472
        return fields
444 473

  
......
446 475
        return select_next_url(
447 476
            self.request,
448 477
            default=reverse('a2-manager-user-detail', kwargs={'pk': self.object.pk}),
449
            include_post=True)
478
            include_post=True,
479
        )
450 480

  
451 481
    def get_context_data(self, **kwargs):
452 482
        context = super(UserEditView, self).get_context_data(**kwargs)
......
464 494
            self.object.save()
465 495
        response = super(UserEditView, self).form_valid(form)
466 496
        if form.has_changed():
467
            hooks.call_hooks('event', name='manager-edit-user', user=self.request.user,
468
                             instance=form.instance, form=form)
497
            hooks.call_hooks(
498
                'event', name='manager-edit-user', user=self.request.user, instance=form.instance, form=form
499
            )
469 500
            self.request.journal.record('manager.user.profile.edit', form=form)
470 501
        return response
471 502

  
503

  
472 504
user_edit = UserEditView.as_view()
473 505

  
474 506

  
......
491 523
        headers = fields + tuple(['attribute_%s' % attr for attr in attributes])
492 524

  
493 525
        at_mapping = {a.id: a for a in Attribute.objects.all()}
494
        avs = AttributeValue.objects.filter(
495
            content_type=ContentType.objects.get_for_model(get_user_model()))\
496
            .filter(attribute__disabled=False).values()
526
        avs = (
527
            AttributeValue.objects.filter(content_type=ContentType.objects.get_for_model(get_user_model()))
528
            .filter(attribute__disabled=False)
529
            .values()
530
        )
497 531

  
498 532
        user_attrs = collections.defaultdict(dict)
499 533
        for av in avs:
......
551 585

  
552 586
    def form_valid(self, form):
553 587
        response = super(UserChangePasswordView, self).form_valid(form)
554
        hooks.call_hooks('event', name='manager-change-password', user=self.request.user,
555
                         instance=form.instance, form=form)
588
        hooks.call_hooks(
589
            'event', name='manager-change-password', user=self.request.user, instance=form.instance, form=form
590
        )
556 591
        self.request.journal.record('manager.user.password.change', form=form)
557 592
        return response
558 593

  
......
583 618
            user=self.request.user,
584 619
            instance=form.instance,
585 620
            form=form,
586
            email=new_email)
621
            email=new_email,
622
        )
587 623
        return response
588 624

  
625

  
589 626
user_change_email = UserChangeEmailView.as_view()
590 627

  
591 628

  
......
618 655

  
619 656
    def is_ou_specified(self):
620 657
        '''Differentiate view of all user's roles from view of roles by OU'''
621
        return (self.search_form.is_valid()
622
                and self.search_form.cleaned_data.get('ou_filter') != 'all')
658
        return self.search_form.is_valid() and self.search_form.cleaned_data.get('ou_filter') != 'all'
623 659

  
624 660
    def get_table_queryset(self):
625 661
        if self.is_ou_specified():
......
629 665
            RoleParenting = get_role_parenting_model()
630 666
            rp_qs = RoleParenting.objects.filter(child__in=roles)
631 667
            qs = Role.objects.all()
632
            qs = qs.prefetch_related(models.Prefetch(
633
                'child_relation', queryset=rp_qs, to_attr='via'))
634
            qs = qs.prefetch_related(models.Prefetch(
635
                'members', queryset=User.objects.filter(pk=self.object.pk),
636
                to_attr='member'))
668
            qs = qs.prefetch_related(models.Prefetch('child_relation', queryset=rp_qs, to_attr='via'))
669
            qs = qs.prefetch_related(
670
                models.Prefetch('members', queryset=User.objects.filter(pk=self.object.pk), to_attr='member')
671
            )
637 672
            qs2 = self.request.user.filter_by_perm('a2_rbac.manage_members_role', qs)
638 673
            managable_ids = [str(pk) for pk in qs2.values_list('pk', flat=True)]
639 674
            qs = qs.extra(select={'has_perm': 'a2_rbac_role.id in (%s)' % ', '.join(managable_ids)})
......
662 697
        if action == 'add':
663 698
            if user.roles.filter(pk=role.pk):
664 699
                messages.warning(
665
                    self.request,
666
                    _('User {user} has already the role {role}.')
667
                    .format(user=user, role=role))
700
                    self.request, _('User {user} has already the role {role}.').format(user=user, role=role)
701
                )
668 702
            else:
669 703
                user.roles.add(role)
670
                hooks.call_hooks('event', name='manager-add-role-member',
671
                                 user=self.request.user, role=role, member=user)
704
                hooks.call_hooks(
705
                    'event', name='manager-add-role-member', user=self.request.user, role=role, member=user
706
                )
672 707
                self.request.journal.record('manager.role.membership.grant', member=user, role=role)
673 708
        elif action == 'remove':
674 709
            if user.roles.filter(pk=role.pk).exists():
675 710
                user.roles.remove(role)
676
                hooks.call_hooks('event', name='manager-remove-role-member', user=self.request.user,
677
                                 role=role, member=user)
711
                hooks.call_hooks(
712
                    'event', name='manager-remove-role-member', user=self.request.user, role=role, member=user
713
                )
678 714
                self.request.journal.record('manager.role.membership.removal', member=user, role=role)
679 715
        return super(UserRolesView, self).form_valid(form)
680 716

  
......
684 720
        kwargs['user'] = self.object
685 721
        kwargs['role_members_from_ou'] = app_settings.ROLE_MEMBERS_FROM_OU
686 722
        kwargs['show_all_ou'] = app_settings.SHOW_ALL_OU
687
        kwargs['queryset'] = self.request.user.filter_by_perm('a2_rbac.view_role', get_role_model().objects.all())
723
        kwargs['queryset'] = self.request.user.filter_by_perm(
724
            'a2_rbac.view_role', get_role_model().objects.all()
725
        )
688 726
        if self.object.ou_id:
689 727
            initial = kwargs.setdefault('initial', {})
690 728
            initial['ou'] = str(self.object.ou_id)
......
711 749
    def delete(self, request, *args, **kwargs):
712 750
        request.journal.record('manager.user.deletion', target_user=self.object)
713 751
        response = super().delete(request, *args, **kwargs)
714
        hooks.call_hooks('event', name='manager-delete-user', user=request.user,
715
                         instance=self.object)
752
        hooks.call_hooks('event', name='manager-delete-user', user=request.user, instance=self.object)
716 753
        return response
717 754

  
718 755

  
......
745 782
        from authentic2.manager import user_import
746 783

  
747 784
        ctx = super(UserImportsView, self).get_context_data(**kwargs)
748
        ctx['imports'] = sorted(user_import.UserImport.all(), key=operator.attrgetter('created'), reverse=True)
785
        ctx['imports'] = sorted(
786
            user_import.UserImport.all(), key=operator.attrgetter('created'), reverse=True
787
        )
749 788
        help_columns = []
750 789
        field_columns = ['username', 'email', 'first_name', 'last_name']
751 790
        key = 'username'
......
756 795
            field = User._meta.get_field(field_column)
757 796
            if Attribute.objects.filter(name=field.name).exists():
758 797
                continue
759
            help_columns.append({
760
                'label': field.verbose_name,
761
                'name': field.name,
762
                'key': field.name == key,
763
            })
798
            help_columns.append(
799
                {
800
                    'label': field.verbose_name,
801
                    'name': field.name,
802
                    'key': field.name == key,
803
                }
804
            )
764 805
        for attribute in Attribute.objects.all():
765 806
            kind = attribute.get_kind()
766 807
            if not kind.get('csv_importable', True):
767 808
                continue
768
            help_columns.append({
769
                'label': attribute.label,
770
                'name': attribute.name,
771
                'key': attribute.name == key,
772
            })
773
        help_columns.append({
774
            'label': _('Password hash'),
775
            'name': 'password_hash',
776
            'key': False,
777
        })
809
            help_columns.append(
810
                {
811
                    'label': attribute.label,
812
                    'name': attribute.name,
813
                    'key': attribute.name == key,
814
                }
815
            )
816
        help_columns.append(
817
            {
818
                'label': _('Password hash'),
819
                'name': 'password_hash',
820
                'key': False,
821
            }
822
        )
778 823
        ctx['help_columns'] = help_columns
779
        example_data = u','.join(column['name'] + (' key' if column['key'] else '') for column in help_columns) + '\n'
780
        example_url = 'data:text/csv;base64,%s' % base64.b64encode(example_data.encode('utf-8')).decode('ascii')
824
        example_data = (
825
            u','.join(column['name'] + (' key' if column['key'] else '') for column in help_columns) + '\n'
826
        )
827
        example_url = 'data:text/csv;base64,%s' % base64.b64encode(example_data.encode('utf-8')).decode(
828
            'ascii'
829
        )
781 830
        ctx['form'].fields['import_file'].help_text = format_html(
782 831
            _('{0}. {1} <a download="{3}" href="{2}">{3}</a>'),
783 832
            ctx['form'].fields['import_file'].help_text,
784 833
            _('ex.:'),
785 834
            example_url,
786
            _('users.csv'))
835
            _('users.csv'),
836
        )
787 837
        return ctx
788 838

  
839

  
789 840
user_imports = UserImportsView.as_view()
790 841

  
791 842

  
......
797 848

  
798 849
    def dispatch(self, request, uuid, **kwargs):
799 850
        from authentic2.manager.user_import import UserImport
851

  
800 852
        self.user_import = UserImport(uuid)
801 853
        if not self.user_import.exists():
802 854
            raise Http404
......
846 898
        ctx['reports'] = sorted(self.user_import.reports, key=operator.attrgetter('created'), reverse=True)
847 899
        return ctx
848 900

  
901

  
849 902
user_import = UserImportView.as_view()
850 903

  
851 904

  
......
857 910

  
858 911
    def dispatch(self, request, import_uuid, report_uuid):
859 912
        from authentic2.manager.user_import import UserImport
913

  
860 914
        self.user_import = UserImport(import_uuid)
861 915
        if not self.user_import.exists():
862 916
            raise Http404
......
876 930
            ctx['report_title'] = _('Execution')
877 931
        return ctx
878 932

  
933

  
879 934
user_import_report = UserImportReportView.as_view()
880 935

  
881 936

  
......
893 948
    duration = 30  # seconds
894 949

  
895 950
    class Media:
896
        js = (
897
            'authentic2/js/js_seconds_until.js',
898
        )
951
        js = ('authentic2/js/js_seconds_until.js',)
899 952

  
900 953
    def dispatch(self, request, *args, **kwargs):
901 954
        if not request.user.is_superuser:
......
906 959
        ctx = super(UserSuView, self).get_context_data(**kwargs)
907 960
        ctx['su_url'] = make_url(
908 961
            'auth_logout',
909

  
910 962
            params={REDIRECT_FIELD_NAME: switch_user.build_url(self.object, self.duration)},
911 963
            request=self.request,
912
            absolute=True)
964
            absolute=True,
965
        )
913 966
        ctx['duration'] = self.duration
914 967
        return ctx
915 968

  
969

  
916 970
su = UserSuView.as_view()
917 971

  
918 972

  
919
class UserAuthorizationsView(FormNeedsRequest, BaseFormView, SingleObjectMixin,
920
                             BaseTableView, PermissionMixin):
973
class UserAuthorizationsView(
974
    FormNeedsRequest, BaseFormView, SingleObjectMixin, BaseTableView, PermissionMixin
975
):
921 976
    permissions = ['custom_user.view_user']
922 977
    template_name = 'authentic2/manager/user_authorizations.html'
923 978
    title = pgettext_lazy('manager', 'Consent Management')
......
929 984

  
930 985
    @property
931 986
    def can_manage_authorizations(self):
932
        return self.request.user.has_perm(
933
            'custom_user.manage_authorizations_user', self.get_object())
987
        return self.request.user.has_perm('custom_user.manage_authorizations_user', self.get_object())
934 988

  
935 989
    def get_table_data(self):
936 990
        qs = OIDCAuthorization.objects.filter(user=self.get_object())
......
948 1002
                self.request.journal.record(
949 1003
                    'manager.user.sso.authorization.deletion',
950 1004
                    service=oidc_authorization.client,
951
                    target_user=self.object)
1005
                    target_user=self.object,
1006
                )
952 1007
        return response
953 1008

  
954 1009

  
src/authentic2/manager/views.py
23 23
from django.core.exceptions import PermissionDenied, ValidationError
24 24
from django.db import transaction
25 25
from django.views.generic.base import ContextMixin
26
from django.views.generic import (FormView, UpdateView, CreateView, DeleteView, TemplateView,
27
                                  DetailView, View)
26
from django.views.generic import FormView, UpdateView, CreateView, DeleteView, TemplateView, DetailView, View
28 27
from django.views.generic.detail import SingleObjectMixin
29 28
from django.views.generic.edit import FormMixin
30 29
from django.http import HttpResponse, Http404
......
62 61

  
63 62
class MultipleOUMixin(object):
64 63
    '''Tell templates if there are multiple OU for adaptation in breadcrumbs for example'''
64

  
65 65
    def get_context_data(self, **kwargs):
66 66
        kwargs['multiple_ou'] = utils.get_ou_count() > 1
67 67
        return super(MultipleOUMixin, self).get_context_data(**kwargs)
......
70 70
@six.add_metaclass(MediaMixinBase)
71 71
class MediaMixin(object):
72 72
    '''Expose needed CSS and JS files as a media object'''
73

  
73 74
    class Media:
74 75
        js = (
75 76
            xstatic('jquery.js', 'jquery.min.js'),
......
81 82
            'authentic2/js/purl.js',
82 83
            'authentic2/manager/js/manager.js',
83 84
        )
84
        css = {
85
            'all': (
86
                'authentic2/manager/css/style.css',
87
            )
88
        }
85
        css = {'all': ('authentic2/manager/css/style.css',)}
89 86

  
90 87
    def get_context_data(self, **kwargs):
91 88
        kwargs['media'] = self.media
......
97 94

  
98 95
class PermissionMixin(object):
99 96
    '''Control access to views based on permissions'''
97

  
100 98
    permissions = None
101 99
    permissions_global = False
102 100

  
......
106 104
            model_name = self.model._meta.model_name
107 105
            add_perm = '%s.add_%s' % (app_label, model_name)
108 106
            self.can_add = request.user.has_perm_any(add_perm)
109
            if hasattr(self, 'get_object') \
110
                    and ((hasattr(self, 'pk_url_kwarg')
111
                          and self.pk_url_kwarg in self.kwargs)
112
                         or (hasattr(self, 'slug_url_kwarg')
113
                             and self.slug_url_kwarg in self.kwargs)):
107
            if hasattr(self, 'get_object') and (
108
                (hasattr(self, 'pk_url_kwarg') and self.pk_url_kwarg in self.kwargs)
109
                or (hasattr(self, 'slug_url_kwarg') and self.slug_url_kwarg in self.kwargs)
110
            ):
114 111
                self.object = self.get_object()
115 112
                permissions = ('view', 'change', 'delete', 'manage_members')
116 113
                for permission in permissions:
117 114
                    perm = '%s.%s_%s' % (app_label, permission, model_name)
118
                    setattr(self, 'can_' + permission,
119
                            request.user.has_perm(perm, self.object))
120
                if self.permissions \
121
                        and not request.user.has_perms(
122
                            self.permissions, self.object):
115
                    setattr(self, 'can_' + permission, request.user.has_perm(perm, self.object))
116
                if self.permissions and not request.user.has_perms(self.permissions, self.object):
123 117
                    raise PermissionDenied
124
            elif self.permissions \
125
                    and not request.user.has_perm_any(self.permissions):
118
            elif self.permissions and not request.user.has_perm_any(self.permissions):
126 119
                raise PermissionDenied
127 120
        else:
128 121
            if self.permissions:
......
167 160

  
168 161

  
169 162
class SearchFormMixin(object):
170
    '''Handle a search form on the current table view.
163
    """Handle a search form on the current table view.
171 164

  
172
       The search form class must implement a .filter(qs) method returning a new queryset.'''
165
    The search form class must implement a .filter(qs) method returning a new queryset."""
173 166

  
174 167
    search_form_class = None
175 168

  
......
219 212

  
220 213
class Action(object):
221 214
    '''Describe an action for view supporting multiples actions.'''
215

  
222 216
    name = None
223 217
    title = None
224 218
    confirm = None
......
227 221
    popup = True
228 222
    permission = None
229 223

  
230
    def __init__(self, name=None, title=None, confirm=None, url_name=None, url=None,
231
                 popup=None, permission=None):
224
    def __init__(
225
        self, name=None, title=None, confirm=None, url_name=None, url=None, popup=None, permission=None
226
    ):
232 227
        if name is not None:
233 228
            self.name = name
234 229
        if title is not None:
......
252 247

  
253 248
class AjaxFormViewMixin(object):
254 249
    '''Implement a JSON response for view which can be included in an AJAX popup'''
250

  
255 251
    success_url = '.'
256 252

  
257 253
    def dispatch(self, request, *args, **kwargs):
258
        response = super(AjaxFormViewMixin, self).dispatch(request, *args,
259
                                                           **kwargs)
254
        response = super(AjaxFormViewMixin, self).dispatch(request, *args, **kwargs)
260 255
        return self.return_ajax_response(request, response)
261 256

  
262 257
    def return_ajax_response(self, request, response):
......
268 263
            # empty location means that the view can be used from anywhere
269 264
            # and so the redirect URL should not be used
270 265
            # otherwise compute an absolute URI from the relative URI
271
            if location and (not location.startswith('http://')
272
                             or not location.startswith('https://')
273
                             or not location.startswith('/')):
266
            if location and (
267
                not location.startswith('http://')
268
                or not location.startswith('https://')
269
                or not location.startswith('/')
270
            ):
274 271
                location = request.build_absolute_uri(location)
275 272
            data['location'] = location
276 273
        if hasattr(response, 'render'):
......
281 278

  
282 279
class TitleMixin(object):
283 280
    '''Mixin to provide a title to the view's template'''
281

  
284 282
    title = ''
285 283

  
286 284
    def get_context_data(self, **kwargs):
......
292 290

  
293 291
class ActionMixin(object):
294 292
    '''Describe the main action implementd by a view'''
293

  
295 294
    action = None
296 295

  
297 296
    def get_context_data(self, **kwargs):
......
303 302

  
304 303
class OtherActionsMixin(object):
305 304
    '''Describe secondary actions possible on a view'''
305

  
306 306
    other_actions = None
307 307

  
308 308
    def get_context_data(self, **kwargs):
......
336 336
                    method = getattr(self, 'action_' + action.name, None)
337 337
                    if method:
338 338
                        response = method(request, *args, **kwargs)
339
                hooks.call_hooks('event', name='manager-action', user=self.request.user,
340
                                 action=action, instance=self.object)
339
                hooks.call_hooks(
340
                    'event',
341
                    name='manager-action',
342
                    user=self.request.user,
343
                    action=action,
344
                    instance=self.object,
345
                )
341 346
                if response:
342 347
                    return response
343 348
                self.request.method = 'GET'
......
350 355

  
351 356
class ExportMixin(object):
352 357
    '''Help in implementd export views'''
358

  
353 359
    http_method_names = ['get', 'head', 'options']
354 360
    export_prefix = ''
355 361

  
......
379 385

  
380 386
    def export_response(self, content, content_type, export_format):
381 387
        response = HttpResponse(content, content_type=content_type)
382
        filename = '%s%s.%s' % (self.get_export_prefix(), now().strftime('%Y%m%d_%H%M%S'),
383
                                export_format)
384
        response['Content-Disposition'] = 'attachment; filename="%s"' \
385
            % filename
388
        filename = '%s%s.%s' % (self.get_export_prefix(), now().strftime('%Y%m%d_%H%M%S'), export_format)
389
        response['Content-Disposition'] = 'attachment; filename="%s"' % filename
386 390
        return response
387 391

  
388 392

  
389 393
class FormNeedsRequest(object):
390

  
391 394
    def get_form_kwargs(self):
392 395
        kwargs = super(FormNeedsRequest, self).get_form_kwargs()
393 396
        if getattr(self.get_form_class(), 'need_request', False):
......
418 421
    def get_table(self, **kwargs):
419 422
        table = super(TableHookMixin, self).get_table(**kwargs)
420 423
        import copy
424

  
421 425
        table = copy.deepcopy(table)
422 426
        hooks.call_hooks('manager_modify_table', self, table)
423 427
        return table
424 428

  
425 429

  
426
class BaseTableView(MultipleOUMixin, TitleMixin, TableHookMixin, FormatsContextData, ModelNameMixin,
427
                    PermissionMixin, SearchFormMixin, FilterQuerysetByPermMixin, TableQuerysetMixin,
428
                    SingleTableView):
430
class BaseTableView(
431
    MultipleOUMixin,
432
    TitleMixin,
433
    TableHookMixin,
434
    FormatsContextData,
435
    ModelNameMixin,
436
    PermissionMixin,
437
    SearchFormMixin,
438
    FilterQuerysetByPermMixin,
439
    TableQuerysetMixin,
440
    SingleTableView,
441
):
429 442
    '''Base class for views showing a table of objects'''
443

  
430 444
    pass
431 445

  
432 446

  
433
class SubTableViewMixin(TableHookMixin, FormatsContextData, ModelNameMixin, PermissionMixin,
434
                        SearchFormMixin, FilterTableQuerysetByPermMixin,
435
                        TableQuerysetMixin, SingleObjectMixin,
436
                        SingleTableMixin, ContextMixin):
447
class SubTableViewMixin(
448
    TableHookMixin,
449
    FormatsContextData,
450
    ModelNameMixin,
451
    PermissionMixin,
452
    SearchFormMixin,
453
    FilterTableQuerysetByPermMixin,
454
    TableQuerysetMixin,
455
    SingleObjectMixin,
456
    SingleTableMixin,
457
    ContextMixin,
458
):
437 459
    '''Helper class for views showing a table of objects related to one object'''
460

  
438 461
    context_object_name = 'object'
439 462
    paginate_by = None
440 463

  
......
445 468
    pass
446 469

  
447 470

  
448
class BaseSubTableView(MultipleOUMixin, TitleMixin, SubTableViewMixin,
449
                       FormNeedsRequest, FormView):
471
class BaseSubTableView(MultipleOUMixin, TitleMixin, SubTableViewMixin, FormNeedsRequest, FormView):
450 472
    '''Base class for views showing a table of objects related to one object'''
473

  
451 474
    success_url = '.'
452 475

  
453 476

  
454
class BaseDeleteView(TitleMixin, ModelNameMixin, PermissionMixin,
455
                     AjaxFormViewMixin, DeleteView):
477
class BaseDeleteView(TitleMixin, ModelNameMixin, PermissionMixin, AjaxFormViewMixin, DeleteView):
456 478
    '''Base class for views implementing deletion of an object'''
479

  
457 480
    template_name = 'authentic2/manager/delete.html'
458 481
    context_object_name = 'object'
459 482

  
......
473 496

  
474 497
class ModelFormView(MediaMixin, FormNeedsRequest):
475 498
    '''Base class for views showing a form for a model'''
499

  
476 500
    fields = None
477 501
    form_class = None
478 502

  
......
480 504
        return self.fields
481 505

  
482 506
    def get_form_class(self):
483
        return modelform_factory(self.model, form=self.form_class,
484
                                 fields=self.get_fields())
507
        return modelform_factory(self.model, form=self.form_class, fields=self.get_fields())
485 508

  
486 509
    def get_form(self, form_class=None):
487 510
        form = super(ModelFormView, self).get_form(form_class=form_class)
......
489 512
        return form
490 513

  
491 514

  
492
class BaseDetailView(MultipleOUMixin, TitleMixin, ModelNameMixin, PermissionMixin, ModelFormView,
493
                     DetailView):
515
class BaseDetailView(MultipleOUMixin, TitleMixin, ModelNameMixin, PermissionMixin, ModelFormView, DetailView):
494 516
    context_object_name = 'object'
495 517
    form_class = None
496 518

  
......
521 543
        return ctx
522 544

  
523 545

  
524
class BaseAddView(MultipleOUMixin, TitleMixin, ModelNameMixin, PermissionMixin,
525
                  AjaxFormViewMixin, ModelFormView, CreateView):
546
class BaseAddView(
547
    MultipleOUMixin, TitleMixin, ModelNameMixin, PermissionMixin, AjaxFormViewMixin, ModelFormView, CreateView
548
):
526 549
    '''Base class for views for adding an instance of a model'''
550

  
527 551
    template_name = 'authentic2/manager/form.html'
528 552
    success_view_name = None
529 553
    context_object_name = 'object'
......
542 566
        return reverse(self.success_view_name, kwargs={'pk': self.object.pk})
543 567

  
544 568

  
545
class BaseEditView(MultipleOUMixin, SuccessMessageMixin, TitleMixin, ModelNameMixin,
546
                   PermissionMixin, AjaxFormViewMixin, ModelFormView, UpdateView):
569
class BaseEditView(
570
    MultipleOUMixin,
571
    SuccessMessageMixin,
572
    TitleMixin,
573
    ModelNameMixin,
574
    PermissionMixin,
575
    AjaxFormViewMixin,
576
    ModelFormView,
577
    UpdateView,
578
):
547 579
    '''Base class for views for editing an instance of a model'''
580

  
548 581
    template_name = 'authentic2/manager/form.html'
549 582
    context_object_name = 'object'
550 583

  
......
564 597

  
565 598
class HomepageView(TitleMixin, PermissionMixin, MediaMixin, TemplateView):
566 599
    template_name = 'authentic2/manager/homepage.html'
567
    permissions = ['a2_rbac.search_role', 'a2_rbac.search_organizationalunit',
568
                   'auth.search_group', 'custom_user.search_user']
600
    permissions = [
601
        'a2_rbac.search_role',
602
        'a2_rbac.search_organizationalunit',
603
        'auth.search_group',
604
        'custom_user.search_user',
605
    ]
569 606
    default_entries = [
570 607
        {
571 608
            'class': 'icon-organizational-units',
......
609 646
    def get_homepage_entries(self):
610 647
        entries = []
611 648
        for hook_entries in itertools.chain(
612
                self.default_entries,
613
                hooks.call_hooks('manager_homepage_entries', self)):
649
            self.default_entries, hooks.call_hooks('manager_homepage_entries', self)
650
        ):
614 651
            if not hasattr(hook_entries, 'append'):
615 652
                hook_entries = [hook_entries]
616 653
            for entry in hook_entries:
......
640 677
        for entry in self.get_homepage_entries():
641 678
            if entry.get('slug') == 'journal':
642 679
                continue
643
            menu_entries.append({
644
                'label': six.text_type(entry['label']),
645
                'slug': entry.get('slug', ''),
646
                'url': request.build_absolute_uri(six.text_type(entry['href'])),
647
            })
680
            menu_entries.append(
681
                {
682
                    'label': six.text_type(entry['label']),
683
                    'slug': entry.get('slug', ''),
684
                    'url': request.build_absolute_uri(six.text_type(entry['href'])),
685
                }
686
            )
648 687
        return menu_entries
649 688

  
650 689

  
......
657 696
    def get_table(self, **kwargs):
658 697
        OU = get_ou_model()
659 698
        exclude_ou = False
660
        if (hasattr(self, 'search_form')
661
                and self.search_form.is_valid()
662
                and self.search_form.cleaned_data.get('ou') is not None):
699
        if (
700
            hasattr(self, 'search_form')
701
            and self.search_form.is_valid()
702
            and self.search_form.cleaned_data.get('ou') is not None
703
        ):
663 704
            exclude_ou = True
664 705
        if OU.objects.count() < 2:
665 706
            exclude_ou = True
......
685 726
        if not widget_class or not hasattr(widgets, widget_class):
686 727
            raise Http404('Missing or unknown widget class.')
687 728
        widget = getattr(widgets, widget_class)()
688
        if not isinstance(widget, (widgets.SimpleModelSelect2Widget, widgets.SimpleModelSelect2MultipleWidget)):
729
        if not isinstance(
730
            widget, (widgets.SimpleModelSelect2Widget, widgets.SimpleModelSelect2MultipleWidget)
731
        ):
689 732
            raise Http404('Reference to invalid widget class')
690 733
        qs = widget.get_queryset()
691 734
        qs.query.where = pickle.loads(base64.b64decode(field_data['where_clause']))
......
701 744

  
702 745

  
703 746
class SiteExport(View):
704

  
705 747
    def get(self, request, *args, **kwargs):
706 748
        if not request.user.is_superuser:
707 749
            raise PermissionDenied
708
        return HttpResponse(
709
            json.dumps(export_site(), indent=4), content_type='application/json')
750
        return HttpResponse(json.dumps(export_site(), indent=4), content_type='application/json')
710 751

  
711 752

  
712 753
site_export = SiteExport.as_view()
src/authentic2/manager/widgets.py
76 76
class SearchUserWidgetMixin(SplitTermMixin):
77 77
    model = get_user_model()
78 78
    search_fields = [
79
        'username__icontains', 'first_name__icontains',
80
        'last_name__icontains', 'email__icontains'
79
        'username__icontains',
80
        'first_name__icontains',
81
        'last_name__icontains',
82
        'email__icontains',
81 83
    ]
82 84

  
83 85
    def label_from_instance(self, user):
......
104 106
    def label_from_instance(self, obj):
105 107
        label = six.text_type(obj)
106 108
        if obj.ou and utils.get_ou_count() > 1:
107
            label = u'{ou} - {obj}'.format(
108
                ou=obj.ou, obj=obj)
109
            label = u'{ou} - {obj}'.format(ou=obj.ou, obj=obj)
109 110
        return label
110 111

  
111 112

  
src/authentic2/managers.py
35 35
    def get_by_natural_key(self, slug):
36 36
        return self.get(slug=slug)
37 37

  
38

  
38 39
GetBySlugManager = GetBySlugQuerySet.as_manager
39 40

  
40 41

  
......
42 43
    def get_by_natural_key(self, name):
43 44
        return self.get(name=name)
44 45

  
46

  
45 47
GetByNameManager = GetByNameQuerySet.as_manager
46 48

  
47 49

  
......
62 64
        content_type = ContentType.objects.get_for_model(model)
63 65
        return self.filter(content_type=content_type, object_id=model.pk)
64 66

  
67

  
65 68
GenericManager = models.Manager.from_queryset(GenericQuerySet)
66 69

  
67 70

  
......
72 75

  
73 76
    def get_by_natural_key(self, ct_nk, owner_nk, attribute_nk):
74 77
        from .models import Attribute, AttributeValue
78

  
75 79
        try:
76 80
            ct = ContentType.objects.get_by_natural_key(*ct_nk)
77 81
        except ContentType.DoesNotExist:
src/authentic2/middleware.py
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17 17
import time
18

  
18 19
try:
19 20
    import threading
20 21
except ImportError:
......
60 61
        else:
61 62
            domain = app_settings.A2_OPENED_SESSION_COOKIE_DOMAIN
62 63
        if hasattr(request, 'user') and request.user.is_authenticated:
63
            response.set_cookie(name, value='1', max_age=None, domain=domain,
64
                                secure=app_settings.A2_OPENED_SESSION_COOKIE_SECURE)
64
            response.set_cookie(
65
                name,
66
                value='1',
67
                max_age=None,
68
                domain=domain,
69
                secure=app_settings.A2_OPENED_SESSION_COOKIE_SECURE,
70
            )
65 71
        elif app_settings.A2_OPENED_SESSION_COOKIE_NAME in request.COOKIES:
66 72
            response.delete_cookie(name, domain=domain)
67 73
        return response
......
129 135

  
130 136

  
131 137
class XForwardedForMiddleware(MiddlewareMixin):
132
    '''Copy the first address from X-Forwarded-For header to the REMOTE_ADDR meta.
138
    """Copy the first address from X-Forwarded-For header to the REMOTE_ADDR meta.
139

  
140
    This middleware should only be used if you are sure the header cannot be
141
    forged (behind a reverse proxy for example)."""
133 142

  
134
       This middleware should only be used if you are sure the header cannot be
135
       forged (behind a reverse proxy for example).'''
136 143
    def process_request(self, request):
137 144
        if 'HTTP_X_FORWARDED_FOR' in request.META:
138 145
            request.META['REMOTE_ADDR'] = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0].strip()
......
140 147

  
141 148

  
142 149
class DisplayMessageBeforeRedirectMiddleware(MiddlewareMixin):
143
    '''Verify if messages are currently stored and if there is a redirection to another domain, in
144
       this case show an intermediate page.
145
    '''
150
    """Verify if messages are currently stored and if there is a redirection to another domain, in
151
    this case show an intermediate page.
152
    """
153

  
146 154
    def process_response(self, request, response):
147 155
        # Check if response is a redirection
148 156
        if response.status_code not in (301, 302, 303, 307, 308):
......
157 165
        if not parsed_url.scheme and not parsed_url.netloc:
158 166
            return response
159 167
        parsed_request_url = urlparse.urlparse(request.build_absolute_uri())
160
        if (parsed_request_url.scheme == parsed_url.scheme or not parsed_url.scheme) and \
161
                (parsed_request_url.netloc == parsed_url.netloc):
168
        if (parsed_request_url.scheme == parsed_url.scheme or not parsed_url.scheme) and (
169
            parsed_request_url.netloc == parsed_url.netloc
170
        ):
162 171
            return response
163 172
        # Check if there is some messages to show
164 173
        storage = messages.get_messages(request)
......
206 215
    def middleware(request):
207 216
        request.journal = journal.Journal(request=request)
208 217
        return get_response(request)
218

  
209 219
    return middleware
210 220

  
211 221

  
......
226 236
                return http.HttpResponseBadRequest('null character in form data')
227 237

  
228 238
        return get_response(request)
239

  
229 240
    return middleware
src/authentic2/migrations/0001_initial.py
15 15
        migrations.CreateModel(
16 16
            name='Attribute',
17 17
            fields=[
18
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
                (
19
                    'id',
20
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
21
                ),
19 22
                ('label', models.CharField(unique=True, max_length=63, verbose_name='label')),
20 23
                ('description', models.TextField(verbose_name='description', blank=True)),
21 24
                ('name', models.SlugField(unique=True, max_length=256, verbose_name='name')),
22 25
                ('required', models.BooleanField(blank=True, default=False, verbose_name='required')),
23
                ('asked_on_registration', models.BooleanField(blank=True, default=False, verbose_name='asked on registration')),
24
                ('user_editable', models.BooleanField(blank=True, default=False, verbose_name='user editable')),
26
                (
27
                    'asked_on_registration',
28
                    models.BooleanField(blank=True, default=False, verbose_name='asked on registration'),
29
                ),
30
                (
31
                    'user_editable',
32
                    models.BooleanField(blank=True, default=False, verbose_name='user editable'),
33
                ),
25 34
                ('user_visible', models.BooleanField(blank=True, default=False, verbose_name='user visible')),
26 35
                ('multiple', models.BooleanField(blank=True, default=False, verbose_name='multiple')),
27
                ('kind', models.CharField(max_length=16, verbose_name='kind', choices=[('string', '<django.utils.functional.__proxy__ object at 0x303d350>')])),
36
                (
37
                    'kind',
38
                    models.CharField(
39
                        max_length=16,
40
                        verbose_name='kind',
41
                        choices=[('string', '<django.utils.functional.__proxy__ object at 0x303d350>')],
42
                    ),
43
                ),
28 44
            ],
29 45
            options={
30 46
                'verbose_name': 'attribute definition',
......
35 51
        migrations.CreateModel(
36 52
            name='AttributeValue',
37 53
            fields=[
38
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
54
                (
55
                    'id',
56
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
57
                ),
39 58
                ('object_id', models.PositiveIntegerField(verbose_name='object identifier')),
40 59
                ('content', models.TextField(verbose_name='content')),
41
                ('attribute', models.ForeignKey(verbose_name='attribute', to='authentic2.Attribute', on_delete=models.CASCADE)),
42
                ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE)),
60
                (
61
                    'attribute',
62
                    models.ForeignKey(
63
                        verbose_name='attribute', to='authentic2.Attribute', on_delete=models.CASCADE
64
                    ),
65
                ),
66
                (
67
                    'content_type',
68
                    models.ForeignKey(
69
                        verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE
70
                    ),
71
                ),
43 72
            ],
44 73
            options={
45 74
                'verbose_name': 'attribute value',
......
50 79
        migrations.CreateModel(
51 80
            name='AuthenticationEvent',
52 81
            fields=[
53
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
82
                (
83
                    'id',
84
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
85
                ),
54 86
                ('when', models.DateTimeField(auto_now=True, verbose_name='when')),
55 87
                ('who', models.CharField(max_length=80, verbose_name='who')),
56 88
                ('how', models.CharField(max_length=32, verbose_name='how')),
......
65 97
        migrations.CreateModel(
66 98
            name='DeletedUser',
67 99
            fields=[
68
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
100
                (
101
                    'id',
102
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
103
                ),
69 104
                ('creation', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
70 105
                ('user', models.ForeignKey(verbose_name='user', to='auth.User', on_delete=models.CASCADE)),
71 106
            ],
......
78 113
        migrations.CreateModel(
79 114
            name='FederatedId',
80 115
            fields=[
81
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
116
                (
117
                    'id',
118
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
119
                ),
82 120
                ('provider', models.CharField(max_length=255, verbose_name='provider')),
83 121
                ('about', models.CharField(max_length=255, verbose_name='about')),
84 122
                ('service', models.CharField(max_length=255, verbose_name='service')),
......
94 132
        migrations.CreateModel(
95 133
            name='LogoutUrl',
96 134
            fields=[
97
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
98
                ('logout_url', models.URLField(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', blank=True)),
99
                ('logout_use_iframe', models.BooleanField(default=False, verbose_name='use an iframe instead of an img tag for logout')),
100
                ('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", verbose_name='iframe logout timeout (ms)')),
135
                (
136
                    'id',
137
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
138
                ),
139
                (
140
                    'logout_url',
141
                    models.URLField(
142
                        help_text='you can use a {} to pass the URL of the success icon, ex.: http://example.com/logout?next={}',
143
                        max_length=255,
144
                        null=True,
145
                        verbose_name='url',
146
                        blank=True,
147
                    ),
148
                ),
149
                (
150
                    'logout_use_iframe',
151
                    models.BooleanField(
152
                        default=False, verbose_name='use an iframe instead of an img tag for logout'
153
                    ),
154
                ),
155
                (
156
                    'logout_use_iframe_timeout',
157
                    models.PositiveIntegerField(
158
                        default=300,
159
                        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",
160
                        verbose_name='iframe logout timeout (ms)',
161
                    ),
162
                ),
101 163
                ('object_id', models.PositiveIntegerField(verbose_name='object identifier')),
102
                ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE)),
164
                (
165
                    'content_type',
166
                    models.ForeignKey(
167
                        verbose_name='content type', to='contenttypes.ContentType', on_delete=models.CASCADE
168
                    ),
169
                ),
103 170
            ],
104 171
            options={
105 172
                'verbose_name': 'logout URL',
......
110 177
        migrations.CreateModel(
111 178
            name='PasswordReset',
112 179
            fields=[
113
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
180
                (
181
                    'id',
182
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
183
                ),
114 184
                ('user', models.ForeignKey(verbose_name='user', to='auth.User', on_delete=models.CASCADE)),
115 185
            ],
116 186
            options={
......
122 192
        migrations.CreateModel(
123 193
            name='UserExternalId',
124 194
            fields=[
125
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
195
                (
196
                    'id',
197
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
198
                ),
126 199
                ('source', models.URLField(max_length=256, verbose_name='source')),
127 200
                ('external_id', models.CharField(max_length=256, verbose_name='external id')),
128 201
                ('created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
src/authentic2/migrations/0003_auto_20150409_1840.py
16 16
        migrations.AlterField(
17 17
            model_name='deleteduser',
18 18
            name='user',
19
            field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
19
            field=models.ForeignKey(
20
                verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
21
            ),
20 22
            preserve_default=True,
21 23
        ),
22 24
        migrations.AlterField(
23 25
            model_name='passwordreset',
24 26
            name='user',
25
            field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
27
            field=models.ForeignKey(
28
                verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
29
            ),
26 30
            preserve_default=True,
27 31
        ),
28 32
        migrations.AlterField(
29 33
            model_name='userexternalid',
30 34
            name='user',
31
            field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
35
            field=models.ForeignKey(
36
                verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
37
            ),
32 38
            preserve_default=True,
33 39
        ),
34 40
    ]
src/authentic2/migrations/0004_service.py
14 14
        migrations.CreateModel(
15 15
            name='Service',
16 16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17
                (
18
                    'id',
19
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
20
                ),
18 21
                ('name', models.CharField(max_length=128, verbose_name='name')),
19 22
                ('slug', models.SlugField(max_length=128, verbose_name='slug')),
20 23
            ],
src/authentic2/migrations/0005_service_ou.py
16 16
        migrations.AddField(
17 17
            model_name='service',
18 18
            name='ou',
19
            field=models.ForeignKey(blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE),
19
            field=models.ForeignKey(
20
                blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE
21
            ),
20 22
            preserve_default=True,
21 23
        ),
22 24
        migrations.AlterUniqueTogether(
src/authentic2/migrations/0006_conditional_slug_index.py
4 4
from django.db import migrations
5 5
from authentic2.migrations import CreatePartialIndexes
6 6

  
7

  
7 8
class Migration(migrations.Migration):
8 9

  
9 10
    dependencies = [
......
11 12
    ]
12 13

  
13 14
    operations = [
14
        CreatePartialIndexes('Service', 'authentic2_service',
15
                             'authentic2_service_uniq_idx', ('ou_id',),
16
                             ('slug',)),
15
        CreatePartialIndexes(
16
            'Service', 'authentic2_service', 'authentic2_service_uniq_idx', ('ou_id',), ('slug',)
17
        ),
17 18
    ]
src/authentic2/migrations/0007_auto_20150523_0028.py
15 15
        migrations.AlterField(
16 16
            model_name='service',
17 17
            name='ou',
18
            field=models.ForeignKey(verbose_name='organizational unit', blank=True, to=settings.RBAC_OU_MODEL, null=True, on_delete=models.CASCADE),
18
            field=models.ForeignKey(
19
                verbose_name='organizational unit',
20
                blank=True,
21
                to=settings.RBAC_OU_MODEL,
22
                null=True,
23
                on_delete=models.CASCADE,
24
            ),
19 25
            preserve_default=True,
20 26
        ),
21 27
    ]
src/authentic2/migrations/0008_auto_20160204_1415.py
15 15
        migrations.AlterField(
16 16
            model_name='passwordreset',
17 17
            name='user',
18
            field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, unique=True, on_delete=models.CASCADE),
18
            field=models.ForeignKey(
19
                verbose_name='user', to=settings.AUTH_USER_MODEL, unique=True, on_delete=models.CASCADE
20
            ),
19 21
            preserve_default=True,
20 22
        ),
21 23
    ]
src/authentic2/migrations/0009_auto_20160211_2247.py
3 3

  
4 4
from django.db import models, migrations
5 5

  
6

  
6 7
def deduplicate_attribute_values(apps, schema_editor):
7 8
    AttributeValue = apps.get_model('authentic2', 'AttributeValue')
8 9
    seen = set()
src/authentic2/migrations/0012_auto_20160211_2255.py
15 15
        migrations.AlterField(
16 16
            model_name='attributevalue',
17 17
            name='multiple',
18
            field=models.NullBooleanField(default=False) if django.VERSION < (2,) else models.BooleanField(default=False, null=True),
18
            field=models.NullBooleanField(default=False)
19
            if django.VERSION < (2,)
20
            else models.BooleanField(default=False, null=True),
19 21
        ),
20 22
    ]
src/authentic2/migrations/0013_auto_20160211_2258.py
17 17
            name='attributevalue',
18 18
            unique_together=set([('content_type', 'object_id', 'attribute', 'multiple', 'content')]),
19 19
        ),
20
        CreatePartialIndexes('AttributeValue', 'authentic2_attributevalue',
21
                             'authentic2_attribute_value_partial_unique_idx',
22
                             (), ('content_type_id', 'object_id', 'attribute_id'),
23
                             where=(('multiple = %s', (False,)),))
20
        CreatePartialIndexes(
21
            'AttributeValue',
22
            'authentic2_attributevalue',
23
            'authentic2_attribute_value_partial_unique_idx',
24
            (),
25
            ('content_type_id', 'object_id', 'attribute_id'),
26
            where=(('multiple = %s', (False,)),),
27
        ),
24 28
    ]
src/authentic2/migrations/0015_auto_20160621_1711.py
15 15
        migrations.AlterField(
16 16
            model_name='passwordreset',
17 17
            name='user',
18
            field=models.OneToOneField(verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
18
            field=models.OneToOneField(
19
                verbose_name='user', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
20
            ),
19 21
        ),
20 22
    ]
src/authentic2/migrations/0018_auto_20170524_0842.py
16 16
        migrations.CreateModel(
17 17
            name='AuthorizedRole',
18 18
            fields=[
19
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19
                (
20
                    'id',
21
                    models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True),
22
                ),
20 23
                ('role', models.ForeignKey(to=settings.RBAC_ROLE_MODEL, on_delete=models.CASCADE)),
21 24
            ],
22 25
        ),
23 26
        migrations.AddField(
24 27
            model_name='service',
25 28
            name='unauthorized_url',
26
            field=models.URLField(max_length=256, null=True, verbose_name='callback url when unauthorized', blank=True),
29
            field=models.URLField(
30
                max_length=256, null=True, verbose_name='callback url when unauthorized', blank=True
31
            ),
27 32
        ),
28 33
        migrations.AddField(
29 34
            model_name='authorizedrole',
......
33 38
        migrations.AddField(
34 39
            model_name='service',
35 40
            name='authorized_roles',
36
            field=models.ManyToManyField(related_name='allowed_services', verbose_name='authorized services', to=settings.RBAC_ROLE_MODEL, through='authentic2.AuthorizedRole', blank=True),
41
            field=models.ManyToManyField(
42
                related_name='allowed_services',
43
                verbose_name='authorized services',
44
                to=settings.RBAC_ROLE_MODEL,
45
                through='authentic2.AuthorizedRole',
46
                blank=True,
47
            ),
37 48
        ),
38 49
    ]
src/authentic2/migrations/0021_attribute_order.py
19 19
        ),
20 20
        migrations.AlterModelOptions(
21 21
            name='attribute',
22
            options={'base_manager_name': 'all_objects', 'ordering': ('order', 'id'), 'verbose_name': 'attribute definition', 'verbose_name_plural': 'attribute definitions'},
22
            options={
23
                'base_manager_name': 'all_objects',
24
                'ordering': ('order', 'id'),
25
                'verbose_name': 'attribute definition',
26
                'verbose_name_plural': 'attribute definitions',
27
            },
23 28
        ),
24 29
        migrations.AlterModelManagers(
25 30
            name='attribute',
src/authentic2/migrations/0022_attribute_scopes.py
14 14
        migrations.AddField(
15 15
            model_name='attribute',
16 16
            name='scopes',
17
            field=models.CharField(default='', help_text='scopes separated by spaces', max_length=256, verbose_name='scopes', blank=True),
17
            field=models.CharField(
18
                default='',
19
                help_text='scopes separated by spaces',
20
                max_length=256,
21
                verbose_name='scopes',
22
                blank=True,
23
            ),
18 24
        ),
19 25
    ]
src/authentic2/migrations/0024_auto_20190617_1113.py
15 15
    operations = [
16 16
        migrations.CreateModel(
17 17
            name='LDAPUser',
18
            fields=[
19
            ],
18
            fields=[],
20 19
            options={
21 20
                'proxy': True,
22 21
            },
src/authentic2/migrations/0025_auto_20191009_1047.py
17 17
        migrations.AlterField(
18 18
            model_name='deleteduser',
19 19
            name='user',
20
            field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='deletion', to=settings.AUTH_USER_MODEL, verbose_name='user'),
20
            field=models.OneToOneField(
21
                on_delete=django.db.models.deletion.CASCADE,
22
                related_name='deletion',
23
                to=settings.AUTH_USER_MODEL,
24
                verbose_name='user',
25
            ),
21 26
        ),
22 27
    ]
src/authentic2/migrations/0026_token.py
17 17
        migrations.CreateModel(
18 18
            name='Token',
19 19
            fields=[
20
                ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='Identifier')),
20
                (
21
                    'uuid',
22
                    models.UUIDField(
23
                        default=uuid.uuid4,
24
                        editable=False,
25
                        primary_key=True,
26
                        serialize=False,
27
                        verbose_name='Identifier',
28
                    ),
29
                ),
21 30
                ('kind', models.CharField(max_length=32, verbose_name='Kind')),
22
                ('content', django.contrib.postgres.fields.jsonb.JSONField(blank=True, verbose_name='Content')),
31
                (
32
                    'content',
33
                    django.contrib.postgres.fields.jsonb.JSONField(blank=True, verbose_name='Content'),
34
                ),
23 35
                ('created', models.DateTimeField(verbose_name='Creation date', auto_now_add=True)),
24 36
                ('expires', models.DateTimeField(verbose_name='Expires')),
25 37
            ],
src/authentic2/migrations/0027_remove_deleteduser.py
9 9
    DeletedUser = apps.get_model('authentic2', 'DeletedUser')
10 10
    User = apps.get_model('custom_user', 'User')
11 11
    User.objects.update(
12
        deleted=models.Subquery(DeletedUser.objects.filter(user_id=models.OuterRef('id')).values_list('creation')[:1]))
12
        deleted=models.Subquery(
13
            DeletedUser.objects.filter(user_id=models.OuterRef('id')).values_list('creation')[:1]
14
        )
15
    )
13 16

  
14 17

  
15 18
class Migration(migrations.Migration):
src/authentic2/migrations/0028_trigram_unaccent_index.py
17 17
                "CREATE OR REPLACE FUNCTION public.immutable_unaccent(text) RETURNS varchar AS $$ "
18 18
                "SELECT public.unaccent('public.unaccent',$1::text); $$ LANGUAGE 'sql' IMMUTABLE",
19 19
                "CREATE INDEX custom_user_name_gist_idx ON custom_user_user USING gist "
20
                "(LOWER(public.immutable_unaccent(first_name || ' ' || last_name)) public.gist_trgm_ops)"
20
                "(LOWER(public.immutable_unaccent(first_name || ' ' || last_name)) public.gist_trgm_ops)",
21 21
            ],
22 22
            reverse_sql=[
23 23
                "DROP INDEX IF EXISTS custom_user_name_gist_idx",
src/authentic2/migrations/0029_auto_20201013_1614.py
14 14
    operations = [
15 15
        migrations.AlterModelOptions(
16 16
            name='attributevalue',
17
            options={'ordering': ('attribute__order', 'id'), 'verbose_name': 'attribute value', 'verbose_name_plural': 'attribute values'},
17
            options={
18
                'ordering': ('attribute__order', 'id'),
19
                'verbose_name': 'attribute value',
20
                'verbose_name_plural': 'attribute values',
21
            },
18 22
        ),
19 23
    ]
src/authentic2/migrations/0030_clean_admin_tools_tables.py
9 9
def clean_admin_tools_tables(apps, schema_editor):
10 10
    try:
11 11
        with schema_editor.connection.cursor() as cursor:
12
            cursor.execute("SELECT table_name FROM information_schema.tables "
13
                           "WHERE table_schema = current_schema() "
14
                           "AND table_name LIKE 'admin_tools%'")
12
            cursor.execute(
13
                "SELECT table_name FROM information_schema.tables "
14
                "WHERE table_schema = current_schema() "
15
                "AND table_name LIKE 'admin_tools%'"
16
            )
15 17
            rows = cursor.fetchall()
16
            for table_name, in rows:
18
            for (table_name,) in rows:
17 19
                cursor.execute('DROP TABLE "%s" CASCADE' % table_name)
18 20
    except Exception:
19 21
        logging.getLogger(__name__).exception('migration authentic2.0030 failed')
src/authentic2/migrations/0031_add_search_vector_to_attributes.py
6 6
def create_trigger(apps, schema_editor):
7 7
    with schema_editor.connection.cursor() as cursor:
8 8
        cursor.execute('SHOW default_text_search_config')
9
        default_text_search_config, = cursor.fetchone()
10
        cursor.execute('''CREATE OR REPLACE FUNCTION authentic2_update_atv_search_vector() RETURNS TRIGGER AS $$
9
        (default_text_search_config,) = cursor.fetchone()
10
        cursor.execute(
11
            '''CREATE OR REPLACE FUNCTION authentic2_update_atv_search_vector() RETURNS TRIGGER AS $$
11 12
BEGIN
12 13
    IF TG_OP = 'INSERT' OR (TG_OP = 'UPDATE' AND NEW.content <> OLD.content) THEN
13 14
        NEW.search_vector = to_tsvector(NEW.content);
14 15
    END IF;
15 16
    RETURN NEW;
16
END; $$ LANGUAGE plpgsql''')
17
        cursor.execute('''CREATE TRIGGER authentic2_attributevalue_search_vector_trigger
17
END; $$ LANGUAGE plpgsql'''
18
        )
19
        cursor.execute(
20
            '''CREATE TRIGGER authentic2_attributevalue_search_vector_trigger
18 21
BEFORE INSERT OR UPDATE OF content
19 22
ON authentic2_attributevalue
20
FOR EACH ROW EXECUTE PROCEDURE authentic2_update_atv_search_vector()''')
23
FOR EACH ROW EXECUTE PROCEDURE authentic2_update_atv_search_vector()'''
24
        )
21 25

  
22 26

  
23 27
def drop_trigger(apps, schema_editor):
24 28
    with schema_editor.connection.cursor() as cursor:
25
        cursor.execute('DROP TRIGGER IF EXISTS authentic2_attributevalue_search_vector_trigger ON authentic2_attributevalue')
29
        cursor.execute(
30
            'DROP TRIGGER IF EXISTS authentic2_attributevalue_search_vector_trigger ON authentic2_attributevalue'
31
        )
26 32
        cursor.execute('DROP FUNCTION IF EXISTS authentic2_update_atv_search_vector')
27 33

  
28 34

  
......
39 45
        ),
40 46
        migrations.AddIndex(
41 47
            model_name='attributevalue',
42
            index=django.contrib.postgres.indexes.GinIndex(fields=['search_vector'], name='authentic2_atv_tsvector_idx')
48
            index=django.contrib.postgres.indexes.GinIndex(
49
                fields=['search_vector'], name='authentic2_atv_tsvector_idx'
50
            ),
43 51
        ),
44 52
        migrations.RunPython(create_trigger, drop_trigger),
45

  
46 53
    ]
src/authentic2/migrations/__init__.py
7 7
class CreatePartialIndexes(Operation):
8 8
    reversible = True
9 9

  
10
    def __init__(self, model_name, table_name, index_name, nullable_columns, non_null_columns,
11
                 null_columns=None, where=None):
10
    def __init__(
11
        self,
12
        model_name,
13
        table_name,
14
        index_name,
15
        nullable_columns,
16
        non_null_columns,
17
        null_columns=None,
18
        where=None,
19
    ):
12 20
        self.model_name = model_name
13 21
        self.table_name = table_name
14 22
        self.index_name = index_name
......
58 66
                        clause, params = clause
59 67
                        assert isinstance(clause, six.string_types)
60 68
                        assert isinstance(params, tuple)
61
                        clause = clause % tuple(schema_editor.quote_value(param) for param in
62
                                                 params)
69
                        clause = clause % tuple(schema_editor.quote_value(param) for param in params)
63 70
                    assert isinstance(clause, six.string_types)
64 71
                    clauses.append(clause)
65 72
                where_clause = ' AND '.join(clauses)
66 73
                # SQLite does not accept parameters in partial index creations, don't ask why :/
67
                schema_editor.execute('CREATE UNIQUE INDEX "%s_%s" ON %s (%s) WHERE %s' %
68
                                      (self.index_name, i, self.table_name, index, where_clause))
74
                schema_editor.execute(
75
                    'CREATE UNIQUE INDEX "%s_%s" ON %s (%s) WHERE %s'
76
                    % (self.index_name, i, self.table_name, index, where_clause)
77
                )
69 78
            else:
70
                schema_editor.execute('CREATE UNIQUE INDEX "%s_%s" ON %s (%s)' %
71
                                      (self.index_name, i, self.table_name, index))
79
                schema_editor.execute(
80
                    'CREATE UNIQUE INDEX "%s_%s" ON %s (%s)' % (self.index_name, i, self.table_name, index)
81
                )
72 82

  
73 83
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
74 84
        if not self.allowed(app_label, schema_editor, to_state):
src/authentic2/models.py
44 44
    from django.contrib.contenttypes.generic import GenericForeignKey
45 45

  
46 46
from . import managers
47

  
47 48
# install our natural_key implementation
48 49
from . import natural_key as unused_natural_key  # noqa: F401
49 50
from .utils import ServiceAccessDenied
50 51

  
51 52

  
52 53
class UserExternalId(models.Model):
53
    user = models.ForeignKey(
54
        settings.AUTH_USER_MODEL,
55
        verbose_name=_('user'),
56
        on_delete=models.CASCADE)
54
    user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('user'), on_delete=models.CASCADE)
57 55
    source = models.CharField(max_length=256, verbose_name=_('source'))
58 56
    external_id = models.CharField(max_length=256, verbose_name=_('external id'))
59 57
    created = models.DateTimeField(auto_now_add=True, verbose_name=_('creation date'))
......
63 61
        return u'{0} is {1} on {2}'.format(self.user, self.external_id, self.source)
64 62

  
65 63
    def __repr__(self):
66
        return '<UserExternalId user: {0!r} source: {1!r} ' \
67
               'external_id: {2!r} created: {3} updated: {4}' \
68
               .format(self.user_id, self.source, self.external_id,
69
                       self.created, self.updated)
64
        return (
65
            '<UserExternalId user: {0!r} source: {1!r} '
66
            'external_id: {2!r} created: {3} updated: {4}'.format(
67
                self.user_id, self.source, self.external_id, self.created, self.updated
68
            )
69
        )
70 70

  
71 71
    class Meta:
72 72
        verbose_name = _('user external id')
......
78 78

  
79 79
class AuthenticationEvent(models.Model):
80 80
    '''Record authentication events whatever the source'''
81

  
81 82
    when = models.DateTimeField(auto_now=True, verbose_name=_('when'))
82 83
    who = models.CharField(max_length=80, verbose_name=_('who'))
83 84
    how = models.CharField(max_length=32, verbose_name=_('how'))
......
90 91
        verbose_name_plural = _('authentication logs')
91 92

  
92 93
    def __str__(self):
93
        return _('Authentication of %(who)s by %(how)s at %(when)s') % \
94
            self.__dict__
94
        return _('Authentication of %(who)s by %(how)s at %(when)s') % self.__dict__
95 95

  
96 96

  
97 97
class LogoutUrlAbstract(models.Model):
98 98
    logout_url = models.URLField(
99 99
        verbose_name=_('url'),
100
        help_text=_('you can use a {} to pass the URL of the success icon, '
101
                    'ex.: http://example.com/logout?next={}'),
100
        help_text=_(
101
            'you can use a {} to pass the URL of the success icon, ' 'ex.: http://example.com/logout?next={}'
102
        ),
102 103
        max_length=255,
103 104
        blank=True,
104
        null=True)
105
        null=True,
106
    )
105 107
    logout_use_iframe = models.BooleanField(
106
        verbose_name=_('use an iframe instead of an img tag for logout'),
107
        default=False)
108
        verbose_name=_('use an iframe instead of an img tag for logout'), default=False
109
    )
108 110
    logout_use_iframe_timeout = models.PositiveIntegerField(
109 111
        verbose_name=_('iframe logout timeout (ms)'),
110
        help_text=_('if iframe logout is used, it\'s the time between the '
111
                    'onload event for this iframe and the moment we consider its '
... Ce différentiel a été tronqué car il excède la taille maximale pouvant être affichée.