0001-misc-add-logo-and-text-color-for-service-and-OU.patch
src/authentic2/a2_rbac/admin.py | ||
---|---|---|
78 | 78 |
'user_can_reset_password', |
79 | 79 |
'user_add_password_policy', |
80 | 80 |
'home_url', |
81 |
'logo', |
|
82 |
'colour', |
|
81 | 83 |
) |
82 | 84 |
readonly_fields = ('uuid',) |
83 | 85 |
prepopulated_fields = {"slug": ("name",)} |
src/authentic2/a2_rbac/migrations/0027_auto_20220331_1521.py | ||
---|---|---|
1 |
# Generated by Django 2.2.24 on 2022-03-31 13:21 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 |
import authentic2.validators |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('a2_rbac', '0026_add_roleparenting_soft_delete'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='organizationalunit', |
|
17 |
name='colour', |
|
18 |
field=models.CharField( |
|
19 |
blank=True, |
|
20 |
max_length=32, |
|
21 |
null=True, |
|
22 |
validators=[authentic2.validators.HexaColourValidator()], |
|
23 |
verbose_name='Colour', |
|
24 |
), |
|
25 |
), |
|
26 |
migrations.AddField( |
|
27 |
model_name='organizationalunit', |
|
28 |
name='logo', |
|
29 |
field=models.ImageField(blank=True, upload_to='services/logos', verbose_name='Logo'), |
|
30 |
), |
|
31 |
] |
src/authentic2/a2_rbac/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import os |
|
17 | 18 |
from collections import namedtuple |
18 | 19 | |
19 | 20 |
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation |
... | ... | |
28 | 29 | |
29 | 30 |
from authentic2.decorators import errorcollector |
30 | 31 |
from authentic2.utils.cache import GlobalCache |
32 |
from authentic2.validators import HexaColourValidator |
|
31 | 33 |
from django_rbac import utils as rbac_utils |
32 | 34 |
from django_rbac.models import ( |
33 | 35 |
VIEW_OP, |
... | ... | |
113 | 115 |
default=760, # two years + 1 month |
114 | 116 |
) |
115 | 117 |
home_url = models.URLField(verbose_name=_('Home URL'), max_length=256, null=True, blank=True) |
118 |
logo = models.ImageField(verbose_name=_('Logo'), blank=True, upload_to='services/logos') |
|
119 |
colour = models.CharField( |
|
120 |
verbose_name=_('Colour'), null=True, blank=True, max_length=32, validators=[HexaColourValidator()] |
|
121 |
) |
|
116 | 122 | |
117 | 123 |
objects = managers.OrganizationalUnitManager() |
118 | 124 | |
... | ... | |
166 | 172 |
) |
167 | 173 | |
168 | 174 |
def delete(self, *args, **kwargs): |
175 |
if self.logo and os.path.exists(self.logo.path): |
|
176 |
os.unlink(self.logo.path) |
|
177 | ||
169 | 178 |
Permission.objects.filter(ou=self).delete() |
170 | 179 |
return super(OrganizationalUnitAbstractBase, self).delete(*args, **kwargs) |
171 | 180 |
src/authentic2/manager/forms.py | ||
---|---|---|
633 | 633 |
def __init__(self, *args, **kwargs): |
634 | 634 |
super().__init__(*args, **kwargs) |
635 | 635 |
self.fields['name'].label = _('label').title() |
636 |
self.fields['colour'].widget = forms.TextInput(attrs={'type': 'color'}) |
|
636 | 637 | |
637 | 638 |
class Meta: |
638 | 639 |
model = OrganizationalUnit |
... | ... | |
650 | 651 |
'clean_unused_accounts_alert', |
651 | 652 |
'clean_unused_accounts_deletion', |
652 | 653 |
'home_url', |
654 |
'logo', |
|
655 |
'colour', |
|
653 | 656 |
) |
654 | 657 | |
655 | 658 |
src/authentic2/migrations/0037_auto_20220331_1513.py | ||
---|---|---|
1 |
# Generated by Django 2.2.24 on 2022-03-31 13:13 |
|
2 | ||
3 |
from django.db import migrations, models |
|
4 | ||
5 |
import authentic2.validators |
|
6 | ||
7 | ||
8 |
class Migration(migrations.Migration): |
|
9 | ||
10 |
dependencies = [ |
|
11 |
('authentic2', '0036_service_profile_types'), |
|
12 |
] |
|
13 | ||
14 |
operations = [ |
|
15 |
migrations.AddField( |
|
16 |
model_name='service', |
|
17 |
name='colour', |
|
18 |
field=models.CharField( |
|
19 |
blank=True, |
|
20 |
max_length=32, |
|
21 |
null=True, |
|
22 |
validators=[authentic2.validators.HexaColourValidator()], |
|
23 |
verbose_name='Colour', |
|
24 |
), |
|
25 |
), |
|
26 |
migrations.AddField( |
|
27 |
model_name='service', |
|
28 |
name='logo', |
|
29 |
field=models.ImageField(blank=True, upload_to='services/logos', verbose_name='Logo'), |
|
30 |
), |
|
31 |
] |
src/authentic2/models.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import datetime |
18 |
import os |
|
18 | 19 |
import time |
19 | 20 |
import urllib.parse |
20 | 21 |
import uuid |
... | ... | |
37 | 38 | |
38 | 39 |
from authentic2.a2_rbac.models import Role |
39 | 40 |
from authentic2.utils.crypto import base64url_decode, base64url_encode |
41 |
from authentic2.validators import HexaColourValidator |
|
40 | 42 | |
41 | 43 |
# install our natural_key implementation |
42 | 44 |
from . import managers |
... | ... | |
382 | 384 |
verbose_name=_('callback url when unauthorized'), max_length=256, null=True, blank=True |
383 | 385 |
) |
384 | 386 |
home_url = models.URLField(verbose_name=_('Home URL'), max_length=256, null=True, blank=True) |
387 |
logo = models.ImageField(verbose_name=_('Logo'), blank=True, upload_to='services/logos') |
|
388 |
colour = models.CharField( |
|
389 |
verbose_name=_('Colour'), null=True, blank=True, max_length=32, validators=[HexaColourValidator()] |
|
390 |
) |
|
385 | 391 | |
386 | 392 |
profile_types = models.ManyToManyField( |
387 | 393 |
to='custom_user.ProfileType', |
... | ... | |
470 | 476 |
urls.update(service.get_base_urls()) |
471 | 477 |
return list(urls) |
472 | 478 | |
479 |
def delete(self, *args, **kwargs): |
|
480 |
if self.logo and os.path.exists(self.logo.path): |
|
481 |
os.unlink(self.logo.path) |
|
482 | ||
483 |
return super().delete(*args, **kwargs) |
|
484 | ||
473 | 485 | |
474 | 486 |
Service._meta.natural_key = [['slug', 'ou']] |
475 | 487 |
src/authentic2/validators.py | ||
---|---|---|
124 | 124 | |
125 | 125 |
def __eq__(self, other): |
126 | 126 |
return isinstance(other, self.__class__) and self.message == other.message and self.code == other.code |
127 | ||
128 | ||
129 |
class HexaColourValidator(RegexValidator): |
|
130 |
"""Validates that the string is a hexadecimal colour""" |
|
131 | ||
132 |
def __init__(self, *args, **kwargs): |
|
133 |
self.regex = '#[0-9a-fA-F]{6}' |
|
134 |
self.message = _('Hexadecimal value only allowed.') |
|
135 |
super().__init__(*args, **kwargs) |
tests/idp_oidc/test_misc.py | ||
---|---|---|
85 | 85 |
'authorization_flow': OIDCClient.FLOW_IMPLICIT, |
86 | 86 |
'idtoken_duration': datetime.timedelta(hours=1), |
87 | 87 |
'post_logout_redirect_uris': 'https://example.com/', |
88 |
'home_url': 'https://example.com/', |
|
88 | 89 |
}, |
89 | 90 |
{ |
90 | 91 |
'frontchannel_logout_uri': 'https://example.com/southpark/logout/', |
... | ... | |
92 | 93 |
{ |
93 | 94 |
'frontchannel_logout_uri': 'https://example.com/southpark/logout/', |
94 | 95 |
'frontchannel_timeout': 3000, |
96 |
'colour': '#ff00ff', |
|
95 | 97 |
}, |
96 | 98 |
{ |
97 | 99 |
'identifier_policy': OIDCClient.POLICY_PAIRWISE_REVERSIBLE, |
... | ... | |
120 | 122 |
response.form.set('unauthorized_url', 'https://example.com/southpark/') |
121 | 123 |
response.form.set('redirect_uris', 'https://example.com/callbac%C3%A9') |
122 | 124 |
for key, value in other_attributes.items(): |
125 |
if isinstance(value, datetime.timedelta): |
|
126 |
value = f'{value.total_seconds()}' |
|
123 | 127 |
response.form.set(key, value) |
124 | 128 |
response = response.form.submit().follow() |
125 | 129 |
assert OIDCClient.objects.count() == 1 |
tests/test_validators.py | ||
---|---|---|
21 | 21 |
import pytest |
22 | 22 |
from django.core.exceptions import ValidationError |
23 | 23 | |
24 |
from authentic2.validators import EmailValidator, validate_password |
|
24 |
from authentic2.validators import EmailValidator, HexaColourValidator, validate_password
|
|
25 | 25 | |
26 | 26 | |
27 | 27 |
def test_validate_password(): |
... | ... | |
34 | 34 |
validate_password('000aaaaZZZZ') |
35 | 35 | |
36 | 36 | |
37 |
def test_validate_colour(): |
|
38 |
validator = HexaColourValidator() |
|
39 |
with pytest.raises(ValidationError): |
|
40 |
validator('abc') |
|
41 |
with pytest.raises(ValidationError): |
|
42 |
validator('blue') |
|
43 |
with pytest.raises(ValidationError): |
|
44 |
validator('#green') |
|
45 |
validator('#ff00ff') |
|
46 | ||
47 | ||
37 | 48 |
def test_digits_password_policy(settings): |
38 | 49 |
settings.A2_PASSWORD_POLICY_REGEX = '^[0-9]{8}$' |
39 | 50 |
settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG = 'pasbon' |
40 |
- |