0001-misc-apply-black-52457.patch
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 ' |