Skip to content

Commit

Permalink
📁 Tab config page & Disable advanced access management (#2745)
Browse files Browse the repository at this point in the history
* Disable advanced access management

* Small refactoring

* Prepare channel tab page

* Added tests

* Prepare frontend logic for tabs configuration

* Add ability to create empty files from connectors

* Update access management for canary

* Fix linter

* Fix import typo

* Fix tests

* Missing an s

* FIx update tab tests

* Changing tests

* Fix test

* Finish Twake Tab token implementation

* Update tests

* FIx typo

* Still fixed the same one

* Fix bad merge import
  • Loading branch information
RomaricMourgues committed Feb 24, 2023
1 parent 3be5fe0 commit c384db2
Show file tree
Hide file tree
Showing 25 changed files with 1,171 additions and 377 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
description: Documents API
---

# Authentication

All the following routes require the usual authentication header. But you can also use other ways of authentication:

- For the download routes, you can use a token generated by the `/internal/services/documents/v1/companies/:company_id/download/token` route (see bellow).
- All the routes can use a query string `?public_token=token` to authenticate the user.
- All the routes can use a query string `?twake_tab_token=token` to authenticate the user in the context of a channel tab for instance.

# Navigation and drive capabilities

## Fetch a drive item

Used to fetch a drive item
Expand Down Expand Up @@ -73,6 +83,7 @@ Used to fetch a drive item
```

### Error Responses

If the item cannot be fetched the server will return an error with one of the following status codes:

- 401 Unauthorized - The user is not authorized.
Expand All @@ -86,7 +97,7 @@ Used to create a drive item

**Method** : `POST`

**Headers**: `Content-Type: application/json` OR `Content-Type: multipart/form-data`
**Headers**: `Content-Type: application/json` OR `Content-Type: multipart/form-data`

**Auth required** : Yes

Expand Down Expand Up @@ -132,6 +143,7 @@ Used to create a drive item
**Code** : `200 OK`

### Error Responses

If the request is missing required fields or the item cannot be created, the server will return an error with one of the following status codes:

- 400 Bad Request - The request is missing required fields.
Expand Down Expand Up @@ -176,6 +188,7 @@ Used to update a drive item
**Code** : `200 OK`

### Error Responses

If the request is missing required fields or the item cannot be updated, the server will return an error with one of the following status codes:

- 400 Bad Request - The request is missing required fields.
Expand Down Expand Up @@ -245,6 +258,26 @@ Used to create a drive item version

**Code** : `200 OK`

# Download

## Get a download token

Before to download, if you can't pass an authorisation token (for example in the browser context) you can generate a token that you will pass in the query string to download the file using the next routes.

**URL** : `/internal/services/documents/v1/companies/:company_id/item/download/token?items=id1,id2,id3&version_id:optional_id`

**Method** : `GET`

**Auth required** : Yes

### Success Response

```
{
"token": string
}
```

## Download

Shortcut to download a file (you can also use the file-service directly).
Expand All @@ -265,3 +298,63 @@ Used to create a zip archive containing the requested drive items ( files and fo
**Method** : `GET`

**Auth required** : Yes

# Tabs (for Twake)

If you want to use the Twake tabs, you must store the configuration of the tabs in the database.

## Get tab configuration

Get a tab configuration to get the attached folder/document id.

**URL** : `/internal/services/documents/v1/companies/:company_id/tabs/:id`

**Method** : `GET`

**Auth required** : Yes

### Success Response

```
{
"company_id": string;
"tab_id": string;
"channel_id": string;
"item_id": string;
"level": "read" | "write";
}
```

## Set tab configuration

Get a tab configuration to get the attached folder/document id.

**URL** : `/internal/services/documents/v1/companies/:company_id/tabs/:id`

**Method** : `POST`

**Auth required** : Yes

**Data constraints** :

```
{
"company_id": string;
"tab_id": string;
"channel_id": string;
"item_id": string;
"level": "read" | "write";
}
```

### Success Response

```
{
"company_id": string;
"tab_id": string;
"channel_id": string;
"item_id": string;
"level": "read" | "write";
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Type } from "class-transformer";
import { Column, Entity } from "../../../core/platform/services/database/services/orm/decorators";

export const TYPE = "drive_twake_tab";

@Entity(TYPE, {
primaryKey: [["company_id"], "tab_id"],
type: TYPE,
})
export class DriveTwakeTab {
@Type(() => String)
@Column("company_id", "string")
company_id: string;

@Type(() => String)
@Column("tab_id", "string")
tab_id: string;

@Type(() => String)
@Column("channel__id", "string")
channel_id: string;

@Type(() => String)
@Column("item_id", "string")
item_id: string;

@Type(() => String)
@Column("level", "string")
level: "read" | "write";
}
83 changes: 82 additions & 1 deletion twake/backend/node/src/services/documents/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import { hasCompanyAdminLevel } from "../../../utils/company";
import gr from "../../global-resolver";
import { DriveFile, TYPE } from "../entities/drive-file";
import { FileVersion, TYPE as FileVersionType } from "../entities/file-version";
import {
DriveTwakeTab as DriveTwakeTabEntity,
TYPE as DriveTwakeTabRepoType,
} from "../entities/drive-twake-tab";
import {
DriveExecutionContext,
DocumentsMessageQueueRequest,
DriveItemDetails,
RootType,
SearchDocumentsOptions,
TrashType,
CompanyExecutionContext,
DriveTwakeTab,
} from "../types";
import {
addDriveItemToArchive,
Expand Down Expand Up @@ -45,6 +51,7 @@ export class DocumentsService {
repository: Repository<DriveFile>;
searchRepository: SearchRepository<DriveFile>;
fileVersionRepository: Repository<FileVersion>;
driveTwakeTabRepository: Repository<DriveTwakeTabEntity>;
ROOT: RootType = "root";
TRASH: TrashType = "trash";
logger: TwakeLogger = getLogger("Documents Service");
Expand All @@ -60,6 +67,11 @@ export class DocumentsService {
FileVersionType,
FileVersion,
);
this.driveTwakeTabRepository =
await globalResolver.database.getRepository<DriveTwakeTabEntity>(
DriveTwakeTabRepoType,
DriveTwakeTabEntity,
);
} catch (error) {
logger.error("Error while initializing Documents Service", error);
}
Expand Down Expand Up @@ -275,7 +287,7 @@ export class DocumentsService {
update = async (
id: string,
content: Partial<DriveFile>,
context: DriveExecutionContext,
context: CompanyExecutionContext,
): Promise<DriveFile> => {
if (!context) {
this.logger.error("invalid execution context");
Expand Down Expand Up @@ -744,4 +756,73 @@ export class DocumentsService {

return new ListResult(result.type, filteredResult, result.nextPage);
};

getTab = async (tabId: string, context: CompanyExecutionContext): Promise<DriveTwakeTab> => {
const tab = await this.driveTwakeTabRepository.findOne(
{ company_id: context.company.id, tab_id: tabId },
{},
context,
);
return tab;
};

setTab = async (
tabId: string,
channelId: string,
itemId: string,
level: "read" | "write",
context: CompanyExecutionContext,
): Promise<DriveTwakeTab> => {
const hasAccess = await checkAccess(itemId, null, "manage", this.repository, context);

if (!hasAccess) {
throw new CrudException("Not enough permissions", 403);
}

const previousTabConfiguration = await this.getTab(tabId, context);
const item = await this.repository.findOne(
{
company_id: context.company.id,
id: itemId,
},
{},
context,
);

await this.driveTwakeTabRepository.save(
Object.assign(new DriveTwakeTabEntity(), {
company_id: context.company.id,
tab_id: tabId,
channel_id: channelId,
item_id: itemId,
level,
}),
context,
);

await this.update(
item.id,
{
...item,
access_info: {
...item.access_info,
entities: [
...(item.access_info?.entities || []).filter(
e =>
!previousTabConfiguration ||
!(e.type === "channel" && e.id !== previousTabConfiguration.channel_id),
),
{
type: "channel",
id: channelId,
level: level === "write" ? "write" : "read",
},
],
},
},
context,
);

return await this.getTab(tabId, context);
};
}
8 changes: 8 additions & 0 deletions twake/backend/node/src/services/documents/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,11 @@ export type exportKeywordPayload = {
file_id: string;
company_id: string;
};

export type DriveTwakeTab = {
company_id: string;
tab_id: string;
channel_id: string;
item_id: string;
level: "read" | "write";
};
20 changes: 13 additions & 7 deletions twake/backend/node/src/services/documents/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ export const checkAccess = async (
item: DriveFile | null,
level: DriveFileAccessLevel,
repository: Repository<DriveFile>,
context: CompanyExecutionContext & { public_token?: string },
context: CompanyExecutionContext & { public_token?: string; twake_tab_token?: string },
): Promise<boolean> => {
const grantedLevel = await getAccessLevel(id, item, repository, context);
const hasAccess = hasAccessLevel(level, grantedLevel);
Expand All @@ -341,7 +341,7 @@ export const getAccessLevel = async (
id: string,
item: DriveFile | null,
repository: Repository<DriveFile>,
context: CompanyExecutionContext & { public_token?: string },
context: CompanyExecutionContext & { public_token?: string; twake_tab_token?: string },
): Promise<DriveFileAccessLevel | "none"> => {
if (!id || id === "root") return (await isCompanyGuest(context)) ? "read" : "manage";
if (id === "trash")
Expand Down Expand Up @@ -384,11 +384,17 @@ export const getAccessLevel = async (
if (matchingUser) return matchingUser.level;

//Channels
//TODO
const matchingChannel = accessEntities.find(
a => a.type === "channel" && a.id === "TODO for no nothing is set here" && false,
);
if (matchingChannel) return matchingUser.level;
if (context.twake_tab_token) {
try {
const [channelId] = context.twake_tab_token.split("+"); //First item will be the channel id
const matchingChannel = accessEntities.find(
a => a.type === "channel" && a.id === channelId,
);
if (matchingChannel) return matchingChannel.level;
} catch (e) {
console.log(e);
}
}

const otherLevels = [];

Expand Down
Loading

0 comments on commit c384db2

Please sign in to comment.