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

Integrate files_sharing_webapppassword #61

Merged
merged 9 commits into from
Apr 19, 2023
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
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@
[![Test](https://github.com/digital-blueprint/webapppassword/actions/workflows/test.yml/badge.svg)](https://github.com/digital-blueprint/webapppassword/actions/workflows/test.yml)

This is a Nextcloud app to generate a temporary app password and set CORS headers to allow
WebDAV/CalDAV access from inside a webpage.
WebDAV/CalDAV and Share API access from inside a webpage.

Place this app in **nextcloud/apps/** or install it from the [Nextcloud App Store](https://apps.nextcloud.com/apps/webapppassword).

## Configuration

You can configure the allowed origins on the settings page of the application.
You can configure the allowed origins for both wwebdav/caldav or files sharing api on the settings
page of the application.


![screenshot](screenshot.png)

Alternatively you can also add this setting to your `config/config.php`
(it will be used if the origins setting on the settings page are empty).

`'webapppassword.origins' => ['https://example.com'],` - array of allowed origins
`'webapppassword.origins' => ['https://example.com'],` - array of allowed webdav/caldav origins

The setting is both used for the origin of the CORS headers for the WebDAV/CalDAV requests and
for the referrer check whether we want to generate a temporary app password.

Also, you can configure in the same way the files sharing part.

`'webapppassword.files_sharing_origins' => ['https://example.com'],` - array of allowed files sharing api origins

Under the hood it exposes parts of the sharing api (CRUD and the preflight OPTIONS endpoint) in this url:
https://example.com/index.php/apps/webapppassword/api/v1/shares


## Docker

You can use this container for development and testing of the application.
Expand Down
44 changes: 44 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,49 @@
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#create_token', 'url' => '/create', 'verb' => 'POST'],
['name' => 'admin#update', 'url' => '/admin', 'verb' => 'PUT'],
/*
* OCS Share API
*/
[
'name' => 'ShareAPI#getShares',
'url' => '/api/v1/shares',
'verb' => 'GET',
],
[
'name' => 'ShareAPI#getInheritedShares',
'url' => '/api/v1/shares/inherited',
'verb' => 'GET',
],
[
'name' => 'ShareAPI#createShare',
'url' => '/api/v1/shares',
'verb' => 'POST',
],
[
'name' => 'ShareAPI#preflighted_cors',
'url' => '/api/v1/shares',
'verb' => 'OPTIONS',
],
[
'name' => 'ShareAPI#pendingShares',
'url' => '/api/v1/shares/pending',
'verb' => 'GET',
],
[
'name' => 'ShareAPI#getShare',
'url' => '/api/v1/shares/{id}',
'verb' => 'GET',
],
[
'name' => 'ShareAPI#updateShare',
'url' => '/api/v1/shares/{id}',
'verb' => 'PUT',
],
[
'name' => 'ShareAPI#deleteShare',
'url' => '/api/v1/shares/{id}',
'verb' => 'DELETE',
]

Copy link
Member

Choose a reason for hiding this comment

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

Aren't those Nextcloud routes? Why do they need to be included in webapppassword?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact it is reimplementing the files_sharing api in webapppassword namespace to be able to apply the filtering of origins.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I see. This really should belong into Nextcloud core. 😅
Can you please mention that there are new sharing endpoints on the settings, like you did in the README.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added, and restructured the settings to sections. It may include the copy url button and the real base url but this may add noise to this MR. maybe in next merge requests...

],
];
4 changes: 4 additions & 0 deletions css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
#webapppassword-origins {
width: 100%;
}

