Skip to content

Commit d23f33f

Browse files
committed
sync w main
2 parents 2580092 + 8e4d8f6 commit d23f33f

29 files changed

+305
-308
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This PR template is here to help ensure you're setup for success:
55
-->
66

77
**JIRA Ticket:**
8-
[SOMEPROJECT-42](https://jira.cms.gov/browse/SOMEPROJECT-42)
8+
[BB2-XXXX](https://jira.cms.gov/browse/BB2-XXXX)
99

1010
**User Story or Bug Summary:**
1111
<!-- Please copy-paste the brief user story or bug description that this PR is intended to address. -->

Dockerfile.selenium

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@ RUN . /tmp/venv/bin/activate
1414
ENV PATH="/tmp/venv/bin:${PATH}"
1515
RUN pip3 install --upgrade pip
1616
RUN pip3 install selenium pytest jsonschema
17-

apps/accounts/admin.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
from django.contrib import admin
23
from django.contrib.auth.models import User
34
from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin
@@ -9,7 +10,6 @@
910
UserIdentificationLabel,
1011
)
1112

12-
1313
admin.site.register(ActivationKey)
1414
admin.site.register(ValidPasswordResetKey)
1515

@@ -30,8 +30,36 @@ def queryset(self, request, queryset):
3030
return queryset
3131

3232

33-
class UserAdmin(DjangoUserAdmin):
33+
class ActiveAccountFilter(admin.SimpleListFilter):
34+
title = "User activation status"
35+
parameter_name = "status"
36+
37+
def lookups(self, request, model_admin):
38+
return [
39+
("active", "Active"),
40+
("inactive_all", "Inactive"),
41+
("inactive_expired", "Inactive (expired activation key)")
42+
]
43+
44+
def queryset(self, request, queryset):
45+
if self.value() == "inactive_expired":
46+
return queryset.filter(
47+
is_active=False,
48+
activationkey__key_status="expired",
49+
) | queryset.filter(
50+
# Since the activation keys only reach "expired" status when they are
51+
# used post-expiration, we need to check the "created" status as well
52+
is_active=False,
53+
activationkey__key_status="created",
54+
activationkey__expires__lt=(datetime.today()),
55+
)
56+
elif self.value() == "inactive_all":
57+
return queryset.filter(is_active=False)
58+
elif self.value() == "active":
59+
return queryset.filter(is_active=True)
3460

61+
62+
class UserAdmin(DjangoUserAdmin):
3563
list_display = (
3664
"username",
3765
"get_type",
@@ -43,7 +71,7 @@ class UserAdmin(DjangoUserAdmin):
4371
"date_joined",
4472
)
4573

46-
list_filter = (UserTypeFilter,)
74+
list_filter = (UserTypeFilter, ActiveAccountFilter,)
4775

4876
@admin.display(
4977
description="Type",

apps/accounts/tests/test_password_reset_while_authenticated.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -193,26 +193,15 @@ def test_password_change_reuse_validation(self):
193193
self.user = User.objects.get(username="fred") # get user again so that you can see password changed
194194
self.assertEquals(self.user.check_password("IchangedTHEpassword#123"), True)
195195

196-
# add 12 minutes to time to expire current password
197-
StubDate.now = classmethod(
198-
lambda cls, timezone: datetime.now().replace(tzinfo=pytz.UTC) + relativedelta(minutes=+12)
199-
)
200-
self.client.logout()
201-
form_data = {'username': 'fred',
202-
'password': 'IchangedTHEpassword#123'}
203-
response = self.client.post(reverse('login'), form_data, follow=True)
204-
self.assertContains(response,
205-
("Your password has expired, change password strongly recommended."))
206-
207196
@override_switch('login', active=True)
208197
@mock.patch("apps.accounts.validators.datetime", StubDate)
209-
def test_password_expire_not_affect_staff(self):
198+
def test_no_password_expire(self):
210199
self.client.logout()
211-
# add 20 minutes to time to show staff is not effected
200+
# add 90 days to time to show expiration is removed
212201
StubDate.now = classmethod(
213-
lambda cls, timezone: datetime.now().replace(tzinfo=pytz.UTC) + relativedelta(minutes=+20)
202+
lambda cls, timezone: datetime.now().replace(tzinfo=pytz.UTC) + relativedelta(days=+90)
214203
)
215-
form_data = {'username': 'staff',
204+
form_data = {'username': 'fred',
216205
'password': 'foobarfoobarfoobar'}
217206
response = self.client.post(reverse('login'), form_data, follow=True)
218207
# assert account dashboard page
@@ -222,8 +211,5 @@ def test_password_expire_not_affect_staff(self):
222211
("The Developer Sandbox lets you register applications to get credentials"))
223212

224213
def test_password_reuse_min_age_validator_args_check(self):
225-
with self.assertRaisesRegex(ValueError,
226-
(".*password_min_age < password_reuse_interval expected.*"
227-
"password_expire < password_reuse_interval expected.*"
228-
"password_min_age < password_expire expected.*")):
229-
PasswordReuseAndMinAgeValidator(60 * 60 * 24 * 30, 60 * 60 * 24 * 10, 60 * 60 * 24 * 20)
214+
with self.assertRaisesRegex(ValueError, ".*password_min_age < password_reuse_interval expected.*"):
215+
PasswordReuseAndMinAgeValidator(60 * 60 * 24 * 30, 60 * 60 * 24 * 10)

apps/accounts/validators.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class PasswordReuseAndMinAgeValidator(object):
8686
def __init__(self,
8787
password_min_age=60 * 60 * 24,
8888
password_reuse_interval=60 * 60 * 24 * 120,
89-
password_expire=60 * 60 * 24 * 30):
89+
password_expire=0):
9090

9191
msg1 = "Invalid OPTIONS, password_min_age < password_reuse_interval expected, " \
9292
"but having password_min_age({}) >= password_reuse_interval({})"
@@ -96,14 +96,11 @@ def __init__(self,
9696
"but having password_expire({}) >= password_reuse_interval({})"
9797

9898
check_opt_err = []
99-
if password_min_age > 0 and password_reuse_interval > 0 \
100-
and password_min_age > password_reuse_interval:
99+
if 0 < password_reuse_interval < password_min_age:
101100
check_opt_err.append(msg1.format(password_min_age, password_reuse_interval))
102-
if password_expire > 0 and password_reuse_interval > 0 \
103-
and password_expire > password_reuse_interval:
101+
if 0 < password_reuse_interval < password_expire:
104102
check_opt_err.append(msg2.format(password_expire, password_reuse_interval))
105-
if password_min_age > 0 and password_expire > 0 \
106-
and password_min_age > password_expire:
103+
if 0 < password_expire < password_min_age:
107104
check_opt_err.append(msg3.format(password_min_age, password_expire))
108105
if len(check_opt_err) > 0:
109106
raise ValueError(check_opt_err)
@@ -234,8 +231,7 @@ def password_expired(self, user=None):
234231
except PastPassword.DoesNotExist:
235232
pass
236233
if passwds is not None and passwds.first() is not None:
237-
if (datetime.now(timezone.utc)
238-
- passwds.first().date_created).total_seconds() >= self.password_expire:
234+
if (datetime.now(timezone.utc) - passwds.first().date_created).total_seconds() >= self.password_expire:
239235
# the elapsed time since last password change / create is more than password_expire
240236
passwd_expired = True
241237
return passwd_expired

apps/accounts/views/login.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ def dispatch(self, request, *args, **kwargs):
2121
return super().dispatch(request, *args, **kwargs)
2222

2323
def form_valid(self, form):
24-
"""
25-
Extend django login view to do password expire check
26-
and redirect to password-change instead of user account home
27-
"""
28-
# auth_login(self.request, form.get_user())
2924
response = super().form_valid(form)
3025
if response.status_code == 302:
3126
passwd_validators = get_default_password_validators()

apps/authorization/management/commands/update_access_grants.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
from django.core.management.base import BaseCommand
22
from apps.authorization.models import DataAccessGrant
33
from apps.dot_ext.models import Application
4+
5+
46
class Command(BaseCommand):
57
help = (
68
'Update Access Grants Per Application.'
79
'Pass in a list of application ids.'
810
)
911

1012
def add_arguments(self, parser):
11-
parser.add_argument('--applications', help="list of applications to update "
13+
parser.add_argument('--applications', help="List of applications to update "
1214
"id, comma separated: "
13-
"eg. 1,2,3 ")
14-
15+
"eg. 1,2,3. Supersedes --all")
16+
parser.add_argument('--all', action='store_true', help="Update access grants for all applications")
17+
parser.set_defaults(all=False)
1518

1619
def handle(self, *args, **options):
1720
if options['applications']:
1821
application_ids = options['applications'].split(',')
22+
elif options['all']:
23+
application_ids = Application.objects.values_list('id', flat=True)
1924
else:
20-
print("You must pass at least one application to update.")
25+
print("You must pass at least one application to update or use the --all option.")
2126
return False
2227
for app_id in application_ids:
2328
application = Application.objects.get(id=app_id)
2429
grants = DataAccessGrant.objects.filter(application=app_id)
30+
if not grants:
31+
continue
2532
if "ONE_TIME" in application.data_access_type:
2633
grants.delete()
2734
elif "THIRTEEN_MONTH" in application.data_access_type:
2835
for grant in grants:
2936
grant.update_expiration_date()
3037
else:
3138
continue
32-
33-

apps/authorization/views.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from datetime import datetime
2+
3+
from django.db.models import Q
14
from django.http import HttpResponse
25
from django.utils.decorators import method_decorator
36
from django.views.decorators.csrf import csrf_exempt
@@ -46,7 +49,10 @@ class AuthorizedGrants(viewsets.GenericViewSet,
4649
serializer_class = DataAccessGrantSerializer
4750

4851
def get_queryset(self):
49-
return DataAccessGrant.objects.select_related("application").filter(beneficiary=self.request.user)
52+
return DataAccessGrant.objects.select_related("application").filter(
53+
Q(expiration_date__gt=datetime.now()) | Q(expiration_date=None),
54+
beneficiary=self.request.user
55+
)
5056

5157

5258
@method_decorator(csrf_exempt, name="dispatch")

apps/dot_ext/forms.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ def __init__(self, user, *args, **kwargs):
5959
self.fields["name"].label = "Name*"
6060
self.fields["name"].required = True
6161
self.fields["client_type"].label = "Client Type*"
62+
self.fields["client_type"].required = False
6263
self.fields["authorization_grant_type"].label = "Authorization Grant Type*"
64+
self.fields["authorization_grant_type"].required = False
6365
self.fields["redirect_uris"].label = "Redirect URIs*"
6466
self.fields["logo_uri"].disabled = True
6567

@@ -86,29 +88,6 @@ class Meta:
8688
required_css_class = "required"
8789

8890
def clean(self):
89-
client_type = self.cleaned_data.get("client_type")
90-
authorization_grant_type = self.cleaned_data.get("authorization_grant_type")
91-
92-
msg = ""
93-
validate_error = False
94-
95-
# Validate choices
96-
if not (
97-
client_type == "confidential"
98-
and authorization_grant_type == "authorization-code"
99-
):
100-
validate_error = True
101-
msg += (
102-
"Only a confidential client and "
103-
"authorization-code grant type are allowed at this time."
104-
)
105-
106-
if validate_error:
107-
msg_output = _(msg)
108-
raise forms.ValidationError(msg_output)
109-
else:
110-
pass
111-
11291
return self.cleaned_data
11392

11493
def clean_name(self):
@@ -176,6 +155,8 @@ def clean_require_demographic_scopes(self):
176155
return require_demographic_scopes
177156

178157
def save(self, *args, **kwargs):
158+
self.instance.client_type = "confidential"
159+
self.instance.authorization_grant_type = "authorization-code"
179160
app = self.instance
180161
# Only log agreement from a Register form
181162
if app.agree and type(self) == CustomRegisterApplicationForm:

apps/dot_ext/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from apps.capabilities.models import ProtectedCapability
3434

3535
TEN_HOURS = _("for 10 hours")
36+
THIRTEEN_MONTHS = _("for 13 months, until ")
3637

3738

3839
class Application(AbstractApplication):
@@ -165,7 +166,7 @@ def access_end_date_text(self):
165166
return TEN_HOURS
166167
# no message displayed for RESEARCH_STUDY
167168
else:
168-
return _("until ")
169+
return THIRTEEN_MONTHS
169170

170171
def access_end_date(self):
171172
if self.data_access_type == "THIRTEEN_MONTH":

0 commit comments

Comments
 (0)