Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ ADMIN_PASSWORD=test
ADMIN_AUTH_BYPASS=false

# Auth Realm for HTTP auth
# Do not change this on an existing install: passwords created before v1.10.0, and any imported
# from Baikal, are md5(username:AUTH_REALM:password), so changing the realm silently invalidates
# every one of them. See the README.
AUTH_REALM=SabreDAV

# Auth Method for the frontend
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,29 @@ AUTH_METHOD=Basic # can be "Basic", "IMAP" or "LDAP"
```
> See [the following paragraph](#specific-environment-variables-for-imap-and-ldap-authentication-methods) for more information if you choose either IMAP or LDAP.

> [!WARNING]
>
> **Do not change `AUTH_REALM` on an existing installation.** Davis stored passwords as
> `md5(username:AUTH_REALM:password)` until v1.10.0 (September 2021), and a database imported from
> Baïkal uses the same scheme. The realm is part of those hashes, so changing it makes every one of
> them stop working, with no error beyond a failed login — the accounts are still there, they simply
> cannot authenticate any more.
>
> Passwords set since v1.10.0 use bcrypt and are unaffected. You can still be carrying legacy ones
> without ever having used Baïkal, if the account predates that version. To find out:
>
> ```sql
> SELECT username FROM users WHERE digesta1 NOT LIKE '$2y$%';
> ```
>
> Any row returned is a legacy hash tied to the current realm.
>
> There is no password reset in Davis. If you must change the realm, the only way back is to open
> each of those accounts in the dashboard and type a new password: the field is blank on the edit
> page and leaving it blank keeps the current hash, so filling it in is what replaces it. The new
> one is stored with bcrypt and no longer depends on the realm. Your own admin login is unaffected —
> it comes from `ADMIN_LOGIN` / `ADMIN_PASSWORD`, not from the users table.

**d. The global flags to enable CalDAV, CardDAV and WebDAV**. You can also disable the option to have calendars public

```shell
Expand Down
20 changes: 19 additions & 1 deletion src/Plugins/DavisIMipPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ public function schedule(ITip\Message $itip)
return $else;
};

$url = $notEmpty('URL', false);
// The invitation renders this as a link, and it is whatever the organiser's client put in
// VEVENT.URL. Anything but a web or mail address is dropped rather than made clickable.
$url = $this->linkableUrl($notEmpty('URL', false));
$description = $notEmpty('DESCRIPTION', false);
$location = $notEmpty('LOCATION', false);
$locationImageDataAsBase64 = false;
Expand Down Expand Up @@ -325,6 +327,22 @@ public function schedule(ITip\Message $itip)
}
}

/**
* @param string|false $url
*
* @return string|false the url if it is one a mail client should offer to open, false otherwise
*/
private function linkableUrl($url)
{
if (!is_string($url) || '' === $url) {
return false;
}

$scheme = parse_url($url, PHP_URL_SCHEME);

return \in_array(strtolower((string) $scheme), ['http', 'https', 'mailto'], true) ? $url : false;
}

/**
* Returns a bunch of meta-data about the plugin.
*
Expand Down
4 changes: 0 additions & 4 deletions src/Services/BirthdayService.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,6 @@ public function buildDataFromContact(string $cardData): ?VCalendar

$leapDay = (2 === (int) $dateParts['month']
&& 29 === (int) $dateParts['date']);
if (null === $dateParts['year'] || $originalYear < 1970) {
$birthday = ($leapDay ? '1972-' : '1970-')
.$dateParts['month'].'-'.$dateParts['date'];
}
Comment on lines -235 to -238

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused


if ($leapDay) {
/* Sabre\VObject supports BYMONTHDAY only if BYMONTH
Expand Down
2 changes: 1 addition & 1 deletion templates/_partials/delegate_row.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<p class="mb-1">{{ "users.username"|trans }} : <code>{{ delegate.username }}</code></p>
<small>{{ "users.uri"|trans }} : <code>{{ delegate.uri }}</code></small>
<div class="btn-group btn-group-sm mt-3 d-flex d-lg-none" role="group">
<a href="#" data-bs-toggle="modal" data-bs-target="#deleteModal-delegates" data-href="{{ path('user_delegate_remove',{userId: userId, principalProxyId: has_write ? principalProxyWrite.id : principalProxyRead.id, delegateId: delegate.id})}}" data-flavour="delegates" class="btn btn-outline-danger flex-fill flex-shrink-1 delete-modal"><span class="d-none d-sm-inline">⚠&nbsp;</span>{{ "remove"|trans }}</a>
<a href="#" data-bs-toggle="modal" data-bs-target="#deleteModal-delegates" data-href="{{ path('user_delegate_remove',{userId: userId, principalProxyId: has_write ? principalProxyWrite.id : principalProxyRead.id, delegateId: delegate.id})}}" data-flavour="delegates" class="btn btn-outline-danger flex-fill flex-shrink-1"><span class="d-none d-sm-inline">⚠&nbsp;</span>{{ "remove"|trans }}</a>
</div>
</div>
4 changes: 2 additions & 2 deletions templates/calendars/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
data-bs-toggle="modal" data-bs-target="#deleteModal-calendars"
data-href="{{ path('calendar_delete',{userId: userId, id: calendar.id})}}"
data-flavour="calendars"
class="btn btn-sm btn-outline-danger ms-1 delete-modal"
class="btn btn-sm btn-outline-danger ms-1"
>⚠ {{ "delete"|trans }}</a>
</div>
</div>
Expand All @@ -67,7 +67,7 @@
data-bs-toggle="modal" data-bs-target="#deleteModal-calendars"
data-href="{{ path('calendar_delete',{userId: userId, id: calendar.id})}}"
data-flavour="calendars"
class="btn btn-outline-danger delete-modal"
class="btn btn-outline-danger"
>⚠ {{ "delete"|trans }}</a>
</div>
</div>
Expand Down
36 changes: 36 additions & 0 deletions tests/Functional/Plugins/ImipPluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,42 @@ public function testANonMailtoRecipientIsLogged(): void
$this->assertSame([], $mailer->sent);
}

/**
* `VEVENT.URL` comes from whoever created the event and the invitation makes it clickable in
* the recipient's mail client, so only web and mail addresses get through.
*
* @dataProvider urls
*/
public function testOnlyLinkableUrlsReachTheInvitation(string $url, bool $expected): void
{
$logs = [];
$plugin = $this->plugin($mailer = $this->mailer(null), $logs);

$message = $this->message();
$message->message->VEVENT->add('URL', $url);
$plugin->schedule($message);

$this->assertCount(1, $mailer->sent);

// The templates render from this context, and both of them only emit a link when it is set
$this->assertSame(
$expected ? $url : false,
$mailer->sent[0]->getContext()['url'],
$url.($expected ? ' should be offered' : ' should not be offered')
);
}

public static function urls(): iterable
{
yield 'https' => ['https://example.org/meeting', true];
yield 'http' => ['http://example.org/meeting', true];
yield 'mailto' => ['mailto:someone@example.org', true];
yield 'javascript' => ['javascript:alert(1)', false];
yield 'data' => ['data:text/html,<script>alert(1)</script>', false];
yield 'file' => ['file:///etc/passwd', false];
yield 'no scheme' => ['example.org/meeting', false];
}

public function testASuccessfulSendIsLoggedAndReported(): void
{
$logs = [];
Expand Down