#files-sharing-webapppassword-origins {
width: 100%;
}
2 changes: 2 additions & 0 deletions js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
document.querySelector("#webapppassword-store-origins").onclick = (e) => {
const data = {
origins: document.querySelector("#webapppassword-origins").value,
files_sharing_origins: document.querySelector("#files-sharing-webapppassword-origins").value,
};

const apiUrl = OC.generateUrl('/apps/webapppassword/admin');
Expand All @@ -19,6 +20,7 @@ document.querySelector("#webapppassword-store-origins").onclick = (e) => {
.then(response => response.json())
.then((data) => {
document.querySelector("#webapppassword-origins").value = data.origins;
document.querySelector("#files-sharing-webapppassword-origins").value = data.files_sharing_origins;
const successIndicator = document.querySelector("#webapppassword-saved-message");
successIndicator.classList.add('show');

Expand Down
45 changes: 45 additions & 0 deletions lib/Config/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,49 @@ public function setOrigins($value)
$this->config->setAppValue('webapppassword', 'origins', $value);
$this->logger->info('Origins were updated!');
}

/**
* Serializes the allowed share api origins in a string.
*
* @return string
* List allowed share api origins separated by commas.
*
*/
public function getFilesSharingOrigins(): string
{
$origins = $this->config->getAppValue('webapppassword', 'files_sharing_origins');

if ($origins === '') {
$origins = implode(',', $this->config->getSystemValue('webapppassword.files_sharing_origins', []));
}

if ($origins === null) {
$origins = '';
}

return implode(',', array_map('trim', explode(',', $origins)));
}

/**
* Gets an array of the defined share api allowed origins
*
* @return array
* List of allowed share api origins.
*/
protected function getFilesSharingOriginList()
{
return explode(',', $this->getFilesSharingOrigins());
}

/**
* Sets the defined share api allowed origins
*
* @param string $value
* Comma separated List of allowed share api origins.
*/
public function setFilesSharingOrigins($value)
{
$this->config->setAppValue('webapppassword', 'files_sharing_origins', $value);
$this->logger->info('Files Sharing Origins were updated!');
}
}
80 changes: 80 additions & 0 deletions lib/Controller/AccessControl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Aleix Quintana Alsius <kinta@communia.org>
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace OCA\WebAppPassword\Controller;

use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSNotFoundException;

trait AccessControl {
/**
* Checks the origin of a request and modifies response.
*
* @param DataResponse $response
* @return DataResponse
* @throws NotFoundException
* @throws OCSBadRequestException
* @throws OCSException
* @throws OCSForbiddenException
* @throws OCSNotFoundException
* @throws InvalidPathException
* @suppress PhanUndeclaredClassMethod
*/
protected function checkOrigin( DataResponse $response
): DataResponse {
$origins_allowed = $this->getOriginList();
if (in_array('access-control-allow-origin', $response->getHeaders())) {
throw new OCSNotFoundException($this->l->t('Could not create share'));
}

$origin = $this->request->getHeader('origin');
if (empty($origin) || !in_array($origin, $origins_allowed, true)) {
throw new OCSNotFoundException($this->l->t('Could not create share'));
}

$response->addHeader('access-control-allow-origin', $origin);
$response->addHeader('access-control-allow-methods', $this->request->getHeader('access-control-request-method'));
$response->addHeader('access-control-allow-headers', $this->request->getHeader('access-control-request-headers'));
$response->addHeader('access-control-expose-headers', 'etag, dav');
$response->addHeader('access-control-allow-credentials', 'true');
return $response;
}

/**
* Serializes the allowed origins in a string.
*
* @return string
* List allowed origins separated by commas.
*
*/
protected function getOrigins(): string
{
// TODO DI $this->config->getAppValue('files_sharing_origins', 'origins');
// __construct must be reimplemented as config prop in parent is private...
$config = \OC::$server->getConfig();
$origins = $config->getAppValue('webapppassword', 'files_sharing_origins');

if ($origins === '') {
$origins = implode(',', $config->getSystemValue('webapppassword.files_sharing_origins', []));
}

if ($origins === null) {
$origins = '';
}

return implode(',', array_map('trim', explode(',', $origins)));
}

/**
* Gets an array of the defined allowed origins
*
* @return array
* List of allowed origins.
*/
protected function getOriginList()
{
return explode(',', $this->getOrigins());
}
}
5 changes: 4 additions & 1 deletion lib/Controller/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ public function __construct(
* @return array with the updated values
*/
public function update(
$origins
$origins,
$files_sharing_origins
) {
$this->config->setOrigins($origins);
$this->config->setFilesSharingOrigins($files_sharing_origins);

return [
'origins' => $this->config->getOrigins(),
'files_sharing_origins' => $this->config->getFilesSharingOrigins(),
];
}
}
Loading