Skip to content

Commit bcbd71a

Browse files
committed
enh(Sharing): backend infrastructre for read-only link shares
- modifies oc_tables_share structure with two columns, token and password - adds ShareOCSController with a route to create link shares - adds a ShareToken value object - extends Share entity with ShareToken and Password properties - extends ShareMapper to find a share by the share token - extends ShareService with a method to easily create link shares Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
1 parent eee8bd7 commit bcbd71a

9 files changed

Lines changed: 261 additions & 33 deletions

File tree

lib/Constants/ShareReceiverType.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ class ShareReceiverType {
1414
public const USER = 'user';
1515
public const GROUP = 'group';
1616
public const CIRCLE = 'circle';
17+
public const LINK = 'link';
1718
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace OCA\Tables\Controller;
4+
5+
use OCA\Tables\AppInfo\Application;
6+
use OCA\Tables\Errors\BadRequestError;
7+
use OCA\Tables\Helper\ConversionHelper;
8+
use OCA\Tables\Middleware\Attribute\RequirePermission;
9+
use OCA\Tables\Service\ShareService;
10+
use OCA\Tables\Service\TableService;
11+
use OCA\Tables\Service\ViewService;
12+
use OCP\AppFramework\Http\Attribute\ApiRoute;
13+
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
14+
use OCP\AppFramework\Http\DataResponse;
15+
use OCP\EventDispatcher\IEventDispatcher;
16+
use OCP\IL10N;
17+
use OCP\IRequest;
18+
use OCP\IURLGenerator;
19+
use OCP\Security\Events\ValidatePasswordPolicyEvent;
20+
use OCP\Security\PasswordContext;
21+
use Psr\Log\LoggerInterface;
22+
23+
class ShareOCSController extends AOCSController {
24+
public function __construct(
25+
IRequest $request,
26+
LoggerInterface $logger,
27+
IL10N $n,
28+
string $userId,
29+
protected ShareService $shareService,
30+
protected TableService $tableService,
31+
protected ViewService $viewService,
32+
protected IEventDispatcher $eventDispatcher,
33+
protected IURLGenerator $urlGenerator,
34+
) {
35+
parent::__construct($request, $logger, $n, $userId);
36+
}
37+
38+
#[NoAdminRequired]
39+
#[RequirePermission(permission: Application::PERMISSION_MANAGE, typeParam: 'nodeCollection')]
40+
#[ApiRoute(verb: 'POST', url: '/api/2/{nodeCollection}/{nodeId}/share')]
41+
public function createLinkShare(
42+
string $nodeCollection,
43+
string $nodeId,
44+
?string $password = null,
45+
): DataResponse {
46+
$collection = ConversionHelper::stringNodeType2Const($nodeCollection);
47+
if ($collection === Application::NODE_TYPE_TABLE) {
48+
$node = $this->tableService->find($nodeId);
49+
} else {
50+
$node = $this->viewService->find($nodeId);
51+
}
52+
53+
if ($password !== null) {
54+
$event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING);
55+
try {
56+
$this->eventDispatcher->dispatchTyped($event);
57+
} catch (\Exception $e) {
58+
$error = new BadRequestError($e->getMessage(), $e->getCode(), $e);
59+
return $this->handleBadRequestError($error);
60+
}
61+
}
62+
63+
$share = $this->shareService->createLinkShare($node, $password);
64+
return new DataResponse([
65+
'shareToken' => (string)$share->getToken(),
66+
'url' => $this->urlGenerator->linkToRouteAbsolute('tables.page.public', ['shareToken' => (string)$share->getToken()]),
67+
]);
68+
}
69+
}

lib/Db/Share.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use JsonSerializable;
1111

1212
use OCA\Tables\ResponseDefinitions;
13+
use OCA\Tables\Service\ValueObject\ShareToken;
1314

