Skip to content

Commit fee902f

Browse files
authored
Merge pull request #2030 from maxberger/master
Consistency updates, security updates, and support for birthday sharing
2 parents 238e3f9 + e3242fc commit fee902f

File tree

15 files changed

+592
-158
lines changed

15 files changed

+592
-158
lines changed

integ_tests/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def start_radicale_server(tmp_path: pathlib.Path) -> Generator[str, Any, None]:
6060
permit_create_token = true
6161
permit_create_map = true
6262
permit_properties_overlay = true
63+
collection_by_bday = true
64+
permit_create_bday = true
6365
6466
"""
6567
)

integ_tests/test_delete.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ def test_delete_wrong_confirmation(page: Page, radicale_server: str) -> None:
4141
page.click('article:not(.hidden) a[data-name="delete"]', force=True)
4242

4343
# Input wrong confirmation
44-
page.fill('#deletecollectionscene input[data-name="confirmationtxt"]', "foo")
45-
page.click('#deletecollectionscene button[data-name="delete"]')
44+
page.fill('#deleteconfirmationscene input[data-name="confirmationtxt"]', "foo")
45+
page.click('#deleteconfirmationscene button[data-name="delete"]')
4646

4747
# Check for error message
48-
error_locator = page.locator('#deletecollectionscene span[data-name="error"]')
48+
error_locator = page.locator('#deleteconfirmationscene span[data-name="error"]')
4949
expect(error_locator).to_be_visible()
5050
expect(error_locator).to_contain_text(
5151
"Please type DELETE in the confirmation field"
5252
)
5353

5454
# Scene should still be visible
55-
expect(page.locator("#deletecollectionscene")).to_be_visible()
55+
expect(page.locator("#deleteconfirmationscene")).to_be_visible()
5656

5757

5858
def test_delete_correct_confirmation(page: Page, radicale_server: str) -> None:
@@ -67,11 +67,11 @@ def test_delete_correct_confirmation(page: Page, radicale_server: str) -> None:
6767
page.click('article:not(.hidden) a[data-name="delete"]', force=True)
6868

6969
# Input correct confirmation
70-
page.fill('#deletecollectionscene input[data-name="confirmationtxt"]', "DELETE")
71-
page.click('#deletecollectionscene button[data-name="delete"]')
70+
page.fill('#deleteconfirmationscene input[data-name="confirmationtxt"]', "DELETE")
71+
page.click('#deleteconfirmationscene button[data-name="delete"]')
7272

7373
# Verify collection is gone
7474
expect(page.locator("article:not(.hidden)")).to_have_count(0)
7575

7676
# Scene should be hidden
77-
expect(page.locator("#deletecollectionscene")).to_be_hidden()
77+
expect(page.locator("#deleteconfirmationscene")).to_be_hidden()

integ_tests/test_scenes.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ def test_navigation_delete_collection_cancel(page: Page, radicale_server: str) -
6868

6969
page.hover("article:not(.hidden)")
7070
page.click('article:not(.hidden) a[data-name="delete"]', force=True)
71-
expect(page.locator("#deletecollectionscene")).to_be_visible()
71+
expect(page.locator("#deleteconfirmationscene")).to_be_visible()
7272

73-
page.click('#deletecollectionscene button[data-name="cancel"]')
74-
expect(page.locator("#deletecollectionscene")).to_be_hidden()
73+
page.click('#deleteconfirmationscene button[data-name="cancel"]')
74+
expect(page.locator("#deleteconfirmationscene")).to_be_hidden()
7575
expect(page.locator("#collectionsscene")).to_be_visible()
7676

7777

@@ -82,18 +82,18 @@ def test_navigation_delete_collection_confirm(page: Page, radicale_server: str)
8282

8383
page.hover("article:not(.hidden)")
8484
page.click('article:not(.hidden) a[data-name="delete"]', force=True)
85-
expect(page.locator("#deletecollectionscene")).to_be_visible()
85+
expect(page.locator("#deleteconfirmationscene")).to_be_visible()
8686

8787
# We need to fill the confirmation text
8888
confirmation_text = page.locator(
89-
"#deletecollectionscene [data-name='deleteconfirmationtext']"
89+
"#deleteconfirmationscene [data-name='deleteconfirmationtext']"
9090
).inner_text()
91-
page.locator("#deletecollectionscene input[data-name='confirmationtxt']").fill(
91+
page.locator("#deleteconfirmationscene input[data-name='confirmationtxt']").fill(
9292
confirmation_text
9393
)
94-
page.click('#deletecollectionscene button[data-name="delete"]')
94+
page.click('#deleteconfirmationscene button[data-name="delete"]')
9595

96-
expect(page.locator("#deletecollectionscene")).to_be_hidden()
96+
expect(page.locator("#deleteconfirmationscene")).to_be_hidden()
9797
expect(page.locator("#collectionsscene")).to_be_visible()
9898
expect(page.locator("article:not(.hidden)")).to_have_count(0)
9999

integ_tests/test_sharing.py

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def test_create_and_delete_share_by_key(page: Page, radicale_server: str) -> Non
5252
"tr[data-name='sharetokenrowtemplate']:not(.hidden) span[data-name='ro']"
5353
)
5454
).to_be_visible()
55-
page.once("dialog", lambda dialog: dialog.accept())
5655
page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
56+
page.click('#deleteconfirmationscene button[data-name="delete"]')
5757
expect(
5858
page.locator("tr[data-name='sharetokenrowtemplate']:not(.hidden)")
5959
).to_have_count(0)
@@ -68,8 +68,8 @@ def test_create_and_delete_share_by_key(page: Page, radicale_server: str) -> Non
6868
"tr[data-name='sharetokenrowtemplate']:not(.hidden) span[data-name='rw']"
6969
)
7070
).to_be_visible()
71-
page.once("dialog", lambda dialog: dialog.accept())
7271
page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
72+
page.click('#deleteconfirmationscene button[data-name="delete"]')
7373
expect(
7474
page.locator("tr[data-name='sharetokenrowtemplate']:not(.hidden)")
7575
).to_have_count(0)
@@ -97,8 +97,8 @@ def test_create_and_delete_share_by_map(page: Page, radicale_server: str) -> Non
9797
"tr[data-name='sharemaprowtemplate']:not(.hidden) span[data-name='ro']"
9898
)
9999
).to_be_visible()
100-
page.once("dialog", lambda dialog: dialog.accept())
101100
page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
101+
page.click('#deleteconfirmationscene button[data-name="delete"]')
102102
expect(
103103
page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
104104
).to_have_count(0)
@@ -115,8 +115,8 @@ def test_create_and_delete_share_by_map(page: Page, radicale_server: str) -> Non
115115
"tr[data-name='sharemaprowtemplate']:not(.hidden) span[data-name='rw']"
116116
)
117117
).to_be_visible()
118-
page.once("dialog", lambda dialog: dialog.accept())
119118
page.click('tr:not(.hidden) button[data-name="delete"]', strict=True)
119+
page.click('#deleteconfirmationscene button[data-name="delete"]')
120120
expect(
121121
page.locator("tr[data-name='sharemaprowtemplate']:not(.hidden)")
122122
).to_have_count(0)
@@ -139,15 +139,29 @@ def test_share_with_property_overrides(page: Page, radicale_server: str) -> None
139139
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
140140
page.click('button[data-name="sharebytoken"]')
141141

142+
# Verify property override is closed by default
143+
expect(
144+
page.locator('input[data-name="displayname_override_enabled"]')
145+
).not_to_be_visible()
146+
page.click('details[data-name="properties_override"] summary')
147+
142148
# Verify defaults
149+
expect(page.locator('input[data-name="displayname_override"]')).to_have_value(
150+
"Test Collection"
151+
)
143152
expect(page.locator('input[data-name="description_override"]')).to_have_value(
144153
"Original Description"
145154
)
146155
expect(page.locator('input[data-name="color_override"]')).to_have_value("#ff0000")
156+
expect(page.locator('input[data-name="displayname_override"]')).to_be_disabled()
147157
expect(page.locator('input[data-name="description_override"]')).to_be_disabled()
148158
expect(page.locator('input[data-name="color_override"]')).to_be_disabled()
149159

150160
# Set overrides
161+
page.click('label[for="newshare_attr_displayname_enabled"]')
162+
page.locator('input[data-name="displayname_override"]').fill(
163+
"Overridden Displayname"
164+
)
151165
page.click('label[for="newshare_attr_description_enabled"]')
152166
page.locator('input[data-name="description_override"]').fill(
153167
"Overridden Description"
@@ -182,8 +196,20 @@ def test_share_journal_no_overrides(page: Page, radicale_server: str) -> None:
182196
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
183197
page.click('button[data-name="sharebytoken"]')
184198

185-
# Verify property override fieldset is hidden
186-
expect(page.locator('fieldset[data-name="properties_override"]')).to_be_hidden()
199+
# Verify property override visibility
200+
expect(page.locator('details[data-name="properties_override"]')).to_be_visible()
201+
expect(
202+
page.locator('input[data-name="displayname_override_enabled"]')
203+
).not_to_be_visible()
204+
page.click('details[data-name="properties_override"] summary')
205+
206+
expect(
207+
page.locator('input[data-name="displayname_override_enabled"]')
208+
).to_be_visible()
209+
expect(
210+
page.locator('input[data-name="description_override_enabled"]')
211+
).to_be_hidden()
212+
expect(page.locator('input[data-name="color_override_enabled"]')).to_be_hidden()
187213

188214
# Create the share
189215
page.click('#newshare button[data-name="submit"]')
@@ -423,3 +449,107 @@ def test_no_incoming_shares_message(page: Page, radicale_server: str) -> None:
423449

424450
page.click('#incomingsharingscene button[data-name="cancel"]')
425451
expect(page.locator("#incomingsharingscene")).to_be_hidden()
452+
453+
454+
def test_create_and_delete_share_by_bday(page: Page, radicale_server: str) -> None:
455+
login(page, radicale_server)
456+
# create collection of type ADDRESSBOOK for bday (bday only works with ADDRESSBOOK)
457+
page.click('a[data-name="new"]')
458+
page.locator('#createcollectionscene select[data-name="type"]').select_option(
459+
"ADDRESSBOOK"
460+
)
461+
page.locator('#createcollectionscene input[data-name="displayname"]').fill(
462+
"Addressbook For Bday"
463+
)
464+
page.click('#createcollectionscene button[data-name="submit"]')
465+
466+
page.hover("article:not(.hidden)")
467+
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
468+
469+
expect(
470+
page.locator("tr[data-name='sharebdayrowtemplate']:not(.hidden)")
471+
).to_have_count(0)
472+
473+
page.click('button[data-name="sharebybday"]')
474+
475+
# verify user is auto-filled with current user (admin)
476+
expect(page.locator('input[data-name="shareuser"]')).to_have_value("admin")
477+
page.locator('input[data-name="sharehref"]').fill("bdaymapped")
478+
479+
# verify that the permissions section is hidden entirely
480+
expect(page.locator("input#newshare_attr_permissions_ro")).to_be_hidden()
481+
expect(page.locator("input#newshare_attr_permissions_rw")).to_be_hidden()
482+
483+
page.click('#newshare button[data-name="submit"]')
484+
expect(
485+
page.locator("tr[data-name='sharebdayrowtemplate']:not(.hidden)")
486+
).to_have_count(1)
487+
488+
# verify no permissions pill in the bday row
489+
expect(
490+
page.locator(
491+
"tr[data-name='sharebdayrowtemplate']:not(.hidden) span[data-name='ro']"
492+
)
493+
).to_have_count(0)
494+
495+
# Close the share scene and verify the virtual bday calendar is now in the collections list
496+
page.click('#sharecollectionscene button[data-name="cancel"]')
497+
expect(page.locator("#sharecollectionscene")).to_be_hidden()
498+
499+
# The virtual calendar (bdaymapped) should appear as its own article
500+
# after the cache was invalidated following the self-share
501+
expect(page.locator("article:not(.hidden)")).to_have_count(2)
502+
503+
# Delete the bday share by re-opening the share scene
504+
page.hover("article:not(.hidden) >> nth=0")
505+
page.click('article:not(.hidden) >> nth=0 >> a[data-name="share"]', force=True)
506+
page.click(
507+
"tr[data-name='sharebdayrowtemplate']:not(.hidden) button[data-name='delete']",
508+
strict=True,
509+
)
510+
page.click('#deleteconfirmationscene button[data-name="delete"]')
511+
expect(
512+
page.locator("tr[data-name='sharebdayrowtemplate']:not(.hidden)")
513+
).to_have_count(0)
514+
515+
516+
def test_bday_section_hidden_for_calendar(page: Page, radicale_server: str) -> None:
517+
"""Verify the bday calendar section is hidden for CALENDAR collections."""
518+
login(page, radicale_server)
519+
520+
page.click('a[data-name="new"]')
521+
page.locator('#createcollectionscene select[data-name="type"]').select_option(
522+
"CALENDAR"
523+
)
524+
page.locator('#createcollectionscene input[data-name="displayname"]').fill(
525+
"My Calendar"
526+
)
527+
page.click('#createcollectionscene button[data-name="submit"]')
528+
529+
page.hover("article:not(.hidden)")
530+
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
531+
532+
expect(page.locator("#sharecollectionscene")).to_be_visible()
533+
expect(page.locator("div[data-name='sharebybday']")).to_be_hidden()
534+
page.click('#sharecollectionscene button[data-name="cancel"]')
535+
536+
537+
def test_bday_section_visible_for_addressbook(page: Page, radicale_server: str) -> None:
538+
"""Verify the bday calendar section is visible for ADDRESSBOOK collections."""
539+
login(page, radicale_server)
540+
541+
page.click('a[data-name="new"]')
542+
page.locator('#createcollectionscene select[data-name="type"]').select_option(
543+
"ADDRESSBOOK"
544+
)
545+
page.locator('#createcollectionscene input[data-name="displayname"]').fill(
546+
"My Addressbook"
547+
)
548+
page.click('#createcollectionscene button[data-name="submit"]')
549+
550+
page.hover("article:not(.hidden)")
551+
page.click('article:not(.hidden) a[data-name="share"]', force=True, strict=True)
552+
553+
expect(page.locator("#sharecollectionscene")).to_be_visible()
554+
expect(page.locator("div[data-name='sharebybday']")).to_be_visible()
555+
page.click('#sharecollectionscene button[data-name="cancel"]')

radicale/httputils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ def _serve_traversable(
198198
return NOT_FOUND
199199
content_type = MIMETYPES.get(
200200
os.path.splitext(traversable.name)[1].lower(), FALLBACK_MIMETYPE)
201-
headers = {"Content-Type": content_type}
201+
headers = {
202+
"Content-Type": content_type,
203+
"Content-Security-Policy": "default-src 'self'; object-src 'none'"
204+
}
202205
if isinstance(traversable, pathlib.Path):
203206
headers["Last-Modified"] = time.strftime(
204207
"%a, %d %b %Y %H:%M:%S GMT",

radicale/web/internal_data/css/main.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ main {
112112
#logoutview span {
113113
width: calc(100% - 60px);
114114
display: inline-block;
115+
word-wrap: break-word;
115116
}
116117

117118
#logoutview a {
@@ -191,6 +192,7 @@ main {
191192
font-size: 1em;
192193
max-height: 130px;
193194
overflow: overlay;
195+
word-wrap: break-word;
194196
}
195197

196198
#collectionsscene article:hover ul {
@@ -518,3 +520,7 @@ button.inline {
518520
margin: 0 2px;
519521
width: 1.4em;
520522
}
523+
524+
.hidden {
525+
display: none !important;
526+
}

0 commit comments

Comments
 (0)