Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/google shared drives #561

Merged
merged 46 commits into from
May 15, 2024
Merged
Changes from 3 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
20bfe16
initial shared drive record models
mikeseibel Feb 26, 2024
005774b
mock shared drive data and initial templating
mikeseibel Feb 29, 2024
fc173ff
shared drive api tests
mikeseibel Feb 29, 2024
d002f3e
shared drive loader upsert
mikeseibel Feb 29, 2024
314ab80
Merge branch 'develop' into feature/google-shared-drives
mikeseibel Mar 19, 2024
da31dd4
Merge branch 'develop' into feature/google-shared-drives
mikeseibel Mar 20, 2024
485b97d
itbill restclient
mikeseibel Apr 9, 2024
72f3bfe
shared drive record to unique drive
mikeseibel Apr 9, 2024
cc60f54
random key_remote
mikeseibel Apr 9, 2024
4e25613
layout interactions align with wireframes
mikeseibel Apr 27, 2024
e56ff52
Merge pull request #545 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel Apr 29, 2024
2a61d0a
Merge branch 'develop' into feature/google-shared-drives
mikeseibel Apr 30, 2024
c0fdf09
mock dates
mikeseibel Apr 30, 2024
23d6bdb
itbill payload integration
mikeseibel May 4, 2024
8e6bad7
Merge pull request #547 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 4, 2024
ce4d1b8
subscription relationships
mikeseibel May 4, 2024
74c8d5e
Merge pull request #548 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 4, 2024
42daf34
shared drive email warnings and tests
mikeseibel May 8, 2024
d4d5a11
shared drive notification templating
mikeseibel May 8, 2024
e9dbd64
Merge pull request #549 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 8, 2024
e6c7768
shared drive warning email tweaking
mikeseibel May 8, 2024
1923ad2
Merge pull request #550 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 8, 2024
8649e0c
misnamed metho
mikeseibel May 8, 2024
8b1dc57
Merge pull request #551 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 8, 2024
1978096
consolidate notification logic, add access lifecycle warnings
mikeseibel May 9, 2024
3182223
Merge pull request #552 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 9, 2024
9de0681
setting for conditional app exposure, prod value to prevent unintenti…
mikeseibel May 10, 2024
2acac2d
Merge pull request #553 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 10, 2024
a88d308
support notification display bug
mikeseibel May 10, 2024
6652531
Merge pull request #554 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 10, 2024
8f5acce
improve notification testing
mikeseibel May 10, 2024
8c4bb16
Merge pull request #555 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 10, 2024
e459a50
unused constants
mikeseibel May 10, 2024
d8d8e1e
Merge pull request #556 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 10, 2024
3c0a538
access notice tweaks
mikeseibel May 10, 2024
8212fa3
Merge pull request #557 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 10, 2024
0c028c7
tabs as links
mikeseibel May 14, 2024
ca971ba
Merge pull request #559 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 14, 2024
3be16be
over quota unsubsidized warning, handlebars logic cleanup
mikeseibel May 15, 2024
e566292
a11y updates
wmwash May 15, 2024
d5673e3
feature branch merge
mikeseibel May 15, 2024
98554c7
Merge pull request #560 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 15, 2024
25b8a63
pycodestyle
mikeseibel May 15, 2024
83aa449
Merge pull request #562 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 15, 2024
f70353d
jshint
mikeseibel May 15, 2024
d8ea610
Merge pull request #563 from uw-it-aca/work/google-shared-drives-mikes
mikeseibel May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@ services:
ITBILL_FORM_URL_BASE_ID: sc_cat_item
ITBILL_SHARED_DRIVE_PRODUCT_SYS_ID: 7078586b2f6cb076cad75ae9aab3ea05
# ENDORSEMENT_PROVISIONING: "services, shared_drives"
# ENDORSEMENT_PROVISIONING: services,mailbox_access
ENDORSEMENT_PROVISIONING: services
restart: always
container_name: app-provision
build:
2 changes: 2 additions & 0 deletions docker/test-values.yml
Original file line number Diff line number Diff line change
@@ -112,6 +112,8 @@ environmentVariables:
value: https://test.provision.uw.edu/saml
- name: CLUSTER_CNAME
value: test.provision.uw.edu
- name: ENDORSEMENT_PROVISIONING
value: services,mailbox_access
externalSecrets:
enabled: true
secrets:
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2024-05-14 17:09

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('endorsement', '0030_shareddriveacceptance'),
]