1415
/**
1516
* @psalm-import-type TablesShare from ResponseDefinitions
@@ -28,6 +29,9 @@
2829
* @method setNodeId(int $nodeId)
2930
* @method getNodeType(): string
3031
* @method setNodeType(string $nodeType)
32+
* @method getToken(): ShareToken
33+
* @method getPassword(): string
34+
* @method setPassword(string $password)
3135
* @method getPermissionRead(): bool
3236
* @method setPermissionRead(bool $permissionRead)
3337
* @method getPermissionCreate(): bool
@@ -50,6 +54,8 @@ class Share extends EntitySuper implements JsonSerializable {
5054
protected ?string $receiverType = null; // user, group, circle
5155
protected ?int $nodeId = null;
5256
protected ?string $nodeType = null;
57+
protected ?ShareToken $token = null;
58+
protected ?string $password = null;
5359
protected ?bool $permissionRead = null;
5460
protected ?bool $permissionCreate = null;
5561
protected ?bool $permissionUpdate = null;
@@ -75,6 +81,10 @@ public function __construct() {
7581
$this->addType('permissionManage', 'boolean');
7682
}
7783

84+
public function setToken(string $token): void {
85+
$this->token = new ShareToken($token);
86+
}
87+
7888
/**
7989
* @psalm-return TablesShare
8090
*/
@@ -92,6 +102,8 @@ public function jsonSerialize(): array {
92102
'receiver' => $this->receiver,
93103
'receiverDisplayName' => $this->receiverDisplayName,
94104
'receiverType' => $this->receiverType,
105+
'token' => $this->token,
106+
'password' => $this->password,
95107
'createdAt' => $this->createdAt,
96108
'lastEditAt' => $this->lastEditAt,
97109
];

lib/Db/ShareMapper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace OCA\Tables\Db;
99

10+
use OCA\Tables\Service\ValueObject\ShareToken;
1011
use OCP\AppFramework\Db\DoesNotExistException;
1112
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
1213
use OCP\AppFramework\Db\QBMapper;
@@ -41,6 +42,16 @@ public function find(int $id): Share {
4142
return $this->findEntity($qb);
4243
}
4344

45+
public function findByToken(ShareToken $token): Share {
46+
$qb = $this->db->getQueryBuilder();
47+
$qb->select('*')
48+
->from($this->table)
49+
->where($qb->expr()->eq('token', $qb->createNamedParameter((string)$token, IQueryBuilder::PARAM_STR)));
50+
return $this->findEntity($qb);
51+
}
52+
53+
public function createLinkShare()
54+
4455
/**
4556
* find share for a node
4657
* look for all receiver types or limit it to one given type

lib/Helper/ConversionHelper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
use InvalidArgumentException;
1111
use OCA\Tables\AppInfo\Application;
12+
use OCA\Tables\Db\Table;
13+
use OCA\Tables\Db\View;
1214

1315
class ConversionHelper {
1416

@@ -33,4 +35,11 @@ public static function stringNodeType2Const(string $nodeType): int {
3335
default => throw new InvalidArgumentException('Invalid node type'),
3436
};
3537
}
38+
39+
public static function object2String(Table|View $node): string {
40+
if ($node instanceof Table) {
41+
return 'table';
42+
}
43+
return 'view';
44+
}
3645
}

lib/Migration/Version000200Date20220428000000.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
5454
'notnull' => true,
5555
'length' => 50
5656
]);
57+
$table->addColumn('token', Types::STRING, [
58+
'notnull' => false,
59+
'length' => 64
60+
]);
61+
$table->addColumn('password', Types::STRING, [
62+
'notnull' => false,
63+
'length' => 255
64+
]);
5765

5866
$table->addColumn('permission_read', Types::BOOLEAN, [
5967
'notnull' => false,
@@ -83,6 +91,8 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
8391
'notnull' => true,
8492
]);
8593
$table->setPrimaryKey(['id']);
94+
$table->addIndex(['node_id', 'node_type'], 'shares_node_idx');
95+
$table->addIndex(['receiver', 'receiver_type'], 'shares_receiver_idx');
8696
}
8797

8898
return $schema;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Tables\Migration;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\IOutput;
16+
use OCP\Migration\SimpleMigrationStep;
17+
use Override;
18+
19+
class Version1000Date20251208192653 extends SimpleMigrationStep {
20+
21+
#[Override]
22+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
23+
/** @var ISchemaWrapper $schema */
24+
$schema = $schemaClosure();
25+
$tableName = 'tables_shares';
26+
if (!$schema->hasTable($tableName)) {
27+
return null;
28+
}
29+
30+
$table = $schema->getTable($tableName);
31+
if (!$table->hasColumn('token')) {
32+
$table->addColumn('token', Types::STRING, [
33+
'notnull' => false,
34+
'length' => 64
35+
]);
36+
}
37+
38+
if (!$table->hasColumn('password')) {
39+
$table->addColumn('password', Types::STRING, [
40+
'notnull' => false,
41+
'length' => 255
42+
]);
43+
}
44+
45+
if (!$table->hasIndex('shares_token_idx')) {
46+
$table->addIndex(['token'], 'shares_token_idx');
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)