operations = [
migrations.AddField(
model_name='shareddriverecord',
name='datetime_over_quota_emailed',
field=models.DateTimeField(null=True),
),
]
12 changes: 10 additions & 2 deletions endorsement/models/shared_drive.py
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
from endorsement.models.base import RecordManagerBase
from endorsement.models.itbill import ITBillSubscription
from endorsement.util.date import datetime_to_str
from endorsement.util.itbill.shared_drive import shared_drive_subsidized_quota
import json


@@ -74,8 +75,7 @@ class SharedDriveQuota(

@property
def is_subsidized(self):
return self.quota_limit <= getattr(
settings, 'ITBILL_SHARED_DRIVE_SUBSIDIZED_QUOTA')
return self.quota_limit <= shared_drive_subsidized_quota()

def json_data(self):
return {
@@ -132,6 +132,13 @@ def get_record_by_drive_id(self, drive_id):
return self.get(
shared_drive__drive_id=drive_id, is_deleted__isnull=True)

def get_over_quota_non_subscribed(self):
return self.filter(
datetime_over_quota_emailed__isnull=True,
shared_drive__drive_quota__quota_limit__gt=\
shared_drive_subsidized_quota(),
subscription__isnull=True, is_deleted__isnull=True)


class SharedDriveRecord(
ExportModelOperationsMixin('shared_drive_record'), models.Model):
@@ -153,6 +160,7 @@ class SharedDriveRecord(
datetime_notice_2_emailed = models.DateTimeField(null=True)
datetime_notice_3_emailed = models.DateTimeField(null=True)
datetime_notice_4_emailed = models.DateTimeField(null=True)
datetime_over_quota_emailed = models.DateTimeField(null=True)
datetime_renewed = models.DateTimeField(null=True)
datetime_expired = models.DateTimeField(null=True)
is_deleted = models.BooleanField(null=True)
8 changes: 3 additions & 5 deletions endorsement/notifications/access.py
Original file line number Diff line number Diff line change
@@ -96,10 +96,8 @@ def warn_accessees(notice_level):
email, subject, text_body, html_body,
"Mailbox Access Warning")

sent_date = {
'datetime_notice_{}_emailed'.format(
notice_level): timezone.now()
}
drives.update(**sent_date)
setattr(drive, 'datetime_notice_{}_emailed'.format(notice_level),
timezone.now())
drive.save()
except EmailFailureException as ex:
pass
40 changes: 38 additions & 2 deletions endorsement/notifications/shared_drive.py
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
from endorsement.dao import display_datetime
from endorsement.policy.shared_drive import SharedDrivePolicy
from endorsement.util.email import uw_email_address
from endorsement.exceptions import EmailFailureException
from endorsement.util.itbill.shared_drive import shared_drive_subsidized_quota
from django.template import loader, Template, Context
from django.utils import timezone
import re
@@ -64,6 +64,42 @@ def warn_members(notice_level):
'datetime_notice_{}_emailed'.format(
notice_level): timezone.now()
}
drives.update(**sent_date)

setattr(drive, 'datetime_notice_{}_emailed'.format(notice_level),
timezone.now())
drive.save()
except EmailFailureException as ex:
pass


def _create_notification_over_quota_non_subsidized(drive):
context = {
'drive': drive,
'subsidized_quota': shared_drive_subsidized_quota()
}

subject = "Action Required: Shared Drive quota has been restricted"
text_template = _email_template("over_quota_non_subscribed.txt")
html_template = _email_template("over_quota_non_subscribed.html")

return (subject,
loader.render_to_string(text_template, context),
loader.render_to_string(html_template, context))


def notify_over_quota_non_subsidized_expired():
for drive in SharedDriveRecord.objects.get_over_quota_non_subscribed():
try:
members = [uw_email_address(netid) for (
netid) in drive.shared_drive.get_members()]
(subject,
text_body,
html_body) = _create_notification_over_quota_non_subsidized(drive)
send_notification(
members, subject, text_body, html_body,
"Over Quota Shared Drive Claim Deadline")

setattr(drive, 'datetime_over_quota_emailed', timezone.now())
drive.save()
except EmailFailureException as ex:
pass
28 changes: 7 additions & 21 deletions endorsement/static/endorsement/js/handlebars-helpers.js
Original file line number Diff line number Diff line change
@@ -21,18 +21,6 @@ $(window.document).ready(function() {

return plural;
},
'equals': function(a, b, options) {
return (a == b) ? options.fn(this) : options.inverse(this);
},
'gt': function(a, b, options) {
return (a > b) ? options.fn(this) : options.inverse(this);
},
'lte': function(a, b, options) {
return (a <= b) ? options.fn(this) : options.inverse(this);
},
'even': function(n, options) {
return ((n % 2) === 0) ? options.fn(this) : options.inverse(this);
},
'slice': function(a, start, end, options) {
if(!a || a.length == 0)
return options.inverse(this);
@@ -43,14 +31,12 @@ $(window.document).ready(function() {

return result.join('');
},
'or': function(a, b, options) {
return (a || b) ? options.fn(this) : options.inverse(this);
},
'ifAndNot': function(a, b, options) {
return (a && !b) ? options.fn(this) : options.inverse(this);
},
'ifNotAndNot': function(a, b, options) {
return (!a && !b) ? options.fn(this) : options.inverse(this);
}
'even': (n) => (n % 2) === 0,
'eq': (a, b) => a === b,
'gt': (a, b) => a > b,
'lte': (a, b) => a <= b,
'and': (a, b) => a && b,
'or': (a, b) => a || b,
'not': (a) => !a
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Hello,
<p>
You are receiving this email because you are identified as {%if drive.shared_drive.members.all.count > 0 %}one of {{ drive.shared_drive.members.all.count }}{% else %}the{% endif %} UW manager{{drive.shared_drive.members.all.count|pluralize}}
of the Google Shared Drive named "{{drive.shared_drive.drive_name}}".
</p>
<p>
This Google Shared Drive's quota has been restricted to the subsidized quota of {{subsidized_quota}}GB and is read-only.
</p>
<p>
If you have any questions, please contact <a href="mailto:[email protected]" target="_blank">[email protected]</a> or 206-221-5000.
</p>
Thank you,<br />
UW-IT<br />

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Hello,

You are receiving this email because you are identified as {%if drive.shared_drive.members.all.count > 0 %}one of {{ drive.shared_drive.members.all.count }}{% else %}the{% endif %} UW manager{{drive.shared_drive.members.all.count|pluralize}}
of the Google Shared Drive named "{{drive.shared_drive.drive_name}}".

This Google Shared Drive's quota has been restricted to the subsidized quota of {{subsidized_quota}}GB and is read-only.

If you have any questions, please contact [email protected] or 206-221-5000.

Thank you,
UW-IT


6 changes: 3 additions & 3 deletions endorsement/templates/handlebars/endorsers_partial.html
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
{{/if}}
{{/if}}
{{/if}}
{{/if}}{{#if new_netid}} new_netid{{/if}} endorsement_row_{{#equals endorsement_index 0}}first top-border{{else}}following hidden-names{{/equals}} endorsee_row_{{#even endorsee_index}}even{{else}}odd{{/even}}" data-netid="{{ netid }}"
{{/if}}{{#if new_netid}} new_netid{{/if}} endorsement_row_{{#if (eq endorsement_index 0) }}first top-border{{else}}following hidden-names{{/if}} endorsee_row_{{#if (even endorsee_index)}}even{{else}}odd{{/if}}" data-netid="{{ netid }}"
data-netid-name="{{ name }}"
data-netid-initial-email="{{ email }}"
data-service="{{ svc }}"
@@ -60,12 +60,12 @@
{{#if endorsement.error }}
<td class="endorsed-error" colspan="3">
<span>Problem Accessing Provision Status</span>
{{#equals endorsement.error 'INACTIVE_NETID' }}
{{#if (eq endorsement.error 'INACTIVE_NETID') }}
UW NetID <b>{{netid}}</b> is currently <a href="https://itconnect.uw.edu/wares/msinf/design/users/inactive-users/" target="_blank">marked as inactive</a>.<br />
Change <a href="https://identity.uw.edu/account/resetpassword/" target="_blank">UW NetID password</a>, wait 4 hours, and try again.
{{else}}
{{{endorsement.error}}}
{{/equals}}
{{/if}}
</td>
{{else}}

2 changes: 1 addition & 1 deletion endorsement/templates/handlebars/persistent_messages.html
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
<div class="alert endorsement-persistent-message-alert alert-{{ message_level }} hidden-print" role="alert">
{{#each messages }}
<div class="endorsement-persistent-message" {{#unless @last}}xstyle="margin-bottom:10px;{{/unless}}" data-message-hash="{{hash}}">
<div class="message-icon"><i class="fa fa-{{#equals ../message_level 'success'}}check-circle{{/equals}}{{#equals ../message_level 'info'}}exclamation-circle{{/equals}}{{#equals ../message_level 'warning'}}exclamation-triangle{{/equals}}{{#equals ../message_level 'danger'}}minus-circle{{/equals}} fa-lg"></i></div>
<div class="message-icon"><i class="fa fa-{{#if (eq ../message_level 'success')}}check-circle{{/if}}{{#if (eq ../message_level 'info')}}exclamation-circle{{/if}}{{#if (eq ../message_level 'warning')}}exclamation-triangle{{/if}}{{#if (eq ../message_level 'danger')}}minus-circle{{/if}} fa-lg"></i></div>
<div class="message-text">{{{message}}}</div>
</div>
{{/each}}
4 changes: 2 additions & 2 deletions endorsement/templates/handlebars/reasons_partial.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% verbatim %}
<script id="reasons_partial" type="text/x-handlebars-template">

{{#ifAndNot endorsement.reason endorsement.is_revoked}}
{{#if (and endorsement.reason (not endorsement.is_revoked))}}
{{endorsement.reason}}<input type=hidden class="reason-editor" placeholder="Reason for provisioning" value="{{endorsement.reason}}">
{{else}}
<div class="endorse-reason">
@@ -25,6 +25,6 @@
</div>
<p class="apply-reason visually-hidden" style="font-size: small;"><a href="javascript:void(0);" class='apply-all'>Apply to all</a></p>
</div>
{{/ifAndNot}}
{{/if}}
</script>
{% endverbatim %}
2 changes: 1 addition & 1 deletion endorsement/templates/handlebars/renew.html
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@
{{#each services}}
{{#if this.count}}<li class="significant">{{this.name}}</li>{{/if}}
{{/each}}
</ul> for {{#gt unique.length 1}}{{unique.length}} {{/gt}}UW NetID{{plural unique.length '' 's'}}{{#gt unique.length 1}}{{else}} <ul class="comma-list">{{#each unique}}<li class="significant">{{this}}</li>{{/each}}</ul>{{/gt}}.
</ul> for {{#if (gt unique.length 1)}}{{unique.length}} {{/if}}UW NetID{{plural unique.length '' 's'}}{{#if (gt unique.length 1)}}{{else}} <ul class="comma-list">{{#each unique}}<li class="significant">{{this}}</li>{{/each}}</ul>{{/if}}.
</div>
<div class="row">
As the provisioner, you are responsible for the proper use, account maintenance, and file privileges of the provision.
2 changes: 1 addition & 1 deletion endorsement/templates/handlebars/revoke.html
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
<div class="modal-body">
<div class="shared-acceptance container col-md-12">
<div class="row">
You are about to revoke the provision for {{#gt unique.length 1}}{{unique.length}} {{/gt}}UW NetID{{plural unique.length '' 's'}}{{#gt unique.length 1}}{{else}} <ul class="comma-list">{{#each unique}}<li class="significant">{{this}}</li>{{/each}}</ul>{{/gt}} to
x You are about to revoke the provision for {{#if (gt unique.length 1)}}{{unique.length}} {{/if}}UW NetID{{plural unique.length '' 's'}}{{#if (gt unique.length 1)}}{{else}} <ul class="comma-list">{{#each unique}}<li class="significant">{{this}}</li>{{/each}}</ul>{{/if}} to
<ul class="comma-list">
{{#each services}}
{{#if this.count}}<li class="significant">{{this.name}}</li>{{/if}}
Original file line number Diff line number Diff line change
@@ -2,24 +2,24 @@
<script id="modal_action_partial_template" type="text/x-handlebars-template">
<div class="row"><div class="col-md-12">
You are about to {{action}} access to the UW Office 365 Exchange Online mailbox belonging
to "UW NetID {{mailbox}}"{{#equals action "provision"}} to another account: "UW NetID {{delegate}}"{{/equals}}.
to "UW NetID {{mailbox}}"{{#if (eq action "provision")}} to another account: "UW NetID {{delegate}}"{{/if}}.
</div></div>
<div class="row"><div class="col-md-12">
With <span class="significant">{{access_type_name}}</span> permissions UWNetID {{delegate}} will be able to:
<ul style="margin-bottom: 0;">
{{#equals access_type "FullAccessandSendAs"}}
{{#if (eq access_type "FullAccessandSendAs")}}
<li>Open the mailbox</li>
<li>View, add and remove the contents of the mailbox</li>
<li>Send messages as if they came directly from the mailbox</li>
{{else}}{{#equals access_type "FullAccess"}}
{{else}}{{#if (eq access_type "FullAccess")}}
<li>Open the mailbox</li>
<li>View, add and remove the contents of the mailbox</li>
{{else}}{{#equals access_type "SendAs"}}
{{else}}{{#if (eq access_type "SendAs")}}
<li>Send messages as if they came directly from the mailbox</li>
{{else}}{{#equals access_type "SendOnBehalf"}}
{{else}}{{#if (eq access_type "SendOnBehalf")}}
<li>Allows the delegate to send messages from the mailbox or group</li>
<li>The From address of these messages clearly shows that the message was sent by the delegate (" {{delegate}} on behalf of {{mailbox}}")</li>
{{/equals}}{{/equals}}{{/equals}}{{/equals}}
{{/if}}{{/if}}{{/if}}{{/if}}
</ul>
</div></div>
</script>
8 changes: 4 additions & 4 deletions endorsement/templates/handlebars/tab/access/office.html
Original file line number Diff line number Diff line change
@@ -60,8 +60,8 @@

<script id="office_access_row_partial" type="text/x-handlebars-template">
<tr class="
endorsement_row_{{#equals access_index 0}}first {{else}}following {{/equals}} endorsee_row_{{#even accessee_index}}even{{else}}odd{{/even}}{{#unless delegate}} no-delegates{{/unless}}{{#if new_delegate}} new_access{{/if}}" data-mailbox="{{ mailbox }}" data-delegate="{{ delegate }}">
<th scope="row" class="access-mailbox" data-mailbox="{{ mailbox }}">{{ mailbox }}</th>
endorsement_row_{{#if (eq access_index 0) }}first {{else}}following {{/if}} endorsee_row_{{#if (even accessee_index)}}even{{else}}odd{{/if}}{{#unless delegate}} no-delegates{{/unless}}{{#if new_delegate}} new_access{{/if}}" data-mailbox="{{ mailbox }}" data-delegate="{{ delegate }}">
<td scope="row" class="access-mailbox" data-mailbox="{{ mailbox }}">{{ mailbox }}</td>
<td class="access-mailbox-name">{{ name }}</td>
{{#if is_valid}}
{{#if delegate}}
@@ -82,8 +82,8 @@

<script id="office_conflict_row_partial" type="text/x-handlebars-template">
<tr class="
endorsement_row_{{#equals access_index 0}}first {{else}}following {{/equals}} endorsee_row_{{#even accessee_index}}even{{else}}odd{{/even}}{{#unless delegate}} no-delegates{{/unless}}{{#if new_delegate}} new_access{{/if}}" data-mailbox="{{ mailbox }}" data-delegate="{{ delegate }}">
<th scope="row" class="access-mailbox" data-mailbox="{{ mailbox }}">{{ mailbox }}</th>
endorsement_row_{{#if (eq access_index 0) }}first {{else}}following {{/if}} endorsee_row_{{#if (even accessee_index)}}even{{else}}odd{{/if}}{{#unless delegate}} no-delegates{{/unless}}{{#if new_delegate}} new_access{{/if}}" data-mailbox="{{ mailbox }}" data-delegate="{{ delegate }}">
<td scope="row" class="access-mailbox" data-mailbox="{{ mailbox }}">{{ mailbox }}</td>
<td class="access-mailbox-name">{{ name }}</td>
<td class="access-delegate-id">{{#if delegate_link}}<a href="{{ delegate_link }}" target="_blank">{{/if}}{{ delegate }}{{#if delegate_link}}</a>{{/if}}</td>
<td colspan="3">
42 changes: 23 additions & 19 deletions endorsement/templates/handlebars/tab/drives/google.html
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
<thead class="thead-light">
<tr>
<th scope="col" class="shared-drive-name">Shared Drive Name</th>
<td scope="col" class="shared-drive-status-icon"></td>
<th scope="col" class="shared-drive-status-icon"></th>
<th scope="col" class="shared-drive-status">Status</th>
<th scope="col" class="shared-drive-est-usage"><span>Est Usage</span> <span class="prt-data-popover" aria-hidden="true" tabindex="0" data-placement="top" data-content="Estimated usage for each drive is updated once a week and may not reflect exact usage." data-original-title="Estimated Usage (Est. Usage)"><i class="fa fa-question-circle"></i></span></th>
<th class="shared-drive-quota"><span>Quota</span></th>
@@ -36,44 +36,48 @@

<script id="shared_drives_row_partial" type="text/x-handlebars-template">
<tr class="shared-drive-row" data-drive-id="{{drive.drive_id}}">
<th scope="row" class="shared-drive-name"><span class="significant">{{ drive.drive_name }}</span></th>
<td class="shared-drive-status-icon">{{#lte expiration_days 90}}<i class="fa {{#lte expiration_days 7}}fa-exclamation-circle text-danger{{else}}fa-exclamation-triangle text-warning{{/lte}}"></i>{{/lte}}</td>
<td scope="row" class="shared-drive-name"><span class="significant">{{ drive.drive_name }}</span></td>
<td class="shared-drive-status-icon">{{#if (lte expiration_days 90)}}<i class="fa {{#if (lte expiration_days 7)}}fa-exclamation-circle text-danger{{else}}fa-exclamation-triangle text-warning{{/if}}"></i>{{/if}}</td>
<td class="shared-drive-status">
<span class="significant">
{{#or drive.drive_quota.is_subsidized subscription}}
{{#lte expiration_days 90}}
{{#if (or drive.drive_quota.is_subsidized subscription) }}
{{#if (lte expiration_days 90)}}
Provision Expiring
{{else}}
Provisioned
{{/lte}}
{{/if}}
{{else}}
{{#unless subscription}}
Quota selection required
{{/unless}}
{{/or}}
{{/if}}
</span>
<p>{{#or drive.drive_quota.is_subsidized subscription}}Renew{{else}}Select quota{{/or}} by {{expiration_date}}{{#lte expiration_days 90}}<br />({{expiration_from_now}}){{/lte}}</p>
<p>{{#if (or drive.drive_quota.is_subsidized subscription) }}Renew{{else}}Select quota{{/if}} by {{expiration_date}}{{#if (lte expiration_days 90)}}<br />({{expiration_from_now}}){{/if}}</p>
</td>
<td class="shared-drive-est-usage">{{drive.drive_usage}}GB</td>
</td>
<td class="shared-drive-quota">{{drive.drive_quota.quota_limit}}GB
{{#each quota_notes}}
{{#if is_capped}}
<div><div><i class="fa fa-exclamation-circle text-danger"></i></div><div class="text-danger">Drive capped: usage is greater than current quota. Delete data or increase quota to resolve.</div></div>
{{#if in_flight}}
<div><div><i class="fa fa-sync text-primary"></i></div><div class="text-primary">We noticed an attempted quota update - confirmed changes will appear here shortly.</div></div>
{{else}}
{{#if is_ending}}
<div><div>&nbsp;</div><div>Ending on {{end_date}}</div></div>
{{#each quota_notes}}
{{#if is_capped}}
<div><div><i class="fa fa-exclamation-circle text-danger"></i></div><div class="text-danger">Drive capped: usage is greater than current quota. Delete data or increase quota to resolve.</div></div>
{{else}}
{{#or is_increasing is_decreasing}}
<div><div>&nbsp;</div><div>{{#if is_increasing}}In{{else}}De{{/if}}creasing to {{quota_limit}}GB on {{start_date}}</div></div>
{{/or}}
{{#if is_ending}}
<div><div>&nbsp;</div><div>Ending on {{end_date}}</div></div>
{{else}}
{{#if (or is_increasing is_decreasing) }}
<div><div>&nbsp;</div><div>{{#if is_increasing}}In{{else}}De{{/if}}creasing to {{quota_limit}}GB on {{start_date}}</div></div>
{{/if}}
{{/if}}
{{/if}}
{{/each}}
{{/if}}
{{/each}}
</td>
<td class="shared-drive-members">
<ul class="comma-list">
{{#gt drive.members.length 5}}
{{#if (gt drive.members.length 5)}}
{{#slice drive.members 0 4}}
{{netid}},
{{/slice}}
@@ -83,7 +87,7 @@
{{#each drive.members}}
<li>{{netid}}</li>
{{/each}}
{{/gt}}
{{/if}}
</ul>
</td>
<td class="shared-drive-action">
4 changes: 2 additions & 2 deletions endorsement/templates/handlebars/tab/endorsed/accept.html
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
<div class="row">
<div class="col">
You are about to provision
{{#gt netid_count 1}}
{{#if (gt netid_count 1)}}
<ul class="comma-list">{{#each services}}{{#if this.count}}<li>{{ shared }}UW NetIDs to {{this.name}}</li>{{/if}}{{/each}}</ul>.
{{else}}
{{#each services}}
@@ -20,7 +20,7 @@
<ul class="comma-list">
{{#each endorsed}}<li style="font-weight: bold;">{{this.netid}}</li>{{/each}}
</ul> to the {{this.name}} service{{#if @last}}{{/if}}{{/if}}{{/each}}.
{{/gt}}
{{/if}}
By selecting {{ plural netid_count 'this' 'these' }} UW NetID{{ plural netid_count '' 's' }}, you confirm that:
</div>
</div>
2 changes: 1 addition & 1 deletion endorsement/templates/handlebars/tab/shared/accept.html
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
{{#if unique}}
<div class="shared-acceptance container-fluid">
<div class="row">
You are about to provision access to UW-IT services for {{#gt unique.length 1}}{{unique.length}} {{/gt}}UW NetID{{plural unique.length '' 's'}}{{#gt unique.length 1}}{{else}}&nbsp;<ul class="comma-list">{{#each unique}}<li style="font-weight: bold;">{{this}}</li>{{/each}}</ul>{{/gt}}.
You are about to provision access to UW-IT services for {{#if (gt unique.length 1)}}{{unique.length}} {{/if}}UW NetID{{plural unique.length '' 's'}}{{#if (gt unique.length 1)}}{{else}}&nbsp;<ul class="comma-list">{{#each unique}}<li style="font-weight: bold;">{{this}}</li>{{/each}}</ul>{{/if}}.
</div>
<div class="row">
As the UW NetID owner, you are responsible for proper use, account maintenance, and file privileges.
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
{{#each services}}
{{#if this.count}}<li>{{this.name}}</li>{{/if}}
{{/each}}</ul>
for the{{#gt netid_count 1}} {{ netid_count }} {{/gt}} UW NetID{{plural netid_count '' 's'}} {{#gt this.count 1}}{{else}} <ul class="comma-list">{{#each unique}}<li style="font-weight: bold;">{{this}}</li>{{/each}}</ul>{{/gt}} has been provisioned.
for the{{#if (gt netid_count 1)}} {{ netid_count }} {{/if}} UW NetID{{plural netid_count '' 's'}} {{#if (gt this.count 1)}}{{else}} <ul class="comma-list">{{#each unique}}<li style="font-weight: bold;">{{this}}</li>{{/each}}</ul>{{/if}} has been provisioned.o
</div>
<br />
{{#each services}}
28 changes: 14 additions & 14 deletions endorsement/templates/index.html
Original file line number Diff line number Diff line change
@@ -9,20 +9,20 @@

{% if provisioning|length > 1 or '*' in provisioning %}
<div class="tabs">
<nav>
<ul class="tabs-list">
{% if '*' in provisioning or 'services' in provisioning %}
<li class="active" data-tab="services"><a class="tab-link" href="/">Services for NetIds</a></li>
{% endif %}
{% if '*' in provisioning or 'shared_drives' in provisioning %}
<li data-tab="drives"><a class="tab-link" href="drives">Google Shared Drives</a></li>
{% endif %}
{% if '*' in provisioning or 'mailbox_access' in provisioning %}
<li data-tab="access"><a class="tab-link" href="access">Outlook Mailbox Permissions</a></li>
{% endif %}
</ul>
</nav>
{%endif%}
<nav>
<ul class="tabs-list">
{% if '*' in provisioning or 'services' in provisioning %}
<li class="active" data-tab="services"><a class="tab-link" href="/">Services for NetIds</a></li>
{% endif %}
{% if '*' in provisioning or 'shared_drives' in provisioning %}
<li data-tab="drives"><a class="tab-link" href="drives">Google Shared Drives</a></li>
{% endif %}
{% if '*' in provisioning or 'mailbox_access' in provisioning %}
<li data-tab="access"><a class="tab-link" href="access">Outlook Mailbox Permissions</a></li>
{% endif %}
</ul>
</nav>
{%endif%}
{% if '*' in provisioning or 'services' in provisioning %}
<div id="services" class="tab active">

13 changes: 9 additions & 4 deletions endorsement/templates/support/notifications.html
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
.tabs { margin-top: 2em; }
.tabs .tabs-list { position: relative; top: 1px; z-index: 100; list-style: none; margin: 0px; padding: 0px; }
.tabs .tabs-list li { margin-right: 1.75em; float: left; padding-bottom: 2px;}
.tabs .tabs-list li span { text-transform: uppercase; font-weight: bold; }
.tabs .tabs-list li .tab-link { text-transform: uppercase; font-weight: bold; color: #333; text-decoration: none; }
.tabs .tabs-list li span:hover { cursor: pointer; }
.tabs .tabs-list li.active { border-bottom: 3px solid black; }
.tabs .tab { position: relative; clear: both; top: -2px; border-top: 3px solid lightgray; display: none; padding-top: 20px; }
@@ -27,9 +27,9 @@ <h2>Lifecycle Notifications</h2>

<div class="tabs">
<ul class="tabs-list">
<li class="active" data-tab="service"><span>Services for NetIDs</span></li>
<li data-tab="shared_drive"><span>Google Shared Drives</span></li>
<li data-tab="access"><span>Elevated Access</span></li>
<li class="active" data-tab="service"><a class="tab-link" href="/support/notifications">Services for NetIDs</a></li>
<li data-tab="shared_drive"><a class="tab-link" href="/support/notifications">Google Shared Drives</a></li>
<li data-tab="access"><a class="tab-link" href="/support/notifications">Elevated Access</a></li>
</ul>
<div id="service" class="tab active">

@@ -109,6 +109,7 @@ <h4>Notification Message Type</h4>
<div class="form-group">
<select class="form-control" id="notification">
<option value="">Select Notification Type...</option>
<option value="phase_0_expiration">Over Quota, No Subscription on Deadline Day</option>
<option value="warning_1">Initial Renewal Notice</option>
<option value="warning_2">Penultimate Renewal Notice</option>
<option value="warning_3">Final Renewal Notice</option>
@@ -117,6 +118,10 @@ <h4>Notification Message Type</h4>
</div>
</div>


<div class="form-group info" id="info_phase_0_expiration">
Sent on the expiration day for shared drives that are over quota with no subscription.
</div>
<div class="form-group info" id="info_warning_1">
First renewal notice sent to provisioner {{ warning_1 }} days prior to service expiration.
</div>
Original file line number Diff line number Diff line change
@@ -8,7 +8,8 @@
from endorsement.test.notifications import NotificationsTestCase
from endorsement.models import SharedDriveRecord
from endorsement.policy.shared_drive import SharedDrivePolicy
from endorsement.notifications.shared_drive import warn_members
from endorsement.notifications.shared_drive import (
warn_members, notify_over_quota_non_subsidized_expired)
from datetime import timedelta


@@ -94,3 +95,7 @@ def test_expiration_and_notice_email(self):

warn_members(4)
self.assertEqual(len(mail.outbox), 12)

def test_over_quota_unsubscribed_expiration(self):
notify_over_quota_non_subsidized_expired()
self.assertEqual(len(mail.outbox), 1)
14 changes: 4 additions & 10 deletions endorsement/test/views/api/test_resolve.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@


from django.urls import reverse
from django.core.management import call_command
from django.test import Client
from userservice.user import get_original_user
from endorsement.test.views import require_url, TestViewApi
@@ -13,15 +12,10 @@

@require_url('access_right_resolve_api', 'access urls not configured')
class TestResolve(TestViewApi):

def setUp(self):
super(TestResolve, self).setUp()

# seed db
call_command('loaddata', 'test_data/accessright.json')
call_command('loaddata', 'test_data/accessee.json')
call_command('loaddata', 'test_data/accessor.json')
call_command('loaddata', 'test_data/accessrecordconflict.json')
fixtures = ['test_data/accessright.json',
'test_data/accessee.json',
'test_data/accessor.json',
'test_data/accessrecordconflict.json']

def test_resolve_api(self):
test_request = {
4 changes: 4 additions & 0 deletions endorsement/util/itbill/shared_drive.py
Original file line number Diff line number Diff line change
@@ -12,3 +12,7 @@ def subscription_name(shared_drive_record):

def product_sys_id():
return getattr(settings, "ITBILL_SHARED_DRIVE_PRODUCT_SYS_ID")


def shared_drive_subsidized_quota():
return getattr(settings, "ITBILL_SHARED_DRIVE_SUBSIDIZED_QUOTA")
24 changes: 14 additions & 10 deletions endorsement/views/api/notification.py
Original file line number Diff line number Diff line change
@@ -14,14 +14,14 @@
from endorsement.policy.access import AccessPolicy
from endorsement.util.auth import SupportGroupAuthentication
from endorsement.notifications.endorsement import (
_get_endorsed_unnotified,
_create_expire_notice_message,
_get_endorsed_unnotified, _create_expire_notice_message,
_create_endorsee_message, _create_endorser_message,
_create_warn_shared_owner_message)
from endorsement.notifications.access import (
_create_accessor_message, _create_accessee_expiration_notice)
from endorsement.notifications.shared_drive import (
_create_notification_expiration_notice)
_create_notification_expiration_notice,
_create_notification_over_quota_non_subsidized)
from endorsement.dao.accessors import get_accessor_email
from datetime import datetime, timedelta
import re
@@ -182,17 +182,21 @@ def _access_notification(self, request):
def _shared_drive_notification(self, request):
notification = request.data.get('notification', None)

record = SharedDriveRecord.objects.filter(
is_deleted__isnull=True).first()

warning_level = None
m = re.match(r'^warning_([1-4])$', notification)
if m:
warning_level = int(m.group(1))

record = SharedDriveRecord.objects.filter(
is_deleted__isnull=True).first()

if warning_level:
subject, text, html = _create_notification_expiration_notice(
warning_level, record, SharedDrivePolicy())
if warning_level:
subject, text, html = _create_notification_expiration_notice(
warning_level, record, SharedDrivePolicy())
else:
return self.error_response(400, "Unknown notification.")
elif notification == 'phase_0_expiration':
subject, text, html = \
_create_notification_over_quota_non_subsidized(record)
else:
return self.error_response(400, "Unknown notification.")